Pouze tento týden sleva až 80 % na e-learning týkající se JavaScriptu
Aktuálně: Postihly zákazy tvou profesi? Poptávka po ajťácích prudce roste, využij slevové akce 30% výuky zdarma!
Discount week - April - 30

Lekce 1 - Úvod do ukazatelů (pointerů) v jazyce C

Vítejte u první lekce pokročilého kurzu o programování v jazyce C. V tomto kurzu se naučíme pracovat s dynamicky alokovanou pamětí v jazyce C a dostaneme se také k práci se soubory. Asi vás nepřekvapí, že předpokladem ke zdolání seriálu je znalost základních konstrukcí jazyka C.

Adresy v paměti

Když jsme se poprvé zmiňovali o proměnných, říkali jsme si, že proměnná je "místo v paměti", kam si můžeme uložit nějakou hodnotu. Také víme, že proměnné mají různé datové typy (např. int) a ty zabírají v paměti různě místa (např. int zabírá 32 bitů, tedy 32 nul a jedniček).

Paměť počítače si můžeme představit jako dlouhou (téměř nekonečnou :) ) řadu nul a jedniček. Některé části paměti jsou obsazené jinými aplikacemi a některé jsou operačním systémem chápány jako volné místo. Aby se dalo s pamětí rozumně pracovat, je adresována, jako jsou např. domy v ulici. Adresy se většinou zapisují v šestnáctkové soustavě, ale stále se jedná o obyčejná čísla. Adresy jdou chronologicky za sebou a na každé adrese se nachází 1 bajt (tedy 8 bitů, protože adresování po drobných bitech by bylo nepraktické).

Jakmile v céčku deklarujeme nějakou proměnnou ve zdrojovém kódu a aplikaci spustíme, Céčko si řekne operačnímu systému o tolik paměti, kolik je pro tuto proměnnou třeba. Od systému získá přidělenou adresu do paměti, na kterou může hodnotu proměnné uložit (zjednodušeně řečeno). Tento proces nazýváme alokace paměti.

Získání adresy proměnné

Jazyk C nás od adres zatím plně odsťiňoval, paměť alokoval za nás a s našimi proměnnými jsme pracovali jednoduše pomocí jejich jmen. Vytvořme si nyní jednoduchý program, který založí proměnnou typu int a do ní uloží hodnotu 56. Adresu této proměnné si získáme pomocí tzv. referenčního operátoru & (ampersand) a vypíšeme ji do konzole. Ve formátovacím řetězci použijeme %p, což ji vypíše v šestnáctkové soustavě tak, jak se na paměťovou adresu sluší a patří.

int main(int argc, char** argv) {
    int a;
    a = 56;
    printf("Proměnná a s hodnotou %d je v paměti uložená na adrese %p", a, &a);
    return (EXIT_SUCCESS);
}

Výsledek:

c_pointery
Proměnná a s hodnotou 56 je v paměti uložená na adrese 0x23aadc

Vidíte, že na mém počítači si systém vybral adresu 0x23aadc. Vy tam budete mít jiné číslo. Situace v paměti počítače bude vypadat takto:

Paměť počítače

(Datový typ int má 32 bitů, proto tedy zabírá 4 osmice bitů na 4 adresách. Udáváme vždy adresu začátku hodnoty.)

Ukazatelé (pointery)

Získat číslo adresy je sice hezké, ale pokud bychom s pamětí takto pracovali, bylo by to poněkud nepraktické. Z toho důvodu jazyk C podporuje tzv. ukazatele (anglicky pointery). Ukazatel je proměnná, jejíž hodnotou je adresa někam do paměti. Céčko ukazatel však nebere jako pouhé číslo, ale ví, že ho má používat jako adresu. Když do ukazatele tedy něco uložíme nebo naopak vypíšeme jeho hodnotu, nevypisuje se adresa (hodnota ukazatele), ale použije se hodnota, na kterou ukazatel ukazuje.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Vraťme se opět k našemu programu. Tentokrát si kromě proměnné a definujeme i ukazatel na proměnnou a. Ten bude také typu int, avšak před jeho názvem bude tzv. dereferenční operátor * (hvězdička). Zvykněte si pointery pojmenovávat vždy tak, aby začínaly na p_. Vyhnete se tak v budoucnu velkým problémům, protože pointery jsou poměrně nebezpečné, jak dále zjistíme, a měli bychom si srozumitelně označit, zda je proměnná pointerem či nikoli.

int main(int argc, char** argv) {
    int a, *p_a;
    a = 56;
    p_a = &a; // Uloží do p_a adresu proměnné a
    *p_a = 15; // Uloží hodnotu 15 na adresu v p_a
    printf("Ukazatel p_a má hodnotu %d ukazuje na hodnotu %d", p_a, *p_a);
    return (EXIT_SUCCESS);
}

Aplikace si vytvoří proměnnou typu int a dále ukazatel na int. Ukazatelé také mají vždy svůj datový typ podle toho, na hodnotu jakého typu ukazují. Do proměnné a se uloží hodnota 56.

Do ukazatele p_a (zatím bez hvězdičky) se uloží adresa proměnné a, kterou získáme pomocí referenčního operátoru &. Nyní chceme tam, kam ukazuje pointer p_a, uložit číslo 15. Použijeme dereferenční operátor (*) a tím neuložíme hodnotu do ukazatele, ale tam, kam ukazatel ukazuje.

Následně vypíšeme hodnotu ukazatele (což je nějaká adresa v paměti, obvykle vysoké číslo, zde ho vypisujeme v desítkové soustavě) a dále vypíšeme hodnotu, na kterou ukazatel ukazuje. Kdykoli pracujeme s hodnotou ukazatele (ne adresou), používáme operátor *.

Výsledek:

c_pointery
Ukazatel p_a má hodnotu 23374500 ukazuje na hodnotu 15

Opět si ukažme i situaci v paměti:

Paměť počítače

Předávání referencí

Umíme tedy na proměnnou vytvořit ukazatel. K čemu je to ale dobré? Do proměnné jsme přeci uměli ukládat i předtím. Jednou z výhod pointerů je tzv. předávání referencí. Vytvořme si funkci, které přijdou v parametru 2 čísla a my budeme chtít, aby jejich hodnoty prohodila (této funkci se anglicky říká swap). Naivně bychom mohli napsat následující kód:

// Tento kód nefunguje
void prohod(int a, int b)
{
    int pomocna = a;
    a = b;
    b = pomocna;
}

int main(int argc, char** argv) {
    int cislo1 = 15;
    int cislo2 = 8;
    prohod(cislo1, cislo2);
    printf("V cislo1 je číslo %d a v cislo2 je číslo %d.", cislo1, cislo2);
    return (EXIT_SUCCESS);
}

Výsledek:

c_pointery
V cislo1 je číslo 15 a v cislo2 je číslo 8.

Proč že aplikace nefunguje? Při volání funkce prohod() ve funkci main() se vezmou hodnoty proměnných cislo1 a cislo2 a ty se zkopírují do proměnných a a b v definici funkce. Funkce dále změní tyto proměnné a a b, avšak původní proměnné cislo1 a cislo2 zůstanou beze změny. Tomuto způsobu, kdy se hodnota proměnné do parametru funkce zkopíruje, říkáme předávání hodnodnou.

Všimněte si, že k prohození 2 čísel potřebujeme pomocnou proměnnou. Kdybychom ve funkci prohod() napsali jen a = b; b = a;, byla by v obou proměnných hodnota b, protože hodnota a se prvním příkazem přepsala.

Libovolnou proměnnou můžeme předat referencí a to tak, že funkci upravíme aby přijímala v parametrech pointery. Při volání takové funkce potom použijeme referenční operátor &:

void prohod(int *p_a, int *p_b)
{
    int pomocna = *p_a;
    *p_a = *p_b;
    *p_b = pomocna;
}

int main(int argc, char** argv) {
    int cislo1 = 15;
    int cislo2 = 8;
    prohod(&cislo1, &cislo2);
    printf("V a je číslo %d a v b je číslo %d.", cislo1, cislo2);
    return (EXIT_SUCCESS);
}

Výsledek:

c_pointery
V cislo1 je číslo 8 a v cislo2 je číslo 15.

Jelikož funkci nyní předáváme adresu, je schopna změnit původní proměnné.

Někteří programátoři v jazyce C používají často parametry funkcí k vracení hodnoty. To ovšem není příliš přehledné a pokud nás netlačí výpočetní čas a je to jen trochu možné, měla by funkce vždy vracet jen jednu hodnotu pomocí příkazu return, případně může vracet strukturu nebo ukazatel na strukturu/pole.

Možná vás napadlo, že konečně rozumíte funkci scanf(), která ukládá hodnoty do proměnných předaných parametry. Operátor & zde používáme proto, abychom funkci předali adresu, na kterou má data uložit:

int a;
scanf("%d", &a);

Předávání pole

Pole a pointery mají v céčku mnoho společného. Proto když předáme pole do parametru nějaké funkce a pole v ní změníme, změny se v původním poli projeví. Pole je na rozdíl od ostatních typů vždy předáváno referencí aniž bychom se o to museli snažit.

void napln_pole(int pole[], int delka)
{
    int i;
    for (i = 0; i < delka; i++)
    {
        pole[i] = i + 1;
    }
}

int main(int argc, char** argv) {
    int cisla[10];
    napln_pole(cisla, 10);
    printf("%d", cisla[5]); // Vypíše číslo 6
    return (EXIT_SUCCESS);
}

Jak jsme si řekli dříve, pole je vlastně spojité místo v paměti. Ale takové místo musíme umět nějak adresovat. Adresujeme ho právě pomocí ukazatele. Proměnná typu pole totiž není nic jiného než ukazatel. To znamená, že nám bez problémů projde následující operace přiřazení:

int pole[10];
int* p_pole = pole;

V kapitole Aritmetika ukazatelů si ukážeme, že je úplně jedno, zda máme pole nebo ukazatel.

NULL

Všem pointerům libovolného typu můžeme přiřadit konstantu NULL. Ta udává, že je pointer prázdný a že zrovna na nic neukazuje. Na většině platforem se NULL rovná hodnotě 0 a tak se v některých kódech můžete setkat s přiřazením 0 místo NULL. To se obecně nedoporučuje kvůli kompatibilitě mezi různými platformami. Tuto hodnotu budeme v budoucnu hojně používat.

Co si zapamatovat: Pointer je proměnná, ve které je uložena adresa do paměti. Můžeme pracovat buď s touto adresou nebo s hodnotou na této adrese a to pomocí operátoru *. Adresu libovolné proměnné získáme pomocí operátoru &.

Ačkoli jsme si pointery poměrně slušně uvedli, jejich pravým účelem je zejména dynamické alokování paměti. Na které se podíváme hned v příští lekci, Dynamická alokace paměti v jazyce C.


 

Všechny články v sekci
Dynamická práce s pamětí v jazyce C
Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
9 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 13 let. Má rád Nirvanu, sushi a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity (8)

 

 

Komentáře

Avatar
Eva D.
Člen
Avatar
Eva D.:3.12.2014 16:34

Dobrý den, chtěla bych Vás upozornit na chybu v posledním zdrojovém kódu ve funkci printf() chybí řídící řetězec formátu. Jinak bych Vám chtěla poděkovat za Vaše články, dost mi, jakožto "programátorovi - začátečníkovi", pomáhají.

 
Odpovědět
3.12.2014 16:34
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Eva D.
David Čápka:3.12.2014 16:55

Díky, opravil jsem :)

Odpovědět
3.12.2014 16:55
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Lukáš Hruda
Redaktor
Avatar
Lukáš Hruda:3.12.2014 21:02

Možná by bylo dobré uvést něco o endianitě. Třeba v příkladu přiřazení hodnoty 15 na adresu 0x23aadc by na Win 7 bylo pořadí bytů přesně obráceně. Pokud člověk nějakým způsobem pracuje s jednotlivými byty v paměti, je dobré o tom vědět.

 
Odpovědět
3.12.2014 21:02
Avatar
Matej
Člen
Avatar
Matej:11.10.2015 12:26

Ako si Ccko interpretuje argumenty? Resp v kode

void prohod(int *p_a, int *p_b)
{
    int pomocna = *p_a;
    *p_a = *p_b;
    *p_b = pomocna;
}

ked sa dosadi &a ,&b , sa len vymeni pamet premennej (tj pamat a = pamat b tj aj hodnota ) alebo iba hodnota (v pamati , (hodnota pameti a sa nadstavi na b ). Ak je to ten prvy pripad , neni zbytocne davat pointeri ako parametre funkcie?

Editováno 11.10.2015 12:26
 
Odpovědět
11.10.2015 12:26
Avatar
tomisoka
Redaktor
Avatar
Odpovídá na Matej
tomisoka:11.10.2015 12:58

Vymění se jen hodnota.

Pokud se volá funkce tak argumenty se kterými pracuješ ve funkci jsou jenom kopie. Takže pokud by jsi tu funkci měl napsanou bez těch pointerů, tak by to prohodilo jenom ty kopie (z venku by to vypadalo, jako že ta funkce nic neudělala). Ale protože jako argument máš pointer, tak ten pointer je sice kopie, ale ukazuje na stejnou adresu jako originální pointer.

Editováno 11.10.2015 12:59
 
Odpovědět
11.10.2015 12:58
Avatar
Matej
Člen
Avatar
Matej:11.10.2015 14:42

ak dosadime do funkcie &a a &b

void prohod(int *p_a, int *p_b) // dosadime &a , &b
{
    int pomocna = *p_a; // pomocna je refencia na pamat a (&a)
    *p_a = *p_b; // pamat _a sa nadstavi na pamat _b tj aj na odkazujucu hodnotu
    *p_b = pomocna; // naopak
}

ak sa to bere takto , tnak sa nezmeni iba hodnota ale swapne sa cela pamat aj s hotou nie? podla predchadzajuceho prikladu na pointeri by sa hodota swapla iba ak by dany pointer (teda ak som pochopil spravne pointer v argument liste sa spava ako pointer) musel odkazovat na danu hodnotu tj

*p_a=b

a nie

*p_a=&b //referencia na pamat

V c som novy takze si to asi zle vykladam ale stale mi to nesedi :D

 
Odpovědět
11.10.2015 14:42
Avatar
tomisoka
Redaktor
Avatar
Odpovídá na Matej
tomisoka:11.10.2015 16:00

Však taky platí:

p_a=&a

a z toho plyne:

*p_a=a

A okomentovaná funkce:

void prohod(int *p_a, int *p_b){ // dosadime &a, &b
  int pomocna = *p_a;//pomocna je přepsána hodnotou na kterou ukazuje pointer p_a(a)
  *p_a = *p_b; // hodnota na kterou ukazuje pointer p_a (a) je přepsána hodnotou na
  //kterou ukazuje pointer p_b (b)
  *p_b = pomocna; // hodnota na kterou ukazuje p_b (b) je přepsána hodnotou pomocna
}

Jinak dál se ve tvém komentáři nějak ztrácím, co znamená:

tnak sa nezmeni iba hodnota ale swapne sa cela pamat aj s hotou nie?

 
Odpovědět
11.10.2015 16:00
Avatar
Matej
Člen
Avatar
Odpovídá na tomisoka
Matej:11.10.2015 16:37

ak som to spravne pochopil tak ak das

*p_a=&a

tak vlastne nastane

p_a=&a;
*p_a= hodnota a

a ked dosadis do pointeru ktory je ako argument adresu k pameti ako parameter, tak sa priradi hodnota k tomu pointeru? :D

 
Odpovědět
11.10.2015 16:37
Avatar
tomisoka
Redaktor
Avatar
Odpovídá na Matej
tomisoka:11.10.2015 17:03

Ale v tom kódu není nic jako:

*p_a=&a

Ta část v parametru "int *" je datový typ a "p_a" je název proměnné.
Pokud do pointeru "p_a" dosadíš adresu k paměti, tak pak pomocí "*p_a" pracuješ s hodnotou, která se nachází na té dosazené adrese. Samotná hodnota není k ničemu přiřazená, ta se prostě nachází na té adrese.

Editováno 11.10.2015 17:04
 
Odpovědět
11.10.2015 17:03
Avatar
pangas
Člen
Avatar
pangas:10.3.2016 14:10

Zdravím, měl bych malý dotaz. Nějak se mi nedaří inicializovat ukazatel na dvourozměrné pole. Chybové hlášení zní: cannot convert 'char()[26]' to 'char' in initialization.

char abcd[26][26];
char * u = abcd;

Díky za každý tip.

 
Odpovědět
10.3.2016 14:10
Avatar
ostrozan
Redaktor
Avatar
Odpovídá na pangas
ostrozan:10.3.2016 15:45
char abcd[26][26];

je sám o sobě ukazatel - a ty chceš ukazatel na ukazatel?

 
Odpovědět
10.3.2016 15:45
Avatar
B42P6
Člen
Avatar
Odpovídá na pangas
B42P6:10.3.2016 15:47

Ahoj, chyba musí byť niekde inde. Čo máš na riadku kde sa vyskytla chyba?

Odpovědět
10.3.2016 15:47
'long long long' is too long for GCC
Avatar
B42P6
Člen
Avatar
Odpovídá na ostrozan
B42P6:10.3.2016 16:45

Nemyslel si

char *pole[5];
(pole pointer-ov)

?
Teraz by získaním adresy tohto poľa dostal pointer na pointer.

Získaním adresy viacrozmerného poľa by získal len samotnú adresu poľa, pretože viacrozmerné pole je v pamäti uložené rovnako ako jednorozmerné.

Editováno 10.3.2016 16:45
Odpovědět
10.3.2016 16:45
'long long long' is too long for GCC
Avatar
pangas
Člen
Avatar
pangas:10.3.2016 17:02

Děkuji, už jsem problém vyřešil. :) Potřeboval jsem vlastně do funkce předat dvourozměrné pole a protože jsem si neuvědomil, že už to vlastně ukazatel je, tak jsem si myslel, že potřebuji jako parametr předat ukazatel na to pole, aby jsem po skončení funkce neměl zase prázdné pole.. Moje chyba, děkuji za odpovědi :-)

 
Odpovědět
10.3.2016 17:02
Avatar
Tomáš Svoboda:20.4.2016 18:05

Zdarec, nedochází mi to.
Chápu co pointery dělají, chápu jakým způsobem asi fungují, nechápu ale jak je využít. Pro dynamické alokování to bude asi esenciální ale mě to nedochází.

Plácnu, když bych je nepoužíval bude alokovaná paměť pro můj daný program při běhu neustále růst.. ? Zavolat proměnou odkudkoliv mohu přeci i tak přimo jejím názvem.

Díky :) [ Tom, the "rozmazlen pythonem"]

Odpovědět
20.4.2016 18:05
Alea iacta est.
Avatar
gusto
Člen
Avatar
Odpovídá na tomisoka
gusto:22.9.2017 15:37

Ale od kedy je int* datovy typ ? Ved datovy typ je len int. A pokial si dobre pamatam, tak tato funkcia

void prohod(int *p_a, int *p_b)
{
    int pomocna = *p_a;
    *p_a = *p_b;
    *p_b = pomocna;
}

ma definovane 2 smerniky na datovy typ int a v tele je dalsia obycajna premenna "pomocna" datoveho typu int

 
Odpovědět
22.9.2017 15:37
Avatar
Honza Skřivánek:30.1.2018 18:41

Jen bych dodal na okraj k té poznámce, že pomocná proměnná není k prohození dvou hodnot potřeba. Například pomocí bitového XORu to jde udělat takto:

int x = 15;                     // 1111
int y = 7;                      // 0111
x = x^y;                        // 1000
y = x^y;                        // 1111 zde už je v y původní hodnota x
x = x^y;                        // 0111 a zde máme v x původní y

Jde to pochopitelně provést i například pomocí sčítání a odčítání (tam ale pozor na overflow)

Editováno 30.1.2018 18:42
 
Odpovědět
30.1.2018 18:41
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Honza Skřivánek
DarkCoder:30.1.2018 20:52

Ano, to je další způsob. Tento způsob se ale v praxi nepoužívá a to z důvodu efektivity. Většina moderních překladačů může optimalizovat dočasnou proměnnou tak, že používá stejné množství paměti a registrů jako za použití XOR. Způsob za použití dočasné proměnně je přinejmenším stejně tak rychlý a často i rychlejší.

Odpovědět
30.1.2018 20:52
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Honza Skřivánek:30.1.2018 21:10

To je fakt, je to spíš taková zajímavost, se kterou se člověk může setkat třeba u pohovoru :-) Navíc tam nastane problém pokud se obě hodnoty budou rovnat. Na druhou stranu, třeba u nějakých jednočipů to stále může mít smysl.

 
Odpovědět
30.1.2018 21:10
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Honza Skřivánek
DarkCoder:30.1.2018 21:30

Přesně tak. Jeho využití je hodně specifické, zejména mikrokontroléry, tam kde paměti není nazbyt a kryptografické aplikace. Z tohoto důvodu jsou překladače optimalizované jinak..

Odpovědět
30.1.2018 21:30
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:29.8.2019 20:32

to nechpu, XOR ma operator znaceny '^'? A proc by xorovani promennych bylo menne efektivni jako incializace pomocne promenne? (jak tvrdis, ze by byla prinejmensim stejne rychla). Jedna se tedy o efektivitu s pohledu mista v pameti nebo efektivitu vypocetnich instrukci? (pamet vs procesor)

 
Odpovědět
29.8.2019 20:32
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:29.8.2019 21:01

Ano, jedná se o bitový operátor XOR (nonekvivalence) a ten má skutečně označení '^'. Operátor XOR nastavuje bit na 1, když jsou oba bity navzájem různé. Je třeba rozlišovat bitové operátory od logických. Jedná se o efektivitu z hlediska počtu výpočetních instrukcí při zachování stejné velikosti potřebné paměti.

Odpovědět
29.8.2019 21:01
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:29.8.2019 22:07

no ale stale nechapu proc by xorovani melo byt vypocetne slozitejsi. Chapu ze to je operace navic, ale prece to je skoro nic pro dnesni procesory

 
Odpovědět
29.8.2019 22:07
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:29.8.2019 22:35

Je to z několika důvodů: Za použití pomocné proměnné dochází k přímému kopírování, kdežto u použití XOR dochází navíc k porovnávání jednotlivých bitů. Dále u procesorů řady x86 jak i Intelu tak i u AMD dochází při pohybu mezi registry k nulové odezvě (eliminace MOV). A nakonec XOR algoritmus je závislý na výsledku předchozí operace. Moderní CPU se snaží provádět instrukce paralelně prostřednictvím tzv. instrukčních rour. A jelikož výměna obsahu proměnných za použití XOR je závislá na předchozím subvýsledku, nelze použít výhod paralelismu. To způsobuje, že výměna obsahu proměnných za použití XOR bude pomalejší nežli za použití pomocné proměnné.

Odpovědět
29.8.2019 22:35
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:29.8.2019 22:49

predpokladam ze "(eliminace MOV)" - je prikaz pro ulozeni hodnoty na adresu? Nejsem si jisty , ale videl jsem to mam dojem v assembleru. Snad na to tady taky casem budou nejake clanky

 
Odpovědět
29.8.2019 22:49
Avatar
Michal H.
Člen
Avatar
Odpovídá na gusto
Michal H.:23.2.2020 6:06

no nevím jak v c, ale například c ++ nejde uložit adresa do ukazatele takto...

int * p_int;
p_int = 0xC7000000;

tohle udělat nelze. Snažíš se do adresy uložit číslo. Číslo musíš explicitně přetypovat aby se z něj stala vlastně adresa.

p_int = (int *) 0xC7000000;

tak že int* bude asi typ :) A ukazatele patří mezi složené typy. Řekl bych že tady rozdíl mezi c a c++ moc velký nebude.

 
Odpovědět
23.2.2020 6:06
Avatar
krepsy3
Redaktor
Avatar
krepsy3:10.10.2020 16:36

V článku se píše:

...Proměnná typu pole totiž není nic jiného než ukazatel. To znamená, že nám bez problémů projde následující operace přiřazení:

int pole[10];
int* p_pole = cisla;

To je nějaký divný (ten kód). Zkusil jsem se nad tím zamyslet, a došlo mi - nemá na druhém řádku být 'int* p_pole = pole;'?

Odpovědět
10.10.2020 16:36
Programátor je stroj k převodu kávy na kód.
Avatar
Odpovídá na krepsy3
Patrik Valkovič:10.10.2020 16:43

Ano má, díky za upozornění. Oprava se již schvaluje.

Odpovědět
10.10.2020 16:43
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
Jozef Vendel
Člen
Avatar
Jozef Vendel:11. ledna 17:42

Je škoda a sklamanie, že v tomto kurze nie sú praktické cvičenia k jednotlivým lekciám ako to bolo v kurze Základy C.

 
Odpovědět
11. ledna 17:42
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Jozef Vendel
DarkCoder:11. ledna 18:33

Pokud Tě zajímají ukazatele a jejich praktické použití, což je alfou a omegou programování v C, pak není nic snazšího, než že napíšeš příspěvek do C sekce s tím, co Tě zajímá.

Odpovědět
11. ledna 18:33
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Jozef Vendel
Člen
Avatar
Odpovídá na DarkCoder
Jozef Vendel:12. ledna 18:52

Takto ti teraz na zaciatku kuru "C ukazatele" neviem povedat, co ma zaujima. Urobil som si prvy kurz "zaklady C" a ten obsahoval aj prakticke cvicenia, ktore ma bavili a pacila sa mi ta myslienka, ze za kapitolou bola prakticka ukazka a nemusel som nic hladat niekde po inych strankach.

 
Odpovědět
12. ledna 18:52
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Jozef Vendel
DarkCoder:12. ledna 20:45

Otázek by naopak mělo být spousty. Proč, jak, kdy a kde ukazatele používat. Ale abys pochopil skutečnou sílu ukazatelů, na to si budeš muset chvilku počkat. Jedná se o jednu z nejtěžších praktik programování v C, ale zároveň i jednu z nejdůležitějších, bez které se u větších projektů a výkonných aplikací neobejdeš. Není to jen líbivá myšlenka, že po teorii by měly následovat ukázky příkladů a praktická cvičení. To je hlavní postup, který činní programování úspěšným.

Pro pochopení ukazatelů je teoretická část důležitější než v kterékoli jiné oblasti. Je předpokladem pro pochopení toho jak to funguje.

Přečti si zdejší články o ukazatelích co tu najdeš a založ nový příspěvek. Zítra Ti do něj napíšu otázky týkající se pouze základů o ukazatelích, na kterých zjistíš, jak dobře si pochopil danou látku.

Odpovědět
12. ledna 20:45
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Jozef Vendel
Člen
Avatar
Jozef Vendel:12. ledna 21:47

Ešte som si neprešiel všetky lekcie z tohto kurzu, ak si ich prejdem poctivo tak sa ti ozvem a tvoju myšlienku môžeme zrealizovať ;)

 
Odpovědět
12. ledna 21:47
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Jozef Vendel
DarkCoder:12. ledna 22:12

Nemusíš procházet všechny články, otázky budou z naprostých základů o ukazatelích. Nicméně čím více si toho projdeš, tím více Ti to může začít dávat smysl. Kdykoli se ozvi, příspěvky v sekci C pročítám pravidelně.

Editováno 12. ledna 22:12
Odpovědět
12. ledna 22:12
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Jozef Vendel
Člen
Avatar
Odpovídá na DarkCoder
Jozef Vendel:14. února 15:21

AhojDarkCoder, prešiel som si celý kurz "Jazyk C a ukazatele" aj s doplnením teoretických informácií z knižky Učebnica jazyka C od P.Herouta. Momentálne si hľadám praktické príklady, kde by som si precvičoval prácu s pointermi. Môžeš mi položiť otázky, ak máš záujem. Poprípade, ak poznáš dobrú stránku s praktickými príkladmi jazyka C(momentálne stačí práca s pointermi) môžeš sa o ňu podeliť.
Ďakujem.
J.V.

 
Odpovědět
14. února 15:21
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Jozef Vendel
DarkCoder:14. února 19:46

Môžeš mi položiť otázky, ak máš záujem.

Dobrá. Jak jsem slíbil, zde je pár otázek a cvičení ohledně základů ukazatelů v C,
do kterých se můžeš pustit, zodpovědět je a zpracovat.

1. Jaké má jazyk C ukazatelové operátory, jak se nazývají a jaký je jejich význam?
Uveď příklad.
2. Jak vypadá inicializace ukazatele na typ float, který na nic neukazuje.
Uveď příklad.
3. Máš proměnou q typu int. Přiřaď ji nějakou hodnotu. Vypiš její adresu a hodnotu pomocí ukazatele.
Uveď příklad.
4. Je následující úryvek kódu správný? Pokud ne, proč?

int *p;
*p = 100;

5. Je následující úryvek kódu správný? Pokud ne, proč?

int *p;
double q, val = 1234.56;

p = &val;
q = *p;
printf("%f", q);

6. Jaké operátory lze použít pro práci s ukazateli?
7. Máš ukazatel p typu int, který obsahuje hodnotu 100.
Představuje tato hodnota adresu? Jaká hodnota ukazatele by byla po provedení následujícího příkazu?

p = p + 4;

8. Máš proměnnou q typu int, jejíž hodnota je 100.
Jak by vypadal zápis, abychom inkrementovali její hodnotu pomocí ukazatele.
Uveď příklad.
9. Máš 5 prvkové pole celých čísel. Vypiš hodnotu čtvrtého prvku pomocí ukazatele.
Uveď příklad.
10.Lze indexovat ukazatele jako by to bylo pole?
11. Hodnotu i-tého prvku pole pomocí indexů lze získat takto: pole[i]
Jak vypadá zápis pomocí kterého lze získat hodnotu i-tého prvku pomocí ukazatele?
Uveď příklad.
12. Co představuje jméno pole bez indexu? V čem se liší od klasického ukazatele?
13. kdy je třeba použít ukazatel jako parametr?
Uveď příklad.
14. Jak vypadá deklarace deklarace pětiprvkového pole ukazatelů na int a v jak vypadá
deklarace ukazatele na pětiprvkové pole typu int.
15. Je následující úryvek kódu správný? Pokud ne, proč?

char *p = "Ucim se ukazatele";
pritnf(p);

16. Napiš alespoň dva způsoby, jak lze předat jednorozměrné pole typu float jako parametr funkce.
17. Napiš funkci, která vrátí počet znaků v řetězci zadaného, předaného jako argument funkce.
18. Máš následující inicializaci pole:

char str[] = "Ucim se ukazatele";

Vytvoř dva ukazatele p_zacatek a p_konec a ukazatel p_zacatek nastav na začátek str
a ukazatel p_konec nastav na konec str. (Nápověda: strlen())
19. Napiš funkci která vrátí obvod a obsah kruhu, funkce přebírá poloměr jako argument funkce.
20. Napiš funkci, která zamění v řetězci str, původní znak c_puvodni za nový znak c_novy.
Funkce vrátí ukazatel na tento upravený řetězec.

Odpovědět
14. února 19:46
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Jozef Vendel
Člen
Avatar
Odpovídá na DarkCoder
Jozef Vendel:18. února 18:29

1.Tak operator „*“ v kontexte *p_i(inak to znamena nasobenie), ktory vyuzivame na oznacenie kompilatoru, ze premena p_i je typu poiner na nejaky datovy typ. Dalej pri pointeroch vyuzívame referencny operator „&“ na ziskanie adresy premennej v pameti. Napr... int *p_i, i; i = 5; /*Ak by som pouzil nasledovny prikaz *p_i = 5 pri dynamickom pridelovani; .. bolo by to chybne, pretoze pointer p_i ukazuje na nahodnu adresu v pameti, preto vyuzivame referencny operator &*/ p_i = &i; /*prikazom priradenia sme adresu premenej i ulozili do premenej p_i, cize hodnotu ktoru ma premenna p_i je hodnota, ktoru ma aj premena i a to je 5, a teraz pomocou premenej p_i vieme upravovat hodnotu, ktora lezi v premenej i.....*/
2.

float *p_f;
p_f = (float *) malloc(sizeof(float));
p_f  = NULL;

3.

int *p_q, q; q = 5; p_q = &q; printf("adresa premenej q je %p a hodnota ulozena v premenej je %d", p_q, *p_q);

4. V knizke od P.Heourta som cital, ze pokial je to staticke priradenie tak je to spravne ale pokial je to dynamicke priradenie tak nie. Pointer p na int ukazuje na nahodne miesto v pamäti takže sme na náhodnej adrese v pamäti nainicializovali hodnotu 100. Tu som sa trosku stratil chapem co je staticke a co dynamicke ale mohol by si mi to vysvetlit este raz podla seba.
5.Nie lebo premenne q, val su typu double a pointer p je typu int, nie je spravne miesat rozne datove typy na oboch stranach, je potrebne pretypovanie.
6. <, <=, >, >=, !=, ==, +, -, ++, --
7.Nie, na adresu v p(kde je uložený int) dá hodnotu 4.Prikaz zobrazený nižšie môžeme použiť napríklad ak mame pole 10-in int prvkov a chceme zistit adresu 5teho prvku tak k prvemu prvku v poli p pripocitame +4( zalezi od kompilatora a od OS ak som spravne pochopil ale vacsinou je int ukaldany ako 4B) to znamena že 4*4=16B, čiže nasledujúci 20B(jeho adresa) bude zapísaná v pointri p.
8.

int *p_q, q;
q = 100;
p_q = &q;
*p_q += 1;
printf("Nova hodnota *p_q je:%d", *p_q);

9.

int *p_q;
    int pole[5] = {2, 5, 7, 10, 11};
    p_q = &pole[0];
    printf("Hodnota 4.prvku je %d", *(p_q + 3));

10.Nie.V priklade 9 som ukazal ako je mozne pristupovat k jednotlivym prvkom pola.
11.*(p_i + i);
12.Staticky pridelena pamät pre prvky v poli. Ukazatele maju pridelenu pamäť dynamický.
13.Keď chceme vo funkcií trvalo zmeniť hodnotu skutočného parametru obyčajného dátového typu alebo hodnotu pointeru. Napr ak chceme zemnit hodnotu skutočného parametru obyčajneho datoveho typu: ..

void vymen(int *a, int *b);
void vymen(int *a, int *b){
    int pomocne;
    pomocne = *a;
    *a = *b;
    *b = pomocne;
  }
int main(){
    int o = 3, v = 4;
    vymen(&o, &v);
printf("Hodnoty po vymene o:%d v:%d", o, v); }

14. int** pole = (int**)malloc(5 * sizeof(int));
Int pole = (init) malloc (5*sizeof(int));
15.Spravny, lebo p nepredstavuje momentalne dynamicky retazec, ale pointer na ryp char. A tento pointer je inicializovany adresou retazovej konstanty, ktora ma obsah Ucim se ukazatele. Len akurat som sa este nikde nestretol s vypisom takeho printf(p), co asi je dobre, lebo printf ma parameter retaca ktory ma vypisat na obrazku, ale rozmyslal nad tym, ci je v retazci ulozeny ukoncovaci znak ‚\0‘.
16. void vymen(float a[], int pocet) alebo void vymen(float *a, int pocet); ..Pridal som este parameter pocet lebo, ak pracujeme vo funkcii s polom, poterbujeme poznat jeho velkost a ak pracujeme s poliami, ktorych velkost nie je konstantna, musime ju funkcii oznamit pouzitim dalsieho parametra. Vo funkcii main funkciu vymen zavolam takto .. vymen(a, pocet);... Na tom ci a je pole staticke alebo dynamicke nezalezi. Ak by som chcel napr. urobit vymenu v poli float f[10] medzi stvrtym a desiatym prvkom, tak by som mohol zavolat takto vymen(f+3, 6);
17.

int pocet(char *retazec1);
int pocet(char *retazec1){
   int vysledok = strlen(retazec1);
    return vysledok;
}
int main(){
    int v;
    char *retazec = (char*) malloc(sizeof(char));
    printf("Zadaj retazec\n");
    scanf("%[^\n]s", retazec);
    v = pocet(retazec);
    printf("\nDlzka retazca je:%d", v);
}

Urobil som to takto ale viem, ze je to zle. Nevedel som urobit funkciu aby som v nej predaval dalsi formalny parameter a to velkost pola.
18.

int dlzka;
   char str[] = "Ucim se ukazatele";
   char *p_zacatek, *p_konec;
   dlzka = strlen(str);
   p_zacatek = &str[0];
   p_konec = &str[dlzka - 1];
   printf("Vypisujem prvy znak %c a posledny znak retazca %c ", *p_zacatek, *p_konec);

19.

#define PI 3.1415927
float *vypocet(float r);


void main() {
 float *a, r;
 printf("Zadaj polomer v cm: ");
 scanf("%f", &r);
 a = vypocet(r);
 printf("Obvod je %.2f a obsah je %.2f", *a, *(a + 1));

}

float *vypocet(float r) {
  float *b = (float*) malloc(2 * sizeof(float));
  float o, s;
  o =  PI * r * r;
  s =  2 * PI * r;
  *b = o;
  *(b + 1) = s;
  return (b);

  free(b);
}

20.

#define VELKOST 17

char *zmen(char c_znak, char c_znak2);

int main() {
  char *a, c_znak, c_znak2;
//  a = (char*) malloc(sizeof(char));
  printf("Zadaj znak, ktory chces zmenit\n");
  scanf(" %c", &c_znak2);
  printf("Zadaj novy znak\n");
  scanf(" %c", &c_znak);
  a = zmen(c_znak, c_znak2);
  printf("Retazec po zmene: %s\n", a);

  return 0;
  // free (a);
  // a = NULL;
}


char *zmen(char c_novy, char c_stari) {
  static char str[] = "Ucim se ukazatele";
  int i, skuska = 0;
  printf("Retazec pred zmenou: %s\n", str);

  for (i = 0; i < VELKOST; i++){
      if (str[i] == c_stari){
          str[i] = c_novy;
          skuska++;
      }
  }

  if (skuska == 0){
      printf("Zadal si znak, ktory sa nenachadza v retazci, nenastala zmena\n");
  }

   return (str);
}
 
Odpovědět
18. února 18:29
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Jozef Vendel
DarkCoder:19. února 5:43

Zde je vyhodnocení toho, jak si se vypořádal s otázkami a příklady k ukazatelům v C.
Ke každé otázce doplním i svůj komentář a řešení, aby si měl zpětnou vazbu.

1. Pro práci s ukazateli se používají dva speciální operátory (* a &). Operátor * má dva významy. První význam je ten, že se používá pro deklarování ukazatelové proměnné. Druhý význam je, že vrací hodnotu uloženou na adrese před kterou stojí. Tento operátor se nazývá operátor dereference nebo operátor indirekce. Druhý operátor & slouží pro získání adresy proměnné před kterou stojí. Tento operátor se nazývá operátor reference nebo adresový operátor.

Ukázkový příklad:

int i = 10, *pi = &i; // ukazatel pi je inicializovan adresou i
printf("%d", *pi); // vypis hodnotu i pomoci ukazatele

Operátor & lze vyjádřit slovy "adresa něčeho".
Operátor * lze vyjádřit slovy "na adrese"

Proto můžeš zápisy v příkladu číst následovně:
Přiřaď ukazateli pi adresu proměnné i
Vypiš hodnotu na adrese pi

Je-li hodnota proměnné získána pomocí ukazatele, říkáme, že je získána odkazem (dereferencováním, nepřímo).

Tedy správně si určil první význam operátoru *. Druhý chyběl. Význam operátoru & jsi uvedl správně. Avšak:

cize hodnotu ktoru ma premenna p_i je hodnota, ktoru ma aj premena i a to je 5

Ne. Hodnota pi není shodná s hodnotou i. p_i obsahuje adresu i, kdežto i obsahuje hodnotu 5.
Pomatuj: Ukazatel je proměnná, která obsahuje adresu objektu.

2. Inicializace ukazatelové proměnné na typ float, která na nic neukazuje, vypadá následovně:

float *p = NULL;

NULL je makro definované v mnoha hlavičkových souborech (stdio.h, string.h, ..) A je definováno jako:

#define NULL ((void*)0)

K následujícímu příkazu:

pf = NULL;

Toto není inicializace ale přiřazovací příkaz. Inicializace znamená přiřazení v definici.
Na dynamickou alokaci nebyly otázky ani cvičení mířeny, nikde není třeba.
Zápis který si uvedl je syntakticky správně, ovšem sémanticky chybně. Za prvé by bylo třeba kontrolovat, zda-li se podařilo alokovat paměť. Za druhé tuto paměť neuvolňuješ pomocí funkce free(), čímž vytváříš memory leak a za třetí dokonce přepisuješ ukazatel na neplatný a nikdy už se tak k alokované paměti nedostaneš. Tato paměť se uvolní až po skončení programu. Takže na to pozor..

3. V pořádku. Základní principy práce s referenčním a dereferenčním operátorem. Zde jen znovu připomínám. Výraz *p_q lze číst - hodnotu na adrese p_q. Všimni si, jak sufix proměnné napomáhá čtení. Pak lze číst výraz i takto - hodnotu q pomocí ukazatele.

4. Úryvek kódu pochopitelně není správný. Neboť ukazatel p neukazuje na žádný známý objekt. Proto je pokus o nepřímé přiřazení hodnoty pomocí p nesmyslný a nebezpečný.

5. Další úryvek kódu který není správný. Příklad ukazuje, proč je důležitý základní typ ukazatele a proč je třeba zajistit, aby byl typ ukazatele shodný s typem objektu na který ukazuje. Při přiřazení:

q = *p;

nedojde ke zkopírování čísla, neboť se přenesou pouze sizeof(int) bytů. Jelikož je p ukazatel na int, nelze jej použít pro přenos hodnoty odpovídající velikosti typu double. Toto je důvod, proč program nebude pracovat správně.

Obvykle míchání odlišných typů levé a pravé strany není vhodné. Někdy však to může být vhodné, jelikož jedním z pravidel je pravidlo při přiřazení, které říká, že typ pravé strany se převádí na typ levé strany.

6. Ano, všechny tyto operátory lze použít pro práci s ukazateli. Pouze doplním, že k nim samozřejmě patří i operátory * a & a také -> přístup k členům a [] přístup k prvkům.

7. Hodnota ukazatelové proměnné je adresa objektu na který ukazatel ukazuje. Tedy správná odpověď je že ano. Pokud ukazatel p bude obsahovat adresu 100, pak příkaz

p = p + 4;

Bude podle ukazatelové aritmetiky ukazovat na 4tý prvek za prvkem na, který ukazoval. Je-li tedy ukazatel p ukazatelem na int, pak adresa, na kterou bude ukazovat bude:

p = p + 4 * sizeof(int); // 100 + 4 * sizeof(int)

Tedy v p bude uložena hodnota 116 (ve 32bit prostředí, kde sizeof(int) == 4).
Velikost typu se určuje zásadně pomocí operátoru sizeof.

8. Je to správně. Snad jen že jazyk C má pro zvyšování o 1 speciální operátor inkrementace ++. Tedy příkaz pro zvyšování proměnné o 1 pomocí ukazatele by vypadal následovně:

(*p_q)++;

Smyslem tohoto příkladu bylo si uvědomit, jaký je rozdíl mezi

(*p_q)++;
// a
*p_q++;

V prvním případě se inkrementuje hodnota prvku, ve druhém případě se inkrementuje nejprve ukazatel a poté se bere hodnota na nové adrese. Příklad ukazuje důležitost použití závorek z důvodu priorit operátorů.

9. V pořádku. Správně si použil bázovou adresu pole a posun. Výsledkem součtu ukazatele a celého čísla je opět ukazatel. Výrazy *(p + i) a p[i] vyjadřují totéž. Výraz, kde je použita pointerová aritmetika je o něco efektivnější. Nicméně moderní kompilátory nahrazují indexaci pole právě adekvátním výrazem pointerové aritmetiky.

Ještě jedna drobnost. Příkaz:

p_q = &pole[0];

// je ekvivalentni prikazu

p_q = pole;

Často se vyskytuje a zapisuje ukazatel na pole druhou formou.
Pokud je vytvářen ukazatel na pole, nepíše se &. Jméno pole adresa na začátek pole.

10. Jde. Následující zápisy výpisu třetího prvku pole jsou totožné

int  pole[] = {1, 2, 3, 4, 5};
int p = pole;

printf("%d", *(pi + 2)); // ukazatelova aritmetika ukazatele
printf("%d", *(pole + 2)); // ukazatelova aritmetika pole
printf("%d", pole[2]); // indexace pole
printf("%d", pi[2]); // indexace ukazatele

Indexovat ukazatel nejspíš dělat nebudeš, neboť ukazatelová aritmetika je vhodnější.

11. Ano. Je třeba mít stále v paměti souvislost mezi *(p + i) a p[i].

12. Jméno pole bez indexu představuje ukazatel na začátek pole. Oproti klasickému ukazateli se liší v tom, že ukazatel na začátek pole je konstantní. Nelze jej tedy měnit. Pokud tedy chceme traversovat pole pomocí ukazatele, přiřadí se ukazatel na toto pole jinému ukazateli, který již lze modifikovat.

Pokud tedy máme pole

int  pole[] = {1, 2, 3, 4, 5};
int p = pole;

pak v programu je:

pole++; // neplatný příkaz
p++; platný příkaz

13. Funkci lze předat argument hodnotou nebo odkazem. Při volání hodnotou se předává kopie proměnné což má za následek, že změna proměnné se neprojeví vně funkce. Pokud se ovšem provádí volání odkazem, předává se adresa na tuto proměnnou a změny se pak projeví vně funkce. Otázkou může být, kdy použít volání hodnotou a kdy volání odkazem. Pomůcka je jednoduchá - volání odkazem, tedy kdy funkce přebírá ukazatel, se vytváří tehdy, chceme-li měnit hodnotu argumentu předaného jako parametr funkce.

Následující příklad ukazuje tuto problematiku:

#include <stdio.h>

void func1(int i);
void func2(int* i);

int main(void) {
        int val = 0;
        func1(val);
        printf("vne func1 %d\n", val);
        func2(&val);
        printf("vne func2 %d\n", val);
        return 0;
}

void func1(int i) {
        i = 10;
        printf("uvnitr func1 %d\n", i);
}

void func2(int *i) {
        *i = 10;
        printf("uvnitr func2 %d\n", *i);
}

Tedy ukazatel jako argument funkce použijeme tehdy, chceme-li, aby se změna proměnné promítla i mimo funkci nikoli jen uvnitř funkce.

14. Deklarace pětiprvkového pole ukazatelů na int vypadá následovně:

int *p[5];

Kdežto deklarace ukazatele na pětiprvkové pole typu int vypadá následovně:

int (*p)[5];

Smyslem příkladu bylo opět ukázat, jak důležité jsou závorky při práci s ukazateli. Jak priorita operátorů mění smysl. Toto bývá častá chyba.

Opět zde není důvod používat dynamickou alokaci. Navíc druhý zápis je chybný (neberu v potaz přepsání se v přetypování na int*. Funkce malloc() vrací ukazatel na void a návratovou hodnotu je třeba přiřadit ukazatelové proměnné nikoli klasické proměnné.

15. Ano, úryvek kódu je správný. Správně jsi zodpověděl, že ukazatel je inicializován adresou řetězcové konstanty. Funkce printf() přebírá jako první argument řetězec a je zcela v pořádku, pokud je tímto argumentem ukazatel na řetězec. Důležité je neměnit tento ukazatel, jinak se ztratí přístup k tomuto řetězci. Ano, nulový znak je přidáván na konec automaticky.

16. Tuto otázku si zodpověděl správně a dodal si i informaci o nutnosti předávání informace o velikosti pole a to, že není nutné předávat pole od začátku ale od ukazatele který je vytvořen s posunem. Pouze upřesním informaci o předávání velikosti pole. Toto není třeba u polí které jsou řetězci. Důvodem je to, že řetězec je ukončen vždy nulovým znakem. tedy lze určit, kdy řetězec končí.

17. Zde jsem nespecifikoval zadání úplně přesně a došlo tak nedorozumění v tom, co jsem požadoval a co jsi vypracoval. Pokud bych totiž chtěl pouze délku řetězce, bylo by neefektivní tuto funkci vytvářet a zaobalovat tak již existující funkci strlen(), která již toto dělá. Úkolem bylo vytvořit funkci, která převezme řetězec zadaný jako argument funkce spolu se znakem zadaným jako druhý argument. Funkce tedy bude mít dva parametry a bude vracet počet nalezených znaků v řetězci. Druhý parametr určuje právě hledaný znak. Abych Tě o tuto úlohu neochudil, dostaneš ji za úkol zpracovat v tomto upřesněném zadání. Opět, uvědom si, kdy je třeba předávat funkci argument odkazem a kdy hodnotou a jak je to s určení velikosti řetězcových polí. A opravdu není třeba použití dynamické alokace.

18. Je to správně. Pouze menší efektivita a zbytečné použití pomocné proměnné. Ale to není někdy na škodu. Následující úryvek kódu ukazuje, jak to lze napsat lépe. Ukazatele p_zacatek a p_konec jsou inicializovány přímo.

char str[] = "Ucim se ukazatele";
char *p_zacatek = str;
char *p_konec = str + strlen(str) - 1;

Je třeba mít stále na paměti, že ukazatel + celočíselná konstanta dává ukazatel.

19. Smyslem úlohy bylo uvědomit si, že vracet hodnoty z funkce lze i přes parametry ne jen přes návratovou hodnotu funkce. Opět, pokud je předáván ukazatel, lze měnit hodnoty argumentů a změny se projeví vně funkce. Stačila mi funkce a nebylo třeba v tom hledat složitosti. Využije se zde přístup do proměnné nepřímo pomocí ukazatele. Funkce vypadá následovně:

void vypocet(double r, double* o, double* s) {
        *o = 2 * PI * r;
        *s = PI * r * r;
}

celý program pak například takto:

#define PI 3.141592653
#include <stdio.h>

void vypocet(double r, double *o, double *s);

int main(void) {
        double obvod, obsah;

        vypocet(10.0, &obvod, &obsah);
        printf("obvod=%f\nobsah=%f\n", obvod, obsah);
        return 0;
}

void vypocet(double r, double* o, double* s) {
        *o = 2 * PI * r;
        *s = PI * r * r;
}

Způsobů, jak vracet vícero hodnot z funkce je více. Lze využít pole popř. struktury. Zde šlo pouze o princip a to, že je třeba předat ukazatel na proměnnou.

Ještě malá odbočka k dynamické alokaci. Funkce free() pro uvolnění alokované paměti se nikdy nezavolá, jelikož funkce je ukončena dříve pomocí příkazu return. Často se způsob alokace ve funkci využívá spolu s tím, že paměť je uvolněna později někde v programu. Takové funkce pak vrací ukazatel aby se mohla tato paměť později, když je třeba, uvolnit.

20. Nejtěžší na konec. Nic však, co by se nedalo vyřešit. Pořád je to o tom, kdy je třeba argument předat voláním odkazem nebo hodnotou. Tedy zda se bude měnit či nikoli. Aby funkce byla soběstačná, je třeba, aby byl řetězec předáván jako argument, spolu s oběma znaky. První je třeba mít představu o tom jak bude vypadat takový prototyp funkce. Mohl by vypadat např. následovně:

char *zamena_znaku(char *str, char c_puvodni, char c_novy);

A celý program pak např. takto:

#include <stdio.h>

char *zamena_znaku(char *str, char c_puvodni, char c_novy);

int main(void) {
        char text[] = "Ucim se ukazatele";
        printf(zamena_znaku(text, 'e', 'x'));

        return 0;
}

char* zamena_znaku(char* str, char c_puvodni, char c_novy) {
        char* s = str;
        while (*str) {
                if (*str == c_puvodni) *str = c_novy;
                str++;
        }
        return s;
}

Pole se vždy předává pomocí ukazatele. Zbylé dva znaky není třeba předávat voláním odkazem. Ukazatel s uvnitř funkce slouží pro uchování adresy začátku pole. Postupně procházím všechny znaky a testuji, zda znak je původním. Pokud ano, přepíšu ho novým. Pro ukončení cyklu se využívá faktu, že řetězec končí nulovou hodnotou (nepravdivou). Podmínka ve while cyklu zjišťuje, zda-li znak který testuji není znakem, který ukončuje řetězec. Aby se prošly všechny znaky, je třeba inkrementace ukazatele. Cyklus while má na starosti změny. A abych předal zpět tento upravený řetězec, využívám lokalní ukazatel s, který mi uchovává adresu začátku tohoto pole. Tento ukazatel s je nutný, neboť pro traversování pole používám ukazatel. Pokud bych používal konstantu posunu, měl bych uchovaný začátek tohoto pole stále v ukazateli str. Nakonec, co stojí za povšimnutí je to jak užitečné je vracet ukazatel. Návratovou hodnotu funkce, tedy ukazatel na tento řetězec mohu použít jako argument funkce printf(). Díky tomu mám definici řetězce, provedení změn a výpisu pole na pouhých dvou řádcích.

Tolik k tvému zpracování otázek a cvičení na téma - Základy ukazatelů v C. Doufám, že Ti vše doplněné o předchozí výklad více přiblížilo pochopení práce s ukazateli a porozuměl si všemu, co si dělal chybně. Pokud Ti cokoli z toho co jsem napsal není jasné, ptej se. A nezapomeň, úloha 17. na tebe stále čeká k vyřešení :-)

Odpovědět
19. února 5:43
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Jozef Vendel
Člen
Avatar
Odpovídá na DarkCoder
Jozef Vendel:19. února 11:19

Dakujem velmi pekne za snahu a ochotu. Idem si to prestudovat, zlepsovat si vedomosti a zrucnosti v tejto teme.

 
Odpovědět
19. února 11:19
Avatar
Jozef Vendel
Člen
Avatar
Odpovídá na DarkCoder
Jozef Vendel:19. února 11:23

Inac myslim si, ze by si mohol kludne tento 20 otazkovy test k ukazovatelom urobit ako 12. cvicenie k tomuto kurzu, ktore by sa nieslo v duchu, ze precvic si ako si pochopil ukazovatelom.­..nieco v tom zmysle ak ma chapes, co by bolo fajn lebo ukazovatelia su dost zlozita tema a treba sa jej venovat a taketo precvicenie je urcite vhodne.

 
Odpovědět
19. února 11:23
Avatar
Jozef Vendel
Člen
Avatar
Odpovídá na DarkCoder
Jozef Vendel:19. února 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 ?

 
Odpovědět
19. února 15:21
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Jozef Vendel
DarkCoder:19. února 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ě

Odpovědět
19. února 15:49
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 42 zpráv z 42.