Programování III.
Mgr. Monika Pinkasová
Zlepšování podmínek pro využívání ICT ve výuce a rozvoj výuky angličtiny na SPŠei Ostrava č.projektu CZ.1.07/1.1.07/03.0089
Ostrava 2011
Obor: Informační technologie Předmět: Programování Ročník: 3.ročník Autor: Mgr. Monika Pinkasová
Doporučená literatura: Herout, P.: Učebnice jazyka C – 1.díl, Páté vydání, Kopp, 2008, ISBN 978-807232-351-7 Herout, P.: Učebnice jazyka C – 2.díl, Druhé přepracované vydání, Kopp, 2004, ISBN 80-7232-221-4 Kadlec, V.: Učíme se programovat v jazyce C, 2. Vyd., Brno : CP Books, 2005, ISBN 80-7226-715-9 Schildt, H.: Nauč se sám C, překlad Lubomír Kočí, Praha : SoftPress, c2001, ISBN 80-86497-16-X
© Mgr. Monika Pinkasová © Střední průmyslová škola elektrotechniky a informatiky, Ostrava, příspěvková organizace
OBSAH Úvod ............................................................................................................................... 9 1
Vícerozměrné statické pole ................................................................................ 10 1.1 1.2
Statické vícerozměrné pole .......................................................................................11 Přístup k prvkům vícerozměrného pole......................................................................12
2
Práce s vícerozměrným statickým polem .......................................................... 14
3
Vícerozměrné pole - procvičování...................................................................... 17
4
Vícerozměrné statické pole a funkce ................................................................. 21
5
Vícerozměrné statické pole – řešené příklady .................................................. 25
6
Vícerozměrné statické pole – neřešené příklady .............................................. 30
7
Přístup k prvkům vícerozměrného pole............................................................. 33
8
Pole pointerů ........................................................................................................ 37
9
Pointer na pole ..................................................................................................... 41
10
Pointer na pointer ............................................................................................ 44
11
Vícerozměrné dynamické pole – shrnutí, příklady ........................................ 48
11.1 11.2
Porovnání vícerozměrných polí .................................................................................48 Příklady .....................................................................................................................49
12
Řetězce – deklarace ......................................................................................... 54
13
Řetězce – základy práce .................................................................................. 57
13.1 13.2 13.3
Načtení řetězce .........................................................................................................57 Výpis řetězce .............................................................................................................58 Přístup řetězcům .......................................................................................................59
14
Řetězce – funkce pro práci s řetězci .............................................................. 60
15
Řetězce – řešené příklady ............................................................................... 64
16
Řetězce – neřešené příklady ........................................................................... 70
17
Pole řetězců a parametry funkce main() ........................................................ 73
17.1 17.2
Pole řetězců ..............................................................................................................73 Parametry funkce main() ...........................................................................................75
18
Datový typ struktura ........................................................................................ 78
19
Práce s položkami struktur ............................................................................. 83
20
Struktury - příklady .......................................................................................... 86
21
Pole struktur ..................................................................................................... 90
22
Struktura ve struktuře a pole ve struktuře ..................................................... 94
23
Dynamická struktura........................................................................................ 98
24
Struktura a funkce.......................................................................................... 101
25
Struktura – řešené příklady ........................................................................... 106
26
Struktura – neřešené příklady ....................................................................... 109
27
Datový typ union ............................................................................................ 113
28
Datový typ enum ............................................................................................ 116
29
Datový typ enum a union - příklady.............................................................. 120
30
Soubory .......................................................................................................... 126
31
Práce se soubory ........................................................................................... 129
32
Funkce pro práci se soubory ........................................................................ 133
32.1 32.2 32.3
Čtení ze souboru .....................................................................................................133 Zápis do souboru .....................................................................................................135 Ostatní funkce pro práci se soubory ........................................................................136
33
Soubory – řešené příklady ............................................................................ 138
34
Soubory – neřešené příklady ........................................................................ 142
35
Shrnutí učiva .................................................................................................. 144
Vysvětlivky k používaným symbolům Obsah hodiny – popisuje náplň hodiny
Cíl hodiny – specifikace dovedností a znalostí, které si studující osvojí během hodiny
Klíčová slova – nové pojmy, specifické termíny či cizí slova, jejichž význam je v textu vysvětlen
Definice – definování a vysvětlení nového pojmu či jevu
Příklad – objasnění nebo konkretizování problematiky na příkladu ze života, z praxe, ze společenské reality apod.
Shrnutí – shrnutí probrané látky, shrnutí kapitoly
Kontrolní otázky a úkoly – prověřují, do jaké míry studující text a problematiku pochopil, zapamatoval si podstatné a důležité informace a zda je dokáže aplikovat při řešení problémů Otázky k zamyšlení - úkoly rozšiřující úroveň základních znalostí
Literatura – literatura a zdroje pro doplnění a rozšíření poznatků kapitoly
9
Úvod Tento výukový modul se zabývá prohlubování znalostí programování v jazyce C. Navazuje tak na výukový modul Programování I. a Programování II. a znalosti těchto modulů jsou nezbytně nutné vstupní předpoklady modulu Programování III. Modul Programování III. je určen pro studenty 3. ročníku oboru Informační technologie. Cílem modulu Programování III. je rozšířit a zdokonalit stávající znalosti programování v jazyce C. Studenti se seznámí se rozšiřujícími ale přesto i základními prvky jazyka C - s vícerozměrnými poli, řetězci, strukturovanými datovými typy – struktura, enum, union, soubor a jejich využitím. Prostudováním tohoto textu si student osvojí základní znalosti a pojmy. Ale jen cvičením a používáním těchto znalostí v praktickém programování se může dostat k aktivnímu použití jazyka C.
10
1
Vícerozměrné statické pole
Obsah hodiny Seznámíme se s vícerozměrným polem, s jeho deklarací, charakteristikou a přístupem k prvkům.
Cíl hodiny Po této hodině budete schopni: ● ● ●
deklarovat statické vícerozměrné pole popsat alokaci statického vícerozměrného pole pracovat s prvky vícerozměrného pole
Klíčová slova Vícerozměrné pole, statické pole, deklarace vícerozměrného pole, přístup k prvkům vícerozměrného pole
Velice často potřebujeme zpracovat větší množství dat stejného datového typu, které jsou seskupeny do skupin.
Příklad V programu, který bude zpracovávat průměrnou denní teplotu v prvních 5 týdnech v roce. Měli bychom 5*7=35 reálných čísel. Pokud bychom potřebovali týdenní průměry, maxima, minima, potřebujeme jen prvních sedm čísel. Pro podobné zpracování třetího týdně potřebujeme jen hodnoty 15 až 21. Bylo by proto rozumnější, seskupit data pro jeden týden do pole. A pak tedy budeme mít pole polí. Můžeme pak se všemi hodnotami v daném týdnu pracovat jednoduše a efektivně. Graficky bychom mohli takto strukturovaná data zobrazit takto:
Jednotlivé týdny
Dny v týdnu
11 Vytvořili jsme tak matici čísel o pěti řádcích a sedmi sloupcích. Tuto datovou strukturu realizujeme v programování pomocí vícerozměrných polí. Vícerozměrné pole chápeme jako pole polí. To znamená, že jde o pole, jehož prvky jsou další pole. Stejně jako u jednorozměrného pole můžeme prvky pole vytvořit staticky (automaticky, v zásobníku) nebo dynamicky (pomocí malloc v haldě)
1.1
Statické vícerozměrné pole
Statické vícerozměrné pole je alokováno v zásobníku. Počet řádků i sloupců musíme znát již před překladem programu. Deklarace statického vícerozměrného pole: datový_typ_prvků název [počet_řádků] [počet_sloupců]
Příklad int pole[3][4]; Vytvoří se v paměti „matice“ 12 celých čísel, které jsou „uspořádány“ do 3 řádků a 4 sloupců. Obdélníkové schéma matice je jen naše představa vícerozměrného pole. Prvky pole se totiž uloží v paměti za sebou.
Název pole je stejně jako u jednorozměrného pole ukazatel na první prvek v poli – resp. adresa prvního prvku pole.
Příklad deklarace s využitím maker #define R 3 //počet řádků #define S 4 //počet sloupců int pole[R][S]; Ještě před překladem preprocesor rozvine makra – zamění tedy R ta číslo 3 a S za číslo 4, proto při překladu bude známa velikost vícerozměrného pole. Pokud makra použijeme v celém programu pro práci s vícerozměrným polem, jednoduchou změnou hodnot maker můžeme změnit rozměr vícerozměrného pole v celém programu.
12
Příklad deklarace s inicializací Stejně jako u jednorozměrného pole můžeme při deklaraci prvky pole ihned inicializovat. int p[ ][4]= {{1,2,3,4}, {11,12,13,14}, {21,22,23,24}}; Počet sloupců musí být v této deklaraci uveden. Počet sloupců bude určen podle počtu čtyřprvkových polí, které jsou uvedeny v závorkách { }. Vytvoří se pole p:
1.2
Přístup k prvkům vícerozměrného pole
Pro přístup k prvkům vícerozměrného pole budeme potřebovat dva indexy prvku – řádkový a sloupcový index.
Příklad int p[3][4]; p[0][1]= 8; Tímto příkazem uložíme číslo 8 do druhého prvku (index 1) na prvním řádku (index 0). p
scanf(“%d”,&p[1][2]); Načteme od uživatele třetí prvek na druhém řádku. srand((long)time(NULL)); p[2][1]= rand() % 10; Náhodně vygenerujeme číslo a uložíme jej do druhého prvku na třetím řádku. Předpokládejme, že uživatel zadá číslo 5 a že bude vygenerováno číslo 7.
13 p[0][4]= 3; Zapisujeme hodnotu 3 do pátého prvku na prvním řádku. V každém řádku máme ale jen 4 prvku. Tento příkaz nebude hlásit chybu, protože C nekontroluje meze polí. Číslo 3 bude zapsáno do dalšího prvku – tedy do dalšího řádku.
Této postup sice nehlásí chybu, ale nedoporučuje se, protože může dojít k přemazání hodnot, které už v poli máme uloženy nebo můžeme zapisovat do nevyblokované paměti, což je činnost velice nebezpečná a chyba těžko dohledatelná. p[3][0]= 6; Chybný příkaz, který píše mimo vyblokovanou paměť.
Shrnutí kapitoly Vícerozměrné pole je pole polí, které odpovídá matematickému pojmu matice o daném počtu řádků a sloupcůU statického vícerozměrného pole musíme znát počet řádků i sloupců již před překladem. Prvky pole jsou alokovány v zásobníku za sebou v souvislém bloku. K prvkům vícerozměrného pole přistupujeme pomocí řádkových a sloupcových indexů.
Kontrolní otázky a úkoly 1) Popište, deklarujte a zakreslete vícerozměrné statické pole o 5 řádcích a 3 sloupcích. Zapište na do všech prvků v prvním sloupci číslo 5. Načtěte od uživatele všechny prvky v druhém sloupci. Náhodně vygenerujte hodnoty pro prvky ve třetím sloupci. 2) Kontroluje jazyk C meze polí?
14
2
Práce s vícerozměrným statickým polem
Obsah hodiny Seznámíme se s prací se všemi prvky vícerozměrného pole.
Cíl hodiny Po této hodině budete schopni: ● ●
načíst všechny prvky vícerozměrného pole vypsat prvky vícerozměrného pole ve tvaru matice
Klíčová slova Načtení vícerozměrného pole, výpis vícerozměrného pole
Ve vícerozměrném poli pracujeme s větším množstvím prvků, které organizujeme do řádků a sloupců. Poté co jste prostudovali učivo o jednorozměrných polích je zřejmé, že nebudeme s prvky pracovat jednotlivě, ale najednou pomocí cyklů. Protože známe rozměry pole – počet řádků i počet sloupců, použijeme cyklus s danám počtem opakování - cyklus for. Jestliže chceme načíst jeden řádek – jedno jednorozměrné pole, použijeme jeden cyklus for. Ve vícerozměrném poli máme těchto řádků více, proto musíme dalším cyklem for zajistit opakování načítání jednotlivých řádků. Načítání vícerozměrného pole budeme provádět vnitřním a vnějším cyklem for.
Příklad Načítání vícerozměrného pole int p[3][4], i, j; srand((long)time(NULL)); for(i=0;i<3;i++) for(j=0;j<4;j++) p[i][j]= rand() % 100;
//nebo scanf(“%d”,&p[i][j]);
15 Vnitřní cyklus for(j=0;j<4;j++) p[i][j]= rand() % 100; zajistí načtení jednoho řádku – všechny 4 prvky na řádku s indexem i. Vnější cyklus for(i=0;i<3;i++) zajistí opakování vnitřního cyklu tak, aby byly načteny všechny tři řádky ve vícerozměrném poli p. Velice podobně provádíme výpis vícerozměrného pole. Abychom jej však zobrazili na více řádcích, musíme po každém vypsání jednoho řádku odřádkovat.
Příklad Výpis vícerozměrného pole for(i=0;i<3;i++) { for(j=0;j<4;j++) //vnitřní cyklus vypíše jeden řádek
printf(“%d ”, p[i][j] ); printf(“\n”);
//po vypsání řádku přejde na nový řádek
}
Pomocí vnitřního a vnějšího cyklu zpracováváme vícerozměrná pole téměř vždy.
Příklad Zjištění maximálního prvku ve vícerozměrném poli int p[3][4], i, j, max; //načtení popř. i výpis pole max=p[0][0];
// nastavíme hodnotu maxima na první prvek
for(i=0;i<3;i++) for(j=0;j<4;j++) if (max
// jestliže je prvek větší než maximum // pak maximum přepíšeme prvkem
printf(“Maximum je %d”, max);
16
Příklad Zjištění aritmetického průměru všech kladných čísel ve vícerozměrném poli. int p[3][4], i, j, pocect=0, soucet=0; //načtení popř. i výpis pole for(i=0;i<3;i++) for(j=0;j<4;j++) if (p[i][j]>0)
// jestliže je prvek kladný
{ pocet++; soucet+=p[i][j]; } printf(“Průměr všech kladných je %f”, (float)soucet/pocet);
Shrnutí kapitoly Ve vícerozměrných polích máme data organizovány do řádků a sloupců, proto při zpracovávání dat ve vícerozměrných polích používáme vnější a vnitřní cyklus for. Vnitřní cyklus provede zpracování dat na jednom řádku a vnější cyklus zajistí opakování dané činnosti pro všechny řádky vícerozměrného pole.
Kontrolní otázky a úkoly 1) Popište podrobně provádění výpisu vícerozměrného pole ve tvaru matice. 2) Vytvořte program, který pro vícerozměrné pole int p[3][4] zjistí počet všech sudých čísel v tomto poli.
17
3
Vícerozměrné pole - procvičování
Obsah hodiny Procvičíme práci s vícerozměrným polem i s přístupem k jednotlivým prvkům vícerozměrného pole.
Cíl hodiny Po této hodině budete schopni: ● ●
zpracovat data ve vícerozměrných polích využít vícerozměrná pole pro řešení problémů
Příklad 1 1) Vytvořte statické reálné vícerozměrné pole matice o 5 řádcích a 4 sloupcích. 2) Načtěte pole matice hodnotami od uživatele. 3) Vypište pole matice ve tvaru matice (obdélník). 4) Načtěte od uživatele řádek a sloupec a vypište hodnotu v poli matice na tomto zadaném řádku a sloupci. 5) Najděte minimální prvek v poli matice (jeho první výskyt). Vypište jej a vypište i na kterém řádku a ve kterém sloupci se nachází. 6) Zjistěte kolik čísel v poli matice je z intervalu <-10;10). 7)Načtěte od uživatele číslo sloupce a najděte maximální hodnotu v tomto zadaném sloupci v poli matice. 8) Načtěte od uživatele číslo řádku a vypočtěte aritmetický průměr hodnot v tomto zadaném řádku v poli matice. 9) Načtěte od uživatele číslo řádku a do tohoto řádku v poli matice zapište samé nuly a pole matice znovu vypište. 10) Načtěte od uživatele číslo řádku a setřiďte vzestupně zadaný řádek v poli matice. Poli matice opět vypište.
18
Řešení Některé úkoly řešte obdobně podle příkladů z předchozí kapitoly. ad 7) float matice[5][4], max; int i, j, sloupec; //načtení popř. i výpis pole matice // načteme číslo sloupce scanf(“%d”,&sloupec); // číslo sloupce snížíme o 1, protože pole indexujeme od nuly sloupec--; // nastavíme hodnotu maxima na první prvek max=matice[0][0]; // sloupec ve zpracování pole matice zůstává stále stejný, // proto bude stačit jen jeden cyklus, který bude měnit // řádkový index // v cyklu budeme zjišťovat, jestli je daný prvek větší než // maximum, pak maximum přepíšeme daným prvkem for(i=0;i<5;i++) if (max< matice[i][sloupec]) max= matice[i][sloupec]; printf(“Maximum je %d”, max); ad 10) Z předchozího modulu víme, že pro třídění jednorozměrného pole Bubble sortem potřebujeme dva cykly for. Stejně tak to bude i třídění jednoho řádku v poli matice, protože porovnáváme jen prvky vedle sebe na stejném řádku - řádkový index zůstává stále stejný float matice[5][4]; int i, j,pom, radek; //načtení popř. i výpis pole scanf(“%d”,&radek); radek --;
19 for(i=0;i<3;i++) for(j=0;j<3;j++) if ( matice[radek][j]< matice[radek][j+1] ) { pom= matice[radek][j]; matice[radek][j] = matice[radek][j+1]; matice[radek][j+1] = pom; }
Příklad 2 1) Napište program, který načte do vícerozměrného pole znaky - matice typu 5 x 7 náhodně vybrané znaky z ASCII tabulky s ordinálními čísly v intervalu <33; 126>. Pole znaky vypište. 2) Zjistěte počet malých písmen a velkých písmen v poli znaky. 3) Vytvořte jednorozměrné dynamické pole mala podle počtu malých písmen v poli znaky, do kterého uložíte všechna malá písmena z pole znaky. 4) Vytvořte jednorozměrné dynamické pole velka, do kterého uložíte všechna velká písmena z pole znaky. 5) Vytvořte jednorozměrné dynamické pole ostatni a uložte do něj všechny ostatní znaky, která jste neuložili do pole mala ani do pole velka. 6) Všechna pole vypište. 7) Setřiďte pole mala vzestupně a vypište. 8) Pole velka setřiďte sestupně a vypište.
Příklad 3 1) Napište program, který bude obsahovat definici typu čtvercové matice celých čísel (velikost bude určena konstantou MAX). 2) Načtěte prvky matice náhodnými čísly z intervalu <-20;20> . 3) Vypište matici. 4) Zjistěte kolik je v matici prvků dělitelných číslem 5.
20
5) Přičtěte číslo 10 ke všem prvkům matice na zadaném řádku, který načtete od uživatele. 6) Zaměňte dva zadané řádky v matici. Jejich indexy načtěte od uživatele. 7) Vypočtěte součet prvků na obvodu matice 8) Vypočtěte průměrnou hodnotu prvků hlavní diagonály. 9) Vypište prvky vedlejší diagonály. 10)Vypište matici tak, aby pod hlavní diagonálou byly samé nuly.
Příklad 4 1) Napište program, který načte náhodná celá čísla do matice celých čísel typu 3 x 4. 2) Zjistěte aritmetický průměr všech prvků této matice. 3) Vytvořte novou matici reálných čísel typu 3 x 4. Do jejich prvků uložte rozdíl příslušného prvku původní matice od aritmetického průměru prvků celočíselné matice.
21
4
Vícerozměrné statické pole a funkce
Obsah hodiny Seznámíme se zpracováním vícerozměrných polí ve funkcích.
Cíl hodiny Po této hodině budete schopni: ● ● ●
co nejvhodněji sestavit hlavičku funkce pro práci s vícerozměrným polem vytvořit funkci pro zpracování vícerozměrného pole správně zavolat funkci pro práci s vícerozměrným polem
Klíčová slova Skutečný parametr, formální parametr
Podobně jako u jednorozměrných polí nebudeme ve funkci, která pracuje s vícerozměrným polem, vytvářet toto pole duplicitně. Znamenalo by to, že bychom ve funkci museli pole znovu vyalokovat a všechny hodnoty nakopírovat ze skutečného parametru do formálního parametru. Byla by to činnost náročná na čas i paměť. Skutečným parametrem funkce, která zpracovává vícerozměrné pole, je název pole – adresa prvního prvku. Formálním parametrem je pointer na pole s uvedeným počtem sloupců. Přesněji jde o název vícerozměrného pole s rozměry, kde první rozměr (závorka [ ]) se zůstává prázdná a druhá dimenze musí být uvedena jako konstanta. Musíme tedy pevně zadat počet sloupců. První rozměr zůstává v zápisu prázdný, protože jde v podstatě o pointer na pole. Můžeme tedy formální parametr zapsat jako pointer na pole.
Příklad Vytvoříme funkci nacti pro načtení vícerozměrného pole int pole[3][4]. Do funkce tedy budeme posílat jen adresu prvního provku pole, která je uložena v pointeru pole. Ve funkci nacti si uložíme tuto adresu do
22 formálního parametru int p[ ][4], který je ukazatelem na čtyřprvková pole. Může být proto také zapsán (*p)[4]. V našem příkladu jsou tyto pole tři. Tento počet polí pošleme do funkce jako další parametr funkce. Oba ukazatelé pole a p ukazují na první prvek vícerozměrného pole. Pro práci s polem používáne pointer pole ve funkci main a pointer p používáme ve funkci nacti.
p
Řešení void nacti(int p[ ][4], int radky ); //nebo pointer na pole
void nacti(int (*p)[4], int radky )
int main() { int pole[3][4]; nacti(pole,3); return 0; } void nacti(int p[ ][4], int radky ) //nebo pointer na pole { int i,j; srand((long)time(NULL)); for(i=0; i
void nacti(int (*p)[4], int radky )
23 Optimální řešení by bylo použití maker bez parametrů – konstant pro rozměry vícerozměrného pole, jak je uvedeno v následujícím příkladu. Změnou maker pak změníte velikost pole v celém programu.
Příklad Vytvořte pro vícerozměrné pole reálných čísel funkci pro výpis pole ve tvaru matice a funkci pro výpočet aritmetického průměru všech prvků pole. #define RADKY 4
//definujeme konstanty pro řádky a sloupce
#define SLOUPCE 4 void vypis(float pole[ ][SLOUPCE], int radky); float prumer(float pole[ ][SLOUPCE], int radky); int main( ) { float matice[RADKY][SLOUPCE]; int i,j; for(i=0;i
//vypsání jednoho řádku //odřádkování
24 float prumer(float pole[][SLOUPCE],int radky) { int i,j; float soucet=0; for(i=0;i
Shrnutí kapitoly Skutečným parametrem funkce, která zpracovává vícerozměrné pole, je název pole – adresa prvního prvku. Formálním parametrem je pointer na pole s uvedeným počtem sloupců. Jde o název vícerozměrného pole, kde první dimenze (závorka [ ]) se zůstává prázdná a druhá dimenze musí být uvedena jako konstanta. To znamená, že musíme pevně zadat počet sloupců
Kontrolní otázky a úkoly 1) Proč posíláme do funkce jen adresu pole? 2) Zapište správně hlavičku funkce, která bude zjišťovat a vracet maximální prvek matice int hodnoty[5][3]. Uveďte i vhodné volání této funkce.
25
5 Vícerozměrné statické pole – řešené příklady Obsah hodiny Seznámíme se s tvorbou funkcí pro zpracovávání vícerozměrných statických polí.
Cíl hodiny Po této hodině budete schopni: ● ● ●
co nejvhodněji sestavit hlavičku funkce pro práci s vícerozměrným polem vytvořit funkci pro zpracování vícerozměrného pole správně zavolat funkci pro práci s vícerozměrným polem
Příklad Napište program, který bude obsahovat definici typu čtvercové matice celých čísel (velikost bude určena konstantou MAX, kterou si určete sami). Vytvořte funkci, která: 1) Načte matici náhodnými čísly z intervalu <-20;20> . 2) Vypíše matici. 3) Vypíše prvek na daném řádku a daném sloupci (požadovaný řádek a sloupec budou parametry funkce) 4) Vrátí maximální prvek v matici a pomocí parametrů vrátí jeho řádkový a sloupcový index 5) Přičte maximální prvek (zjištěný předchozím podprogramem) ke všem prvkům matice na zadaném řádku (zadaný řádek bude parametrem funkce) 6) Setřídí vzestupně zadaný sloupec matice (zadaný sloupec bude parametrem funkce) 7) Zamění dva zadané řádky v matici, jejichž indexy budou parametry funkce
26 #define MAX 4 void nacti(int pole[][MAX], int radky); void vypis(int pole[][MAX], int radky); int prvek(int pole[][MAX], int r, int s); int maximum(int pole[][MAX], int radky, int *indexR, int *indexS); void prictiMAX(int pole[][MAX], int maxim, int radek); void setrid(int pole[][MAX], int sloupec); void zamena(int pole[][MAX], int rad1, int rad2); int main() { int matice[MAX][MAX]; int i,j,radek, radek2, sloupec, maxim; srand((long)time(NULL)); //1) nacti(matice, MAX); //2) vypis(matice, MAX); //3) printf("Zadej cislo radku: "); scanf("%d",&radek); printf("Zadej cislo sloupce: "); scanf("%d",&sloupec); printf("Prvek na %d.radku a %d.sloupci je d.\n\n", radek, sloupec, prvek(matice, radek,sloupec)) //4) maxim=maximum(matice,MAX,&radek,&sloupec); printf("Maximum je %d a je na %d.radku a %d.sloupci.\n\n", maxim,radek, sloupec); //5) printf("Zadej cislo radku: "); scanf("%d",&radek); prictiMAX(matice,maxim,radek); printf("Po pricteni maxima k radku %d: \n\n", radek); vypis(matice, MAX); //6) printf("Zadej cislo sloupce: ");
27 scanf("%d",&sloupec); setrid(matice,sloupec); printf("Po serizeni %d.sloupce: \n\n",sloupec); vypis(matice, MAX); //7) printf("Zadej cislo prvniho radku: "); scanf("%d",&radek); printf("Zadej cislo druheho radku: "); scanf("%d",&radek2); zamena(matice,radek,radek2); printf("Po zamene %d.radku s %d.radkem je pole: \n\n", radek, radek2); vypis(matice, MAX); getch(); return 0; } //--------------------------------------------------------------------------void nacti(int pole[][MAX],int radky) { int i,j; for(i=0;i<MAX ;i++) for(j=0;j<MAX;j++) pole[i][j]= rand() % 41-20; } void vypis(int pole[][MAX],int radky) { int i,j; for(i=0;i<MAX;i++) { for(j=0;j<MAX;j++) printf("%5d",pole[i][j]); printf("\n"); } } int prvek(int pole[][MAX], int r, int s) {
28 return(pole[r-1][s-1]); } int maximum(int pole[][MAX], int radky, int *indexR, int *indexS) { int i,j,max=-20; for(i=0;i<MAX ;i++) for(j=0;j<MAX;j++) if(max < pole[i][j]) { max=pole[i][j]; *indexR=i+1; *indexS=j+1; } return max; } void prictiMAX(int pole[][MAX], int maxim, int radek) { int i; for(i=0;i<MAX ;i++) pole[radek-1][i]+=maxim; } void setrid(int pole[][MAX], int sloupec) { int i,j,pom; for(i=0;i<MAX-1;i++) for(j=0;j<MAX-1;j++) if(pole[j][sloupec-1]<pole[j+1][sloupec-1]) { pom=pole[j][sloupec-1]; pole[j][sloupec-1]=pole[j+1][sloupec-1]; pole[j+1][sloupec-1]=pom; } }
29 void zamena(int pole[][MAX], int rad1, int rad2) { int i,pom; for(i=0;i<MAX ;i++) { pom=pole[rad1-1][i]; pole[rad1-1][i]=pole[rad2-1][i]; pole[rad2-1][i]=pom; } }
Kontrolní otázky a úkoly Vyzkoušejte funkčnost programu v kapitole a doplňte jej další funkce: • Vypočte součet prvků na obvodu matice • Vypočte průměrnou hodnotu prvků hlavní diagonály • Vypíše prvky vedlejší diagonály • Vypíše matici tak, aby pod hlavní diagonálou byly samé nuly
30
6
Vícerozměrné statické pole – neřešené příklady
Obsah hodiny Samostatně vytvoříte statických polí.
funkce
pro
zpracovávání
vícerozměrných
Cíl hodiny Po této hodině budete schopni: ● ● ●
co nejvhodněji sestavit hlavičku funkce pro práci s vícerozměrným polem vytvořit funkci pro zpracování vícerozměrného pole správně zavolat funkci pro práci s vícerozměrným polem
Příklad 1 Vytvořte program s následujícími úlohami: 1) Do statického pole A pěti celých čísel načtěte pět náhodně vybraných čísel z intervalu <5,20>. 2) Vytvořte funkci pro vypsání pole pěti čísel. Pomocí této funkce vypište pole A. 3) Pomoci pointeru B vytvořte dynamické pole pěti celých čísel. Načtěte do něj prvních pět násobku zadaného čísla (př: zadané číslo 3 -> do pole uložíte čísla 3,6,9,12,15) Pole B vypište pomocí již vytvořené funkce (ad. 2.) 4) Ze zadaných poli vytvořte matici MATICE 5x5 celých čísel. Do matice uložte prvky z pole A (označené A1 až A5) a prvky pole B (označené B1 až B5) podle následujících řádků, kde / znamená celočíselné dělení. A1 A2 A3 A4 A5 B1 B2 B3 B4 B5 A1+B1 A2+B2 A3+B3 A4+B4 A5+B5 A1*B1 A2*B2 A3*B3 A4*B4 A5*B5 A1/B1 A2/B2 A3/B3 A4/B4 A5/B5
31 5) Vytvořte funkci pro vypsání dvourozměrného pole ve tvaru matice. Pomocí této funkce vypište pole MATICE. 6) Pomoci funkce setřiďte sestupně zadaný sloupec matice MATICE. Index sloupce načtěte od uživatele ve funkci main() a pošlete jej do funkce jako parametr. Matici opět vypište. 7) Vytvořte funkci, která bude vracet(!!!) tři hodnoty- minimální sudou hodnotu v matici MATICE a její pozici (číslo řádku i sloupce). Tyto hodnoty vypište ve funkci main().
Příklad 2 Vytvořte program s následujícími úlohami: 1) Do statického pole A čtyř reálných čísel načtěte čtyři náhodně vybraná čísla z intervalu <-10,20> (Abyste získali reálnou hodnotu, vygenerujte pomocí rand() číslo z intervalu <-70,140> a vydělte jej číslem 7. Výsledek pak uložte do pole A.). 2) Vytvořte funkci pro vypsání pole čtyř čísel. Pomocí této funkce vypište pole A. 3) Pomoci pointeru B vytvořte dynamické pole čtyř reálných čísel. Načtěte do něj hodnoty od uživatele. Pole B vypište pomocí již vytvořené funkce (ad. 2.) 4) Ze zadaných poli vytvořte matici POLE 6x4 celých čísel. Do matice uložte prvky z pole A (označené A1 až A4) a prvky pole B (označené B1 až B4) podle následujících řádků. A1 B1 A1+B1 A1-B1 A1*B1 A1/B1
A2 B2 A2+B2 A2-B2 A2*B2 A2/B2
A3 B3 A3+B3 A3-B3 A3*B3 A3/B3
A4 B4 A4+B4 A4-B4 A4*B4 A4/B4
5) Vytvořte funkci pro vypsání dvourozměrného pole ve tvaru matice. Pomocí této funkce vypište pole POLE. 6) Pomoci funkce setřiďte vzestupně zadaný sloupec matice POLE. Index sloupce načtěte od uživatele ve funkci main() a pošlete jej do funkce jako parametr. Matici opět vypište.
32 7) Vytvořte funkci, která bude vracet(!!!) tři hodnoty- průměrnou hodnotu a počet záporných čísel v matici POLE a součet všech kladných prvků v matici POLE. Tyto hodnoty vypište ve funkci main().
Příklad 3 Napište program, který bude obsahovat definici typu čtvercové matice reálných čísel (velikost bude určena konstantou). Napište funkce, které budou řešit následující operace s maticemi: • součet dvou matic • součin reálného čísla a matice • součin dvou matic.
Příklad Vytvořte dynamické celočíselné dvojrozměrné pole matice podle zadaných rozměrů od uživatele. Vytvořte dynamické jednorozměrné celočíselné pole radky podle počtu řádků a dynamické jednorozměrné celočíselné pole sloupce podle počtu sloupců. Vytvořte funkci pro naplnění pole matice náhodnými čísly z intervalu <-100,100>. Vytvořte funkci, která uloží do pole radky součty všech prvků v jednotlivých řádcích pole matice. Vytvořte funkci, která uloží do pole sloupce součty všech prvků v jednotlivých sloupcích pole matice. Pomocí funkce vypište pole matice. Pomocí funkce vypište pole radky a pak i pole sloupce.
33
7
Přístup k prvkům vícerozměrného pole
Obsah hodiny Seznámíme se s různými možnostmi přístupů k prvkům vícerozměrného statického pole s využitím pointerové aritmetiky.
Cíl hodiny Po této hodině budete schopni: využít pointerové aritmetiky k přístupu k prvkům odlišit zápis adres a zápis hodnot prvků vícerozměrného pole.
● ●
Při užití pointerové aritmetiky v jednorozměrném poli jsme si vysvětlili, že v jednorozměrném poli int p[5] můžeme hodnotu druhého prvku získat pomocí zápisu p[1] nebo *(p+1). Tyto dva zápisy jsou ekvivalentní. Totéž bude samozřejmě platit i ve vícerozměrných polích. Ve vícerozměrných polích popisujeme prvky pomocí dvou indexů – řádek a sloupec – např. p[2][3] je prvek na třetím řádku a čtvrtém sloupci. Proto využití pointerové aritmetiky budeme moci využít pro oba indexy.
Příklad Všechny možnosti zápisů adres i hodnot vícerozměrného pole si předvedeme na příkladu pole int p[3][4]. •
Adresy jednotlivých řádku resp. adresy prvních prvků na řádku můžeme popsat těmito pointery: p[0] adresa prvního řádku p[1] adresa druhého řádku p[2] adresa třetího řádku Ekvivalentní zápisy jsou *(p+0), *(p+1), *(p+2).
•
Pokud zapíšeme p[2]+1 znamená to adresa třetího řádku plus dna prvky typu int. Jde tedy o adresu druhého prvku na třetím řádku. Pokud rozepíšeme adresu třetího řádku pomocí pointerové aritmetiky, získáme ekvivalentní zápis adresy druhého prvku na třetím řádku *(p+2)+1. Další zápis téže adresy je &p[2][1].
34
•
Podobně můžeme přepsat i hodnoty prvků ve vícerozměrném poli. Vezměme si například čtvrtý prvek v druhém řádku p[1][3], který můžeme také zapsat zápisem *(p[1]+3). Tento zápis můžeme číst jako hodnota na adrese druhého řádku posunuté o tři prvky int. Pokud rozepíšeme i řádkový index pomocí pointerové aritmetiky, získáme další ekvivalentní zápis *((p+1)+3).
Přehledný popis zápisu adres i hodnot prvků vícerozměrného pole int p[3][4] najdete v následující obrázku.
HODNOTY
ADRESY
Příklad Otestujte následující program a překontrolujte výpisy hodnot i adres prvků.
35 #include <stdio.h> main() { int x[3][4]={{1,2,3,4},{4,5,6,7},{7,8,9,0}}; int i,j; for (i=0;i<3;i++) { for (j=0;j<4;j++) printf(" %d",x[i][j]); printf(" \n"); } printf(" \n\nAdresy \n"); for (i=0;i<3;i++) { for (j=0;j<4;j++) printf(" %p",&x[i][j]); printf(" \n"); } printf(" \n\nAdresy \n"); printf(" Prvni v prvnim radku : %p %p %p %p \n", &x[0][0], x[0], *x, x); printf(" Druhy v prvnim radku : %p %p %p \n" , &x[0][1], x[0]+1, *x+1 ); printf(" Prvni v druhem radku : %p %p %p \n", &x[1][0], x[1], *(x+1)); printf(" Treti v druhem radku : %p %p %p \n", &x[1][2], x[1]+2, *(x+1)+2); printf(" Prvni v tretim radku : %p %p %p\n", &x[2][0], x[2], *(x+2)); printf(" \n\nHodnoty \n"); printf(" Prvni v prvnim radku: %d %d %d \n", x[0][0], *(x[0]),*(*x)); printf(" Druhy v prvnim radku:%d %d %d \n", x[0][1], *(x[0]+1), *(*x+1)); printf(" Prvni v druhem radku: %d %d %d \n", x[1][0], *(x[1]), *(*(x+1))); printf("Treti v druhem radku: %d %d %d \n",x[1][2],*(x[1]+2),*(*(x+1)+2)); printf(" Prvni v tretim radku: %d %d %d \n", x[2][0], *(x[2]), *(*(x+2))); getch(); return 0; }
36
Shrnutí kapitoly Pointerovou aritmetiku můžeme použít pro zápis adres i hodnot prvků ve vícerozměrném poli. Využíváme toho, že zápis p[i] je ekvivalentní zápisu*(p+i), který ve vícerozměrném poli znamenají adresu i-tého řádku. Od tohoto zápisu se pak odvíjí všechny další zápisy.
Kontrolní otázky a úkoly 1) V poli float pole[5][4] vypište všemi způsoby adresu třetího prvku na čtvrtém řádku a adresu prvního prvku na páté řádku. 2) V poli char znaky[4][10] vypište všemi způsoby hodnotu osmého prvku v druhém řádku a hodnotu prvního prvku na čtvrtém řádku.
37
8
Pole pointerů
Obsah hodiny Seznámíme se s polem pointerů dynamického vícerozměrného pole.
–
jedné
z možností
tvorby
Cíl hodiny Po této hodině budete schopni: ● ●
vytvořit dynamické vícerozměrné pole pomocí pole pointerů popsat tvorbu dynamického vícerozměrného pole – pole pointerů
Klíčová slova Dynamické vícerozměrné pole, pole pointerů
V následujících kapitolách se seznámíme s třemi možnostmi vytvoření vícerozměrného dynamického pole. První možností je pole pointerů, kterou si předvedeme a popíšeme na praktickém příkladě.
Příklad Deklarace int *p[3]; vytvoří v zásobníku pole tří pointerů, tzn. tříprvkové pole, jehož prvku jsou pointery, které mohou ukazovat na celá čísla.
Tyto tři pointery jsou ukazatele na jednotlivé řádky budoucího vícerozměrného pole. Proto konstanta 3 uvedena v deklaraci int *p[3]; představuje počet řádků vícerozměrného pole, který musí být uvedena a musí být znám před překladem.
38 Pro tyto tři pointery vyblokujeme v haldě jednotlivé řádky. Počet prvků v každém řádku může zadat i uživatel až za běhu programu. p[0]=(int*)malloc(sizeof(int)*5); Příkaz malloc vyblokuje pět prvků v haldě, jejichž adresu uloží do pointeru p[0]. Těchto pět prvků tvoří první řádek vícerozměrného pole.
Podobně alokujeme prvky dalších dvou řádků.
p[1]=(int*)malloc(sizeof(int)*3); p[2]=(int*)malloc(sizeof(int)*4);
Z obrázku je zřejmé, že řádky se nealokují za sebou v souvislém bloku, jako to bylo u statického vícerozměrného pole. Přístup k prvkům pole je však neliší.
39
p[0][2]=64; p[1][0]=57;
Po využití dynamického vícerozměrného pole musíme odalokovat prvky, které jsme vyalokovali pomocí malloc. free(p[0]); Odalokuje prvky prvního řádku. V pointeru p[0] zůstane adresa prostoru v paměti, kde byl první řádek alokován.
Proto abychom smazali i tuto adresu, pointer zakotvíme. p[0]=NULL; Totéž provedeme i s ostatními řádky. Můžeme také využít cyklus for.
40
for(i=0;i<3;i++) { free(p[i]); p[i]=NULL; } Poté zůstane v zásobníku jen původní pole tří pointerů, které jsme nealokovali pomocí malloc a které se odalokuje na konci funkce.
Shrnutí kapitoly Dynamické vícerozměrné pole můžeme vytvořit pomocí pole pointerů. Musím před překladem znát počet řádků. Každý řádek může mít jiný počet prvků, který můžeme načíst i od uživatele za běhu programu. Jednotlivé řádky se nealokují v jednom bloku za sebou. Práce s prvky pole je stejná jako u statického pole. Na konci musíme jednotlivé řádky odblokovat.
Kontrolní otázky a úkoly 1) Popište samostatně postup tvorby vícerozměrného pole pomocí pole pointerů. 2) Zakreslete postup tvorby pole pointerů.
41
9
Pointer na pole
Obsah hodiny Seznámíme se s pointrem na pole – další možností tvorby dynamického vícerozměrného pole.
Cíl hodiny Po této hodině budete schopni: ● ●
vytvořit dynamické vícerozměrné pole pomocí pointeru na pole popsat tvorbu dynamického vícerozměrného pole – pointer na pole
Klíčová slova Pointer na pole
Pokud známe před překladem počet sloupců vícerozměrného pole, můžeme využít tvorbu tohoto pole pomocí pointeru na pole.
Příklad Deklarace int (*p)[4]; vytvoří v zásobníku pointer, který může ukazovat na pole čtyř celočíselných prvků. Kolik těchto polí bude, tedy kolik řádků bude mít naše vícerozměrné pole, můžeme načíst i od uživatele za běhu programu.
Vícerozměrné dynamické pole musíme alokovat v haldě pomocí příkazu malloc. počet sloupců p=(int(*)[4])malloc(sizeof(int)*3*4); počet řádků
42 Tímto příkazem chceme vyalokovat pole o třech řádcích pro čtyři prvky na každém řádků. Proto díky parametru sizeof(int)*3*4 vyalokuje příkaz malloc dvanáct celých čísel v haldě. Pomocí (int(*)[4]) přetypujeme ukazatel na typ void, který vrací příkaz malloc, na ukazatel na ukazatel na pole čtyř celých čísel. V paměti se vyblokuje souvislý blok dvanácti prvků, který je velmi podobný vícerozměrnému statickému poli. Prvky pole v tomto případě jsou však alokovány v haldě.
K prvkům pole přistupujeme stejně jako u statického vícerozměrného pole. p[0][2]=64; p[1][0]=57;
Můžeme využít souvislého bloku a zapisovat do pole i takto. p[0][6]=39; Před ukončením práce s tímto polem musíme opět odalokovat prvky, které jsme alokovali pomocí příkazu malloc. free(p); V haldě se odalokuje paměť pro dvanáct prvků pole. V pointeru p však zůstane adresa místa, kdy byly prvky alokovány.
43
Toto adresu zrušíme zakotvením pointeru p. p=NULL; Pointer p se odalokuje automaticky s ukončením bloku či funkce, kde byl deklarován.
Shrnutí kapitoly Pro tvorbu dynamické vícerozměrné pole, u kterého známe před překladem počet sloupců pole, použijeme typ pointer na pole. Deklarací int (*p)[4]; vyalokujeme pointer na čtyřprvková pole. Počet těchto polí – tzn. počet řádků, můžeme zadat až za běhu programu. Prvky pole se alokují v haldě jako souvislý blok. Práce s prvky pole je stejná jako u statického pole. Na konci musíme prvky pole odalokovat.
Kontrolní otázky a úkoly 1) Popište samostatně postup tvorby vícerozměrného pole pomocí pointeru na pole. 2) Zakreslete postup tvorby vícerozměrného dynamického pole pointer na pole.
44
10 Pointer na pointer Obsah hodiny Seznámíme se tvorbou vícerozměrného dynamického pole pomocí pointeru na pointer.
Cíl hodiny Po této hodině budete schopni: ● ●
vytvořit dynamické vícerozměrné pole pomocí pointeru na pointer popsat tvorbu dynamického vícerozměrného pole – pointer na pointer
Klíčová slova Pointer na pointer
Pointer na pointer je možnost tvorby vícerozměrného dynamického pole, kdy před překladem nevíme ani počet řádků ani počet sloupců. Oba rozměry pro toto vícerozměrné pole můžeme načíst od uživatele až za běhu programu.
Příklad Deklarace int **p; vytvoří v zásobníku jediný pointer, který může ukazovat na další pointer na datový typ int.
Nejprve musíme alokovat pole pointerů, které budou pointery na jednotlivé řádky vícerozměrného dynamického pole. p=(int**)malloc(sizeof(int*)*3); Alokujeme tři pointery. Proto parametrem malloc je sizeof(int*)*3. Ukazatel na void, který vrací malloc musíme přetypovat pomocí (int**) na pointer na pointer na celočíselný typ, aby pravá a levá strana příkazu byla kompatibilní.
45
Nyní budeme alokovat jednotlivé řádky. Počet prvků na řádku můžeme načíst od uživatele. p[0]=(int*)malloc(sizeof(int)*5);
Podobně alokujeme další řádky. Pro alokaci můžeme využít i cyklus for. p[1]=(int*)malloc(sizeof(int)*3); p[2]=(int*)malloc(sizeof(int)*4);
46 Řádky tohoto pole nejsou alokovány v souvislém bloku. Práce s prvky pole se opět neliší od práce s prvky statického vícerozměrného pole. p[0][2]=64; p[1][0]=57;
Při odalokaci pole musíme postupovat v opačném pořadí. Nejprve musíme odalokovat jednotlivé řádky a pak teprve pole pointerů. Pro odalokaci řádků můžeme opět použít cyklus for. for(i=0;i<3;i++) { free(p[i]); p[i]=NULL; } V paměti zůstane jen pole pointerů, které jsme alokovali jako první.
Toto pole odalokujeme příkazy: free(p); p=NULL;
47
Shrnutí kapitoly Pro tvorbu dynamické vícerozměrné pole, u kterého neznáme před překladem ani počet sloupců ani počet řádků pole, použijeme typ pointer na pointer. Deklarací int **p; vyalokujeme pointer na pointer. Počet řádků i počet sloupců můžeme načíst od uživatele až za běhu programu. Nejprve vyblokujeme pole pointerů, které budou ukazovat na jednotlivé řádky. Pak alokujeme jednotlivé řádky. Řádky pole se nealokují v haldě za sebou jako souvislý blok. Práce s prvky pole je stejná jako u statického pole. Na konci práce s polem musíme prvky pole odalokovat v opačném pořadí, než v jakém jsme pole alokovali – tzn. nejprve odalokujme řádky a nakonec odalokujeme pole pointerů.
Kontrolní otázky a úkoly 1) Popište samostatně postup tvorby vícerozměrného pole pomocí pointeru na pointer. 2) Zakreslete postup tvorby vícerozměrného dynamického pole pointer na pointer.
48
11 Vícerozměrné dynamické pole – shrnutí, příklady Obsah hodiny Shrneme vlastnosti statických i dynamických vícerozměrných polí. Vyzkoušíme si jednotlivé typy dynamických vícerozměrných polí na příkladech.
Cíl hodiny Po této hodině budete schopni: ● ● ●
popsat shodné a rozdílné vlastnosti statických i dynamických vícerozměrných polí vhodně vybrat typ dynamického vícerozměrného pole pro zadanou úlohu použít vícerozměrné dynamické pole v příkladech
11.1 Porovnání vícerozměrných polí Z předchozích kapitol jste zjistili, že přístup k prvkům vícerozměrných polí je shodný, ať se jedná o statické vícerozměrné pole nebo o dynamické vícerozměrné pole. Možná jste si také všimli, že některé části statických a dynamických vícerozměrných polí se alokují v paměti podobně. Statické vícerozměrné pole a dynamické pole pointer na pole se alokují jako souvislý blok paměti. Pro dynamické pole alokujeme prvky pole pomocí malloc a jsou alokovány v haldě. U statického pole se tyto prvky alokují v zásobníku stejně jako ukazatel na první prvek pole. Dynamická vícerozměrná pole vytvořená pomocí pole pointerů a pomocí pointer na pointer jsou také velice podobná. Všechny prvky polí jsou alokovány v haldě a nejsou alokovány jako souvislý blok. U pointeru na pointer alokujeme v haldě i adresy jednotlivých řádků – prvky pole pointerů. Společné a rozdílné vlastnosti alokace jsou zobrazeny v následujícím obrázku.
49
11.2 Příklady Příklad Realizujte pomocí následující úkol:
vhodného
dynamického
vícerozměrného
pole
Místní dopravce má 4 garáže a v nich nákladní auta stejné značky i modelu. Do každé garáže se vejde maximálně 6 aut, ale ne všechny stání v garáži jsou zaplněny. Dopravce sleduje stav nádrže svých aut. Vytvořte program a datovou strukturu pro toto sledování. Při zadávání počtu aut v jednotlivých garážích spolupracujte s dopravcem (uživatelem). Uvažujte zda si informace o zaplněnosti jednotlivých garáží neuložíte do pomocného jednorozměrného pole. Načtěte aktuální stavy nádrží v jednotlivých zaparkovaných aut, maximum je 50litrů. (Pomožte si náhodně generovanými čísly.) Vypište přehledně stavy nádrží. (Vytvořte si funkci pro tento výpis.)
50 Všem autům, které mají méně než polovinu nádrže dotankujte nádrž do plna a zjistěte kolik litrů paliva bylo k tomuto třeba. (Opět si vytvořte na tuto činnost funkci, která počet litrů dotankovaného paliva bude vracet) Vypište opět pole a zkontrolujte správné stavy nádrží. Přeparkujte auta v jednotlivých garážích tak, aby ty s největším množstvím paliva stály hned vepředu a ty s nejméně množstvím paliva vzadu. Pole vytiskněte a zkontrolujte, zda se Vám přeparkování podařilo.
Řešení #include <stdio.h> #include <stdlib.h> //--------------------------------------------------------------------------void info(int **pole,int pole2[]); int palivo (int **pole,int pole2[]); void preparkovani (int **pole,int pole2[]); //--------------------------------------------------------------------------int main() { int *pole[4],pole2[4],i,j,pal; srand((long)time(NULL)); for (i = 0; i < 4; i++) { printf ("Zadej pocet aut v %d garazi\n",i+1); scanf ("%d",&pole2[i]); pole[i]=(int*)malloc(sizeof(int)*pole2[i]); printf ("v %d garazi je %d aut\n",i+1,pole2[i]); } for (i = 0; i <4 ; i++) for (j = 0; j <pole2[i]; j++) pole[i][j]=rand () % 100; info(pole,pole2); pal=fuel(pole,pole2); printf ("Celkove jsme dotankovali %d procent paliva\n",pal); info(pole,pole2); preparkovani(pole,pole2); printf ("\n");
51 printf ("Po preparkovani \n"); printf ("\n"); info(pole,pole2); getchar(); for(i=0;i<4;i++) free(pole[i]); } //--------------------------------------------------------------------------void info(int **pole,int pole2[]) { int i,j; for (i = 0; i < 4; i++) { printf ("Dalsi garaz\n"); for (j = 0; j <pole2[i]; j++) printf ("V %d. aute v %d. garazi je %d %% paliva\n\n", j+1,i+1,pole[i][j]); } } //--------------------------------------------------------------------------int fuel (int **pole,int pole2[]) { int i,j,fuel=0; for (i = 0; i < 4; i++) for (j = 0; j <pole2[i]; j++) if(pole[i][j]<50) { fuel=fuel+(100-pole[i][j]); printf ("V %d. aute v %d. garazi bylo mene nez 50 %% paliva\n Třeba dotankovat %d procent\n\n",j+1,i+1,(100-pole[i][j])); pole[i][j]=100; } return (fuel); } //--------------------------------------------------------------------------void preparkovani (int **pole,int pole2[]) { int i,j,pom,k; for(i=0; i<4;i++) for(k=0; k<pole2[i]-1;k++)
52 for(j=0; j<pole2[i]-1;j++) if(pole[i][j]<pole[i][j+1]) { pom=pole[i][j]; pole[i][j]=pole[i][j+1]; pole[i][j+1]=pom; }}
Příklad Upravte předchozí program tak, aby jej mohli použít i dopravcovi konkurenti, kteří mají variabilní počet garáží.
Shrnutí kapitoly K prvkům statického vícerozměrného pole i k prvkům dynamického vícerozměrného pole přistupujeme stejně. Podobně jsou také v paměti alokovány vícerozměrné pole statické a dynamické pole pointer na pole – jde o souvislý blok v paměti. Druhá dvojice podobných alokací dynamických vícerozměrných pole je pointer na pole a pointer na pointer, která se nealokují jako souvislý blok a pro pointer na pointer se alokuje v haldě i pole pointerů, které ukazují na jednotlivé řádky pole.
53
Kontrolní otázky a úkoly 1) Napište program, ve kterém vytvoříte dynamické vícerozměrné celočíselné pole pomocí pointeru na pointer pro zadaný počet řádků a zadaný počet sloupců. • • • • •
Načte matici čísly z intervalu <-10;10>. Vypište matici. Vypočtěte součet prvků na hlavní diagonále. Vypište prvky na vedlejší diagonále. Vytvořte podprogram, který vrátí adresu maximálního prvku na zadaném řádku. Zadaný řádek bude parametrem funkce. Ve funkci main vypište nalezenou adresu i hodnotu na této adrese.
2) Napište program, ve kterém vytvoříte dynamické vícerozměrné znakové pole pomocí pointeru na pole pro zadaný počet řádků a 3 sloupce . • • • •
Načte matici znaky načtenými z klávesnice. Vypište matici. Vypište všechna malá písmena z matice. Všechna velká písmena v matici přepište odpovídajícími velkými písmeny.
3) Setřiďte zadaný sloupec vzestupně podle ordinálních hodnot znaků. Napište program, ve kterém vytvoříte dynamické vícerozměrné reálné pole pointerů na pole pro 4 řádky a postupně pro 4, 3, 2, 1 sloupce. Př. :
• • • •
5 5 5 5 5 5 5 5 5 5 Načte matici reálnými čísly načtenými z klávesnice. Vypište matici ve tvaru horní trojúhelníkové matice (viz. Př.). Vypište minimální hodnotu v matici, její řádkový i sloupcový index. Zaměňte nalezenou minimální hodnotu v matici za prvek na zadaném řádku a zadaném sloupci.
54
12 Řetězce – deklarace Obsah hodiny Seznámíme se v programech.
způsoby
definování
řetězců
a
s jejich
použití
Cíl hodiny Po této hodině budete schopni: ● ● ●
popsat rozdíl mezi znakem a řetězcem nadeklarovat statickou proměnnou typu řetězec vytvořit dynamickou proměnnou typu řetězec
Klíčová slova Statický řetězec, dynamický řetězec, ukončení řetězce ‘\0‘
Pro řetězce nemá jazyk C speciální datový typ. Řetězec je jednorozměrné pole znaků a jako pole se deklaruje a jako s polem se s ním pracuje. Každý řetězec však musí být ukončen znakem EOS (end of string), což je znak s hodnotou nula v ASCII tabulce. Proto se také zapisuje ‘\0‘. Hodnoty za tímto znakem se při běžné práci s řetězci nevyužívají. Délka řetězce je tedy omezena pouze velikostí paměti, kterou pro řetězec můžeme alokovat. Řetězce používáme již od začátku programování v jazyce C bez toho, že bychom se řetězcích dříve zmiňovali. Například pokud chceme vypsat na obrazovku informaci pro uživatele, použijeme příkaz printf, jehož prvním parametrem je řetězec. Každý konkrétní řetězec musíme označit uvozovkami. Je důležité uvědomit si rozdíl mezi znaky a řetězci. Znaky zapisujeme do apostrofů a překladač tak informujeme o tom, že budeme používat jediný znak. Jazyk C dále nepracuje přímo s tímto znakem, ale s jeho hodnotou c ASCII tabulce – celým číslem.
55
Příklad Pokud tedy zapíšeme písmeno a do apostrofů, zapsali jsme jediný znak typu char. Tento znak zabere v paměti jeden Byte. Jestliže zapíšeme znak a do uvozovek, zapsali jsme řetězec. Ten zabere v paměti dva Byte, protože musíme započítat také ukončovací znak ‘\0‘.
Na následujících příkladech si ukážeme možnosti deklarace řetězců – proměnných typu řetězec.
Příklady deklarace statických řetězců char a[5]; Deklarujeme řetězec, který může obsahovat nevýše 4 znaky. Poslední znak řetězce musí být znak ‘\0‘. Tímto se pozná, kde řetězec končí. Například po uložení řetězce “ahoj” bude řetězec a v paměti vypadat takto:
Můžeme také řetězce při deklaraci inicializovat. char b[6]=“Ahoj”;
char b[ ]=“Ahoj”; Velikost řetězce není v deklaraci uvedena. Je doplněna podle délky řetězce, kterým řetězec při deklaraci inicializujeme.
char d[ ]={‘A’,’h’,’o’,’j’,’\0’};
56 Velikost opět není uvedena. Řetězec je zadán pomocí jednotlivých písmen, proto v tomto zadání nesmíme zapomenout na ukončovací nulu.
Příklady deklarace dynamického řetězce char *a; Deklarujeme jen ukazatel na znaky. Samotný řetězec musíme vyalokovat v haldě. a = (char*)malloc(sizeof(char)*5);
Všimněte si, že se alokace dynamického řetězce neliší od alokace statického pole. Jediným rozdílem je umístění prvků řetězce – u statického řetězce jsou v zásobníku a u dynamického řetězce jsou v haldě. Také se nijak neliší práce s dynamickým a statickým řetězcem. Práci s dynamickým řetězcem nebudeme tedy v dalším textu
Shrnutí kapitoly Řetězec je jednorozměrné pole znaků ukončené znakovou nulou ‘\0‘. Řetězce zapisujeme do uvozovek. Deklarace řetězců jsou shodné s deklarací jednorozměrného pole. Práce s dynamickým i statickým řetězcem je stejná.
Kontrolní otázky a úkoly 1) Co je to řetězec? 2) Jak poznáme konec řetězce? 3) Jaký je rozdíl mezi statickým a dynamickým řetězcem?
57
13 Řetězce – základy práce Obsah hodiny Seznámíme se se základy práce a zpracování řetězců.
Cíl hodiny Po této hodině budete schopni: ● ●
načíst a vypsat vhodným způsobem řetězec zapsat do řetězcové proměnné konkrétní řetězec
Klíčová slova Načtení řetězce, výpis řetězce, bílé znaky, čtecí buffer
V předchozí kapitole jsme si řekli, že řetězec je pole znaků, které jsou zakončeny ‘\0‘ tzv. znakovou nulou. Pokud bychom zapomněli zapsat ukončovací znak řetězce ‘\0‘ nebo bychom si jej smazali, byl by za řetězec považován celá obsah paměti – všechny znaky až do výskytu první ukončovací nuly.
13.1 Načtení řetězce Formátované načítání řetězce provádíme příkazem scanf.
Příklad char retez[10]; scanf(“%s“,retez); %s je formát pro čtení i vypisování řetězce. Není potřeba před názvem řetězce retez psát adresní operátor &, protože název řetězce je adresou prvního znaku řetězce. Proto scat ví na jakou adresu uživatelem zadaný řetězec uložit. Znak ‘\0‘ je uložen na konec zadaného řetězce automaticky – uživatel jej nemusí psát. V jazyce C nejsou kontrolovány meze polí tedy ani řetězců. Do našeho řetězce retez může uložit nejvýše 9-ti znakový řetězec. Posledním znakem bude ‘\0‘. Pokud uživatel zadá delší řetězec, nebude hlášena chyba. Zadaný řetězec se bude zapisovat dále do paměti, která je za
58 řetězcem retez a která není vyalokovaná. Příkazem scanf můžeme kontrolovat počet načtených znaků.
Příklad char retez[10]; scanf(“%9s“,retez); Tento příkaz načte prvních 9 znaků a ostatní zůstanou v čtecím bufferu. Znak ‘\0‘ bude opět zapsán na konec řetězce automaticky. Pokud cokoliv zůstane ve čtecím bufferu, musíme čtecí buffer vyčistit pomocí následujícího cyklu. while (getchar() != ´\n´); Cyklus načítá znaky až do nalezení znaku ´\n´, znaky nikam neukládá, jen je načítá z čtecího bufferu. Nevýhodou příkazu scanf je, že nečte bílé znaky, anglicky white spaces, což jsou znaky, které nejsou vidět na obrazovce. Jsou to oddělovací znaky jako mezera, tabulátor, nový řádek a podobně. Jestliže uživatel zadá dvě slova oddělená mezerou, načte příkaz scanf první slovo a mezera i další slovo zůstanou ve čtecím bufferu, který opět budeme muset vyčistit. Načítání řetězce s bílými znaky řeší příkaz gets.
Příklad char retez[10]; gets(retez); Příkaz gets načte řetězec až do zadání enter, který se neuloží, ale nahradí se znakem ´\0´. Příkaz gets čte bíle znaky a nekonroluje meze řetězce.
13.2 Výpis řetězce Řetězce vypisujeme pomocí příkazu printf nebo pomocí příkazu puts. Oba nemají problém s bílými znaky, oba vypisují znaky z řetězce až to nalezení znaku ‘\0‘.
Příklad char retez[ ]=”PRG je fajn!”; puts(retez); printf(“%s“,retez); V příkazu printf nepíšeme *a, protože %s nese informaci, že budeme vypisovat řetězec, který je zadáván svou adresou.
59
13.3 Přístup řetězcům Protože řetězec je pole, pracujeme s ním jak s polem. Název pole tedy i řetězce chápeme jako adresu prvního prvku, z toho vyplývá, že nemůžeme pro naplnění řetězce použít následující příkaz. char retez[10]; retez=”Ahoj”;
//CHYBA!!!!!!!
V tomto příkazu se snažíme uložit řetězec “Ahoj“ do pointeru retez, což je velká chyba. Pokud chceme do proměnné retez uložit konkrétní řetězec, musíme jej uložit znak po znaku. retez[0]=´A´; retez[1]=´h´; retez[2]=´o´; retez[3]=´j´; retez[4]=´\0´; Musíme zapsat i znak ´\0´, protože jinak by do řetězce patřily všechny znaky v paměti až do prvního výskytu znaku ´\0´ nebo až do konce obsahu paměti. Tento složitý přístup lze nahradit funkcí pro kopírování řetězců strcpy, kterou si předvedeme v následující kapitole.
Shrnutí kapitoly Pro načtení řetězce používáme příkazy gets a scanf, který nečte bílé znaky, ale umí kontrolovat meze načítaného řetězce. Pro výpis řetězce na obrazovku používáme příkazy puts a printf, které vypíšou řetězec až po první výskyt znaku ´\0´.
Kontrolní otázky a úkoly 1) Co jsou to bílé znaky? 2) V čem je nevýhodný příkaz scanf pro načítání řetězců? 3) Co se vypíše na obrazovku, pokud vypíšeme řetězec, kterému jsme smazali znak ´\0´?
60
14 Řetězce – funkce pro práci s řetězci Obsah hodiny Seznámíme se se standardními funkcemi pro práci s řetězci.
Cíl hodiny Po této hodině budete schopni: ● ●
použít pro práci s řetězci standardní základní funkce rozlišit a popsat činnost jednotlivých funkcí
Klíčová slova string.h, strlen, strcpy, strcmp, strcat, strchr, strstr
S řetězci pracujeme v programátorské praxi docela často. Základní operace s řetězci se velice často opakují, proto pro ně mám předdefinované funkce. V této kapitole se seznámíme s těmi nejzákladnějšími. Všechny následující funkce jsou popsány v hlavičkovém souboru string.h, který musíme k našemu programu připojit #include <string.h> Zjištění délky řetězce int strlen(char *r);
Příklad char retez[ ]=“Ahoj“; int a; a=strlen(retez); Funkce strlen vrací délku řetězce retez bez ukončovacího znaku ‘\0‘. Tzn. a = 4 Kopírování řetězce char *strcpy(char *kam, char *co);
61
Příklad char retez[6]; strcpy(retez, “Ahoj“); Funkce strcpy zkopíruje řetězec “Ahoj“ do řetězce retez Tzn. že retez obsahuje znaky ´A´,´h´,´o´,´j´,´\0´ Porovnání řetězců Pro řetězce nemůžeme použít relační operátory <, >, <=, >= ani ==. Pro porovnání řetězců používáme následující funkci. int strcmp(char *r1, char *r2);
Příklad char retez[ ]=“Ahoj“; int a; a=strcmp(retez,“ahoj“); Funkce strcmp porovná řetězce retez a “ahoj“. Vrátí číslo -1, 0 nebo 1, je-li retez lexikograficky menší, rovno nebo větší než řetězec “ahoj“. Znamená to, že se porovnají odpovídající si znaky v řetězcích. Jestliže jsou všechny znaky shodné, bude funkce vracet hodnotu 0. Jestliže se v některém znaku budou řetězce lišit, porovná se ordinální hodnota tohoto znaku. V našem případě a= -1, protože ordinální hodnota znaku ´A´ je menší než ordinální hodnota znaku ´a´. Spojení dvou řetězců char *strcat(char *co, char *s_cim);
Příklad char retez[ ]=“Ahoj“; strcat(retez,“ mami“); Funkce strcat přidá řetězec “ mami“ na konec řetězce retez. Pak řetězec retez =“Ahoj mami“ Nalezení znaku v řetězci char *strchr(char *r, char znak);
62
Příklad char retez[ ]=“Ahoj“, znak=´h´,*p; p=strchr(retez,znak); Funkce strchr hledá, zda je v řetězci retez obsažen znak znak, tedy znak ’h’. Pokud ano, vrátí funkce ukazatel na první výskyt tohoto znaku; pokud v řetězci znak není, vrátí funkce hodnotu NULL. V našem případě p=&retez[1]. Nalezení řetězce v řetězci char *strstr(char *r, char znak);
Příklad char retez[ ]=“Ahoj mami“, slovo[ ]=“mam“,*p; p=strstr(retez,slovo); Funkce strstr hledá, zda je v řetězci retez obsažen řetězec slovo. Pokud ano, vrátí funkce ukazatel na první výskyt tohoto řetězce; pokud v řetězci řetězec není, vrátí funkce hodnotu NULL. V našem případě p=&retez[5]. Převod řetězce na číslo
Tyto funkce jsou popsány v hlavičkovém souboru stdlib.h. Jsou určeny k tomu, aby převedly řetězec na číslo daného datového typu. atol(r) - převod řetězce na číslo long atoi(r) - převod řetězce na číslo int atof(r) - převod řetězce na číslo double Funkcí pro práci s řetězci je mnohem víc, ale v této kapitole jsme si uvedli jen ty nejzákladnější.
Shrnutí kapitoly Pro základní práci s řetězci používáme funkce, které jsou popsány v hlavičkovém souboru string.h.
63
Kontrolní otázky a úkoly 1) Co bude výstupem jednotlivých příkazů v následujícím kódu? #include <stdio.h> #include <string.h> main() { char r[20],s[]="je fajn!",*p; int a; strcpy(r,"PRG"); a=strlen(r); printf("Delka retezce r - %s - je %d.\n\n",r,a); a=strcmp(r,"Prg"); printf("Porovnani retezcu PRG a Prg je %d \n \n" ,a); a=strcmp(r,"prg"); printf("Porovnani retezcu PRG a prg je %d \n\n",a); a=strcmp("ahoj", PRG); printf("Porovnani retezcu PRG a PRG je %d \n\n",a); strcat(r,s); printf("Retezec r obsahuje : %s\n\n",r); p=strchr(r,'a'); if (p!=NULL) printf("Retezec r obsahuje pismeno a \n\n"); p=strstr(r,s); if (p!=NULL) printf("Retezec r obsahuje retezec s\n\n"); getch(); return 0; }
Otázky k zamyšlení 1) Jak byste naprogramovali své vlastní funkce, které by pracovaly stejně, jako standardní funkce uvedené v textu táto kapitoly.
64
15 Řetězce – řešené příklady Obsah hodiny Procvičíme si základy práce s řetězci.
Cíl hodiny Po této hodině budete schopni: ● ●
aplikovat získané informace při řešení příkladů s řetězci vhodně použít standardní funkce pro práci s řetězci
Příklad – Funkce pro práci s řetězci #include <stdio.h> #include <string.h> main() { char r[20],s[]="mami",*p; int a; strcpy(r,"Ahoj"); a=strlen(r); printf("Delka retezce r - %s - je %d.\n\n",r,a); a=strcmp(r,"ahoj"); printf("Porovnani retezcu Ahoj a ahoj je %d -> protoze A < a \n \n" ,a); a=strcmp(r,"Ahoj"); printf("Porovnani retezcu Ahoj a Ahoj je %d -> protoze A == A\n\n",a); a=strcmp("ahoj",r); printf("Porovnani retezcu ahoj a Ahoj je %d -> protoze a < A\n\n",a); strcat(r,s);
65 printf("Retezec r obsahuje : %s\n\n",r); p=strchr(r,'a'); if (p!=NULL) printf("Retezec r obsahuje pismeno a \n\n"); p=strstr(r,s); if (p!=NULL) printf("Retezec r obsahuje retezec s\n\n"); getch(); return 0; }
Příklad – Věta Vytvořte program, který bude obsahovat funkce pro práci s řetězcem veta, jehož maximální délka je 60 znaků a po jeho načtení budeme předpokládat, že je věta ukončenou tečkou, její slova jsou oddělena jedinou mezerou. 1) Vytvořte funkci, která načte řetězec s maximálním počtem 60 znaků. 2) Vytvořte funkci, která vypíše zadanou větu pozpátku. 3) Vytvořte funkci, která vytiskne zadanou větu tak, aby každé slovo bylo na novém řádku. 4) Vytvořte funkci, která vrátí počet výskytů zadaného znaku v načtené větě. Načtěte požadovaný znak z klávesnice ve funkci main(). Znak i věta budou parametry funkce. 5) Vytvořte funkci, která vypíše větu na obrazovku tak, že první písmeno každého slova bude velké písmeno. Ve větě znaky neměňte, jen je podle zadání vypište. 6) Vytvořte funkci, která vrátí počet slov v zadané větě. 7) Vytvořte funkci, která vrátí průměrnou délku slova v zadané větě. Před uvedením řešení příkladu si připomeňme, že řetězec je pole a pole (proto i řetězec) se do funkce posílají jen adresou prvního prvku. Proto do funkce budeme posílat jen název pole či řetězce.
66
Řešení #include <stdio.h> #include
#include <string.h> void nacti(char *retez); void pozpatku(char *retez); void na_radek(char *retez); int kolik_znaku(char *retez, char znak); void prvni_velka(char *retez); int pocet_slov(char *retez); float prumerna_delka(char *retez); int main(int argc, char* argv[]) { char veta[60],zn; int x; nacti(veta); puts(veta); // vypisu retezec pozpatku(veta); na_radek(veta); printf("\n\nZadej znak: "); scanf(" %c",&zn); x=kolik_znaku(veta,zn); printf("Znak %c je ve vete %dkrat.",zn,x); prvni_velka(veta); printf("\n\nVe vete je %d slov.",pocet_slov(veta)); printf("\n\nPrumerna delka slov ve vete je %.2f.", prumerna_delka(veta)); getch(); return 0; } void nacti(char *retez) { do{ printf("Zadej retezec : "); gets(retez); if(strlen(retez)>60) printf("Retezec je moc dlouhy. Zadej jej znovu.");
67 } while(strlen(retez)>60); //pokud je řetězec moc dlouhý, musí jej uživatel zadat znovu } void pozpatku(char *retez) { int i; printf("\nVypis pozpatku: \n"); for(i=strlen(retez)-1;i>=0;i--) { printf("%c",retez[i]); } } void na_radek(char *retez) { int i; printf("\n\nVypis na jednotlive radky: \n"); for(i=0;i<strlen(retez);i++) { if(retez[i]==' ') //jestliže najde mezeru, odřádkuje printf("\n"); else printf("%c",retez[i]); } } int kolik_znaku(char *retez, char znak) { int i,pocet=0; for(i=0;i<strlen(retez);i++) if(retez[i]==znak) pocet++; return(pocet); } void prvni_velka(char *retez) { /* Nejprve otestujeme první písmeno ve větě. Jestliže je to malé písmeno, zvětšíme jej (posuneme se vs ASCII tabulce o -32) a vypíšeme jej, jinak jej vypíšeme nezměněný. Jestliže v dalších znacích řetězce najde mezeru, zjistí, zda následující znak za mezerou je malé písmeno. Jestliže ano, zvětší jej a vypíše i s mezerou před ním, jinak jej vypíše nezměněný i s mezerou před
68 ním. Aby tento otestovaný znak už netestoval, posuneme se i++ na další znak. Ostatní znaky, které nejsou mezera a které jsou hned za mezerou, se vypíšou normálně. */ int i; printf("\n\nVypis s velkymi pismeny: \n"); if(retez[0]>='a'&&retez[0]<='z') //jestliže je první znak malé písmeno printf("%c",retez[0]-32); // posunu se v ASCII tabulce o -32 else printf("%c",retez[0]);
for(i=1;i<strlen(retez);i++) { if(retez[i]==' ') {if(retez[i+1]>='a'&&retez[i+1]<='z') printf(" %c",retez[i+1]-32); else printf(" %c",retez[i+1]); i++; } else printf("%c",retez[i]); } } int pocet_slov(char *retez) { int i,pocet=0; for(i=0;i<strlen(retez);i++) if(retez[i]==' '||retez[i]=='.') pocet++; return(pocet); } float prumerna_delka(char *retez) {
int i,pocet; pocet=pocet_slov(retez); return((strlen(retez)-pocet)/(float)pocet); /* od délky věty odečteme mezery a tečky (tedy počet slov) a podělíme tuto hodnotu počtem slov.*/
69 }
Ukázka práce programu
Kontrolní otázky a úkoly Pokračujte v předchozím programu tím, že napíšete a otestujete následující funkce 1) Vytvořte funkci, která vypíše nejdelší slovo v načtené větě. 2) Vytvořte funkci, která vrátí 1, pokud je v zadané větě více samohlásek než souhlásek, v opačném případě vrátí -1. Pokud je počet souhlásek a samohlásek shodný, vrátí 0.
70
16 Řetězce – neřešené příklady Obsah hodiny Procvičíme si práci s řetězci s využitím znalostí předchozích kapitol.
Cíl hodiny Po této hodině budete schopni: ● ●
aplikovat získané informace při řešení příkladů s řetězci vhodně použít standardní funkce pro práci s řetězci
Příklad - Rodné číslo 1. Načtěte do řetězce rodné číslo od uživatele ve tvaru: AABBCC/XXXX AA - poslední dvojčíslí roku narození BB - měsíc narození (u žen zvětšený o 50) CC - den narození. XXXX - rozlišení osob narozených ve stejný den. 2. Vytvořte funkci pro kontrolu správnosti rodného čísla. • BB - měsíc je pro muže 01 až 12, pro ženy 51 až 62 • CC – den má své maximum podle daného měsíce (pro zjednodušení můžete pracovat s maximem 31) • Součet AA+BB+CC+XXXX musí být dělitelný bezezbytku číslem 11. • Pokud jedna z podmínek neplatí, rodné číslo je zadáno špatně a funkce bude vracet 0. Pokud je rodné číslo správné funkce bude vracet 1. • Kontrola: 015319/0554 - platné, 871009/2732 – platné, 056010/5091 - neplatné 3. Vytvořte funkci, která pro zadané rodné číslo vrátí 1, jde-li o rodné číslo ženy a 0 jde-li o rodné číslo muže. 4. Vytvořte funkci, která ze zadaného rodného čísla vypíše na obrazovku datum narození a věk člověka s tímto rodným číslem.
71
Příklad- Heslo 1. Načtěte od uživatel heslo do řetězce - minimálně 8 znaků, maximálně 12 znaků. Pokud nebude počet znaků dodržen informujte uživatele a vyžádejte si nové zadání hesla, dokud nebude zadán správný počet znaků. (Popřemýšlejte nad zadáváním hesla tak, aby zadávané znaky byly na obrazovce nahrazovány znakem * ) 2. Vyžádejte si od uživatele nové kontrolní zadání hesla. 3. Zkontrolujte, zda první i druhé zadání hesla jsou shodné. Pokud ne, informujte o tom uživatele a vyžádejte si nové zadání kontrolního hesla, dokud jej uživatel nezadá správně. 4. Zkontrolujte, zda zadané heslo vyhovuje následující podmínkám : • • •
heslo musí obsahovat alespoň jedno velké písmeno, alespoň jedno malé písmeno a alespoň jednu číslici. pokud zadané heslo nevyhovuje podmínce, oznamte to uživateli a opakujte celé zadávání hesla znovu – viz. 1) pokud se celé zadávání a kontrola hesla opakuje již potřetí, vygenerujte pro uživatele heslo s 10 znaky dle podmínky o písmenech a číslicích. Vypište jej na obrazovku s tím, že si jej má zapsat nebo zapamatovat pro další přihlašování.
Příklad - Email I. – kontrola 1. Načtěte od uživatel emailovou adresu do řetězce (max 60 znaků). 2. Vytvořte funkci na kontrolu správnosti emailové adresy, pokud pro ni má platit: • obsahuje přesně jeden znak @ - adresa ho v sobě musí obsahovat, ale nesmí tam být dvakrát • v části před znakem @ musí být nějaký text • v části za znakem @ musí být nějaký text • v části za znakem @ musí být alespoň jedna tečka, protože adresy končí na @neco.cz, nebo @neco.com, apod. •
v každé z obou částí musí být tečka uprostřed textu, není tedy přípustné například [email protected]. - žádná tečka nesmí být na začátku, ani na konci části před @, ani části za @.
72
•
nikde nemohou být dvě tečky vedle sebe
•
za poslední tečkou v adrese musí být dvě, nebo tři písmena (cz, com, org, de apod.).
•
nesmí obsahovat znaky ()<>@,;:\".[], dále nesmí obsahovat kontrolní znaky (veškeré znaky v ASII tabulce před mezerou, tedy s kódy 0 až 31, a potom také znak s ASCII hodnotou 127) a také nesmí obsahovat mezeru
Příklad - Email II. - tvorba 1. Vytvořte emailovou adresu pro uživatele podle zadaných informací. 2. Načtěte od uživatele jméno do jednoho řetězce (max. 20znaků) a příjmení do druhého řetězce (max. 35 znaků). 3. Změňte všechny velká písmena na malá v obou řetězcích. 4. Zjistěte, které variantě mailu dává uživatel přednost, zda plné jméno nebo jen první písmeno jména či příjmení, s tečkou nebo bez tečky – všechny možnosti pro zadané jméno a příjmení vypište na obrazovku Př. pro Jan Novák nabídnete tyto možnosti (můžete i jiné): jan.novak jannovak j.novak jnovak jan.n jann 5. Zjistěte, na kterém serveru chce emailovou schránku vytvořit a dejte mu na výběr (např. seznam.cz, centrum.cz, atlas.cz, gmail.com) 6. Přesně podle zadaného výběru vyalokujte dynamický řetězec potřebné délky pro uložení celé vybrané emailové adresy. (např. [email protected]) Tuto adresu uložte do dynamického řetězce. Vypište adresu na obrazovku a ukončete práci s dynamickou proměnnou. 7. Ošetřete možnost, že uživatel má dvě jména nebo dvě příjmení tím, že ještě před bodem 3) zrušíte mezeru mezi jmény nebo zrušíte pomlčku mezi příjmeními.
73
17 Pole řetězců a parametry funkce main() Obsah hodiny Seznámíme se vícerozměrným polem pro řetězce – polem řetězců a s jejich využitím ve vstupních parametrech funkce main().
Cíl hodiny Po této hodině budete schopni: ● ● ●
použít pole řetězců v praktických úlohách spustit program se vstupními parametry použít parametry funkce main() v praktických úlohách
Klíčová slova Pole řetězců, parametry funkce main() – argc, argv
17.1 Pole řetězců Text podkapitoly. Stejně jako můžeme vytvořit pole jednorozměrných polí, můžeme vytvořit vícerozměrné pole z řetězců. Takovéto pole řetězců můžeme vytvořit staticky i dynamicky. Dynamické pole řetězců můžeme realizovat pomocí pole pointerů, pointeru na pole nebo pointeru na pointer. V této kapitole se budeme věnovat statickému poli řetězců.
Příklad Při deklaraci char retezy[3][7]; se v paměti vyalokuje souvislý blok pamětiv zásobníku.
74 Adresy retezy[0], retezy[1], retezy[2] jsou adresy jednotlivých řetězců v poli a přes tyto adresy pak s řetězci pracujeme. V následujícím příkladu si ukážeme různé možnosti naplnění řetězců v poli a jejich vypsání.
Příklad #include <string.h> int main(int argc, char* argv[]) {
int i; char retezy[3][7]; //načteme první řetězec od uživatele
scanf("%s",retezy[0]); strcpy(retezy[1],"jak se");
//nakopírujeme řetězec do druhého řetězce retezy[2][0]='m'; retezy[2][1]='a'; retezy[2][2]='s'; retezy[2][3]='?'; retezy[2][4]='\0'; //uložíme řetězec do třetího řetězce po jednotlivých znacích printf("\n"); for(i=0;i<3;i++) puts(retezy[i]);
//výpis pole řetězců
getch(); return 0; } Výstup programu bude vypadat takto:
75
17.2 Parametry funkce main() V předchozím modulu jsme se důkladně seznámili s funkcemi i s všemi možnostmi parametru funkcí. Od první hodiny programování v C pracujeme ve funkci main(). Tato funkce má výsadní postavení, protože v programu musí být vždy uvedena, je spouštěna ihned po začátku programu a s jejím koncem končí i celý program. Stejně jako ostatní funkce i hlavní funkce main() může mít své parametry. Tyto parametry musíme zadat při spuštění programu například při spuštění z příkazové řádky. Pokud chceme tyto parametry našemu programu předat musíme funkci main() deklarovat takto: int main(int argc, char* argv[]) Parametr argc určuje počet zadaných parametrů. Jednotlivé parametry zadáváme oddělené mezerou. Jestliže chceme mít jeden parametr řetězec s mezerami, musíme jej zapsat do uvozovek. Parametr argv představuje pole řetězců, ve kterých jsou jednotlivé parametry uloženy – jde o pole pointerů na řetězce. Kromě zadaných parametrů je v poli argv, jako jeho nultá položka, uložen i řetězec se jménem a cestou k spouštěnému programu. Hodnota parametru argc započítává i tento řetězec.
Příklad int main(int argc, char* argv[]) { int i; for(i=0;i<argc;i++) puts(argv[i]); getch(); return 0; } Po spuštění tohoto programu bude na obrazovku vypsán jediný parametr tohoto programu a tím je cesta k exe souboru.
76 Jestliže tento program spustíme z příkazové řádky i s dalšími parametry jako je uvedeno na následujícím obrázku, budou vypsány všechny parametry, které jsme na příkazové řádce zadali.
Parametr argc bude 4 a v poli řetězců budou uloženy řetězce: “D:\MUJ\Debug_Build\Project1.exe “ “prvni“ “druhy“ “treti“ Které se vypíšou na obrazovku.
Pokud chceme použít řetězec s mezerami, musíme jej uzavřít do uvozovek.
77
Shrnutí kapitoly Vícerozměrné pole můžeme vytvořit i pro řetězce. S jednotlivými řetězci v poli pak pracujeme přes adresy těchto řetězců. Příklad: char pole[2][10]; má adresy jednotlivých řetězců pole[0] a pole[1]. Parametry funkce main() používáme, pokud chceme při spuštění programu předat vstupní parametry – například při spuštění programu z příkazové řádky. Funkci main() pak deklarujeme takto: int main(int argc, char* argv[]). Přičemž parametr argc určuje počet zadaných parametrů a parametr argv je pole řetězců, ve kterých jsou vstupní řetězce uloženy.
Kontrolní otázky a úkoly 1) Vytvořte program s polem řetězců pro uložení informací o vaší úplné poštovní adrese. Naplňte toto pole a vypište jej. 2) Vysvětlete význam parametrů funkce main().
78
18 Datový typ struktura Obsah hodiny Seznámíme se dalším strukturovaným datovým typem – strukturou a naučíme se tento datový typ deklarovat.
Cíl hodiny Po této hodině budete schopni: ● ●
popsat datový typ struktura nadeklarovat co nejvhodněji datový typ struktura
Klíčová slova Struktura, struct, typedef
Struktura je datový typ, který požíváme pro práci proměnnými, které jsou charakterizovány více než jednou hodnotou. Například pokud chceme pracovat s informacemi o vozidlech, budeme potřebovat pro jedno vozidlo jeho SPZ, jméno majitele, rok výroby a průměrnou spotřebu paliva. Položky jedné takovéto proměnné jsou data různých datových typů, přičemž je chápeme jako celek, jako informace o jednom vozidle. Pro tuto proměnnou musíme nadefinovat nový datový typ – strukturu. Struktura je heterogenní strukturovaný datový typ. Heterogenní znamená, že položky jedné proměnné mohou být různého datového typu. Strukturovaný datový typ je datový typ s jistou strukturou. Strukturovaným datovým typem je také pole a řetězec. Oba jsou ale homogenní, protože jejich položky jsou stejného datového typu. Datový typ struktura můžeme definovat mnoha způsoby. Obecně definice vypadá takto: struct název_struktury { typ prvek1; : typ prvekN; } seznam proměnných;
79 Název struktury nebo seznam proměnných nejsou povinné části definice, ale alespoň jedna z nich by měla být uvedena. Definicí struktury nealokujeme paměť, ale vytváříme jen logickou šablonu, podle které budeme tvořit proměnné. V programu definici datového typu uvádíme na začátek programu za příkazy preprocesoru mimo všechny funkce nebo do hlavičkového souboru. V následujících příkladech si uvedeme možnosti definice struktury a možnosti deklarace její proměnných. Vytvoříme datový typ struktura pro studenta, který má své jméno a své IQ.
Příklad standardní definice struct student { char jm[8]; int IQ; } prvni,druhy; V paměti se vyalokují dvě proměnné prvni,druhy jako souvislé bloky paměti pro řetězec jm[8] a celé číslo IQ.
Pokud bychom chtěli vytvořit další proměnnou, museli bychom ji nadeklarovat takto: struct student treti; datový typ
proměnná
Samotné struct pro deklaraci nové proměnné nestačí. Za struct musíme uvést název struktury student, protože v programu můžeme mít definováno více struktur. Musíme tedy překladači jednoznačně popsat pomocí jména, kterou strukturu má vytvořit. V paměti se pak alokuje treti proměnná.
80
Příklad definice bez názvu struktury struct { char jm[8]; int IQ; } prvni,druhy; V této definici struktury chybí její název. Proto se na strukturu nemůžeme odkázat a vytvořit tak další proměnné v jiné části programu. Nemůžeme strukturu použít ve funkcích, jako parametry. Všechny potřebné proměnné jsou deklarovány ihned za definicí.
Příklad definice bez deklarovaných proměnných struct student { char jm[8]; int IQ; }; U definice nejsou deklarovány proměnné, ale můžeme je kdekoliv v programu deklarovat. struct student prvni,druhy; Pro další typ definice struktury se seznámíme s příkazem typedef. Pomocí typedef vytváříme uživatelské datové typy. typedef definice_typu
název_nového_typu;
Mezi typedef a název nového datového typu zapíšeme typ, se kterým má být námi definovaný typ ekvivalentní. V další části programu pak už místo definovaného typu můžeme uvádět náš nový název. Například po příkazu typedef int integer; můžeme deklarovat novou celočíselnou proměnnou pomocí jejího nového názvu integer. Musíme si však uvědomit, že proměnná typu int a proměnná typu integer nebudou vzájemně kompatibilní, i když jde vždy o celé číslo. Příkaz typedef se nejčastěji používá při definici datového typu struktura.
81
Příklad definice s použitím typedef typedef struct student { char jm[8]; int IQ; }STUDENT; Definujeme takto nový datový typ, který budeme označovat identifikátorem STUDENT. Samotná struktura má také název – student. Štábní kultura jazyka C říká, že název struktury a název datového typu rozlišujeme velkostí písma. Proměnnou pak můžeme deklarovat kdekoliv v programu dvěma způsoby: struct student první; STUDENT druhy; Jak jsme již uvedli výše, proměnná prvni není kompatibilní s proměnnou druha. V obou případech se však vyalokuje souvislý blok paměti.
Příklad definice s použitím typedef bez názvu struktury typedef struct { char jm[8]; int IQ; }STUDENT; Takto definujeme datový typ struktura nejčastěji. Proměnné pak deklarujeme jen pomocí názvu datového typu STUDENT.
82
Shrnutí kapitoly Struktura je heterogenní strukturovaný datový typ. Proměnná typu struktura obsahuje položky různých datových typů. Datový typ struktura definujeme klíčovým slovem struct nejčastěji s definicí nového datového typu pomocí typedef. typedef struct { typ prvek1; : typ prvekN; } NÁZEV_DAT_TYPU;
Kontrolní otázky a úkoly 1) Charakterizujte datový typ struktura. 2) Nadeklarujte nový datový typ pro informace o vozidlech, pro které budeme potřebovat jejich SPZ (řetězec), rok výroby a průměrnou spotřebu paliva. Vytvořte dvě proměnné tohoto datového typu.
83
19 Práce s položkami struktur Obsah hodiny Seznámíme se prací s proměnnými typu struktura.
Cíl hodiny Po této hodině budete schopni: ● ●
naplnit položky struktur daty použít proměnné typu struktura v praktických úlohách
Klíčová slova Operátor tečky
K jednotlivým proměnným typu struktura přistupujeme pře jejich identifikátor – název. K jednotlivým položkám proměnné typu struktura přistupujeme pomocí operátoru tečka. Práci s proměnnými i jejich položkami si ukážeme v následujícím příkladu.
Příklad typedef struct { char jm[8]; int IQ; }STUDENT; //definice nového datového typu int main() { STUDENT první, druhy treti; prvni.IQ=120; //uloží prvnímu studentovi do jeho položky IQ hodnotu 120
84 strcpy(prvni.jm,“Pepa“); //nakopíruje řetězec Pepa prvnímu studentovi do jeho položky jm
scanf(“%d”,&druhy.IQ); //načte od uživatele IQ druhého studenta gets(druhy.jm);
//načte od uživatele jméno druhého studenta
Jestliže uživatel zadá studenta Toníčka s IQ 125 bude proměnná naplněna takto:
Mimo operátor tečka je pro struktury definován i operátor přiřazení =. Můžeme proto uložit obsah proměnné struktura do jiné proměnné stejného typu. trerti=prvni; Obsah položek struktury prvni se nakopírují do proměnné treti.
85 Podobně můžeme pracovat s položkami i jejich částmi při výpisu. printf(“Jmeno prvniho studenta je %s”,prvni.jm); printf(“ a má IQ %d \n”,prvni.IQ); printf(“Jmeno druhého studenta je %s”,druhy.jm); printf(“ a má IQ %d \n”,druhy.IQ); printf(“Druhy znak ve jmene prviho studenta je %c \n”,prvni.jm[1]); if(prvni.IQ> druhy.IQ) printf(“Vetsi IQ ma %s”,prvni.jm); else printf(“Vetsi IQ ma %s”,druhy.jm); getch(); return 0; }
Shrnutí kapitoly K položkám proměnné typu struktura přistupujeme pomocí operátoru tečka. Pro přiřazení všech položek dvou struktur můžeme použít uperátor přiřazení =.
Kontrolní otázky a úkoly 1) Kontrolní otázky a úkoly prověřují, 2) Pokračujte v úkolu z minulé kapitoly: Nadeklarujte nový datový typ pro informace o vozidlech, pro které budeme potřebovat jejich SPZ(řetězec), rok výroby a průměrnou spotřebu paliva. Vytvořte dvě proměnné tohoto datového typu. Naplňte tyto dvě proměnné hodnotami zadanými uživatelem. Vypište přehledně všechny informace o obou vozidlech.
86
20 Struktury - příklady Obsah hodiny Procvičíme si základy práce se strukturami
Cíl hodiny Po této hodině budete schopni: ● ●
využít struktury v praktických úlohách napsat vlastní program s co nejvhodnějším využitím struktur
Příklad Vytvořte program pro práci s dvěma obrázky. Pro jeden obrázek budete potřebovat informace o názvu obrázků – o objektu, který zobrazuje (auto, květina aj.), o výšce a šířce obrázku, zadaných v cm. 1. Načtěte údaje pro oba obrázky. 2. Přehledně vypište údaje o obou zadaných obrázcích. 3. Vypište názvy obrázků, které jsou čtvercové. 4. Vypište rozměry obrázků, jejichž název je „domecek“. 5. Změňte první písmeno názvů obou obrázků na velké písmeno, je-li malé a informace o obou obrázcích opět vypište na obrazovku.
Řešení #include <stdio.h> #include #include <string.h> typedef struct { char nazev[40]; float vyska,sirka; } TOBRAZEK;
87
int main(int argc, char* argv[]) { TOBRAZEK prvni,druhy; int pocet=0; //1. načtení struktur printf("Zadej nazev pro 1. obrazek: "); scanf("%s",prvni.nazev); printf("Zadej vysku pro 1. obrazek: "); scanf("%f",&prvni.vyska); printf("Zadej sirku pro 1. obrazek: "); scanf("%f",&prvni.sirka); printf("\nZadej nazev pro 2. obrazek: "); scanf("%s",druhy.nazev); printf("Zadej vysku pro 2. obrazek: "); scanf("%f",&druhy.vyska); printf("Zadej sirku pro 2. obrazek: "); scanf("%f",&druhy.sirka); //2. vypsání struktur printf("\n\nNazev prvniho obrazku je %s ",prvni.nazev); printf("a ma rozmery %.2f X %.2f \n\n",prvni.vyska, prvni.sirka); printf("Nazev druheho obrazku je %s ",druhy.nazev); printf("a ma rozmery %.2f X %.2f \n\n",druhy.vyska, druhy.sirka); //3. čtvercové obrázky printf("Ctvrecove obrazky: \n"); if(prvni.vyska==prvni.sirka) {printf("%s \n",prvni.nazev); pocet++; } if(druhy.vyska==druhy.sirka) {printf("%s \n",druhy.nazev); pocet++; } if(pocet==0) printf("Zadny obrazek neni ctvercovy. \n");
88 //4. obrázky s domečkem printf("\nObrazky s domeckem: \n"); pocet=0; if(!strcmp(prvni.nazev,"domecek")) {printf("Rozmery %.2f X %.2f \n\n",prvni.vyska, prvni.sirka); pocet++; } if(!strcmp(druhy.nazev,"domecek")) {printf("Rozmery %.2f X %.2f \n\n",druhy.vyska, druhy.sirka); pocet++; } if(pocet==0) printf("Obrazek s domeckem nemame.\n"); //5. první velké písmeno v názvu obrázku if((prvni.nazev[0]>='a')&&(prvni.nazev[0]<='z')) prvni.nazev[0]=prvni.nazev[0]-('a'-'A'); if((druhy.nazev[0]>='a')&&(druhy.nazev[0]<='z')) druhy.nazev[0]=druhy.nazev[0]-('a'-'A'); printf("\n\nNazev prvniho obrazku je %s ",prvni.nazev); printf("a ma rozmery %.2f X %.2f \n\n",prvni.vyska, prvni.sirka); printf("Nazev druheho obrazku je %s ",druhy.nazev); printf("a ma rozmery %.2f X %.2f \n\n",druhy.vyska, druhy.sirka); getch(); return 0; } Všimněte si, jak je práce s jednotlivými strukturami komplikovaná. Pokud bychom chtěli zpracovávat informace například o dvaceti obrázcích, asi bychom se po čase v kódu přestali orientovat. Proto se struktury nejčastěji používají v polích, což si ukážeme v další kapitole.
89
Kontrolní otázky a úkoly Vytvořte program, který bude řešit následující úkoly: 1. Vytvořte tři proměnné struktury LIDI typedef struct { char jmeno [20]; int znamka; int iq; }LIDI; 2. Načtěte informace od těchto tří proměnných (iq a znamka např. rand) a určete proměnnou s minimálním iq. Vypište jmeno z této proměnné 3. Vypište jmeno, znamka i iq pro všechny načtené proměnné typu LIDI. 4. Vypočtěte a vypište průměrnou známku těchto tří osob. 5. Vypište jméno osoby s nejmenším IQ. 6. Zkontrolujte, zda jména všech tří osob začínají velkým písmenem. Pokud ne, opravte jej. Vypište znovu všechny tři jména.
Otázky k zamyšlení 1) Zamyslete se nad dalšími objekty, pro které bychom mohli vytvořit datový typ struktura. Jak byste nedefinovali jejich položky?
90
21 Pole struktur Obsah hodiny Seznámíme se s využitím datového typu pole pro struktury.
Cíl hodiny Po této hodině budete schopni: ● ●
vytvořit pole struktur zpracovat informace pro více struktur s využitím datového typu pole
Klíčová slova Pole struktur
Pokud bychom se větším množstvím struktur pracovali jako s jednotlivými proměnnými, kód programu by byl velmi obsáhlý a nepřehledný. Proto pro zpracování většího počtu struktur – objektů se stejnou strukturou, tvoříme jednorozměrné pole těchto struktur, které práci podstatně zjednodušuje a urychluje. Použití struktur v polích si ukážeme v následujícím příkladu.
Příklad Mějme opět datový typ struktura STUDENT pro studenta se jménem a s IQ. A vytvořme pole pro pět studentů typu STUDENT. typedef struct { char jm[8]; int IQ; }STUDENT; int main() { STUDENT pole[5];
//definice nového datového typu
91 Tímto jsme v paměti alokovali poel pěti prvků, přičemž jeden prvek je typu STUDENT s jeho jménem a s jeho IQ.
Každá struktura - každý student má pak v poli svůj index, například druhý student bude popsán jako pole[1]. K položkám jednotlivých struktur – studentů se dostaneme opět operátorem tečka.
Přístup k položkám struktur v poli se provádí takto: lidi[0].IQ=120;
//uloží IQ prvního studenta 120
strcpy(lidi[0].jm,”Pepik”); //uloží jméno prvního studenta Pepík scanf(“%d“,&lidi[2].IQ); //načte IQ druhého studenta- např. Jara gets(lidi[2].jm);
//načte jméno druhého studenta- např. 125
Pak naše pole bude vypadat:
Veškeré operace s daty, jako jsou vyhledávání, porovnávání či třídění provádíme pomocí cyklu for a jsou tak velice přehledné a jednoduše realizovatelné.
92
Příklad Hledání studenta s největším IQ pro v předchozím příkladu vytvořené pole pěti studentů po načtení všech položek pro všechny studenty může vypadat v programu takto: int i, index, max=0; for(i=0; i<5; i++) if (pole[i].IQ>max) { max= pole[i].IQ; index=i;
//jestliže najdeme vyšší IQ, než které máme v max //přepíšeme hodnotu max //uložíme si do proměnné index index studenta s maximálním IQ
} printf(“Nejvetsi IQ ma %s“, pole[index].jm);
Příklad Setřízení studentů podle jejich IQ. int i,j; STUDENT pom; for(j=0; j<4; j++) for(i=0; i<4; i++) if (pole[i].IQ > pole[i+1].IQ) { pom = pole[i].IQ; pole[i].IQ = pole[i+1].IQ; pole[i+1].IQ = pom; }
Shrnutí kapitoly Pro zpracování více struktur vytváříme jednorozměrné pole struktur, které nám umožní přehlednější, efektivnější a rychlejší práci s těmito daty pomocí cyklů for.
93
Kontrolní otázky a úkoly Vytvořte program, který bude řešit následující ukoly. 1. Vytvořte pětiprvkové pole s prvky proměnné struktury CAR typedef struct { char spz[7]; char znacka [20]; int vyroba; float najeto; }CAR; 2. Načtěte údaje pro všech pět aut. 3. Přehledně vypište údaje všech zadaných aut. 4. Vypočtěte a vypište průměr hodnoty najeto všech načtených aut. 5.Vypište SPZ všech aut značky Ford. 6. Vypište SPZ všech aut, která jsou starší než 10 let. 7. Vzestupně setřiďte auta podle hodnoty najeto a pole znovu vypište.
94
22 Struktura ve struktuře a pole ve struktuře Obsah hodiny Seznámíme se s využitím struktur a polí jako prvků ve struktuře.
Cíl hodiny Po této hodině budete schopni: ● ● ●
vysvětlit přístup k prvkům struktury, které je položkou jiné struktury vysvětlit přístup k prvkům pole, které je položkou struktury použít strukturu ve struktuře a datový typ pole ve struktuře
Klíčová slova Struktura ve struktuře, pole ve struktuře
Prvkem struktury může být opět struktura. Musí však platit, že ve struktuře použitá struktura musí být definována před svým použitím.
Příklad Vytvoříme strukturu OSOBA jejíž položkou bude kromě jmena také datum narození. Toto datum narození je další struktura, která má položka den,mesic, rok. typedef struct { int den,mesic,rok; } DATUM; typedef struct { char jmeno[15]; DATUM narozen; }OSOBA; Po deklaraci promněné první typu OSOBA se vyalokuje souvislá paměť pro jméno i pro datum narození. OSOBA prvni;
95
prvni.jmeno
prvni.narozen
prvni
prvni.narozen.den
prvni.narozen.rok
prvni.narozen.mesic
K jednotlivým položkám proměnné prvni se dostaneme jako obvykle přes tečky. To znamená, že: •
prvni.jmeno je první položka struktury – řetězec velikost 15B
•
první.narozen je struktura typu DATUM, která má svou položku den, mesic a rok. K těmto položkám se dostaneme přes další tečku, jak naznačuje předchozí obrázek
Program pak může pokračovat například takto: printf("Zadej jmeno osoby: "); gets(prvni.jmeno); printf("Zadej den narozeni: "); scanf("%d",&prvni.narozen.den); printf("Zadej mesic narozeni: "); scanf("%d",&prvni.narozen.mesic); printf("Zadej rok narozeni: "); scanf("%d",&prvni.narozen.rok); printf("Zadal si osobu se jmenem %s ", prvni.jmeno); printf("narozenou %d.%d.%d", prvni.narozen.den, prvni.narozen.mesic, prvni.narozen.rok); Obdobným způsobem můžeme použít jako položku struktury pole.
96
Příklad Vytvoříme strukturu pro studenta, který má své jméno a čtyři známky ze čtvrtletních prací z matematiky. typedef struct { char jmeno[15]; int znamky[4]; }STUDENT; Po deklaraci proměnné typu STUDENT se opět vyalokuje souvislý blok paměti. A protože jednotlivé známky jsou prvky pole, přistupujeme k nim pomocí jejich indexu, jak je zakresleno v následujícím obrázku. STUDENT prvni;
prvni.jmeno
prvni.znamky
prvni
prvni.znamky[0]
prvni.znamky[2]
Program pak může pokračovat třeba takto: int i; printf("Zadej jmeno studenta: "); gets(prvni.jmeno); for(i=0;i<4;i++) { printf("Zadej %den znamku: ",i+1); scanf("%d",&prvni.znamky[i]); } printf("Zadal si studenta se jmenem %s a znamkami ", prvni.jmeno); for(i=0;i<4;i++) printf(" %d, ", prvni.znamky[i]);
97
Shrnutí kapitoly Jako položky struktur můžeme použít již vytvořenou strukturu a také pole. K položkám, které jsou také strukturami, přistupujeme pomocí další tečky. K položkám, které jsou typu pole, přistupujeme pomocí indexů.
Kontrolní otázky a úkoly Vytvořte datový typ MATURANT, který bude mít položky: jméno, datum maturity, známky z maturity a průměrnou známku. Datum maturity bude datového typu struktura s položkami den, měsíc, a rok. Známky z maturity bude pole pěti celých čísel. Průměrná známka bude vypočtena jako aritmetický průměr z pěti známek v poli. Vytvořte program, který načte všechny informace o studentovi, a tyto informace pak vypište přehledně na obrazovku.
98
23 Dynamická struktura Obsah hodiny Seznámíme se s využitím dynamických proměnných pro struktury a s přístupem k jejich položkám.
Cíl hodiny Po této hodině budete schopni: ● ●
vytvořit dynamickou proměnnou typu struktura přistupovat správným způsobem k jednotlivým dynamických struktur
položkám
Klíčová slova Dynamické struktury
Strukturu můžeme stejně jako všechny proměnné vytvořit dynamicky. To znamená, že ji musíme alokovat i odalokovat sami pomocí příkazů malloc a free. Tato dynamická proměnná se alokuje v haldě, nemá vlastní identifikátor a přistupujeme k ní jen pomocí pointeru.
Příklad Nejprve vytvoříme datový typ struktura pro knihu s názvem a počtem stran. typedef struct { char nazev[15]; int strany; } KNIHA; Ve funkci main() nadeklarujeme pointer prvni, který může ukazovat na strukturu typu KNIHA. KNIHA *prvni ; Vytvoříme dynamickou proměnnou. prvni=( KNIHA *) malloc (sizeof(KNIHA)) ;
99
Funkce malloc vyalokuje v haldě proměnnou velikosti datového typu KNIHA a adresu této vytvořené proměnné uloží do pointeru prvni. V paměti to pak bude vypadat takto: prvni
zásobník
halda
Dynamická proměnná v haldě nemá Dostaneme se k ní je přes pointer prvni.
své
jméno
–
identifikátor.
(*prvni).strany=120; Odkaz na místo, kam ukazuje pointer prvni, musíme uzávorkovat (*prvni), aby bylo nejprve nalezeno místo dynamické proměnné v haldě a teprve v ní pak přejdeme k části strany, kam zapíšeme hodnotu 120.
prvni 120
zásobník
halda
Zápis zapsání do dynamické proměnné je takto docela komplikovaný. Je proto nahrazován ekvivalentním zápisem s využitím takzvané šipky. prvni -> strany = 120; Tímto příkazem provedeme zápis hodnoty 120 tam, kam ukazuje pointer prvni do části strany. Je možná lépe čitelný než varianta s (*prvni). Do části nazev pro jméno osoby můžeme zapsat konkrétní jméno takto: strcpy(prvni-> nazev,”Runcajs”); Pokud budeme chtít název i počet stran načíst od uživatele, pak použijeme příkazy: scanf(“%d“, &( prvni->strany));
100 scanf(“%s“, prvni->nazev)); nebo gets(prvni->nazev); Po ukončení práce s dynamickou proměnnou musíme proměnnou odalokovat a pointer první ukotvit. free(prvni); první=NULL;
Shrnutí kapitoly Dynamickou proměnnou typu struktura vytváříme pomocí příkazu malloc. ukazatel=(STRUKTURA*) malloc(sizeof(STRUKTURA)); Přístup k jednotlivým částem dynamické proměnné realizujeme obecně: (*ukazatel).položka nebo ukazatel->položka
Kontrolní otázky a úkoly 1. Vytvořte ukazatel ja na strukturu OSOBA typedef struct { char jmeno [20]; DATE narozen; }OSOBA; kde struktura DATE je definována typedef struct { int den,mes,rok; }DATE; 2. Pomocí ukazatele ja vytvořte dynamickou proměnnou. 3. Načtěte položky vytvořené dynamické proměnné. Vypište všechny položky dynamické proměnné. 4. Zjistěte, zda se načtená osoba narodila na jaře (v měsících březen, duben nebo květen).
101
24 Struktura a funkce Obsah hodiny Seznámíme se s využitím struktur ve funkcích.
Cíl hodiny Po této hodině budete schopni: ● ● ●
využít struktury ve funkcích jako jejich parametry poslat vhodným způsobem strukturu do funkce správně vytvořit funkci, která vrací strukturu
Klíčová slova Struktura jako parametr funkce, struktura jako návratový typ funkce
Struktury můžeme použít jako parametr funkce i jako návratovou hodnotu funkce. Při práci se strukturami ve funkcích musíme přemýšlet o co nejvhodnější práci s pamětí a také časem. Vhodný i nevhodný postup si ukážeme na příkladech.
Příklad Vytvoříme strukturu pro zpracování komplexního čísla. Každé komplexní číslo má svou reálnou a imaginární část. typedef struct { float re,im; } TKOMP; Vytvoříme si funkci pro sečtení dvou komplexních čísel. Dvě komplexní čísla budou vstupní parametry této funkce a výstupem bude opět komplexní číslo. (Pozn. Při sčítání komplexních čísel sčítáme reálné části komplexních čísel a imaginární části).
102 TKOMP secti1(TKOMP x, TKOMP y) { TKOMP z; z.re=x.re+y.re; z.im=x.im+y.im; return (z); } Ve funkci main() použijeme vytvořenou funkci pro dvě zadané komplexní čísla. int main() { TKOMP a,b,sou; a.re=2; a.im=3; b.re=4; b.im=5; sou=secti1(a,b); //do funkce posíláme komplexní čísla a,b a výsledné komplexní číslo, které vrací funkce secti, uložíme do proměnné sou, kterou vypíšeme v dalším příkazu printf(“součet je %f + %f i“, sou.re, sou.im); return 0; } Rozebereme se tento příklad pomocí obrázků. Po deklaraci TKOMP a,b,sou; a přiřazení a.re=2; b.im=5; budeme mít v paměti:
b
a 2
3
a.im=3; b.re=4;
sou 4
5
V příkazu sou=secti1(a,b); voláme funkci secti1, která má hlavičku TKOMP secti1(TKOMP x, TKOMP y) proto se musí vyalokovat parametry x, y a návratová hodnota pro funkci secti1. Ve funkci využíváme lokální proměnnou TKOMP z;, která je take vyalokovaná.
b
a 2
3
sou 4
5
y
x 2
3
z 4
5
secti1
103 Parametry a,b nazýváme skutečné parametry a parametry x,y nazýváme formální parametry. Při volání funkce předají skutečné parametry své hodnoty formálním parametrům. Toto předání je v obrázku zaznačeno šipkou. Po příkazech z.re=x.re+y.re; z.im=x.im+y.im; naplníme proměnnou z hodnotami 6 a 8.
z 6
8
Příkazem return(z); uložíme hodnoty z proměnné z do návratové hodnoty funkce secti1. Příkaz return ukončuje funkci. Návratová hodnota se ze secti1 předává do proměnné sou do funkce main(). Končí také platnost všech lokálních proměnných, které jsme vytvořili pro funkci secti1.
b
a 2
3
sou 4
y
x 2
3
6
5
8
z 4
5
secti1 6
8
6
8
Uvědomte si jak tento postup, kde využíváme parametry volané hodnotou, je náročný na čas a paměť. Pro sečtení dvou komplexních čísel musíme vyalokovat další čtyři struktury, předávat jejich hodnoty z jedné do druhé a na konci všechny odalokovat. Pokud bychom měli rozsáhlou strukturu a prováděli funkci častěji, bude náš program časově i paměťově náročný. Pokud však využijeme ve funkci parametry volané odkazem, ušetříme alokace, aodalokace i předávání hodnot mezi strukturami. V následující příkladu vytvoříme porgram, který bude řešit stejný problém jako příklad předchozí, ale využijeme zde parametrů volaných odkazem.
104
Příklad typedef struct { float re,im; } TKOMP; void secti2(TKOMP *x, TKOMP *y, , TKOMP *z) { z->re=x->re+y->re; z->im=x->im+y->im; } int main() { TKOMP a,b,sou; a.re=2; a.im=3; b.re=4; b.im=5; secti2(&a, &b, &sou); //do funkce posíláme jen adresy proměnných a,b,sou printf(“součet je %f + %f i“, sou.re, sou.im); return 0; } Příklad si opět rozebereme i s obrázky vyalokovaných proměnných. Po deklaraci a přiřazení hodnot do proměnných a,b voláme funkci sou=secti2(&a,&b); do funkce však posíláme jen adresy struktur a,b. Formální parametry funkce secti2 jsou proto pointery na struktury typu TKOMP a při volání funkce secti2 budou naplněny adresami, které jim posíláme při volání funkce.
b
a 2
x
&a
3
sou 4
y
&b
5
z
&sou
Proto se formální parametry - pointery x,y,z takzvané odkazují na skutečné parametry – struktury a,b,sou a mohou měnit jejich hodnoty. Se strukturami a,b,sou pracujeme ve funkce jen pomocí pointerů. Příkazy z->re=x->re+y->re; z->im=x->im+y->im; provedou uložení hodnot 6 a 8 přímo do proměnné sou. Poté jsou odalokovány formální parametry x,y,z.
105 Zhodnoťte nakolik je tato verze řešení jednodušší, elegantnější a zejména méně náročná na paměť i čas. Proto vám při psaní funkcí pro zpracování struktur doporučuji používat parametry volné odkazem.
Shrnutí kapitoly Struktury můžeme použít jako parametr funkce i jako návratovou hodnotu funkce. Formálním parametrem funkce by měl být odkaz na strukturu, tedy pointer na strukturu, abychom šetřili čas i paměť.
Kontrolní otázky a úkoly 1. Vytvořte dvě proměnné typu CAR typedef struct { char znacka [20]; int vyroba; int najeto; }CAR; 2. Vytvořte funkci pro načtení údajů pro jedno auto. 3. Vytvořte funkci pro přehledný výpis údajů o autě. 4. Vytvořte funkci pro výpočet průměrné hodnoty najeto pro dvě auta. 5. Vytvořte funkci, která bude porovnávat dvě auta a bude vracet auto (celou strukturu), která má hodnotu najeto nejblíže průměrné hodnotě, kterou vrací funkce ad 4. 6. Vytvořte funkci, která zjistí pro dané auto, zda má značku Ford. 7. Vytvořte funkci, která zjistí pro dané auto, zda je starší než 10 let.
Otázky k zamyšlení Přemýšlejte nad možností, že budete psát funkci pro zpracování pole struktur. Jak bude tato funkce vypadat? Co budou skutečné a formální parametry?
106
25 Struktura – řešené příklady Obsah hodiny Procvičíme využití struktur v praktických úlohách.
Cíl hodiny Po této hodině budete schopni: ● ●
aplikovat získané informace při řešení příkladů s využitím struktur vhodně pracovat s položkami struktur a vhodně použít struktury ve funkcích
Příklad Vytvořte program pro práci se strukturou závodník orientačního běhu, pro kterého budeme potřebovat jméno, startovací číslo a celkový čas v sekundách. Vytvořte pole 10 závodníků, načtěte jej a vypište. Pomocí funkce zjistěte vítězného závodníka. Pomocí funkce zjistěte průměrný čas běhu všech závodníků. Pomocí funkce setřiďte závodníky od nejlepšího.
Řešení #include <stdio.h> #include typedef struct { char jmeno [20]; int cislo, cas; }ZAVODNIK; void nacti(ZAVODNIK *x); //budeme načítat jen jednoho závodníka void vypis(ZAVODNIK *x); //budeme vypisovat celé pole int vitez(ZAVODNIK *x); float prumer(ZAVODNIK *x); void seradit(ZAVODNIK *x);
107 int main(int argc, char* argv[]) { ZAVODNIK pole[10]; int i, vitezi; for(i=0;i<10;i++) nacti(&pole[i]); //postupně do funkce posílám jednotlivé prvky pole vypis(pole); //do funkce posílám pole, které se v ní vypíše jako celek vitezi=vitez(pole); printf("Vitezem je %s se startovacím cislem %d ", pole[vitezi].jmeno, pole[vitezi].cislo); printf("a casem %d.\n\n", pole[vitezi].cas); seradit(pole); vypis(pole); getch(); return(0); } void nacti(ZAVODNIK *x) //pracuje s jedním závodníkem { printf("Zadej jmeno zavodnika: "); gets(x->jmeno); printf("Zadej startovaci cislo: "); scanf("%d",&(x->cislo)); printf("Zadej cas: "); scanf("%d",&(x->cas)); while(getchar()!='\n'); } void vypis(ZAVODNIK *x) //pracuje s celým polem závodníků { int i; for(i=0;i<10;i++) { printf("Bezec %s ma startovaci cislo %d",x[i].jmeno,x[i].cislo); printf(" a cas %d sekund\n",x[i].cas); }} int vitez(ZAVODNIK *x) { int i,index=0,min;
108 min=x[0].cas; //nastavíme hodnotu min na hodnotu casu prvního prvku v poli for(i=0;i<10;i++) if(x[i].cas < min) { min=x[i].cas; index=i; } return index; //funkce bude vracet index vítěze v poli } float prumer(ZAVODNIK *x) { int i; float soucet=0; for(i=0;i<10;i++) soucet+=x[i].cas; return (soucet/10); } void seradit(ZAVODNIK *x) { int i,j; ZAVODNIK pom; for(j=0;j<9;j++) for(i=0;i<9;i++) if(x[i].cas > x[i+1].cas ) { pom=x[i]; x[i]=x[i+1]; x[i+1]=pom; } } Pro načtení a výpis jsme vytvořili dvě funkce, které mají velice podobnou hlavičku funkce, ale pracují odlišně. Funkce nacti načítá jen jeden prvek pole a je volána desetkrát pro každý prvek. Funkce vypis vypíše celé pole. Pointer x ve funkci bude ukazovat na první prvek pole a funkce přes něj zpracuje celé pole najednou – funkce je volána jen jednou. Pro práci s polem struktur můžeme zvolit jeden z těchto postupů, přičemž práce s celým polem najednou méně paměťově i časově náročná.
109
26 Struktura – neřešené příklady Obsah hodiny Procvičíme si práci se strukturami s využitím znalostí předchozích kapitol.
Cíl hodiny Po této hodině budete schopni: ● ●
aplikovat získané informace při řešení příkladů se strukturami vhodně pracovat s položkami struktur a vhodně použít struktury ve funkcích
Příklad 1) Vytvořte nový datový typ pro komplexní čísla a tří proměnné tohoto datového typu. 2) Vytvořte funkci pro načtení jednoho komplexního čísla. Načtěte pomocí této funkce dvě komplexní čísla ve funkci main(). 3) Vytvořte funkci pro výpis jednoho komplexního čísla v příslušném tvaru. Vypište dvě načtené čísla ve funkci main. 4) Vytvořte funkci pro součet dvou komplexních čísel. Výsledné číslo bude funkce vracet. 5) Vytvořte funkci pro násobení dvou komplexních čísel. Výsledné číslo bude funkce vracet. 6) Vytvořte funkci pro výpočet absolutní hodnoty dvou komplexních čísel. Výsledné číslo bude funkce vracet.
Příklad 1) Vytvořte databázi kamarádů (address book). Použijte pole a následujících struktur: typedef struct { int cislo; char ulice[15];
110 char mesto[20]; } ADRESS; typedef struct { int den,mes,rok; }DATE; typedef struct { char jmeno [10]; char prijmeni[20]; ADRESS adresa; DATE naroz; }LIDI; 2) Vytvořte funkci pro načtení údajů pro zadaný počet osob. 3) Vytvořte funkci pro výpis údajů všech zadaných osob. 4) Vytvořte funkci pro vyhledání lidí narozených v zadaném měsíci. 5) Vytvořte funkci pro vypsání všech příjmení kamarádů, kteří žijí v Ostravě. 6) Vytvořte funkci pro vyhledání a vypsání všech údajů pro zadané příjmení kamaráda. 7) Vytvořte funkci pro vypsání informací o nejmladším kamarádovi.
Příklad Vytvořte program pro správu databáze CD nosičů. Použijte následující struktury: typedef struct { char nazev[40]; char zpevak[40]; char skladatel[40]; float delka; } TSONG; typedef struct { char nazev[40]; int pocet_pisni; float cenaSK, cenaCZ; TSONG pisen[20]; } TCD;
111 1) Vytvořte databázi (pole) 10 CD. 2) Naplňte pole všemi údaji. 3) Pole přehledně vypište. 4) Setřiďte pole podle počtu písní na CD nebo podle ceny – jednu z možností vybere uživatel. Pole znovu vypište. 5) Vyhledejte a vypište názvy všech písní od zadaného zpěváka. 6) Vyhledejte a vypište názvy všech CD na kterých je píseň od zadaného skladatele.
Příklad Vytvořte dva projekty A a B, které budou řešit kvadratický trojčlen. Část A Sestavte projekt, který bude obsahovat modul s funkcí main(), přeložený modul (*.obj) z části B a modul s funkcemi (viz. níže). Doplňte projekt o hlavičkové soubory ke každému modulu s funkcemi. Vytvořte datový typ struktura pro práci s kvadratickým trojčlenem. Pro každý člen budeme potřebovat jeho koeficienty – a,b,c. Ve funkci main() načtěte od uživatele celočíselné koeficienty a,b,c pro jednu kvadratickou funkcí i rovnicí. Pro tyto koeficienty vypište s využitím Vašich i spolužákových funkcí souřadnice vrcholu kvadratické funkce, obor hodnot, zda je funkce sudá, zda je nad nebo pod osou x a nebo jí protíná, typ kvadratické rovnice (ryze kvadratický, bez absolutního člene, úplná), hodnotu diskriminantu kvadratické rovnice a pokud to bude mít smysl vypíšete její případné kořeny a její rozklad na kořenové činitele. Všechny parametry budete načítat ve funkci main() a do funkcí je budete posílat. Pokud má funkce hodnotu vracet, budete ji vypisovat ve funkci main(). Funkce v části A pro práci s koeficienty: •
funkce, která bude vracet x-ovou souřadnici vrcholu kvadratické funkce
•
funkce, která bude vracet y-ovou souřadnici vrcholu kvadratické funkce
•
funkce, která bude vypisovat obor hodnot kvadratické funkce
•
funkce, která bude vracet číslo 1, pokud je kvadratická funkce sudá, nebo číslo 0, pokud není sudá
112
•
funkce, která bude vracet číslo 1, pokud je kvadratická funkce jen nad osou x, číslo -1, pokud je funkce jen pod osou x, a číslo 0, pokud osu x protíná
Přeložený soubor (*.obj) s funkcemi části A i správně zapsaný hlavičkový soubor (s podmíněným překladem a komentářem funkcí) vložte do projektu B. Část B Sestavte projekt B, který bude obsahovat modul s funkcí main(), přeložený modul (*.obj) z části A a modul s funkcemi (viz. níže). Doplňte projekt o hlavičkové soubory ke každému modulu s funkcemi. Vytvořte datový typ struktura pro práci s kvadratickým trojčlenem. Pro každý člen budeme potřebovat jeho koeficienty – a,b,c. Ve funkci main() načtěte od uživatele celočíselné koeficienty a,b,c pro práci s kvadratickou funkcí i rovnicí. Pro tyto koeficienty vypište s využitím funkcí z části A i B souřadnice vrcholu kvadratické funkce, obor hodnot, zda je funkce sudá, zda je nad nebo pod osou x a nebo jí protíná, typ kvadratické rovnice (ryze kvadratická, bez absolutního člene, úplná), hodnotu diskriminantu kvadratické rovnice a pokud to bude mít smysl vypíšete její případné kořeny a její rozklad na kořenové činitele. Všechny parametry budete načítat ve funkci main() a do funkcí je budete posílat. Pokud má funkce hodnotu vracet, budete ji vypisovat ve funkci main(). Funkce v části B pro práci s koeficienty: •
funkce vypíše o jaký typ kvadratické rovnice se jedná - ryze kvadratická, bez absolutního člene, úplná
•
funkce, která bude vracet hodnotu diskriminantu.
•
funkce, která bude vypisovat počet kořenů kvadratické rovnice a vypíše i jejich případné hodnoty a rozklad na kořenové činitele.
Přeložený soubor (*.obj) s funkcemi části B i správně zapsaný hlavičkový soubor (s podmíněným překladem a komentářem funkcí) vložte do projektu A.
113
27 Datový typ union Obsah hodiny Seznámíme se s novým strukturovaným datovým typem – union.
Cíl hodiny Po této hodině budete schopni: ● ● ●
charakterizovat datový typ union popsat rozdíl mezi strukturou a unionem použít union v praktických úlohách
Klíčová slova union
Datový typ union je ve své definici velice podobný definici struktury, nepoužijeme však klíčové slovo struct ale klíčové slovo union.
Příklad definice unionu typedef union pokus { char retez[20]; int cislo; } POKUS; Definujeme takto nový datový typ union, který budeme označovat identifikátorem POKUS. Samotný union má také název – pokus, ale stejně jako u struktur toto pojmenování unionu nebude podstatné ani potřebné. Zatím tedy není téměř žádný rozdíl od struktur. Rozdíl nastane až při vytvoření proměnné tohoto datového typu. POKUS x; Tato proměnná x má také své části retez a cislo, stejně jako u struktur. Pro proměnnou typu union se vyalokuje jen velikost její největší položky a vždy jen jedna z položek může být použita.
114 Proměnná x bude v paměti velká, jako její největší položka a protože položka retez zabere v paměti 20B a položka cislo jen 6B, bude proměnná x vyalokovaná na 20B. x
20B Přístup k položkám union je opět shodný jako u struktur. x.cislo=456; Uloží na prvních 6B hodnotu 456. x 456
Pokud využijeme druhou položku unionu, přepíšeme si tu stávající. strcpy(x.retez, “PRG je super!“); x “Prg je super!“ Z příkladu vyplývá otázka, proč se tedy union používá, když z něj můžeme využít vždy jen jednu položku? Pokud se nad problémem zamyslíte, možná k vysvětlení dojdete sami. Union se používá, pokud chceme šetřit pamětí.
Příklad Představte si velkou databázi informací o zaměstnancích velké firmy. O každém zaměstnanci budeme potřebovat mnoho informací – jméno, příjmení, adresa, plat atd. Pro vedoucí oddělení budeme potřebovat informaci o telefonním čísle služebního mobilu, pro jejich podřízené budeme potřebovat číslo oddělení, na kterém pracují. Pro vedoucí ale nepotřebujeme číslo oddělení a pro zaměstnance oddělení nepotřebujeme telefonní číslo. V takovém případě si vytvoříme pro tyto informace datový typ union, který bude mít položku číslo oddělení a položku telefonní číslo.
115 typedef union { char telefon[10]; int oddeleni; } INFO; Položku tohoto typu pak vložíme do struktury pro jednoho zaměstnance. typedef struct { char jmeno[10]; int plat; INFO info; } ZAMESTNANEC; Každý zaměstnanec (proměnná tohoto typu) bude mít informaci o jméně, o platu a buď informaci o telefonním čísle, nebo o čísle oddělení. Ve větší databázi takto ušetříme dost paměti.
Shrnutí kapitoly Union je strukturovaný datový typ podobný struktuře. Na rozdíl od struktury se však pro něj alokuje jen velikost jeho největší položky. K položkám se přistupuje stejně jako u struktur, ale můžeme použít vždy jen jednu z nich, protože se vzájemně přepisují.
Kontrolní otázky a úkoly 1) Popište shodné a rozdílné vlastnosti struktur a unionů. 2) Jak pracujeme s položkami unionu? 3) Kde a proč můžeme využít union?
Otázky k zamyšlení 1) Promyslete, jak by vypadala dynamická proměnná datového typu union.
116
28 Datový typ enum Obsah hodiny Seznámíme se s výčtovým datovým typem – enum.
Cíl hodiny Po této hodině budete schopni: ● ●
charakterizovat datový typ enum použít enum v praktických úlohách
Klíčová slova enum
Název datového typu enum vznikl ze slova enumerace, které znamená výčet, vyjmenování či vypočítávání řady věcí. Při definici tohoto datového typu vyjmenujeme jednotlivé slovní hodnoty. Pořadí, v jakém je zapíšeme, udává jejich „očíslování“. Jde tedy o vypsání seznamu pojmenování celočíselných konstant.
Příklad typedef enum {red, green, blue} BARVY; Tímto seznamem názvů barev jsme těmto pojmenováním přiřadili celé číslo. Barva red má hodnotu 0, barva green má hodnotu 1 a barva blue má hodnotu 2. V podstatě jsme teď tyto tři celá pojmenovali – číslo nula se jmenuje red atd. Velice podobně pracujeme v matematice s hodnotou Ludolfova čísla, pojmenovanou také slovem Pí nebo označované symbolem π. Jde o konkrétní číslo, ale my jsme mu dali jméno. Při výpočtech na kalkulačce či v počítači pracujeme s jeho číselným vyjádřením a při zobrazení výsledku neočekáváme, že počítač či kalkulačka zobrazí případný výsledek 3,14159… jako Pi nebo symbol π. Jde jen o pojmenování čísla pro nás. Velice podobně se pracuje s proměnnými typu enum. Hodnoty v datovém typu enum jsou jen pojmenováním celočíselných konstant, nejde o
117 řetězce. Proto je nikdy nebudeme pomocí jejich pojmenování načítat od uživatele a nebudeme je slovně vypisovat na obrazovku pomocí proměnné (musíme si pomoct například příkazem switch, jako v následujícím příkladu). Vždy můžeme takto pracovat jen s celočíselnými hodnotami. typedef enum {red, green, blue} BARVY; int main() { BARVY moje, tvoje; moje = blue;
//totéž jako moje=2;
scanf(“%d“, &tvoje); switch(tvoje) { case 0: printf(“ RED“); break; case 1: printf(“ GREEN“); break; case 2: printf(“ BLUE“); }}
Celočíslené hodnoty v definici datového typu enum můžeme změnit.
Příklad typedef enum {red=10, green, blue=15} BARVY; Pak barva red je pojmenováním čísla 10, green je pojmenováním čísla 11 a barva blue je pojmenování čísla15.
Příklad typedef enum {red=10, green, blue=10} BARVY; V tomto případě je čílso 10 pojmenováno jako red i jako blue. Barva green je pojmenováním čísla 11. V následujícím příkladu si ukážeme praktické použití datového typu enum.
118
Příklad Vytvořte program s datovým typem pro osobu, která bude mít své jméno a pro muže bude obsahovat informaci o výši jeho platu a pro ženu barvu vlasů.
Řešení Vytvoříme si datový typ enum pro rozlišení, zda jde o muže nebo ženu. Vytvoříme si datový typ union, do jehož proměnné budeme ukládat informaci o platu nebo informaci o barvě vlasů. Oba datové typy následně použijeme v datovém typu TOSOBA. #include <stdio.h> #include #include <string.h> typedef enum {muz,zena}TPOHLAVI; typedef union { int plat; char vlasy[10]; }TINFO; typedef struct { char jmeno[12]; TPOHLAVI pohlavi; TINFO info; }TOSOBA; int main(int argc, char* argv[]) { TOSOBA osoba1, osoba2; int a; char x[10]; // nacteni osoba1 strcpy(osoba1.jmeno,"Pepik"); osoba1.pohlavi=muz; osoba1.info.plat=25000; // nacteni osoba2 printf("Zadej jmeno:"); gets(osoba2.jmeno); printf("\nZadej pohlavi (muz=0, zena=1) :"); scanf("%d",&osoba2.pohlavi); if (osoba2.pohlavi==muz)
119 { printf("\nZadej vysi platu :"); scanf("%d",&osoba2.info.plat); while(getchar()!='\n'); } else { while(getchar()!='\n'); printf("\nZadej barvu vlasu :"); gets(osoba2.info.vlasy); }
//vypis printf("\n\nVypis udaju o osobach\nPrvni :"); if (osoba1.pohlavi==muz) printf("\n %s je muz s platem %d\n",osoba1.jmeno,osoba1.info.plat); else printf("\n %s je zena a ma %s\ vlasy\n", osoba1.jmeno, osoba1.info.vlasy); printf("\n\nDruhy :"); if (osoba2.pohlavi==muz) printf("\n %s je muz s platem %d\n",osoba2.jmeno,osoba2.info.plat); else printf("\n %s je zena a ma %s\ vlasy\n", osoba2.jmeno, osoba2.info.vlasy ); getchar(); return 0; }
Shrnutí kapitoly Datový typ enum vytváříme pro pojmenování celočíselných konstant. Jde o název této konstanty, se kterým nepracujeme jako s řetězcem, nemůžeme jej načíst ani vypsat. Pracujeme vždy jen s celým číslem.
Kontrolní otázky a úkoly 1) Co je to datový typ enum? 2) Jak ovlivníte číselné hodnoty, která jsou datovým typem enum pojmenovány? 3) Definujte datový typ enum pro dny v týdnu od pondělí do neděle tak, aby pondělí začínalo číslem 1.
120
29 Datový typ enum a union - příklady Obsah hodiny Procvičíme práci se všemi zatím známými strukturovanými datovými typy.
Cíl hodiny Po této hodině budete schopni: ● ●
použít enum a union v praktických úlohách aplikovat získané informace při řešení příkladů
Příklad Projděte si kód následujících programu a pokuste se formulovat zadání tohoto programu. #include <stdlib.h> #include <string.h> #include <stdio.h> #include #include <math.h> #define MAX 3 typedef enum{zena=1, muz} POHLAVI; typedef union{ int vek; char email[40]; } SPECIALNI; typedef struct{ char jmeno[30]; char telefon[20]; long psc; POHLAVI pohlavi; SPECIALNI special; } KAMARADI;
121
KAMARADI nacti(); void vypis(KAMARADI *x); void vypisJednotlivce(KAMARADI *x, int i); void najdi(char hledane[30], KAMARADI *x); void setrid(KAMARADI *x, int *nejmladsi); // funkce slouzi zaroven pro nalezeni nejmladsi kamaradky void kamaradky(KAMARADI *x, int *pocet, float *prumer, int *lidi); int centrum(KAMARADI *x); int main(){ KAMARADI pole[MAX]; char jmeno[30]; int i=0, kamaradek, pocetlidi, nejmladsi; float prumernyvek; srand((long)time(NULL)); for( ;i<MAX;i++) pole[i] = nacti(); vypis(pole); printf("Zadejte jmeno hledaneho kamarada: "); while(getchar()!='\n'); gets(jmeno); najdi(jmeno, pole); setrid(pole, &nejmladsi); printf("\n\t ---Nejmladsi osoba ---\n"); vypisJednotlivce(pole, nejmladsi); vypis(pole); kamaradky(pole, &kamaradek, &prumernyvek, &pocetlidi); printf("\nCelkem bylo nalezeno %d kamarádek", kamaradek); printf(" a jejich prumerny vek je %f", prumernyvek); printf("\nCelkem bylo nalezeno %d lidi ze našeho kraje", pocetlidi); printf("\n%d kamaradu ma email na @centrm.cz", centrum(pole)); getch();
122 return(0);} KAMARADI nacti(){ KAMARADI x; while(getchar()!='\n'); printf("Zadejte jmeno osoby: "); gets(x.jmeno); printf("Zadejte telefonni cislo: "); gets(x.telefon); printf("Zadejte PSC: "); scanf("%ld", &x.psc); printf("Zadejte pohlavi zena(1) nebo muz(2): "); scanf("%d", &x.pohlavi); if(x.pohlavi == zena){ printf("Zadejte vek zeny: "); scanf("%d", &x.special.vek); } else{ printf("Zadejte emailovou adresu muze: "); while(getchar()!='\n'); gets(x.special.email); } printf("\n"); return x; } void vypis(KAMARADI *x) { int i=0; printf("\tVypis vsech kamaradu:\n\n"); for( ;i<MAX;i++){ printf("Jmeno: %s\n", x[i].jmeno); printf("Telefon: %s\n", x[i].telefon); printf("PSC: %ld\n", x[i].psc); if(x[i].pohlavi == zena){ printf("Vek zeny: %d", x[i].special.vek); } else{ printf("Email muze: %s", x[i].special.email); } printf("\n\n"); } }
123
void vypisJednotlivce(KAMARADI *x, int i) { printf("Jmeno: %s\n", x[i].jmeno); printf("Telefon: %s\n", x[i].telefon); printf("PSC: %ld\n", x[i].psc); if(x[i].pohlavi == zena){ printf("Vek zeny: %d", x[i].special.vek); } else{ printf("Email muze: %s", x[i].special.email); } printf("\n\n"); } void najdi(char hledane[30], KAMARADI *x) { int i=0; for( ;i<MAX;i++) if(stricmp(hledane, x[i].jmeno)==0){ printf("Jmeno: %s\n", x[i].jmeno); printf("Telefon: %s\n", x[i].telefon); printf("PSC: %ld\n", x[i].psc); if(x[i].pohlavi == zena){ printf("Vek zeny: %d", x[i].special.vek); } else{ printf("Email muze: %s", x[i].special.email); } } } void setrid(KAMARADI *x, int *nejmladsi) { int i, j, pomoc=999; KAMARADI pomocna; printf("\n\nSetrizuji podle PSC a hledam nejmladsi kamaradku\n"); for(j=0;j<MAX-1;j++) for(i=0;i<MAX-1;i++){ if(x[i].psc < x[i+1].psc){ pomocna = x[i]; x[i] = x[i+1]; x[i+1] = pomocna; } }
124
// Zde se uklada i nejmladsi zen for(j=0;j<MAX-1;j++) if(x[i].pohlavi == zena) if(x[i].special.vek < pomoc){ *nejmladsi = i;pomoc = x[i].special.vek; } printf("Serazeni podle PSC probehlo uspesne!\n"); } void kamaradky(KAMARADI *x, int *pocet, float *prumer, int *lidi){ int i=0, soucet=0; (*pocet) = 0; (*lidi) = 0; for( ;i<MAX;i++){ if(x[i].pohlavi == zena){ (*pocet)++; soucet += x[i].special.vek; } if((x[i].psc / 70000) == 1 && (x[i].psc % 70000)<10000) (*lidi)++; } *prumer = (float)soucet/(*pocet); } int centrum(KAMARADI *x){ int i=0, pocet=0; for(i=0;i<MAX;i++){ if(x[i].pohlavi == muz) if(strstr(x[i].special.email, "@centrm.cz")!=0) pocet++; } return pocet; }
Příklad Vytvořte program pro správu vozového parku firmy. 1) U každého vozidla nás zajímají tyto údaje: SPZ, rok výroby, druh vozidla – osobní, nákladní a autobusy. Pro osobní auta budeme mít údaj o barvě auta, pro nákladní auta budeme mít údaj o nosnosti a pro autobusy údaj, zda mají klimatizaci nebo ne.
125
2) Vytvořte vhodný datový typ pro vozidla firmy. 3) Vytvořte pole vozidel (velikost pole určete konstantou). 4) Načtěte údaje pro všechny vozidla pole. 5) Přehledně vypište všechny údaje z pole. 6) Vypište všechny SPZ vozidel, která byla vyrobena po roce 2000. 7) Podle požadavku uživatele vypište na obrazovku všechny SPZ osobních nebo nákladních nebo autobusů z pole.
Příklad Vytvořte program pro knihovnu s knihami a časopisy. 1) U každé knihy i časopisu nás zajímají tyto údaje: název, rok vydání. Pro knihy budeme mít jméno autora a pro časopis jeho číslo. 2) Vytvořte vhodný datový typ pro vozidla firmy. 3) Vytvořte pole knih a časopisů (velikost pole určete konstantou). 4) Načtěte údaje pro všechny prvky pole. 5) Přehledně vypište všechny údaje z pole. 6) Vypište všechny názvy knih a časopisů, které byly vydány po roce 2000. 7) Podle požadavku uživatele vypište na obrazovku všechny názvy knih nebo názvy časopisů.
126
30 Soubory Obsah hodiny Seznámíme se s dalším datovým typem – soubor, který nám umožní ukládat data mimo operační paměť.
Cíl hodiny Po této hodině budete schopni: ● ● ● ●
popsat soubor z pohledu programování vysvětlit rozdíl mezi textovým souborem a binárním souborem rozlišit ukládání dat do textových a binárních souborů popsat datový typ FILE
Klíčová slova Soubor, textový soubor, binární soubor, datový typ FILE
Se soubory pracujeme na počítači docela často. Jsou to data, která jsou uložena na některém paměťovém médiu (pevný disk, CD, flash disk apod.) mimo operační paměť. Dosud se veškerá data z našich programů ukládaly do proměnných do operační paměti a po ukončení programu jsme tyto data ztratili. Pokud se nad touto skutečností zamyslíte, zjistíte, že bez ukládání dat do souborů a jejích uchování do dalšího spuštění programu nemůže fungovat žádný komerční či profesionální program. Soubor chápeme jako uspořádanou posloupnost dat uloženou mimo operační paměť počítače, která přijímá nebo poskytuje tyto data. K souborům můžeme přistupovat dvěma způsoby, ve dvou režimech – textově a binárně. Tyto dva režimy se liší v principu zápisu dat nebo čtení dat ze souboru.
127
•
Textové soubory – obsahují jen text a jejich obsah je čitelný v textových editorech. Data se při ukládání převádějí na posloupnost znaků.
•
Binární soubory – pracují s binárními zápisy dat, nejsou určeny pro textové editory. Data se ukládají stejným způsobem jako v paměti počítače, jsou tedy bližší práci počítače.
Příklad Jak zapíšeme reálné číslo 12.56 do binárního a jak do textového souboru? Do textového souboru ukládáme znaky. Jeden znak zabere v paměti 1B, proto číslo 12.56 bude zapsáno znak po znaku ‘1‘, ‘2‘, ‘.‘, ‘ 5‘,‘ 6‘ na 5B. Každý Byte je jeden znak. Když pak otevřeme soubor v textovém editoru (např. v Poznámkovém bloku), bude se číst opět znak po znaku, po jednotlivých Byte, a pomocí kódu z ASCII tabulky budou jednotliví Byte převedeny na znaky. V textovém editoru pak uvidíme 12.56.
1
2
.
5
6
1B Do binárního souboru zapisujeme data jako do paměti počítače, podle velikosti datového typu. Číslo 12.56 je reálné číslo a datový typ float zabere v paměti nejčastěji 4B. Proto se toto číslo do binárního souboru zapíše na 4B v binárním tvaru. Když pak otevřeme binární soubor v textovém editoru, čte se po jednotlivých Byte, a proto se určitě nezobrazí číslo 12.56.
1 2 . 56 5B Soubory jsou běžným operačním systémem chápány jako n-tice bytů. Zda se jedná o soubor textový nebo binární, je jen vlastností režimu práce s ním. Pro oba typy souborů má jazyk C odlišnou sadu funkcí pro práci s obsahem souborů. Datový typ a základní práce se soubory je pro oba typy shodný. V hlavičkové soboru stdio.h je definován datový typ pro práci se soubory – FILE. Zapisuje se velkými písmeny, protože jde ve skutečnosti o
128 strukturu, která v sobě zahrnuje několik informací o souboru např. aktuální pozici v souboru, velikost, režim přístupu. Proměnnou soubor deklarujeme jako ukazatel na strukturu FILE. FILE *soubor; Vytvořili jsme tak proměnnou soubor, která je ukazatelem na strukturu FILE a může v našem programu reprezentovat soubor, která máme uložený na některém paměťovém mediu. Všimněte si, že u deklarace neřešíme typ souboru. Ten se určí až při otevření souboru.
Shrnutí kapitoly Soubory jsou data uložena mimo operační paměť. Textové soubory obsahují text. Data do něj ukládáme znak po znaku, po jednotlivých Byte. Binární soubory pracují s binárními zápisy dat, která se ukládají stejným způsobem jako v paměti počítače. Pro práci se soubory v jazyce C používáme datový typ FILE. Jde o strukturu popsanou v souboru stdio.h. Proměnné tohoto typu jsou pointery – ukazatele na soubory.
Kontrolní otázky a úkoly 1) Proč používáme soubory v programování? 2) Popište rozdíl mezi textovým a binárním souborem. 3) Na kolik Byte se uloží do textového a na kolik do binárního souboru: a) číslo 15 b) číslo 456.789 c) řetězec „ahoj“ K ověření vašich hodnot použijte počítač a operátor sizeof.
129
31 Práce se soubory Obsah hodiny Seznámíme se základy práce se soubory.
Cíl hodiny Po této hodině budete schopni: ● ● ●
otevřít soubor použít vhodný režim pro otevření souboru zavřít soubor a vysvětlit, proč je tato operace nutná
Klíčová slova fopen, režim otevření, fclose
Před samotnou prací se souborem musíme soubor otevřít. Otevření souboru provedeme pomocí příkazu fopen. soubor = fopen(jméno_souboru,režim_otevření) Příkaz fopen vrátí ukazatel na strukturu FILE, pokud se soubor podaří otevřít. V případě, že se soubor nepodaří otevřít (např. pokud chceme otevřít neexistující soubor, pokud chceme otevřít soubor pro zápis, který je read only) vrací příkaz fopen hodnotu NULL. Je tedy vhodné každé otevření souboru testovat a zabezpečit tak správnou práci se souborem.
Příklad FILE *soubor1, *soubor2; soubor1= fopen(“muj.txt“,“r“); soubor2= fopen(“D:\\slozka\\data.dat“,“wb“); Prvním příkazem jsme otevřeli soubor muj.txt, který se hledá v aktuální složce. Odkaz na něj se uloží do proměnné soubor1. Druhý parametr příkazu fopen je režim otevření, o kterém si řekneme více v dalším textu.
130 Druhý příklad hledá soubor na zadané cestě. Při zadávání cesty nesmím zapomenout zdvojit lomítka. Druhým parametrem fopen je režim otevření souboru, který je zadáván jako řetězec do uvozovek. Jde o kombinaci několika znaků. r - otevře soubor pro čtení w - otevře soubor pro zápis a smaže jeho obsah a - otevře soubor pro připsání textu na konec b - otevře v binárním režimu + - otevře soubor pro čtení i zápis Co se stane, pokud otevíraný soubor bude či nebude existovat, popisuje následující tabulka. Režim
Význam
Akce, pokud soubor už existuje
"r"
Otevření souboru pro čtení
čte od začátku
"w"
Otevření souboru pro zápis
"a"
Připojit k souboru
smaže obsah píše na konec obsahu souboru
"r+" "w+"
Otevření souboru pro čtení i zápis Vytvoření souboru pro čtení i zápis
Akce, pokud soubor neexistuje nepodaří se otevřít vytvoří nový vytvoří nový
čte od začátku
chyba
smaže obsah
vytvoří nový
Po otevření souboru se automaticky nastavuje ukazatel pozice (file pointer) tedy místa, odkud se bude ze souboru číst nebo kam se bude zapisovat. Pro režim w a r je tento pointer nastaven na začátek souboru, v režimu a je nastaven na pozici za posledním Bytem souboru. V úvodu kapitoly jsme si řekli, že je vhodné kontrolovat otevírání souborů, což si ukážeme v následujícím příkladu.
Příklad FILE *soubor1, *soubor2; if ((soubor1= fopen(“muj.txt“,“wb“)==NULL) { printf(“Chyba otevření”); return1; }
131 Pokud soubor muj.txt v aktuální složce existuje, bude jeho obsah smazán a odkaz na něj se uloží do proměnné soubor1. Soubor bude otevřen pro zápis v binárním režimu. Pokud soubor muj.txt v aktuální složce nebude, vytvoří se tento soubor, bude prázdný a odkaz na něj se uloží do proměnné soubor1. Soubor bude otevřen pro zápis v binárním režimu. Pokud soubor muj.txt nepůjde v aktuální složce vytvořit (např. z nedostatku paměti), uloží se do proměnné soubor1 hodnota NULL. Podmínka pak bude vyhodnocena jako true a vypíše se na obrazovku chybové hlášení a celý program skončí s návratovou hodnotou 1, která naznačuje systému, že došlo v programu k chybě. Uvědomte si, že příkazem fopen vznikne propojení mezi logickým a fyzickým souborem. Logický soubor je název naší proměnné typu FILE. Fyzický soubor je soubor uložený na paměťovém mediu mimo operační paměť. Díky tomuto propojení program ví, že použitím proměnné typu FILE pracujeme se souborem, který je uložen na konkrétní adrese mimo operační paměť. Po skončení práce se souborem musíme toto propojení ukončit, což provedeme příkazem pro uzavření souboru fclose. Uzavírání souboru musíme opět testovat, protože se může stát, že soubor nepůjde uzavřít např. z nedostatku paměti. Při nesprávném uzavření souboru vrací fclose hodnotu EOF, jinak vrací hodnotu 0. if ( fclose(soubor1)==EOF ) { printf(“Chyba uzavření”); return1; } Příkaz fclose ukončí propojení logického a fyzického souboru, odpojí datový proud a uloží veškerá ukládaná data z čtecího bufferu do souboru.
Shrnutí kapitoly Soubory musíme před využitím jejich dat otevřít příkazem fopen, ve kterém zadáváme název popř. cestu k souboru a režim otevření souboru. Režim otevření souborů je kombinace znaku w, r, a, b, +. Po ukončení práce se soubory musíme soubory ukončit příkazem fclose.
132
Kontrolní otázky a úkoly 1) 2) 3) 4)
Jak pracuje příkaz fopen? Jaké parametry má příkaz fopen? Vyjmenujte a popište režimy otevření souborů. Vysvětlete proč je potřeba zavírat soubory? Jak se soubory zavírají?
133
32 Funkce pro práci se soubory Obsah hodiny Seznámíme se s funkcemi pro práci se soubory. Naučíme se číst i zapisovat do binárních i textových souborů a seznámíme se s příkazy pro pohyb či zjištění polohy v souboru.
Cíl hodiny Po této hodině budete schopni: ● ●
použít vhodný příkaz pro čtení nebo zápis do souboru použít příkazy pro pohyb v souboru, popřípadě zjištění pozice v souboru
Klíčová slova getc, fscanf, fgets, fread, putc, fprintf, fputs, fwrite, feof, ftell, rewind, fseek
32.1 Čtení ze souboru V předchozích kapitolách jsme si vysvětlili rozdíl v zápisu dat do binárního a textového souboru. Budeme proto muset rozlišit čtení z těchto dvou typů souborů. V obou případech však musíme mít soubor otevřený pro čtení.
32.1.1 Čtení z textových souborů Pro čtení z textových souborů můžeme použít tři příkazy podle toho, co potřebujeme číst.
Příklad c= getc(soubor1); - načte znak do proměnné c, která musí být typu int - pokud znak nenačte, vrací EOF
Příklad fscanf(soubor1, ”%c”,&zn );
134 - přečte znak ze souboru soubor1 a uloží jej do proměnné zn - vrací počet přečtených znaků nebo EOF fscanf(soubor1, ”%d %f”,&cele,&real ); - přečte celé a reálné číslo do proměnných cele a real
Příklad fgets(retez, pocet, soubor1); - načte pocet-1 znaků ze souboru soubor1 do řetězce retez, pokud tam není konec řádku nebo konec souboru
32.1.2 Čtení z binárních souborů Pro čtení z binárních souborů používáme příkaz fread, který má čtyři parametry. fread (adresa_proměnné, sizeof(datový_typ), počet_položek, soubor);
Příklad float real; fread(&real, sizeof(float), 1, soubor1); Ze souboru soubor1 přečteme jednu položku velikosti float do proměnné real.
Příklad int pole[5]; fread(pole, sizeof(int), 5, soubor2); Ze souboru soubor2 přečteme pět položek velikosti int do pole pole. Nesmíme zapomenout, že identifikátor pole je adresou prvního prvku pole, proto v příkazu fread nepíšeme &.
Příklad typedef struct {char jmeno[15]; int zn; } ZAK; ZAK prvni; fread(&prvni, sizeof(ZAK), 1, soubor3); Ze souboru soubor3 přečteme jednu položku velikosti datového typu ZAK do proměnné prvni.
135
32.2 Zápis do souboru I pro zápis do souborů musíme rozlišit binární a textové soubory a opět musíme mít správně otevřený soubor – pro zápis.
32.2.1
Zápis do textových souborů
Pro zápis do textových souborů používáme jeden z tří následujících příkazů, které jsou odpovídající příkazům pro čtení.
Příklad putc(zn,soubor1); - zapíše znak z proměnné zn, která musí být typu int do souboru soubor1
Příklad fprintf(soubor2, ”%d %d”,i,j ); - zapíše dvě celá čísla z proměnných i a j do souboru soubor2
Příklad fputs(retez, soubor3); - zapíše řetězec retez do souboru soubor3 - neukládá koncovou nulu řetězce
32.2.2
Zápis do binárních souborů
Pro zápis z binárních souborů používáme příkaz fwrite, který má čtyři parametry stejně jako příkaz fread pro čtení. fwrite (adresa_proměnné, sizeof(datový_typ), počet_položek, soubor);
Příklad float real; fwrite(&real, sizeof(float), 1, soubor1); Do souboru soubor1 uložíme jednu položku velikosti float z proměnné real.
136
Příklad typedef struct {char jmeno[15]; int zn; } ZAK; ZAK prvni; //načtení proměnné prvni fwrite(&prvni, sizeof(ZAK), 1, soubor2); Do souboru soubor2 uložíme jednu položku velikosti datového typu ZAK z proměnné prvni.
32.3 Ostatní funkce pro práci se soubory feof(soubor) - informuje o tom, zda jsme dosáhli konce souboru. Pokud nejsme na konci souboru, vrací 0, jinak kladnou hodnotu Pokud čteme ze souboru, o kterém nevíme, kolik má položek, čteme tak dlouho, dokud nastane konec souboru. Můžeme pak například v cyklu využít této funkce. Někdy je však výhodnější testovat přímo funkce pro čtení, které nás mohou upozornit na to, že ze souboru už není co číst. ftell(soubor) -vrací hodnotu aktuální pozice kurzoru v souboru rewind(soubor) -posune kurzor na začátek souboru fseek (FILE *file, long posun, int odkud); - přesune kurzor na pozici vzdálenou posun bytů od pozice odkud, přičemž za parametr odkud je možné zvolit jednu z následujících konstant: SEEK_SET Začátek souboru SEEK_CUR Aktuální pozice v souboru SEEK_END Konec souboru
Příklad zápis
file-pointer se přesune na...
fseek(f, 0, SEEK_SET);
začátek souboru
fseek(f, 0, SEEK_END);
konec souboru
137 fseek(f, -10, SEEK_CUR); pozici o deset bytů zpět od aktuální pozice fseek(f, 10, SEEK_SET);
jedenáctý byte souboru
Shrnutí kapitoly Pro práci se soubory musíme rozlišovat v příkazech pro čtení či zápis zda jde o binární nebo o textový soubor. Samozřejmě nesmíme zapomenout vhodně soubor otevřít.
Kontrolní otázky a úkoly 1) Jaké příkazy používáme pro čtení a zápis do textových souborů? 2) Jaké příkazy používáme pro čtení a zápis do binárních souborů?
138
33 Soubory – řešené příklady Obsah hodiny Procvičíme si práci se soubory s využitím znalostí předchozích kapitol.
Cíl hodiny Po této hodině budete schopni: ● ●
aplikovat získané informace při řešení příkladů se soubory vhodně použít standardní funkce pro práci se soubory
Příklad Zapíšeme znak a do textového i do binárního souboru. Řešení int main(int argc, char *argv[]) { FILE *souborPIS; char zn = ´a´; if(souborPIS = fopen("muj.txt", "w")==NULL){ printf("Nelze otevrit soubor pro zapis"); return 1; } fprintf(souborPIS, "%d", zn); fclose(souborPIS);
//zapíšeme znak zn do souboru
if(souborPIS = fopen("muj.bin", "wb")==NULL){ printf("Nelze otevrit soubor pro zapis"); return 1; } fwrite(&zn, sizeof(char), 1, souborPIS); fclose(souborPIS); return 0; }
Příklad Zkopírujeme obsah jednoho souboru do druhého.
139
Řešení int main(int argc, char *argv[]) { FILE *vstup, *vystup; char zn; if((vstup = fopen("in.txt", "r")) == NULL){ printf("Nelze otevrit soubor pro cteni"); return 1; } if((vystup = fopen("out.txt", "w")) == NULL){ printf("Nelze otevrit soubor pro zapis"); return 1; } while((zn=fgetc(vstup)) != EOF) //dokud načtený znak není EOF */ { fputc(zn, vystup); } printf("Zkopirovano"); fclose(vstup); fclose(vystup); return 0; }
Příklad Vytvořte datový typ pro film, pro který nás bude zajímat jeho název a jeho hodnocení diváků. Načtěte od uživatele počet načítaných filmů a načtěte informace o filmech do souboru filmy.dat. Vypište filmy ze souboru na obrazovku. Řešení typedef struct { char nazev[30]; float hodnoceni; }FILM; int main(int argc, char* argv[])
//definice datového typu
140 {
FILE *soubor; FILM a; int i, pocet; printf("Zadej pocet filmu: "); scanf("%d",&pocet); if ((soubor=fopen("filmy.dat","wb"))==NULL) { printf("Chyba pri otevreni souboru "); getch(); return(-1); }
//otevření souboru
printf("\n"); for(i=0;i<pocet;i++) { while(getchar()!='\n'); //vyčištění čtecího bufferu printf("Zadej nazev filnu: "); gets(a.nazev); printf("Zadej hodnoceni filnu: "); scanf("%f",&a.hodnoceni); fwrite(&a, sizeof(FILM),1,soubor); //binární zápis struktury a do souboru soubor } if (fclose(soubor)==EOF) printf("Chyba pri otevreni souboru ");
//uzavření souboru
//vypsání souboru printf("\n"); if ((soubor=fopen("filmy.dat","rb"))==NULL) { printf("Chyba pri otevreni souboru "); getch(); return(-1); }
//otevření souboru
for(i=0;i<pocet;i++) { fread(&a, sizeof(FILM),1,soubor); //načtení jedné struktury printf("%d.film je %s ", i+1, a.nazev); printf("s hodnocenim %f \n", a.hodnoceni); }
141 if (fclose(soubor)==EOF) printf("Chyba pri uzavreni souboru "); getch(); return 0; }
//uzavření souboru
142
34 Soubory – neřešené příklady Obsah hodiny Procvičíme si práci se soubory s využitím znalostí předchozích kapitol.
Cíl hodiny Po této hodině budete schopni: ● ●
aplikovat získané informace při řešení příkladů se soubory vhodně použít standardní funkce pro práci se soubory
Příklad Každý bod zadání vypracujte samostatně, nevypracovávejte dva body najednou (např. ve stejném cyklu for) Vytvořte program, ve kterém: • načtete celé číslo a jeho deset násobků uložíte do souboru moje.dat • soubor moje.dat vypíšete. • soubor moje.dat vypíšete s pozicí čísla v souboru. • zjistíte a vypíšete počet sudých čísel v souboru moje.dat • uložíte všechna sudá čísla ze souboru moje.dat do souboru suda.dat a lichá čísla do souboru licha.dat • vypište soubory suda.dat i licha.dat na obrazovku • vypočtete a vypíšete součet všech čísel v souboru suda.dat
Příklad Vytvořte program, ve kterém budete načítat od uživatele a zapisovat do souboru jména kamarádů až do zadání slova konec. • Soubor přehledně vypište. • Zjistěte a vypište počet zadaných jmen.
143 • Zjistěte kolikrát bylo zadáno jméno David. • Zjistěte kolikrát bylo zadáno jméno uživatelem požadované.
Příklad Vytvořte program, který bude zapisovat do binárního souboru znaky tak dlouho, dokud nazadáne ENTER. Uzavřete soubor pro zápis a otevřete jej pro čtení. Zjistětě aktuální pozici v souboru, přesuňte se na začátek souboru a jeho obsah vytiskněte na obrazovku.
Příklad Vytvořte program, který bude zapisovat do binárního souboru celá čísla z 1D pole. Uzavřete soubor pro zápis a otevřete jej pro čtení a jeho obsah vytiskněte na obrazovku.
Příklad Vytvořte program, který bude zapisovat do binárního souboru reálná čísla z 2D pole. Uzavřete soubor pro zápis a otevřete jej pro čtení a jeho obsah vytiskněte na obrazovku.
Příklad Vytvořte program, který bude zapisovat do binárního souboru struktury. Uzavřete soubor pro zápis a otevřete jej pro čtení a jeho obsah vytiskněte na obrazovku. Obsah struktury: char jmeno[15], char prijemni[15], int vek, float vyska;
144
35 Shrnutí učiva Obsah hodiny Pomocí testu si ověříte znalosti, které jste získali v tomto výukovém modulu.
Cíl hodiny Po této hodině budete schopni: ● ●
analyzovat, kterou část učiva jste zvládli a kterou část učiva musíte ještě prostudovat posoudit vlastní úspěšnost v kurzu
Test Odpovězte na následující otázky. V každé otázce je jedna nebo více správných odpovědí. 1) Která deklarace vícerozměrného pole není správná a) int pole[ ][4]; b) int pole[4][ ]={{1,5,9,4},{3,5,7,1}}; c) int pole[ ][4]={{1,5,9,4},{3,5,7,1}}; d) int pole[4][4]; 2) Paramter argc ve funkci main určuje a) řetězce zadané z příkazové řádky b) všechny řetězce načtené v programu uživatelem c) počet řetězců zadaných na příkazové řádce d) počet řetězců, které může program načíst od uživatele. 3) Datový typ enum a) vyhradí pro svou proměnnou v paměti místo největší položky b) určuje seznam pojmenovaných konstant c) šetří paměť d) je výčtový datový typ
145 4) Hodnotu ve dvojrozměrném poli mat uloženou na 3. řádku a 4. sloupci získáme: a) *( mat+3)+4 b) mat[2][3] c) mat[3]+4 d) *(mat[2]+3) 5) Příkaz fopen a) provede konverzi textu do binárního zápisu b) propojí loogický s fyzickým souborem c) otevře daný soubor v zadaném režimu d) vrací EOF, pokud soubor nemůže otevřít 6) int *p[3]; je deklarace a) statické vícerozměrné pole b) pointer na pointer c) pole pointerů d) pointer na pole 7) Pro čtení ze souboru používáme příkazy a) fscanf b) fread c) fwrite d) fputs 8) Do řetězce char retez[10]; můžeme uložit řetězec a) “Ahoj mami“ b) “PRG je super!!!“ c) “Ahoj tati!“ d) “NE!“ 9) Pokud načítáme řetězec příkazem scanf a) nenačtou se bílé znaky. b) nenačtou se tečky.
146 c) nemůžeme kontrolovat meze řetězce d) načtou se všechny znaky zadaného řetězce až do zadání enteru. 10) Struktura je a) heterogenní datový typ b) datový typ podobný typu enum c) výčtový datový typ d) strukturovaný datový typ 11) Do funkce pracující se statickým polem int matice[5][7] posíláme toto pole jak parametr funkce takto: a) int *mat b) int mat[5][7] c) int mat[ ][7] d) int mat[5][ ] 12) Datový typ union a) šetří paměť b) je výčtový datový typ c) vyhradí pro svou proměnnou v paměti místo největší položky d) určuje seznam pojmenovaných konstant 13) U dynamického vícerozměrného pole pole pointerů a) neznáme před překladem ani počet řádků ani počet sloupců b) známe před překladem počet sloupců c) známe před překladem počet řádků d) nejsou všechny prvky uloženy za sebou 14) Soubory používáme a) pro ukládání dat do operační paměti b) pro ukládání dat mimo operační paměť c) jen pro čtení dat d) pro čtení i zápis dat
147 15) V části kódu: typedef struct {char spz[7]; int km; } VUZ; void main { VUZ x,*y; y=(VUZ*)malloc(sizeof(VUZ)); scanf(“%s”,x.spz); (*y).km=5000; }
je chybně část :
a) y=(VUZ*)malloc(sizeof(VUZ)); b) scanf(“%s”,x.spz); c) (*y).km=5000; d) žádná 16) int (*p)[3]; je deklarace a) pole pointerů b) pointer na pointer c) pointer na pole d) statické vícerozměrné pole 17) U dynamického vícerozměrného pole pointer na pole známe: a) známe před překladem počet řádků b) známe před překladem počet sloupců c) neznáme před překladem ani počet řádků ani počet sloupců d) jsou všechny prvky uloženy za sebou
Řešení 1.
a) b)
2.
c)
3.
b) d)
4.
b) d)
5.
b) c)
6.
c)
7.
a) b)
148 8.
a) d)
9.
a)
10.
a) d)
11.
c)
12.
a) c)
13.
c) d)
14.
b) d)
15.
d)
16.
c)
17.
b) d)