1 / 73

Pole

Pole. prom ě nn á , obsahuj ící v í ce hodnot stejn é ho typu , jednotlivé hodnoty uloženy v tzv. prvcích pole identifikace prvku podle pořadí v proměnné (index). POZOR: Nekontroluje se, zda hodnota indexu „nemíří“ mimo pole. typ jméno_proměnné [ po čet_prvků ];.

gitel
Télécharger la présentation

Pole

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Pole • proměnná, obsahující více hodnot stejného typu, jednotlivé hodnoty uloženy v tzv. prvcích pole • identifikace prvku podle pořadí v proměnné (index) POZOR: Nekontroluje se, zda hodnota indexu „nemíří“ mimo pole. typ jméno_proměnné[počet_prvků]; • hranaté závorky jsou operátor pole • indexy jsou nezáporná celá čísla (je to pořadové číslo, žádná abstrakce) • v C začínají indexy vždy od nuly (první prvek má index 0, dále vzestupná řada až po počet_prvků - 1) • počet prvků musí být určen konstantou nebo konstantním výrazem (hodnota musí být při překladu známá) • index může být výraz • Specifikaci počtu prvků lze vynechat: • v deklaraci (počet bude určen v definici) • v definici, kde všechny prvky inicializovány (určí se automaticky) • v deklaraci pole uvnitř struktury int a[10]; // desetiprvkové pole ... a[0] = 8; // první prvek a[1] = 5; // druhý prvek a[9] = 4; // desátý (poslední) prvek

  2. Pole Inicializace proměnné typu pole: int c[5]= {1,5,8,9,0}; • inicializační hodnoty musí být vyčíslitelné v okamžiku, kdy inicializovaná proměnná vzniká (to platí obecně, nejen pro pole), (tato zřejmě nejasná poznámka se vyjasní někdy příště) • je-li inicializátorů méně než prvků pole, jsou zbývající prvky inicializovány na hodnotu 0 (nebo NULL, je-li prvkem směrník, viz dále) • více inicializátorů, než je prvků pole, je chyba • Pro pole jsou definované operace: • zpřístupnění prvku • získání adresy prvku (viz dále) • předání pole jako parametru funkce (viz dále) • s polem jako celkem nelze provádět žádné další operace (přiřazení apod.), vše je nutno realizovat pomocí operací s jednotlivými prvky (inicializace není přiřazení!) • S prvkem pole lze provádět všechny operace, jaké lze provádět s jednoduchou proměnnou daného typu.

  3. Vícerozměrná pole • dvourozměrné pole lze považovat za jednorozměrné pole, jehož každým prvkem je pole (lze zobecňovat) • to se projeví i při inicializaci, dále inicializováno pole 3x2, tzn. tříprvkové pole, jehož prvky jsou dvouprvková pole typ jméno_proměnné[počet_prvků1][počet_prvků2]...; int b[10][10]; // stoprvkové pole (matice 10 x 10) ... b[1][7] = 8; b[2,3] = 6; // chyba, znamená b[3] // (čárka je operátor, ne oddělovač) p[0][0] p[1][0] p[2][0] int p[3][2] = {{0,1},{3,4},{6,7}}; p[0][1] p[1][1] p[2][2]

  4. Vícerozměrná pole • prvky pole uloženy v paměti za sebou tak, že nejrychleji se mění poslední index (lze si snadno představit jako jednorozměrné pole, jehož každý prvek je opět pole) • identifikace prvku podle pořadí v proměnné (index) int a[3][2] = {{1,0},{2,4},{3,6}}; jakoby a[0], tedy 1. prvek a[0][0], tedy 1. prvek pole a[0] a[0][0] a: 1 a[0][1] 0 a[1][0] 2 a[1][1] 4 3 a[2][0] a[2][1] 6

  5. Vícerozměrná pole • je přípustné inicializovat vícerozměrné pole podobně jako pole jednorozměrné: • podobně jako u jednorozměrného pole lze vynechat specifikaci počtu prvků pole v 1. rozměru, vždy ale musí být známá velikost prvků int a[3][2] = {1,0,2,4,3,6}; int a[][2] = {1,0,2,4,3,6}; Za velikost v 1. rozměru se automaticky dosadí 3. int a[][2] = {1,0,2,4,3}; Za velikost v 1. rozměru se automaticky dosadí 3, prvek a[2][1] bude inicializován na 0. int a[][2] = {{1,0},{2},{3,6}}; Za velikost v 1. rozměru se automaticky dosadí 3, prvek a[1][1] bude inicializován na 0.

  6. Směrníky • Všechny objekty (proměnné, funkce) jsou uloženy v paměti. • Paměťové místo je identifikováno adresou (pořadové číslo paměťové buňky - byte). • Rezervace paměťového místa nastane při definici, pak s jeho obsahem můžeme pracovat pomocí jména (proměnné, funkce). • Adresu proměnné lze získat pomocí operátoru&, adresu funkce představuje přímo její jméno (viz dále). • Adresa je číslo, v případě potřeby lze hodnotu adresy (čehokoliv) uložit do proměnné. • Adrese nějakého objektu se říká směrník (ukazatel, pointer) na tento objekt. • Lze definovat proměnné typu směrník na něco, operátorem směrníku je *. int a;// proměnná typu int int *b; // proměnná typu směrník na int b = &a; // v b je adresa proměnné a *b = 5; // do proměnné typu int, jejíž adresa je // uložena v proměnné b (na niž ukazuje // směrník b), se přiřadí hodnota 5 // v tomto případě totéž jako: a = 5 hvězdička je zde tzv. operátor dereference

  7. Směrníky a: 6 a = 6; b: a: 6 b= &a; b: a: -11 *b= -11; b: Paměťové místo, do kterého se vejde právě jedna hodnota typu int. int a; int *b; a: b: Paměťové místo, do kterého se vejde právě jedna hodnota typu adresa. POZOR: Dříve, než je směrník použit, se musí směrníku přiřadit hodnota. Použití neinicializovaného (nebo špatně nastaveného) směrníku může způsobovat záhadné havárie. Pro směrník, který nikam neukazuje, se používá hodnota NULL (ve skutečnosti je to 0 - důležité např. pro logické operace).

  8. Souvislost polí a směrníků • Jméno pole má význam adresy prvního prvku. • Protože lze napsat b = a;mělo by být možné b a a používat podobně, proto platí ekvivalence: int a[4] = {1,5,8,4}; int *b; Protožea a &a[0] je totéž, musí být totéž také *a a a[0] b = &a[0]; b = a; a: 1 5 a[0] b[0] a[1] b[1] ... 8 Zápis vypadá stejně, ale situace v paměti je při použití a nebo b odlišná (a odlišný je tedy i mechanismus přístupu k prvku pole), viz obr. 4 b: • ALE: • &b je adresa proměnné b (hodnota typu int**) • &a je nesmysl int a[4] = {1,5,8,4}; int *b, i; *b = a; for(i=0; i < 4; i++) printf("%8d %8d\n",a[i],b[i]); Zde a je pole, b je jednoduchá proměnná (ovšem typu směrník, proto na ni lze aplikovat index). 1 1 5 5 8 8 4 4

  9. Souvislost polí a směrníků, směrníková aritmetika • Hodnota směrníku je adresa. • Adresa je číselná hodnota. • S číselnými hodnotami lze provádět aritmetické operace. • Aritmetické operace se směrníky se provádějí jinak než s běžnými čísly - tzv. směrníková aritmetika. Pokud je b směrník na int, pak b+npředstavuje adresu paměťového místa nikoliv o n vyšší (tzn. o n bytů v paměti dále), ale místa o n hodnot typu int dále, čili obecně b + n * (velikost paměťového místa pro uložení hodnoty typu, na který směrník ukazuje), Přičtení hodnoty ke směrníku je analogií posouvání se v poli, tedy používání indexu. int a[4] = {1,5,8,4}; int *b = a; Protožea a &a[0] je totéž, musí být totéž také *a a a[0] b[i]*(b+i)je totéž! a[i]*(a+i)je totéž! a: 1 a[0] *(a+0) *(b+0) b[0] b++b-- modifikuje směrník tak, aby ukazoval na další/předchozí prvek pole. Lze aplikovat jen na směrník, ne na jméno pole! 5 a[1] *(a+1) *(b+1) b[1] 8 a[2] *(a+2) *(b+2) b[2] 4 a[3] *(a+3) *(b+3) b[3] b:

  10. Souvislost polí a směrníků, směrníková aritmetika int a[4] = {1,5,8,4}; int *b = a; int i; for(i = 0; i < 4; i++) printf("%d\n",*(b+i)); for(i = 0; i < 4; i++) printf("%d\n",*(a+i)); for(i = 0; i < 4; i++) printf("%d\n",b[i]); for(i = 0; i < 4; i++) printf("%d\n",a[i]); for(b = a; b <= &a[3]; b++) printf("%d\n",*b); doublex[4] = {1.2,-55.5,8.12,4.008}; double *y = x; int i; for(i = 0; i < 4; i++) printf("%7.2f\n",*(y+i)); 1.20 -55.50 8.12 4.01 while(y <= &x[3]) printf("%7.2f\n",*(y++));

  11. Souvislost polí a směrníků, vícerozměrná pole a je jednorozměrné pole, každý prvek je dvouprvkové pole int int a[3][2] = {{1,0},{2,4},{3,6}}; int (*b)[2]; b = a; směrník na dvouprvkové pole int (tedy i na pole dvouprvkových polí int), závorka v deklaraci/definici je nutná - viz dále jméno pole má význam adresy jeho prvního prvku Opět platí směrníková aritmetika, všechny následující zápisy provedou totožnou činnost: a[1][1] = 6; b[1][1] = 6; *(a + 1)[1] = 6; *(b + 1)[1] = 6; *(a[1]+1) = 6; *(b[1]+1) = 6; *( *(a + 1) + 1) = 6; *( *(b + 1) + 1) = 6; a[0][0] b[0][0] a: 1 a[0][1] b[0][1] 0 a[1][0] b[1][0] 2 a[1][1] b[1][1] 4 3 a[2][0] b[2][0] a[2][1] b[2][1] 6 b:

  12. Souvislost polí a směrníků, vícerozměrná pole int a[3][2] = {{1,0},{2,4},{3,6}}; int (*b)[2]; b = a; a: 1 a[0] druhý prvek pole a 0 2 a[1] a[1][1] 4 -> 6 druhý prvek pole a[1] 3 a[2] 6 b: Na druhý prvek pole a se lze odkázat: a[1] b[1] *(a + 1)*(b + 1) Tímto druhým prvkem je dvouprvkové pole hodnot typu int. Na druhý prvek tohoto pole se lze odkázat obdobnými způsoby, lze použít kombinace: a[1][1] = 6;b[1][1] = 6; *(a + 1)[1] = 6;*(b + 1)[1] = 6; *(a[1]+1) = 6;*(b[1]+1) = 6; *( *(a + 1) + 1) = 6;*( *(b + 1) + 1) = 6;

  13. Kompatibilita směrníků, generický směrník • Hodnota adresy má v daném počítači vždy stejnou délku a strukturu, podle hodnoty adresy nelze poznat, co se nachází na odkazovaném paměťovém místě. • Překladač potřebuje znát směrníkem odkazovaný datový typ (směrníkové aritmetika potřebuje znát velikost prvku, ve výrazech je nutné znát typ operandu),proto se v definici proměnné typu směrník udává, „na co“ směrník ukazuje. • Směrníky na různé datové typy jsou obecně vzájemně nekompatibilní. • Pro potřebu specifikovat obecnou, na odkazovaném typu nezávislou adresu, existuje tzv. generickýsměrník, který je formálně směrníkem na datový typ void. • V jazyce C je generický směrník oboustranně kompatibilní s ostatními směrníky. • V C++ je generický směrník kompatibilní jen jednostranně; směrníku na void lze přiřadit směrník na libovolný typ, opačně to ale neplatí. • Nekompatibilitu směrníků lze vyřešit přetypováním (viz dále). int a; int *b; float *c; void *D; ... b = &a; // O.K., stejné typy c = &a; // chyba, různé typy c = b; // chyba, různé typy D = &a; // O.K. b = D; // O.K., v C++ chyba D = c; // O.K. c = D; // O.K., v C++ chyba

  14. Složitější deklarace a definice • Potřeba vytvářet komplikovanější proměnné (např. pole směrníků, směrník na vícerozměrné pole apod.). • Při definicích/deklaracích využívány operátory. • Operátory mají priority. • Pořadí vyhodnocování lze ovlivnit závorkami. [] operátor pole * operátor směrníku () operátor funkce [] a () mají nejvyšší prioritu (oba stejnou), vyhodnocování zleva doprava. Operátor * má prioritu druhou nejvyšší (tedy nižší), vyhodnocuje se zprava doleva. int *(*x)[3]; int *(*x)[3];proměnná x int *(*x)[3];závorka, * má přednost, x je směrník int *(*x)[3];podle priority, x je směrník na pole int *(*x)[3];x je směrník na pole směrníků int*(*x)[3];x je směrník na pole směrníků na int int a;// proměnná typu int int *b; // směrník na int int c[3]; // tříprvkové pole hodnot typu int int *d[3]; // tříprvkové pole směrníků na int int (*e)[3]; // směrník na tříprvkové pole int int *(*f)[3]; // směrník na tříprvkové pole směrníků na int int **g[3]; // tříprvkové pole směrníků na směrníky na int

  15. Dynamické proměnné • Za běhu programu lze vytvářet a rušit proměnné (přesněji: podle potřeby - momentálních datových nároků - obsazovat nebo uvolňovat paměť počítače). • Tyto tzv. dynamické proměnné vyžadují: • definovat směrník na příslušný typ, • alokovat paměť potřebné velikosti (voláním knihovní funkce), • adresu alokované paměti přiřadit uložit do proměnné ad 1), • nyní lze k alokované paměti přistupovat pomocí směrníku bežným způsobem, • až není proměnná dále potřeba, lze alokovanou paměť uvolnit (voláním knihovní funkce). sizeof - operátor ke zjištění velikosti potřebné/obsazené paměti (v bytech), operandem může být buď jméno typu nebo jméno proměnné. vyhoví pro jednoduchou proměnnou int nebo jednorozměrné pole int *x; x = calloc(10, sizeof(int)); x[7] = 4; ... free(x); x = NULL; calloc je funkce, která alokuje jednorozměrné pole, první parametr je počet prvků, druhý velikost prvku, alokovaná paměť je vynulována vracíme paměť k dispozici operačnímu systému Kromě calloc existují i další alokační funkce, bylo by možné např. napsat x = malloc(10*sizeof(int)); označíme směrník za nevyužitý - nikam neukazuje (nikoliv nezbytné, ale velmi vhodná praxe)

  16. Dynamické proměnné Poznámka: Většina dnešních překladačů jsou překladače jazyka C++ (nikoliv C).I v „neobjektové“ oblasti se C a C++ mírně liší.Uvedený příklad nejde překladačem C++ přeložit. Alokační funkce vracejí hodnotu typu void*. Chceme-li tuto hodnotu přiřadit proměnné typu směrník (obvykle na jiný typ než void), je nutné v C++ použít přetypování. int *x; x = calloc(10, sizeof(int)); x[7] = 4; ... free(x); x = NULL; calloc je funkce, která vrací hodnotu typu void*, kterou nelze do proměnné typu int* přiřadit.Uvedený zápis změní typ této hodnoty na int*. int *x; x = (int*)calloc(10, sizeof(int)); x[7] = 4; ... free(x); x = NULL; Funkce free očekává parametr typu void*, ten může být zastoupen směrníkem na libovolný jiný typ, přetypování zde není nutné.

  17. Dynamické proměnné • Možné problémy: • Neinicializovaný směrníkJako 2), některé překladače inicializují neinicializovanou proměnnou typu směrník na zvláštní nesmyslnou hodnotu, pak je hlášena běhová chyba programu. • Použití paměti, která nebyla alokovaná (použití směrníku po uvolnění paměti, neinicializovaný směrník, nesmyslná hodnota směrníku, stejný efekt má i index mimo rozsah velikosti pole, ať už dynamického nebo ne). • pokud paměť programu nepatří, operační systém úlohu ukončí, snadná lokalizace chyby • jinak nesmyslné chování programu až havárie (obvykle na jiném místě, než kde chyba skutečně je), často za zdánlivě náhodných okolností, velmi obtížná lokalizace chyby: • při čtení bude použita nesmyslná hodnota, • při zápisu bude přepsáno něco jiného, možná potřebného (změněn obsah úplně jiné proměnné, modifikována část kódu), • Neuvolňování již nepotřebné alokované pamětiVyčerpávání volné paměti, zpomalování počítače, až havárie OS.(Kvůli fragmentaci paměti doporučeno uvolňovat v opačném pořadí, než bylo alokováno, v praxi nelze dodržet.) • Uvolnění paměti, která nebyla alokována.Okamžitá havárie programu, tedy snadná lokalizace chyby. Někteří následovníci C a C++, jako např. C# nebo Java klasické alokace paměti neobsahují. Tvůrci těchto jazyků jako důvod tohoto ochuzení deklarují (ne zcela poctivě) snahu nedat programátorovi příležitost udělat chybu. Proto se doporučuje směrníkům, které momentálně "nikam neukazují", přiřazovat hodnotu NULL. free(NULL) je neškodné.

  18. Dynamické proměnné a dvourozměrná pole • Velmi častým objektem pro realizaci výpočtů jsou dvourozměrná pole (matice). • Pokud je předem (při psaní programu) znám počet sloupců, lze použít pro dynamickou alokaci matice následující postup: Pro b lze alokovat matici s libovolným počtem řádků, ale vždy se dvěma sloupci. směrník na dvouprvkové pole int Alokujeme matici 10 x 2, k určení velikosti potřebné paměti lze použít i jiný zápis, důležitý je počet bytů, vyhoví i např. calloc(1, 20 * sizeof(int)). int (*b)[2]; ... b = (int(*)[2])calloc(10,sizeof(int[2])); ... b[5][0] = 12; ... free(b); běžná práce s dvojrozměrným polem dealokace pole 0 b: b[0] 0 Podmínka znalosti počtu sloupců již ve fázi psaní programu je často nesplnitelná. Proto se naznačený postup používá velmi zřídka. 0 b[1] b[1][1] 0 0 b[2] 0

  19. Dynamické proměnné a dvourozměrná pole • Dynamicky alokovaná matice s předem neznámým počtem řádků i sloupců • Pokud chceme zachovat souvislé (lineární) uložení v paměti, pak je možné alokovat jednorozměrné pole o patřičném počtu prvků. Pak nelze používat dva indexy, ale jediný index, jeho hodnotu je nutno vypočítat pomocí mapovací funkce. Alokujeme jednorozměrné pole, do kterého se vejdou 5 x 3 prvky typu int. int *b; int radku= 5, sloupcu = 3; ... b = (int*)calloc(radku * sloupcu, sizeof(int)); ... b[1 * sloupcu + 2] = 12; ... free(b); b = NULL; b1 2 = 12 b[0] b0 0 0 b0 1 b[1] 0 b: b0 2 b[2] 0 Tato metoda vede k nepřehlednému programu a používá se jen výjimečně. Výhodou je, že celé pole je v paměti uloženo souvisle. b1 0 b[3] 0 b1 1 b[4] 0 b1 2 b[5] 12 b2 0 b[6] 0 b2 1 b[7] 0 Takto to vnitřně dělá překladač u nedynamických vícerozměrných polí nebo dynamických polí dle předchozího příkladu. b2 2 b[8] 0 b3 0 0 b[9]

  20. Dynamické proměnné a dvourozměrná pole • Dynamicky alokovaná matice s předem neznámým počtem řádků i sloupců • Požadujeme používání dvou indexů, netrváme na souvislém uložení pole v paměti. Potřebujeme „dvouhvězdičkový“ směrník. int **b; int radku = 3, sloupcu = 2; int i; ... b = (int**)calloc(radku,sizeof(int*)); for(i = 0; i < radku; i++) b[i] = (int*)calloc(sloupcu, sizeof(int)); ... b[1][0] = 12; ... for(i = 0; i < radku; i++) free(b[i]); free(b); b = NULL; Alokujeme pole směrníků na řádky matice. Alokujeme každý řádek zvlášť jako jednorozměrné pole. Na prvky matice se lze odvolávat běžným způsobem. Dealokovat napřed každý řádek, pak pole směrníků na řádky. b[0][0] 0 b: b[0][1] 0 b[1][0] 12 Nejobvyklejší způsob práce s dynamickým dvourozměrným polem. b[0] b[1][1] 0 b[1] b[2] b[2][0] 0 0 b[2][1]

  21. Textové řetězce • Neexistuje speciální typ pro ukládání textových řetězců. • Používá se pole hodnot typu char (textový řetězec je lineární posloupnost znaků). • Konstanta typu textový řetězec: text uzavřený do uvozovek. • Konstanta může obsahovat zobrazitelné znaky a escape sekvence. • Řetězec vždy končí bytem o hodnotě 0 (tzn. v proměnných řetězec obsazuje vždy o jeden prvek pole více, než kolik má znaků), tzv. ASCIIZ řetězce (často jen ASCIZ). • Proměnnou typu pole char lze při definici inicializovat textovým řetězcem. printf("Hello, world.\n"); Escape sekvence LF (linefeed). Konstanta typu textový řetězec v uvozovkách. Řetězec má celkem 5 znaků (včetně zakončovací nuly), zbylých 5 prvků pole je inicializováno na hodnotu 0 (nemají uveden inicializátor). char text[10] = "Ahoj"; char text1[10] = {'A','h','o','j',0} Klasická inicializace pole prvek po prvku je také možná, u řetězců je ale nepohodlná. Explicitní uvedení inicializátoru pro pátý prvek pole není nutné (pokud není uveden inicializátor, nastaví se prvek na hodnotu 0, tedy posledních 6 prvků pole bude nulových).

  22. Textové řetězce • K jednotlivým znakům řetězce uloženého v proměnné (pole char) lze přistupovat pomocí indexu (stále se jedná o pole). Hello,world. Hellx %sje formátová specifikace pro zobrazení textového řetězce, odpovídající parametr je typu char*. char t[20] = "Hello, world."; ... printf("%s\n",t); t[4] = 'x'; t[5] = 0; printf("%s\n",t); Pátý znak v řetězci (dosud 'o') je změněn na 'x'. Délka řetězce je zkrácena na 5 „užitečných“ znaků, na pozici šestého znaku je zapsána „zakončovací nula“.

  23. Textové řetězce • Zcela v souladu se souvislostí polí a směrníků lze pro práci s textovými řetězci využívat i směrníky. char txt[20] = "Nazdar, vespolek!"; char *p; ... p = txt; p[0] = 'n'; printf("%s\n",p); nazdar, vespolek! • První (povinný) parametr funkce printf je typu char*, proto může na jeho místě být nejen konstantní textový řetězec, ale i proměnná. Nazdar, vespolek! char txt[20] = "Nazdar, vespolek!"; ... printf(txt); Kromě zkrácení zápisu v tomto konkrétním případě je hlavní využití v situaci, kdy je třeba formátovací řetězec vygenerovat programem podle momentálních okolností. Na rozdíl od minulého příkladu se zde po vypsání textu neodřádkuje (znak LF by musel být součástí řetězce uloženého v proměnné txt).

  24. Textové řetězce • Proměnnou typu char* lze inicializovat konstantním textovým řetězcem. Nazdar, vespolek! char *p = "Nazdar, vespolek!"; ... printf("%s\n",p); • Každá konstanta je uložena v paměti. • Protože konstanta typu textový řetězec se navenek jeví jako hodnota typu char* (adresa prvního znaku řetězce v paměti), lze ji použít v inicializaci směrníku. char txt[20] = "Nazdar, vespolek!"; ... printf(txt); Nazdar, vespolek! • Oba výše uvedené příklady jsou inicializace, nikoliv přiřazení. char *a; char b[20]; ... a = "ahoj"; // lze, ale nedoporučuje se b = "ahoj"; // chyba Proměnné typu char* lze hodnotu typu konstantní textový řetězec i přiřadit, příliš se to však nedoporučuje. Přiřazení textového řetězce proměnné typu pole není definované.

  25. Textové řetězce • Manipulace s textovými řetězci: • k dispozici je řada standardních funkcí (kopírování, porovnání, modifikace, zjištění délky, ...), • většina manipulací je jednoduchá, lze řešit ve vlatní režii - často stačí jeden cyklu for • Př.: Zjištění délky řetězce Použití standardní funkce. char txt[20] = "Nazdar, vespolek!"; int i; i = strlen(txt); // řešení ve vlastní režii for(i = 0; txt[i]; i++) ; Možná je řetězec prázdný. Opakujeme, dokud nenarazíme na zakončovací nulový byte. Pokud není konec, inkrementovat čítač. Tělo cyklu je prázdné, vše řeší 3 výrazy v závorce. Někteří programátoři dávají přednost řešení řetězcových operací ve vlastní režii. Nelze to sice obecně doporučit, ale (spornou) výhodu lze vidět v tom, že není nutno si pamatovat názvy příslušných funkcí. Vzhledem k jednoduchosti nedojde k významnému prodloužení nebo snížení čitelnosti programu.

  26. Funkce • V jazyku C je jediný typ podprogramů - vše jsou funkce. • U funkcí, které nevrací hodnotu (tedy procedury dle terminologie Fortran/Pascal), se jako návratový typ používá typ void, který znamená "žádná hodnota". • V souladu s pravidly jazyka C není povinnost využít návratovou hodnotu funkce. • Dříve než je funkce použita (volána) musí být deklarovaná nebo definovaná. • Každá funkce (včetně funkce main) může používat parametry nebo může být bezparametrická. • Formální parametry musí být deklarované při definici funkce. • Funkce main dostává skutečné parametry z prostředí, ze kterého byl program spuštěn (např. příkazový řádek). • Deklarace i defince funkcí se vyvíjela, původní K&R styl není podporován ANSI standardem a nebyl zahrnut do C++. • Definice funkcí se nesmějí vnořovat. • Jméno funkce musí být unikátní identifikátor. • Protože i hlavní program je funkce (main), nemůže být v jazyce C umístěn jakýkoliv příkaz mimo funkci;

  27. Definice funkce • Klasický styl K&R • Zastaralé,nepoužívat! Není v ANSI C, nezahrnuto do C++, zde uváděno jen pro úplnost (občas lze nalézt zdrojové texty programů - zejm. pro UNIX -, které tento archaismus ještě stále používají). • typ tzv. typ funkce, typ výsledku funkce (návratové hodnoty) typjméno_funkce ( <seznam parametrů> ) <definice parametrů> { <definice lokálních proměnných>; <příkazy>; } Pokud funkce nemá žádné parametry, uvede se v seznamu parametrů slovo void. Typ výsledku bude int. Funkce má dva parametry. int suma (k, l) int k, l; { int soucet; soucet = k+l; return soucet; } Definice parametrů, až zde jsou popsány jejich typy. Lokální proměnná soucet je zde zbytečná. Definice lokální proměnné. int suma (k, l) int k, l; { return k + l; } Předání výsledku volajícímu.

  28. Definice funkce • Moderní styl • dle ANSI C, zahrnuto do C++. • informace o typech parametrů zahrnuta do seznamu parametrů • informace o parametrechseznam jmen formálních parametrů předcházený typem • Pokud funkce nemá mít žádné parametry, uvede se jako seznam parametrů • slovo void. typjméno_funkce ( <informace o parametrech> ) { <definice lokálních proměnných>; <příkazy>; } Pokud funkce nemá žádné parametry, uvede se v seznamu parametrů slovo void. Typ výsledku bude int. int suma (int k, int l) { return k + l; } Funkce má dva parametry, první i druhý bude typu int. Není žádná možnost zjednodušení, ve výčtu parametrů musí být uveden typ pro každý parametr zvlášť ( i kdyby byly všechny parametry stejného typu).

  29. Příkaz return • ukončení provádění funkce a návrat na místo volání (tedy do minulé funkce), • návratová hodnotavýraz, jehož hodnota určuje výsledek funkce • u funkcí typu void se návratová hodnota vynechává • k návratu z funkce dojde: • vykonáním příkazu return • vyčerpáním všech příkazů těla funkce (pouze u funkcí typu void, jinak příkaz return nelze vynechat) return [návratová hodnota]; Ve funkci typu void se nesmí vyskytovat příkaz return se specifikací návratové hodnoty. Ve funkci jiného typu než void se u všech příkazů return musí vyskytovat specifikace návratové hodnoty a nelze vynechat příkaz return, i když je posledním příkazem těla funkce.

  30. Volání funkce • funkce se volá pomocí zápisu: • v případě, že funkce nemá žádné parametry, musí se uvést dvojice prázdných závorek • volání funkce, která vrací hodnotu, se může použít všude, kde je povolena odpovídajícího typu (tzn. i jako příkaz, v tomto případě se návratová hodnota nepoužije) • volání funkce typu void se může použít pouze jako příkaz • v žádném případě se volání funkce nesmí vyskytovat na levé straně přiřazovacího operátoru(není to l-value) jméno_funkce ([seznam skutečných parametrů]); Nevyužití návratové hodnoty není ničím mimořádným. Např. funkce printf vrací počet vystoupivších znaků, tato hodnota obvykle není potřebná a funkce se běžně používá jako příkaz. int deset (void) { return 10; } ... deset(); Bezparametrická funkce. Volání bezparametrické funkce. Funkce deset() je samozřejmě pouze příklad, její existence by byla velmi nepraktická..

  31. Předávání parametrů • Předává se vždy hodnota parametru, je-li skutečným parametrem výraz, je před zavoláním funkce vyhodnocen a předává se jeho výsledek. • Ve funkci je pro každý parametr vytvořena dočasně existující lokální proměnná, která je inicializovaná na hodnotu odpovídajícího skutečného parametru, při návratu z funkce tyto dočasné proměnné zanikají. • Tento mechanismus zajišťuje zachování hodnoty skutečného parametru, i když je odpovídající parametr uvnitř funkce změněn. • Má-li mít funkce možnost hodnotu parametru změnit (tzv. výstupní parametr), musí mít funkce formální parametr typu směrník. Tato funkce už něco dělá (ovšem je nepraktická). void osm (int a) { a = 8; } ... int x = 0; osm(x); printf("%d",x); Prakticky zbytečná funkce (její provedení nemá efekt). void osm8 (int *a) { *a = 8; } ... int x = 0; osm8(&x); printf("%d",x); Zobrazí se 0, hodnota x se nezměnila. Zobrazí se 8, hodnota x se změnila.

  32. Pole jako parametry funkce • Parametr typu pole se vždy předává jako směrník (tzn. změna některého prvku pole uvnitř funkce se projeví na místě volání). void nuluj (int a[], int n) { int i; for(i = 0; i < n; i++) a[i] = 0; } int main(void) { int x[40]; nuluj(x,40); ... } Zde není nutné specifikovat počet prvků, postačí informace, že jde o jednorozměrné pole. Dojde k vynulování všech prvků pole x. Volání funkce nuluj() by samozřejmě bylo možné nahradit voláním knihovní funkce memset(). memset(x, 0, 40 * sizeof(int)); void nuluj (int *a, int n) { int i; for(i = 0; i < n; i++) a[i] = 0; } Vzhledem k souvislosti polí a směrníků lze změnit funkci tímto způsobem. Je zřejmé, že v obou případech se funkce volá stejně a že se v obou případech jako skutečný parametr předává směrník.

  33. Pole jako parametry funkce • I u vícerozměrných polí se jako parametr předává směrník na pole. • void nuluj (int *a, int n) • { • int i; • for(i = 0; i < n; i++) • a[i] = 0; • } • void nuluj2 (int a[][10], int n) • { • int i; • for(i = 0; i < n; i++) • nuluj(a[i],10); • } • void main(void) • { • int x[10][10] = { 1,2,3,4,5,6,7,8,9}; • nuluj2(x,10); • ... • } Zde není nutné specifikovat počet prvků v prvním rozměru, postačí informace o velikosti prvku (každý prvek pole a bude desetiprvkové pole int). Dojde k vynulování všech prvků pole x. Volání funkce nuluj2() by samozřejmě bylo možné nahradit voláním knihovní funkce memset(). memset(x, 0, 10 * 10 * sizeof(int));

  34. Deklarace funkce • Volání funkce by v ideálním případě nemělo předcházet její definicí (nelze vždy zajistit a ani není vždy účelné - knihovní funkce). • Překladač potřebuje znát počet a typy parametrů a také typ výsledku funkce. • Problém řeší deklarace funkce. • Deklarace se historicky vyvíjela. • 1. Implicitní dekarace • Zastaralé (už několik desítek let),nepoužívat! (Není v ANSI C, nepodporováno dnešními překladači, nezahrnuto do C++, zde uváděno jen pro úplnost.) • V podstatě žádná deklarace. • Není-li funkce definovaná dříve, než je zavolaná, překladač předpokládá, že funkce je typu int a má mít takové argumenty, s jakými byla zavolána. • Je-li později v definici funkce zjištěno něco jiného, je hlášena chyba při překladu definici funkce.

  35. Deklarace funkce • 2. Klasický styl deklarace • Zastaralé,nepoužívat! (Není v ANSI C, nezahrnuto do C++, zde uváděno jen pro úplnost - stále existují texty programů, které tento archaismus používají.) • Deklarace obsahuje typ funkce, neposkytuje informace o parametrech. typ jméno_funkce ();

  36. Deklarace funkce • 3. Moderní styl deklarace - prototyp • Dnes jediný doporučovaný způsob. • Zahrnuto do ANSI C, převzato (s jednou drobnou úpravou) do C++. • Deklarace se provádí uvedením prototypu. • Prototyp je hlavička funkce (první řádek definice) zakončený středníkem. • Nemá-li funkce parametry, uvádí se v prototypu na místě seznamu parametrů slovo void (jinak by prototyp nebylo možné odlišit od klasického stylu deklarace - používalo se souběžně). • V prototypech není nutné uvádět jména formálních parametrů, zcela postačí uvádět typy (použití jmen je ale někdy přehlednější, jméno parametru může napovědět jeho význam, často se tak sníží potřeba komentářů). voidprvni(int a); int druha(int a, char *b); int druha(int, char*); double treti(void); char ctvrta(); Dvě podoby prototypu téže funkce. Toto v C není prototyp! V C++ se nemusí u bezparametrických funkcí uvádět mezi závorkami slovo void, tedy i poslední výše uvedený příklad se v C++ považuje za prototyp. V C++ ovšem již nejsou podporovány archaismy a jediným povoleným způsobem deklarace funkce je právě prototyp.

  37. Deklarace funkce • Prototypy lze vytvářet i pro funkce sproměnným počtem parametrů - viz např. printf(). Za poslední povinný parametr se v prototypu uvádí tři tečky (ellipsis). • Modifikátor const - překladač kontroluje, zda hodnota odpovídajícího parametru není ve funkci změněna (přiřazení, operátor s vedlejšími účinky). Má význam pouze pro parametr typu směrník. Prototyp funkce printf. První parametr je typu char*, tento parametr je povinný. Může následovat libovolný počet dalších parametrů libovolných typů. int printf (const char*, ...);

  38. Funkce main • Program v C nebo C++ musí obsahovat právě jednu funkci main(). (Některá prostředí tento požadavek modifikují, např. grafické windows aplikace používají funkci WinMain() apod.) • Po spuštění programu je tato funkce zavolána. • Návrat z funkce main() znamená ukončení programu. • Main musí vracet hodnotu typu int (obvykle překladače tolerují i typ void). • Výsledek funkce main() je tzv. exit status, tedy kód předávaný nadřízenému procesu jako informace úspěšnosti běhu programu (obvykle 0 znamená úspěch, nenulová hodnota kód chyby). • Funkce main() může být bezparametrická nebo může používat 2 parametry. (Některé překladače v některých prostředích přidávají další parametry.) void main (int argc, char *argv[]) { int i; for(i = 0; i < argc; i++) printf("argv[%d]: %s\n",i,argv[i]); } Obvyklé (nikoliv povinné) je dodržování zvykových názvů parametrů funkce main(): argc (argument count) argv (argument vector) U některých překladačů je v prvním parametru pouze text, který byl skutečně zapsaný jako příkaz, v tomto případě "prvni". C:\priklad>prvni aaa b.txt -ls /x argv[0]: C:\priklad\prvni.exe argv[1]: aaa argv[2]: b.txt argv[3]: -ls argv[4]: /x C:\priklad> Častým rozšířením překladačů je třetí parametr, který obsahuje pole parametrů prostředí (zakončený NULL).

  39. Směrník na funkci • Kód programu je obsažen ve funkcích. • Kód programu je uložen v paměti. • Přesné místo uložení je často nezajímavé. V případě potřeby je lze zjistit. • Jméno funkce bez kulatých závorek představuje adresu funkce (operátor & se zde nepoužívá). • Lze definovat proměnnou typu směrník na funkci a jejím prostřednictvím funkci zavolat. int dvakrat(int x) { return 2*x; } void main(void) { int (*pfce)(int); int y; pfce = dvakrat; y = (*pfce)(5); y = pfce(5); } Směrník na funkci typu int s jedním parametrem typu int Samotné jméno funkce (bez závorek) má význam adresy funkce. Funkce dvakrat() je zavolána nepřímo (pomocí směrníku). Tento jednodušší zápis je také správný (není z něj ovšem zřejmé, že pfce je směrník na funkci).

  40. Směrník na funkci • Podobně lze definovat pole směrníků na funkci. • Pole funkcí nejsou přípustná. int dvakrat(int x) { return 2*x; } int trikrat(int x) { return 3*x; } void main(void) { int (*pfce[3])(int); int y, z; pfce[0] = dvakrat; pfce[1] = trikrat; y = (*pfce[0])(5); z = pfce[1](5); } Pole směrníků na funkci lze inicializovat obvyklým způsobem: int (*pfce[3])(int)= {dvakrat, trikrat}; Prvek pfce[2] je zde inicializován na NULL. Tříprvkové pole směrníků na funkci typu int s jedním parametrem typu int. Funkce dvakrat() je zavolána nepřímo (pomocí směrníku). Funkce trikrat() je zavolána nepřímo (pomocí směrníku) - jednodušší zápis.

  41. Třídy uložení, doba života, viditelnost • vlastnost proměnných a funkcí • viditelnost (scope, visibility) - otázka přístupnosti (použitelnosti) proměnných a funkcí z různých míst programu - z různých funkcí, z různých modulů • v jedné funkci (lokální ve funkci, tzv. lokální viditelnost) - pouze proměnné, proměnná není použitelná v žádné jiné funkci než v té, ve které je definována • v modulu - od deklarace/definice do konce modulu, a to buď s omezením na jediný modul programu, nebo s možností použít ve všech modulech, kde je uvedna deklarace (tzv. globální viditelnost). • doba života (duration) - jen proměnné, zda proměnná existuje (je jí přidělena paměť) po celou dobu běhu programu, nebo pouze v některých fázích jeho běhu • globální - proměnné je přidělena paměť před zahájením provádění funkce main a paměť je uvolněna až při ukončování programu (po skončení funkce main resp. posledního příkazu programu) • lokální - proměnná vznikne až při zavolání funkce, ve které je proměnná definovaná a zanikne při návratu z této funkce (nejde o dynamické proměnné!) V některých programovacích jazycích (typ. Pascal) se nehovoří samostatně o době života a viditelnosti, ale o lokálních a globálních proměnných (resp. podprogramech). Lokální proměnné pak mají lokální dobu života i viditelnost a naopak.

  42. Třídy uložení, doba života, viditelnost • lze ovládat pomocí kombinace • místa definice (jen proměnné) • vnitřní úroveň - uvnitř funkce, pouze proměnné (definice funkcí se nesmějí vnořovat), viditelnost pouze uvnitř této funkce, doba života lokální i globální (static) • vnější úroveň - vně jakékoliv funkce, vždy globální doba života, viditelnost omezena na jediný modul (static) nebo může být ve všech modulech (extern) • specifikací třídy uložení (klíčové slovo) • extern - jen vnější úroveň (default pro tuto úroveň), globální doba života, viditelnost ve všech modulech, kde je platná deklarace • static - vždy globální doba života, na vnější úrovni viditelnost omezena na jediný modul • auto - jen vnitřní úroveň (default pro tuto úroveň), lokální doba života a viditelnost • register - jako auto, doporučení, aby se překladač tuto pokusil umístit do registru procesoru místo do paměti • viz následující tabulka

  43. Třídy uložení, doba života, viditelnost úroveň položka třída uložení doba života viditelnost vnější proměnná static globální jeden modul extern všechny moduly, kde je deklarováno funkce static jeden modul extern všechny moduly, kde je deklarováno vnitřní proměnná static jedna funkce auto lokální register

  44. Struktury • Odvozený(nestandardní) datový typ. • Umožňuje v jedné proměnné sdružovat více hodnot obecně různých typů. • jmenovka_struktury (tag) - jméno přidělené této struktuře (jakožto datovému typu) • jméno_proměnné - jméno proměnné, která je tímto popisem definovaná nebo deklarovaná • seznam_složek - výčet složek (prvků) struktury ve tvaru: • typ jméno; struct jmenovka_struktury {<seznam_složek>} jméno_proměnné; struct ABCD { int a; double b; char c[10]; } x; struct ABCD y; jmenovka struktury je ABCD Vnitřní složky struktury jsou a, b, c Proměnná x Proměnná y je formálně i fakticky shodného typu jako x.

  45. Struktury • Musí být použita buď alespoň jmenovka nebo jméno proměnné. • Jmenovka slouží k pojmenování dané struktury, je tak možné definovat a deklarovat proměnné tohoto typu na různých místech programu, používat struktury jako parametry funkcí apod. • Není-li použita jmenovka, jde o tzv. nepojmenovanou strukturu (untagged). (Má smysl pouze pro struktury použité na jediném místě programu.) • proměnnou typu struktura lze v definici inicializovat Nedeklaruje ani nedefinuje se žádná proměnná, pouze se zavádí pojmenování pro strukturu s tímto obsahem. struct ABCD { int a; double b; char c[10]; }; struct ABCD x, y, z; struct ABCD { int a; double b; char c[10]; } w = {1, 3.14, "nic"}; Jednotlivé složky se inicializují podle pravidel pro jednotlivé typy.

  46. Struktury • Operace: • zpřístupnění prvku • získání adresy proměnné typu struktura (samozřejmě lze získat i adresu kteréhokoliv prvku proměnné typu struktura) • přiřazení • předání jako parametru funkce resp. použití jako návratové hodnoty funkce • Zpřístupnění prvku struct ABCD { int a; double *b; char c[10]; } z; double x; ... z.a = -15; z.b = &x; z.*b = z.a * 0.1569; z.c[1] = 'b'; Tečka je operátorem přístupu k prvku struktury. Je-li prvkem struktury směrník, dereferencuje se běžným způsobem. Posloupnost dvou operátorů.*se někdy chápe jako operátor jediný - dereference prvku struktury. Bylo by možné použít i zápis *(z.b), je to ale neobvyklé. S prvkem struktury lze provádět stejné operace jako s běžnou proměnnou daného typu.

  47. Struktury Získání adresy proměnné typu struktura struct ABCD { int a; double *b; char c[10]; } s; struct ABCD *p; double x; ... p = &s; Proměnná typu směrník na strukturu ABCD. Získání adresy proměnné typu struktura běžným způsobem (operátor &). Přístup k prvku struktury prostřednictvím směrníku na tuto strukturu vyžaduje běžnou dereferenci. (*p).a = 7; p->a = 7; Pro stručnost a přehlednost je pro přístup k prvku struktury prostřednictvím směrníku zaveden nový operátor ->. ... p->a = -15; p->b = &x; p->*b = p->a * 0.1569; p->c[1] = 'b';

  48. Struktury Přiřazení struct ABCD { int a; double *b; char c[10]; } aa, bb; double x; int i; aa.a = -15; aa.b = &x; aa.*b = aa.a * 0.1569; aa.c[1] = 'b'; Rozepisovat kopírování struktur prvek po prvku není nutné a silně se nedoporučuje (při každé modifikaci deklarace struktury by bylo nutné toto podrobně rozepsané kopírování upravovat). bb.a = aa.a; bb.b = aa.b; for(i = 0; i < 10; i++) bb.c[i] = aa.c[i]; bb = aa; O.K., přiřazení je definováno. Okopírují se i pole, která jsou prvkem struktury, i když pro samotná pole přiřazení definováno není.

  49. Struktury Struktury lze používat jako parametry funkcí i jako návratové hodnoty zcela bez omezení. Aby se zamezilo kopírování velkých bloků paměti u rozsáhlých struktur, lze samozřejmě používat i směrníky (u návratové hodnoty může být problematické). Struktury jako parametry a návratové hodnoty funkcí struct CPLX { double Re; double Im; }; struct CPLX add_cplx(struct CPLX a, struct CPLX b) { struct CPLX c; c.Re = a.Re + b.Re; c.Im = a.Im + b.Im; return c; } void main(void) { struct CPLX x = {1,-3.14}, y = {-0.6, 2}, z; ... z = add_cplx(x,y); ... } nebo zkráceně: { struct CPLX c = {a.Re + b.Re, a.Im + b.Im}; return c; }

  50. Struktury Struktury a dynamické proměnné struct ABCD { int a; double *b; char c[10]; } *p; p = calloc(1,sizeof(struct ABCD)); p->a= -15; ... free(p); Jednoduchá dynamická proměnná typu struktura. p je směrník na strukturu. struct ABC { int a; char c[10]; } *p1; p1= calloc(10,sizeof(struct ABC)); p1[2].a= -15; ... free(p1); Dynamické desetiprvkové pole struktur. p1 je dynamické pole struktur.

More Related