Prostudujte si přednášku z jazyka C na stránce http://efis.tul.cz/~dana.nejedlova/ v odstavci "Programování I a II" od smínku "Pointerová aritmetika" do snímku "Jednorozměrná pole". Vyzkoušejte si všechny programy na snímcích, které si máte prostudovat. Níže v tomto souboru je program ze snímku "Pointer jako skutečný parametr funkce" doplněný o vysvětlení dynamické alokace paměti a pointerové aritmetiky. #include #include const int SIZE = 10; void init(double **p_f) { *p_f = (double *) malloc(SIZE * sizeof(double)); } void cteni(double *p_f) { int i; for (i = 0; i < SIZE; i++) { printf("Zadejte %d. cislo: ", i + 1); scanf("%lf", p_f + i); } } void nasob(double *p_f, int velikost, double *p_soucin) { for (velikost--, *p_soucin = *(p_f + velikost); --velikost >= 0; ) *p_soucin *= *(p_f + velikost); } int main(void) { double *p_dbl, souc; init(&p_dbl); cteni(p_dbl); nasob(p_dbl, SIZE, &souc); printf("Soucin cisel je: %.3f.\n", souc); return 0; } Ve výše uvedeném programu nejsou plně využity možnosti dynamické alokace paměti. Dynamická alokace paměti v jazyce C spočívá ve využití funkcí malloc() nebo calloc(). Dynamická alokace paměti umožňuje zjistit velikost paměti pro pole až v průběhu programu například tak, že uživatel do programu zadá velikost pole. Dynamická alokace paměti umožňuje uvolnit paměť v průběhu vykonávání funkce, ve které byla paměť alokována, voláním funkce free(). Po uvolnění paměti již není možné do pole přistupovat ani jej další alokací paměti obnovit tak, že bychom získali přístup do jeho původního umístění v paměti. Příští týden bude dynamická alokace paměti porovnána se statickým polem. Níže jsou uvededy některé řádky z výše uvedeného programu s komentáři. init(&p_dbl); Výraz "&p_dbl" je typu pointer na pointer na double. Operátor "&" v tomto kontextu zjišťuje adresu, na které je hodnota proměnné "p_dbl". Adresa paměti je uložena v datovém typu pointer. Protože funkce init() má změnit hodnotu svého paramteru, je třeba jí jej předat jako adresu, na které je jeho hodnota, protože při předávání parametrů do funkcí se vždy skutečné parametry kopírují do formálních parametrů. Skutečné a formální parametry jsou vysvětleny na snímku mých přednášek z jazyka C "Definice funkce s parametry". Funkce init() tedy dostane jako parametr adresu proměnné "p_dbl" a do této adresy zapíše novou hodnotu této proměnné. Tento způsob předání parametrů se nazývá "předání odkazem" (by reference). Kdyby funkce init() dostala jako parametr přímo proměnnou "p_dbl", tak by zapsala její novu hodnotu jen do svého formálního parametru "p_f", který existuje jen po dobu vykonávaní funkce. Tento způsob předání parametrů se nazývá "předání hodnotou" (by value). Předávat odkazem se také musí parametry funkci scanf(), a proto se při tom používá operátor "&". double *p_dbl Takto deklarovaná proměnná "p_dbl" je datového typu "pointer na double", což znamená, že je to název pro místo v paměti, kde je uložena adresa, na které je uložen datový typ double. Výraz "&p_dbl" je adresou, na které je obsah proměnné "p_dbl", což je také adresa. Je to tedy adresa, na které je adresa, na které je datový typ double. Adresa paměti je adresou prvního bajtu, od kterého je v paměti uložen obsah proměnné. Program ví, kolik bajtů má od této adresy načíst při načítání hodnoty proměnné, díky definici datového typu pointeru. Když definujeme pointer, vždy je součásti definice i datový typ na který ukazuje. const int SIZE = 10; Můžeme-li do programu přímo napsat hodnotu velikosti pole a ta hodnota není příliš veliká, viz například "Třetí program" v souboru "11.txt", tak je místo dynamické alokace paměti vhodnější vytvoření statického pole double p_dbl[SIZE] a pro zajištění kompatibility programu s ANSI normou jazyka C, která maximalizuje množství překladačů, které program umí přeložit, by se měla konstanta SIZE definovat pomocí direktivy pro preprocesor #define SIZE 10 cteni(p_dbl); Funkce cteni() mění nebo naplňuje prvky pole které začíná v paměti od bajtu s adresou uloženou v proměnné "p_dbl". Parametr funkce cteni() jí nemusí být předáván odkazem jako do funkce init(), protože se nemění adresa, na které pole začíná. for (velikost--, *p_soucin = *(p_f + velikost); --velikost >= 0; ) Každá závorka za for cyklem v jazyce C musí obsahovat 2 středníky a to je vše. Ve výrazu "velikost--, *p_soucin = *(p_f + velikost)" je operátor čárky, viz snímek mých přednášek z jazyka C "Operátor čárky". Výraz před prvním středníkem se ve for cyklu provede jen jedenkrát na jeho začátku. Výraz mezi dvěma středníky se vyhodnocuje při každé iteraci (obrátce) cyklu a když se vyhodnocí jako false (nepravda), tak cyklus skončí. Výraz za druhým středníkem zpravidla obsahuje inkrementaci řídící proměnné cyklu, viz snímek mých přednášek z jazyka C "Příkaz for", ale v tomto příkladu je inkrementace, nebo spíš dekrementace, obsažena ve výrazu mezi středníky. Pole v tomto cyklu se prochází od posledního prvku, který má index s hodnotu SIZE - 1, protože první index pole má hodnotu 0. výsledný součin se inicializuje posledním prvkem a potom se k němu přinásobují předchozí prvky pole. Výraz "p_f + velikost" je příkladem použití pointerové aritmetiky pro přístup k prvku pole. Dle pravidel pointerové aritmetiky se výraz "p_f + velikost" vyhodnotí jako adresa bajtu v proměnné (pointeru) p_f + velikost * sizeof(double). Parametr operátoru sizeof() je dán definicí pointeru "p_f", což je "double *p_f", tedy pointer na double. Operátor sizeof() je poprvé představen na snímku mých přednášek z jazyka C "Proměnné" a jeho znalost bude požadována u zkoušky z jazyka C v předmětu Programování II. Výraz "*(p_f + velikost)" je hodnotou na adrese "p_f + velikost", tedy prvek pole s indexem v proměnné "velikost", viz snímek mých přednášek z jazyka C "Referenční operátor &". *p_f = (double *) malloc(SIZE * sizeof(double)); Volání funkce malloc() nebo calloc by se mělo ověřovat na úspěch, viz snímek mých přednášek z jazyka C "Funkce malloc()", tak aby program oznámil, že již není k dispozici dostatek operační paměti, což by se mohlo stát voláním těchto funkci v cyklu nebo v rekurzi nějakým chybným způsobem, nebo tím, že paměť je spotřebována nějakými jinými běžícími programy v daném počítači. Následující program je novu verzí výše uvedeného programu, který využívá možnosti dynamické alokace paměti pro určení velikosti pole uživatelem namísto konstanty, ve funkci init() ověřuje úspěšnost alokace paměti, uvolňuje alokovanu paměť funkcí free(), funkci init() a nasob() má přepracované tak, již nemají charakter procedur, a prochází pole co nejkonvenčnějším způsobem. #include #include double *init(int velikost) { double *p_f; if ((p_f = (double *) malloc(velikost * sizeof(double))) == NULL) { printf("Malo pameti!\n"); /* Vypise se take, kdyz je argument velikost zaporne cislo. */ exit(1); } return p_f; } void cteni(double *p_f, int velikost) { int i; for (i = 0; i < velikost; i++) { printf("Zadejte %d. cislo: ", i + 1); scanf("%lf", p_f + i); /* stejne jako scanf("%lf", &p_f[i]); */ } } double nasob(double *p_f, int velikost) { double soucin = *p_f; /* stejne jako double soucin = p_f[0]; */ int i; for (i = 1; i < velikost; i++) /* stejne jako ve funkci cteni(), ale potrebujeme navic promennou i. */ soucin *= p_f[i]; return soucin; } int main(void) { int size, pocet_nactenych_parametru; double *p_dbl; printf("Zadej pocet cisel, ktera chcete vynasobit: "); pocet_nactenych_parametru = scanf("%d", &size); printf("Vytvori se pole pro %d cisel.\n", size); if (pocet_nactenych_parametru == 0) { size = 3; } p_dbl = init(size); cteni(p_dbl, size); printf("Soucin cisel je: %.3f.\n", nasob(p_dbl, size)); free((void *) p_dbl); /* Kdyz se prehodi s predchozim radkem, tak se vytiskne nula, protoze k poli jiz neni pristup. */ return 0; } Když se v tomto programu zadá jako počet čísel něco jiného než číslo, tak se do proměnné "size" nic neuloží a zůstane v ní hodnota, se kterou byla automaticky inicializována, a program se tolikrát, kolik je tato hodnota, pokusí číst prvek pole ze vstupu, kde zůstává nenačtená nečíselná hodnota. Aby to nevypadalo jako zacyklení a program brzy skončil, byla do něj přidána část if (pocet_nactenych_parametru == 0) { size = 3; } aby se program pokusil číst jen 3 čísla pole. Proto bychom měli vždy pro čtení čísel celých i reálných využívat funkce, které ověří, že bylo zadáno číslo daného typu, a vyprázdní buffer, viz funkce precti_cele_cislo() a precti_cislo() v souboru "11.txt".