Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

Diskusia – 1. diel - Úvod do ukazovateľov (pointer) v jazyku C

Späť

Upozorňujeme, že diskusie pod našimi online kurzami sú nemoderované a primárne slúžia na získavanie spätnej väzby pre budúce vylepšenie kurzov. Pre študentov našich rekvalifikačných kurzov ponúkame možnosť priameho kontaktu s lektormi a študijným referentom pre osobné konzultácie a podporu v rámci ich štúdia. Toto je exkluzívna služba, ktorá zaisťuje kvalitnú a cielenú pomoc v prípade akýchkoľvek otázok alebo projektov.

Komentáre
Avatar
Jozef Vendel
Člen
Avatar
Odpovedá na DarkCoder
Jozef Vendel:19.2.2021 15:21
#include <stdio.h>
#include <stdlib.h>

int spocitaj(char *retazec, char znak){

    retazec = (char*) malloc(20 * sizeof(char));
    int a = 0;
    printf("Zadaj rezatec\n");
    scanf("%[^\n]", retazec);
    printf("Zadaj skumany znak\n");
    scanf(" %c", &znak);

     while (*retazec) {
                if (*retazec == znak) a++;
                retazec++;
            }

   // free(retazec);
    return a;
}

int main(){
    char *str, c;

   printf("Pocet znakov v retazci je %d", spocitaj(str, c));

}

Viem, ze si hovoril ze sa to da aj bez dynamickej alokacie, ale chcel som robit s retazcom zadanym od uzivatela.
Mam na teba 2 otazky. Prva je, ze kde v tomto kode by som mal pouzit funkciu free(retazec) a druha je, ze chcel by som alokovat pomocou funkcie malloc presny pocet B pre retazec zadany uzivatelom, ked rozmyslam mohol by som to reisit tak, ze by som si vytvoril pomocne staticke pole char str[128], tam by som si ulozil vstupny retazec, zistil jeho velkost a podla toho uz alokoval konkretny pocet B. Bola by to tiez moznost nie ?

 
Odpovedať
19.2.2021 15:21
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Jozef Vendel
DarkCoder:19.2.2021 15:49

Příliš přezskakuješ. Funkce free() se volá tehdy, když se ví, že už se nebude s alokovanou pamětí pracovat. Bylo by to za while cyklem ale před return. Pokud chceš pracovat s řetězcem zadaným z klávesnice, musíš se rozhodnout, kam načítací sekci umístíš. Pokud mimo funkci, pak funkci zůstanou parametry, pok dovnitř, pak je funkce nebude mít, ale pak vše musíš ošetřit uvnitř funkce. Pro načítání řetězců používej funkci fgets() s ukazatelem na soubor stdin. Aby se vyhradil přesný počet bytů pro řetězec, je třeba alokované pole zmenšovat či zvětšovat dle potřeby během načítání znaků. Jde to ale je to dosti komplikovaným proces. Dále pokud měníš ukazatel předány jako argument funkce, je třeba předávat na něj ukazatel. Tím se dostáváš do vícenásobné dereference, kterou můžeš řešit až tehdy, znáš-li základy ukazatelů. Pokud chceš používat buffer pro načítání řetězce z klávesnice, použij staticky alokované pole. Ale znovu opakuji, postupuj postupně

Odpovedať
19.2.2021 15:49
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovedá na DarkCoder
Martin Russin:28.7.2021 15:01

Ahoj, chcel by som Ťa poprosiť o vysvetlenie:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 120

char *zamena_znakov(char *p_retazec, char povodny_znak, char novy_znak) {
    char *s = p_retazec; // prečo som musel použiť pomocnú premennú ukazovateľ?
// chápem princíp, že ukazovateľ s preberá adresu začiatku poľa ale veď to aj
// ukazovateľ p_retazec, alebo sa mýlim? Ak som pomocnú premennú nepoužil,
// tak mi program nefungoval správne.
    while(*p_retazec != NULL) {
        if(*p_retazec == povodny_znak)
           *p_retazec = novy_znak;
        p_retazec++;
    }
    return s;
}

char *vytvor_retazec(char *zdroj) {
    char *p_retazec = (char*)malloc(sizeof(zdroj));
    if(p_retazec == NULL) {
        return 1;
    }
    strcpy(p_retazec, zdroj);
    return p_retazec;
}

int main(int argc, char** argv) {
    char buffer[BUFFER_SIZE + 1];
    char povodny_znak, novy_znak;
    printf("Zadaj reťazec:\n");
    scanf(" %120[^\n]", buffer);
    char *p_retazec = vytvor_retazec(buffer);
    printf("Zadaj pôvodny znak na zamenenie:\n");
    scanf(" %c", &povodny_znak);
    printf("Zadaj nový znak:\n");
    scanf(" %c", &novy_znak);
    printf("Reťazec po zámene: %s\n",
            zamena_znakov(p_retazec, povodny_znak, novy_znak));
    free(p_retazec);
    p_retazec = NULL;
    return (EXIT_SUCCESS);
}

Vopred ďakujem za Tvoju odpoveď.

 
Odpovedať
28.7.2021 15:01
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:28.7.2021 16:15

Důvod použití pomocného ukazatele s je ten, že je potřeba uchovat adresu začátku řetězce, který je předáván funkci pomocí ukazatele jako její argument. Původní ukazatel na řetězec je ve funkci použit pro přístup k jednotlivým znakům řetězce, tento ukazatel se mění. A aby se vrátil ukazatel na řetězec, vrací funkce právě pomocný ukazatel ve kterém je uložena adresa začátku řetězce.

Odpovedať
+1
28.7.2021 16:15
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:29.7.2021 14:15

Ještě se vrátím ke kódu programu na záměnu znaků, který si poslal. Jsou v něm logické i sémantické chyby. Takže postupně:

Funkce zamena_znakov()

*p_retazec != NULL

Výše uvedený výraz v příkazu while nelze použít. Výsledkem *p_retazec je celočíselná hodnota, nikoli ukazatel na typ a tuto hodnotu tak nelze porovnávat s NULL. Funkce zamena_znaku() by mohla vypadat následovně:

char* zamena_znaku(char* str, char c_puvodni, char c_novy) {
        char* s = str; // uchovaní adresy začátku řetězce
        while (*str) {  // nekončí znakem '\0', neplést s NULL !!!
                if (*str == c_puvodni) *str = c_novy; // přepis znaků
                str++;
        }
        return s;
}

Funkce vytvor_retazec()

Tato funkce je úplně zbytečná. Vždyť už máš staticky alokovaný buffer, a není tedy nutné vytvářet něco navíc. Pro přístup k poli se využívá jméno pole bez indexu, což je ukazatel na začátek pole. V případě neúspěšného pokusu alokování dynamické paměti nelze vracet celočíselnou hodnotu, když funkce vrací znakový ukazatel. Dále se dynamicky alokuje paměť o velikosti ukazatele na char, nikoli pamět pro uložení řetězce. A nakonec ve funkci strcpy() musí být argumentem pro cílové pole ukazatel na pole, ne samotný ukazatel na typ. Tedy musí být vyhraněn prostor pro uložení dat.

Toto je špatně:

char* p;
strcpy(p, "Hello");

Toto je správně:

char p[10];
strcpy(p, "Hello");
Odpovedať
+1
29.7.2021 14:15
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Odpovedá na DarkCoder
Martin Russin:29.7.2021 15:01
while (*str)

by sa dalo prepísať ako

while (*str != '0')

?

ve funkci strcpy() musí být argumentem pro cílové pole ukazatel na pole, ne samotný ukazatel na typ.

Prepísal som funkciu na vytvorenie reťazca správne?

char *vytvor_retazec(void) {
    char buffer[BUFFER_SIZE + 1];
    printf("Zadaj reťazec:\n");
    scanf(" %120[^\n]", buffer);
    char *p_retazec = (char*)malloc(strlen(buffer)+1);
// je to isté ako (char*)malloc(sizeof(char)*(strlen(buffer)+1); ?
    strcpy(p_retazec, buffer);
    return p_retazec;
}

Ďakujem za Tvoju odpoveď.

 
Odpovedať
29.7.2021 15:01
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:29.7.2021 15:54

Ne. Znak s hodnotou nula (NUL , neplést s NULL) je něco jiného než '0'. Znak '0' má nenulovou hodnotu, konkrétně 48 (dle ASCII).

Tedy kód

while (*str)

je totéž co

while (*str != 0)

Nepleť do toho staticky alokované pole, vytváříš řetězec od nuly, ne z něčeho. Ani nic nekopíruješ. Vytvoření řetězce se skládá z alokované paměti a přiřazení textu do dynamicky vytvořeného pole.

Následovně:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MEM_COUNT 10

char* create_str(unsigned size, char *s);

int main(int argc, char* argv[]) {
        char* str = NULL;

        str = create_str(MEM_COUNT, "123456789");
        if (!str) exit(1);

        puts(str);

        free(str);
        str = NULL;

        return 0;
}

char* create_str(unsigned size, char* s) {

        if (strlen(s) > size - 1) return NULL; // kontrola presahu

        char* p = (char*)malloc(size * sizeof(char)); // alokace
        if (p) strcpy(p, s); // prirazeni textu

        return p;
}
Odpovedať
+1
29.7.2021 15:54
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Martin Baroš:7.1.2022 20:52

Ahoj, skvělý článek. Celá tahle série se mi moc líbí a moc mi to pomáhá. Dvě věci v tomto článku mě ale zarazily.

Zaprvé, v první podkapitole "Adresy v paměti" se ve třetím odstavci píše "Céčko si řekne operačnímu systému o tolik paměti, kolik je pro tuto proměnnou třeba". To ale není tak úplně pravda, ne? Protože o tu paměť si řekne ta naše aplikace, která je v Céčku napsaná.

A ta druhá věc... ty obrázky znázorňující paměť počítače jsou špatně. Ano, datový typ int má 4 bajty, ale na obrázku má každý bajt jenom 4 bity namísto 8. Tohle je úplně zbytečná chyba, která uškodí jinak super článku.

 
Odpovedať
7.1.2022 20:52
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:7.1.2022 22:12

Funkci na vytvoření řetězce, kterou jsem napsal ve svém posledním příspěvku, lze ještě zjednodušit. Velikost textu který chceme do řetězce uložit si určíme přesně uvnitř funkce, není třeba alokovat určitou velikost a testovat, zda-li se nám tam text vejde. Funkce má tak pouze jeden parametr a to ukazatel na řetězec.

char* create_str(char* s) {

        char* p = (char*)malloc((strlen(s) + 1) * sizeof(char)); // alokace
        if (p) strcpy(p, s); // prirazeni textu

        return p;
}

Délku řetězce lze určit pomocí funkce strlen(), jejíž deklarace se nachází v hlavičkovém souboru string.h. Ta určí velikost řetězce bez ukončovacího znaku '\0'. Jelikož tento znak musí být součástí řetězce, je připočtena jeho velikost v podobě celočíselného literálu 1. Tedy délka řetězce včetně nulového znaku je (strlen(s) + 1). Následně je alokován prostor odpovídající přesné velikosti řetězce který chceme uložit. Následuje přiřazení řetězce pomocí funkce strcpy() a nakonec je vrácen ukazatel na začátek alokované paměti, tedy na začátek řetězce. V hlavním programu pak je třeba alokovanou paměť uvolnit, to zůstává beze změny.

Odpovedať
7.1.2022 22:12
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Jakub Hrbáček:25.4.2022 17:46

Ahoj, skvělá lekce, jen mi trošku nejdou do hlavy ty obrázky s adresami a obsahem v paměti. Možná se pletu, ale nemělo by být v políčku na jedné adrese 8 cifer? Píše se že jedna adresa má 1B, což je 8 bitů a na obrázcích jsou v políčku na jedné adrese jen 4 cifry - bity. Takže třeba u prvního obrázku, kde je v paměti uložena proměnná typu int (4B - 32b) s hodnotou 56 by neměla být takto?

0x23aadc [0000 0000]
0x23aadd [0000 0000]
0x23aade [0000 0000]
0x23aadf  [0011 1000]
 
Odpovedať
25.4.2022 17:46
Robíme čo je v našich silách, aby bola tunajšia diskusia čo najkvalitnejšia. Preto do nej tiež môžu prispievať len registrovaní členovia. Pre zapojenie sa do diskusie sa zaloguj. Ak ešte nemáš účet, zaregistruj sa, je to zadarmo.

Zobrazené 10 správy z 58.