Pointery II 1
Pointery a pole Dosavadní způsob práce s poli zahrnoval: definici pole jakožto kolekce proměnných (prvků) jednoho typu, umístěných v paměti za sebou int pole[10]; práci s jednotlivými prvky pole pomocí indexu v hranatých závorkách for (int i = 0; i < 10; i++) pole[i] = i + 1; Výše uvedený způsob často postačuje, ale jazyk C nabízí rozšíření možností práce s poli a její zefektivnění pomocí úzkého vztahu polí a pointerů. Ten vyplývá z toho, že název pole je ve skutečnosti pointerem na první prvek pole 2
Statické pole int pole[6]; proměnná pole je pointer na int obsahuje adresu počátku bloku paměti, kde je za sebou uloženo 6 proměnných typu int tímto způsobem vznikne tzv. statické pole, uložené na zásobníku (má tedy lokální obor platnosti odpovídající funkci či bloku, ve kterém bylo definováno, po jejím opuštění zanikne) u statického pole je název pole konstantní pointer (adresa v něm uložená se nedá měnit) předpokládejme, že int zabere 4 bajty paměti, pak definice int pole[6]; vytvoří pole[0] pole[1] pole[2] pole[3] pole[4] pole[5] pole: adresa (např.): 100 104 108 112 116 120 samotná proměnná pole je pointer na int, obsahující adresu 100 můžeme napsat int * pint = pole;, pak pint ukazuje do stejného místa paměti jako pole (tedy na první prvek pole) 3
Dynamické pole int * pole = (int*) malloc(6 * sizeof(int)); proměnná pole je opět pointer na int, obsahující adresu počátku bloku paměti, kde je za sebou uloženo 6 proměnných typu int tímto způsobem vznikne tzv. dynamické pole, uložené na haldě, které zanikne až uvolněním paměti příkazem free(pole);, na což nesmíme zapomínat název pole není konstantní pointer (pokud ho tedy sami jako konstantní nedefinujeme) s dynamickým polem lze pracovat stejně jako se statickým, tzn. funguje tu indexování pole[2] = 7; 7 dynamická a statická pole se liší se umístěním v paměti a životností, ale práce s nimi je stejná Funkce přebírající pole jako parametr může být deklarována takto nebo takto int SumaPrvku(int pole[], int pocetprvku); int SumaPrvku(int * pole, int pocetprvku); Obě funkce mohou uvnitř pracovat naprosto stejně. 4
Pointerová aritmetika s pointery je možné provádět aritmetické operace, které využijeme hlavně při práci s poli smysl mají 4 operace: 1) součet pointeru a celého čísla (pointer + n) výsledkem je pointer, ukazující na n-tý prvek za prvkem, na který ukazuje původní pointer k adrese dané pointerem se tedy nepřičítá n, ale n * velikost typu, na který pointer ukazuje příklad: proměnná int a zabírá 4 bajty paměti, pointer pa ukazuje na tuto proměnnou: int a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 pa pa + 1 pa + 2 pa + 3 pa + 1... k hodnotě pa se přičte hodnota 4 (velikost typu int) pa + 1 neukazuje o jeden bajt dál, ale o jeden int dál pint = pint + 1; // poskočení o 1 prvek dozadu pint++; // totéž 5
2) rozdíl pointeru a celého čísla (pointer - n) opačná operace, výsledkem je pointer, ukazující na n-tý prvek před prvkem, na který ukazuje původní pointer posun zpět o n prvků, tedy o n * sizeof(*pointer) bajtů pint--; // posun o jeden prvek doleva 3) porovnávání pointerů lze použít relační operátory <, <=, >, >=, ==,!= if (p1 < p2)... test, jestli p1 ukazuje před p2 (tzn. jestli adresa v p1 je menší než v p2) if (p1 == p2)... test, jestli p1 a p2 ukazují na stejné místo paměti smysl má pouze porovnávat pointery na stejný datový typ 3) odečítání pointerů pkonec pzacatek... výraz vrací počet prvků mezi pointery (ne počet bajtů) smysl má pouze, pokud pointery ukazují na stejný datový typ a zároveň ukazují do stejného pole pzacatek pkonec pkonec pzacatek == 4 6
Využití pointerové aritmetiky při práci s poli pomocí indexu pomocí pointerů přístup k prvku pole pole[i] *(pole + i) adresa prvku pole &pole[i] pole + i průchod polem (spojený např. s vynulováním prvků) int pole[10]; for (int * p = pole; p < pole + 10; p++) *p = 0; kopírování obsahu pole A do pole B int * p1 = polea; int * p2 = poleb; for (int i = 0; i < pocetprvku; i++) { *p2 = *p1; p1++; p2++; } pozn. cyklus lze napsat i úsporněji: for (int i = 0; i < pocetprvku; i++) *p2++ = *p1++; 7
Využití pointerové aritmetiky při práci s poli zvětšení dynamického pole máme dynamické pole 100 prvků, ketré je zaplněné a pro přidávání dalších hodnot mu musíme navýšit kapacitu (např. ji zdvojnásobíme): int * pole = (int*) malloc(100 * sizeof(int));... // pole je plné, zvýšíme mu kapacitu: int * novepole = (int*) malloc(200 * sizeof(int)); int *p1 = pole, *p2 = novepole; for (int i = 0; i < 100; i++) *p2++ = *p1++; free(pole); pole = novepole; // a nikdo nic nepozná... 8
Využití pointerové aritmetiky při práci s poli Motivace - proč používat pointerovou aritmetiku? práce s polem pomocí pointerů je efektivnější než pomocí indexů (ačkoliv výsledek je stejný) příklad: máme pole int pole[1000]; a chceme jeho prvky vynulovat: A) pomocí indexů: for (int i = 0; i < 1000; i++) pole[i] = 0; v každé iteraci se pro přístup k prvku pole 1. spočítá (i * velikost prvku pole) 2. výsledek se přičte k bázové adrese pole B) pomocí pointerů: for (int * p = pole; p < pole + 1000; p++) *p = 0; v každé iteraci se pouze přičte k pointeru p konstanta Ovšem platí, že práci s polem pomocí indexů a pointerů lze libovolně kombinovat či používat jen jeden způsob, a to jak u statických tak i u dynamických polí. 9
Vícerozměrná pole Způsob uložení vícerozměrných polí v paměti např. 2D pole int pole[3][4] je pole o 3 řádcích a 4 sloupcích každý řádek je uložen zvlášť jako jednorozměrné pole (délky 4) jednorozměrné pole je uloženo jako pointer na první prvek, takže: pole[3][4] je pole 3 pointerů, každý z nich ukazuje na 1D pole délky 4 pole[0] pole[1] pole[2] Proměnná Význam Aritmetika pole pointer na 2D pole (typ int**) pole + 1 posun na další řádek pole[i] pointer na 1D pole (typ int*) pole[i] + 1 posun na další prvek pole[i] pole[i][j] jednoduchá proměnná (typ int) 10
Dvourozměrné dynamické pole Způsoby vytvoření dvourozměrného pole na haldě jako pole pointerů int * pole[3]; for (int i = 0; i < 3; i++) pole[i] = (int*)malloc(4 * sizeof(int)); jako pointer na pointer int ** pole = (int**)malloc(3 * sizeof(int*)); for (int i = 0; i < 3; i++) pole[i] = (int*)malloc(4 * sizeof(int)); Jaký je rozdíl mezi těmto dvěma poli??? pozn. cyklus pro vytvoření řádek je u obou způsobů stejný u obou polí lze využívat jak pointerovou aritmetiku, tak indexování (stejně jako u statického pole) Smazání pole (uvolnění paměti) for (int i = 0; i < 3; i++) free(pole[i]); free(pole); 11
Dvourozměrné dynamické pole dynamické pole může být zubaté (každá řádka jinak dlouhá) char * pole[4]; pole[0] = "ahoj"; pole[1] = "baf"; pole[2] = "kebule"; pole[3] = "cejn"; pole[0] pole[1] pole[2] pole[3] a h o j \0 b a f \0 k e b u l e \0 c e j n \0 12
Pointer na funkci kód funkce je uložen v paměti stejně jako data, může na něj být odkazováno paměťovou adresou adresu kódu je dostupná prostřednictvím názvu funkce, který funguje jako pointer deklarace funkce, vracející double double Funkce(int a); pointer na funkci, vracející double (závorky kolem názvu pointeru jsou nutné, stejně tak jako závorky na konci definice) double (*pfunkce)(); dosazení adresy funkce do pointeru pfunkce = Funkce; volání funkce pomocí pointeru double vysledek = pfunkce(10.3); 13
Pointer na funkci při psaní uživatelské nabídky může být výhodné vytvořit pole pointerů na funkci př. vytvoření pole 3 pointerů na funkce, vracející hodnotu double: double (*funkce[3])(); // pole 3 pointerů na funkce double (*funkce[3])(); // funkce prumer, rozptyl a median byly deklarovány dřív // všechny přebírají pole čísel a počet prvků pole funkce[0] = prumer; funkce[1] = rozptyl; funkce[2] = median; int volba; printf("co chcete spocitat?\n"); printf("<0> prumer\n"); printf("<1> rozptyl\n"); printf("<2> median\n"); printf("vase volba: "); scanf("%i", &volba); double vysledek = funkce[volba](pole, n); // výpočet výsledku 14