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:12.1.2021 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.

 
Odpovedať
12.1.2021 18:52
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Jozef Vendel
DarkCoder:12.1.2021 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.

Odpovedať
12.1.2021 20:45
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Jozef Vendel
Člen
Avatar
Jozef Vendel:12.1.2021 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ť ;)

 
Odpovedať
12.1.2021 21:47
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Jozef Vendel
DarkCoder:12.1.2021 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ě.

Editované 12.1.2021 22:12
Odpovedať
+1
12.1.2021 22:12
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Jozef Vendel
Člen
Avatar
Odpovedá na DarkCoder
Jozef Vendel:14.2.2021 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.

 
Odpovedať
14.2.2021 15:21
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Jozef Vendel
DarkCoder:14.2.2021 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.

Odpovedať
+2
14.2.2021 19:46
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Jozef Vendel
Člen
Avatar
Odpovedá na DarkCoder
Jozef Vendel:18.2.2021 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);
}
 
Odpovedať
18.2.2021 18:29
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Jozef Vendel
DarkCoder:19.2.2021 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í :-)

Odpovedať
+1
19.2.2021 5:43
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Jozef Vendel
Člen
Avatar
Odpovedá na DarkCoder
Jozef Vendel:19.2.2021 11:19

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

 
Odpovedať
19.2.2021 11:19
Avatar
Jozef Vendel
Člen
Avatar
Odpovedá na DarkCoder
Jozef Vendel:19.2.2021 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.

 
Odpovedať
19.2.2021 11:23
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.