Prezentace se nahrává, počkejte prosím

Prezentace se nahrává, počkejte prosím

1 Programovací jazyk C Dana Nejedlová. 2 Použitá a doporučená literatura Pavel Herout: Učebnice jazyka C, 1. díl, nakladatelství Kopp, České Budějovice,

Podobné prezentace


Prezentace na téma: "1 Programovací jazyk C Dana Nejedlová. 2 Použitá a doporučená literatura Pavel Herout: Učebnice jazyka C, 1. díl, nakladatelství Kopp, České Budějovice,"— Transkript prezentace:

1 1 Programovací jazyk C Dana Nejedlová

2 2 Použitá a doporučená literatura Pavel Herout: Učebnice jazyka C, 1. díl, nakladatelství Kopp, České Budějovice, 2008, ISBN Pavel Herout: Učebnice jazyka C, 2. díl, nakladatelství Kopp, České Budějovice, 2007, ISBN Sylabus s další literaturou na STAGu –Programování I –Programování II Internet dle odkazů na snímcích

3 3 Historie 50. léta 20. století – asemblery 1957 – Fortran (IBM) 1960 – Algol 60 (mezinárodní vývoj) –Byl strojově nezávislý. Nevycházel z Fortranu. –Byl modernější, ale komerčně neúspěšný. –Vyšly z něj moderní jazyky. 60. léta 20. století – CPL (Combined Programming Language) –Ovlivněn jazykem Algol – BCPL (Basic/Bootstrap CPL) –systémové programování, překladače –Bootstrapping neboli self-hosting znamená překlad programu v určitém jazyce pomocí překladače psaného v tom samém jazyce.Bootstrappingself-hosting 1969 – B (Bell Labs) 1972 – C (Bell Labs) 1978 – K&R CK&R C –Brian Kernighan a Dennis Ritchie 1989 – Standardizace –ANSI C = C89, C99 = současný standardANSI

4 4 Bootstrapping Program v jazyce A →Překladač jazyka A pro stroj A →Vykonavatelný program pro stroj A Program, kterým je překladač jazyka A pro stroj B →Překladač jazyka A pro stroj A →Crosscompiler – Na stroji A vytváří programy vykonavatelné na stroji B. Program, kterým je překladač jazyka A pro stroj B →Crosscompiler→Překladač jazyka A pro stroj B

5 5 Charakteristika jazyka C univerzální, nízkoúrovňový, kompilovaný Určen původně pro tvorbu OS UNIX. Umožňuje tvořit efektivní SW pro HW s omezenými zdroji. strojově nezávislý (vyšší prog. jazyk) Je to jeden z nejrozšířenějších jazyků. Ovlivnil například jazyky Java, Perl, PHP. Program v jazyce C se skládá z funkcí. Měl původně jen 32 klíčových slov. Nemá standardní knihovny pro grafiku.

6 6 Vznik programu v jazyce C Zdrojový kód je textový soubor s příponou „.c“. Preprocesor –Odstraní komentáře. –Interpretuje direktivy pro preprocesor označené #. Kompilátor (překladač) –Vytvoří objektový (relativní) kód - soubor „*.obj“. –Adresy proměnných a funkcí ještě nejsou známy. Linker (sestavovací program) –Přidělí relativnímu kódu absolutní adresy. –Připojí k programu knihovny a vše další, na co objektový kód odkazuje. –Vytvoří spustitelný program. Debugger (ladící program) –Nalézá chyby, které nastávají při běhu programu.

7 7 Struktura programu v jazyce C #include int main(void) { printf("Hello, world!\n"); return 0; } Direktiva pro preprocesor pro připojení knihovny Knihovna obsahující funkce pro tisk výsledku na obrazovku Funkce z knihovny stdio.h pro tisk výsledku na obrazovku Textové hodnoty musí být uzavřeny do uvozovek – "". Program v jazyce C se skládá pouze z funkcí a hlavní funkce main() musí vrátit hodnotu. Nový řádek je vyjádřen pomocí "\n". Tabelátor je vyjádřen pomocí "\t". Příkazy se oddělují středníkem. Každý program musí obsahovat právě jednu hlavní funkci main(). Funkce vrací hodnotu typu int a nemá vstupní argumenty, což je indikováno tím, že v závorkách za main je void, nebo by mohly byt také prázdné.

8 8 Komentáře Text ve zdrojovém kódu ignorovaný překladačem. /* Víceřádkový komentář */ // Jednořádkový komentář zavedený v C99 Komentáře popisují činnost programu –Program by měl být srozumitelný nejen pro překladač ale především pro lidi! –Podrobný popis může být využit k automatické tvorbě dokumentace, viz například program Doxygen.Doxygen –Komentáře je dobré psát souběžně s psaním kódu. –Doporučuje se dokonce začít nejdřív s psaním algoritmu v přirozeném jazyce (pseudocode) a potom jej přeměňovat na komentáře a dopisovat k němu vlastní kód, viz kniha Code Complete: Pseudocode for Pros.Pseudocode for Pros –Podrobnosti viz kniha Code Complete: Commenting Techniques.Commenting Techniques Komentáře mohou obsahovat znaky s diakritikou. Komentáře nelze vnořovat. –To se může stát v situaci, kdy chceme pomocí zakomentování vypnout část programu s komentáři. Správné řešení je pomocí příkazu if nebo pomocí podmíněného překladu.Správné řešenípodmíněného překladu

9 9 Identifikátory Označují objekty, které program používá. –datové typy, –proměnné, konstanty, metody, třídy, –funkce, podprogramy, moduly, –návěští (labels). Standardní –Jsou definované standardními knihovnami jazyka. Definované programátorem –Musí být jiné než standardní identifikátory a než klíčová slova.

10 10 Závažnější zásady formátování zdrojového textu programu Jazyk C rozlišuje malá a velká písmena (case sensitive). –prom, Prom, PROM jsou tři různé identifikátory. Z důvodu možnosti lidských chyb je lepší to nevyužívat. Klíčová slova (např. if, while, for) musí být psána malými písmeny. Podtržítko „_“ a číslice nesmí být na začátku identifikátoru. –_prom je vyhrazeno pro systémový identifikátor. ANSI C rozeznává jen prvních 31 znaků v identifikátoru. –Externí identifikátory (sdílené proměnné a funkce) musí být rozlišitelné podle prvních 6 znaků.Externí identifikátory Program se píše jen pomocí znaků s kódem 0 až 126 v ASCII tabulce (s výjimkou komentářů). ASCII –Identifikátory nesmí obsahovat znaky s diakritikou.

11 11 Zásady pro psaní identifikátorů Nepoužívat podobné, např. systst a sysstst. Označovat slovy podle významu objektů, které reprezentují, např. plat a ne p1. Konzistentnost ve stylu –view_file, view_menu, view_window –ViewFile, ViewMenu, ViewWindow Používání velkých písmen se v C spíše nedoporučuje z důvodu snadného vzniku chyb. Konvenční názvy –i, j, k – indexy, řídící proměnné cyklů –c, ch – znaky –m, n – čítače (např. m = m + 1, ++n) –f, r – reálná čísla –p_ – začátek identifikátoru pointeru (např. p_plat) –s – řetězce znaků (od slova „strings“)

12 12 Jednoduché datové typy ModifikátoryAplikovatelnost shortint longint, double signedchar unsignedchar, int NázevVýznam charznak textu intcelé číslo floatreálné číslo doublereálné číslo velké Datový typ je vlastnost dat určující rozsah hodnot a výčet operací, které je možné s daty v programu provádět. Boolean – TRUE / FALSE –Implicitně pomocí typu int: 0 = FALSE, jinak = TRUE –Explicitní možnost reprezentace je ve verzi C99.

13 13 Proměnné Proměnná je symbol zastupující hodnotu. Všechny proměnné musí být před použitím deklarovány. Příklady deklarace proměnných –int cislo; Proměnná cislo je typu int. –short int cislo; Proměnná cislo je typu short int. –short cislo; Proměnná cislo je typu short int. Slovo int může být vynecháno. To, kolik bytů datové typy zabírají, závisí na konkrétní implementaci jazyka C. –Podrobnosti je tedy nutné hledat v nápovědě pro dané vývojové prostředí. –Operátor sizeof určí velikost typu v bytech.

14 14 Konstanty Zapisují se jako –symbolické konstanty (konstanty pojmenované svými identifikátory),symbolické konstanty –literály (hodnoty napsané v programu přímo neboli nepojmenované konstanty).literály Na rozdíl od proměnné se tato hodnota během chodu programu nemění, tedy nelze do ní přiřazovat. Příklady deklarace a definice symbolických konstantdeklarace a definice symbolických konstant int const MAX_TEPLOTA = 50; /* Deklarace konstanty MAX_TEPLOTA s hodnotou 50. */ const int MIN_TEPLOTA = -70; /* Je možné i obrácené pořadí deklarace. */ const int ROZSAH_TEPLOT = MAX_TEPLOTA - MIN_TEPLOTA; /* Nové konstanty mohou využívat již dříve definovaných konstant. /* #define MAX_TEPLOTA 50 /* Definice konstanty MAX_TEPLOTA makrem pro preprocesor /* #define MIN_TEPLOTA -70 #define ROZSAH_TEPLOT (MAX_TEPLOTA - MIN_TEPLOTA) –Symbolické řetězcové konstantySymbolické řetězcové konstanty Štábní kultura dle knihy Code Complete: Naming Constants.Naming Constants –Konstanty se pojmenovávají velkými písmeny. int const SEST = 6; /* Špatné */ Kdyby SEST bylo něco jiného než 6, bylo by to hloupé. int const KAPACITA = 6; /* Dobré */ Pojmenováváme abstraktní entitu reprezentovanou konstantou spíše než hodnotu samotnou. –Jediné literály, které by se měly objevit v těle programu, jsou 0 a 1.

15 15 Zápis konstant jako literálů 255 decimálně 0377 oktálně (předpona „0“) 0XFF nebo 0xff hexadecimálně (předpona „0x“ nebo „0X“) 'A' znak A zabírající 1 byte typu int "A" řetězec A zabírající 2 byty typu string (null terminated string)null terminated string '\101' = 'A' (Znak A má v ASCII tabulce kód 65 = 101 oktálně.) '\x41' = 'A' (65 dekadicky = 41 hexadecimálně.) 1.23 typ double = 0.123e1 = 12.3e-1 = 12.3E-1 = 123e-2

16 16 Implicitní typ literálů Konstanty, které nejsou deklarované nebo definované, tedy literály, mají automaticky určený (tj. implicitní) typ. Typ se automaticky určuje tak, že se vybere první z následujícího seznamu, do kterého se konstanta vejde. –int, unsigned int, long, unsigned long, double Typ lze vynutit příponou L, U, LU, F za číslem. –Například 1024L je konstanta 1024 typu long int. –Užití: Program je odladěn na stroji, kde int zabírá 32 bitů a potom je puštěn na stroji, kde int je 16 bitů.Užití –Při výpočtech je výsledku přiřazen typ zúčastněného operandu, který zabírá nejvíc bitů. –Například: k * 1024, kde k je proměnná typu int, má výsledek typu int, ale k * 1024L má výsledek typu long int. Pro k >= 32 dojde jinak k přetečení.

17 17 Operátory Jazyk C má velké množství operátorů. Vyhodnocování operátorů –Precedence = pořadí operátorů dané tabulkouPrecedence –Asociativita operátorů (levá nebo pravá)Asociativita Rozhoduje, když mají operátory stejnou precedenci. Operátor „=“ má pravou asociativitu, což znamená, že operand se se váže k operátoru „=“ na jeho pravé straně. Díky pravé asociativitě operátoru „=“ je možný příkaz a=b=c=0;. Příkaz se vyhodnocuje od konce. Výraz 4 / 3 * pi se vyhodnocuje od začátku, protože operátory „/“ a „*“ mají levou asociativitu. –Pořadí vyhodnocování lze ovlivnit závorkami (). –Výsledné datové typy závisí na operandech. Výsledek 1 / 3 je typu int a jeho hodnota je 0. Výsledek 1.0 / 3 nebo 1 / 3.0 je typu double =

18 18 Operátory – precedence Když je x záporné nebo je x dělitelné 3 a zároveň větší než 50 tak vytiskni „x je záporné nebo dělitelné 3 a větší než 50.“ #include main(void) { int x; scanf("%d", &x); if (x 50) printf("x je záporné nebo dělitelné 3 a větší než 50.\n"); else printf("x je kladné a zároveň není dělitelné 3 nebo je menší nebo rovno 50.\n"); return 0; }

19 19 Operátory Operátory je možné kombinovat s operandy velkým množstvím způsobů. Chybné užití operátorů je proto často syntakticky správné, tudíž programátor musí sám hledat svoji chybu. Příklad: if (x = 3) … Správně mělo být if (x == 3)Příklad –Záměrem bylo otestovat, jestli x se rovná 3. –Ve skutečnosti se do x přiřadí hodnota 3. –Výsledkem závorky () je 3. Přiřazení je výraz s výslednou hodnotou z pravé strany. –Výsledkem ale mělo být TRUE nebo FALSE. Za klíčovým slovem if musí být hodnota typu boolean.boolean V C platí, že hodnota rovná nule je FALSE a jinak je TRUE. –3 není rovno 0, takže je test roven vždy TRUE. –Prevence této chyby: if (3 == x). if (3 = x) je syntaktická chyba.

20 20 Operátory přiřazení x += y znamená x = x + y (také -, *, /, %) Inkrement a dekrement – ++x znamená x = x+1 (také - pro dekrement) –Přeložený kód pro ++x je většinou efektivnější než pro x = x + 1. Totéž platí pro varianty +=. –Prefix ++x a postfix x++ verze: int x, y; x = 0; y = ++x; potom x = 1, y = 1. int x, y; x = 0; y = x++; potom x = 1, y = 0. –Použití více než jednoho výskytu proměnné s in/dekrementem v příkazu nemá definované chování, to znamená takto nepoužívat! int x = 0; x = x++; potom x = 0 nebo 1. int x = 0; int y = x++ + x++; potom y = 0, 1 nebo 2.

21 21 Příkaz a výraz Příkaz (statement)statement –nejmenší kompilovatelná jednotka imperativního programovacího jazykaimperativního –jednoduchý Skládá se z výrazů. –složený Skládá se z příkazů a výrazů. Výraz (expression)expression –kombinace operandů (literálů a pojmenovaných konstant a proměnných), operátorů a funkcí, která je vyhodnocena jako hodnotaoperandůliterálůoperátorů –V matematice výraz reprezentuje hodnotu. x = y * 2; a = b = c = 0; je příkaz, protože končí středníkem. –Přiřazení c = 0 je výraz vyhodnocený jako 0. Výraz je možné přiřadit. VýrazPříkaz

22 22 L-hodnota a R-hodnota L-hodnota (lvalue) –Může stát na levé straně přiřazovacího příkazu, ale může být i na pravé. –Typicky je to proměnná. Má adresu v paměti. –x = y * 2; x je L-hodnota a je zde na levé straně příkazu. y je L-hodnota, protože je to stejně jako x proměnná, která je zde implicitně převedena na R-hodnotu. 2 je R-hodnota, protože nesmí stát na levé straně. y * 2 je R-hodnota. R-hodnota (rvalue) –Může stát jen na pravé straně přiřazovacího příkazu. –Debugger: …lvalue required… konstanty ++3; výrazy ++(x + y); (y * 2) = x; y * 2 = x;

23 23 Typová konverze převod proměnné určitého typu na typ jiný v průběhu výpočtu Implicitní (automatická, programátorem neovlivnitelná) –Kdykoliv je ve výrazu kombinováno více datových typů, tyto typy se automaticky převedou na jediný typ. –Při kombinaci celých a reálných datových typů může dojít ke ztrátě přesnosti. –Výhoda Překladač nehlásí syntaktickou chybu, tudíž programátor nemusí provádět explicitní typovou konverzi, což umožňuje psát krátké programy výhodné zvláště v systémovém nízkoúrovňovém programování. –Nevýhoda Překladač nehlásí syntaktickou chybu, tudíž, dostaly-li se různé datové typy do výrazu omylem, programátor se bude divit, proč je výsledek jiný, než očekává, a musí najít chybu sám. Explicitní (určená programátorem) –Používá se nejčastěji v případě pointerů a při neshodě skutečných a formálních parametrů funkce.skutečných a formálních parametrů funkce –Používá se hlavně pro konverze vedoucí ke ztrátě přesnosti, aby bylo jasné, že to programátor opravdu chtěl. –Pokud se neprovede, překladač může vydat varovné hlášení, čemuž je lepší se vyhnout, protože varovná hlášení většinou indikují chyby a nedomyšlenosti. –Pište programy tak, aby překladač nevydával varovná hlášení.

24 24 Implicitní typová konverze char c; /* Proměnná c je typu char. */ int i; /* Proměnná i je typu int. */ double r; /* Proměnná r je typu double. */ c = 65; /* Proměnná c má hodnotu znaku s kódem 65 = 'A'. */ c++; /* Proměnná c má hodnotu znaku s kódem 66 = 'B'. */ c = c + '1'; /* Proměnná c má hodnotu znaku s kódem 115 = 's' = je kód znaku '1'. */ i = 'A'; /* Proměnná i má hodnotu kódu znaku 'A' = 65. */ i = 'A' + 1; /* Proměnná i má hodnotu kódu znaku 'B' = 66. */ i = 3.8; /* Proměnná i má hodnotu 3. (0.8 bylo odříznuto.) */ r = 5; /* Proměnná r má hodnotu 5.0. (Konstanta 5 byla z implicitního typu int zkonvertována na typ z levé strany double.) */implicitního i = r * c; 1.Hodnota z proměnné c se zkonvertuje na datový typ int. 2.Výsledná hodnota se z typu int zkonvertuje na typ double. 3.Výsledek výrazu r * c je typu double a z něj se zkonvertuje do typu proměnné i z levé strany int. Podrobná pravidla jsou v knize Pavla Herouta Učebnice jazyka C, 1. díl, páté vydání, KOPP České Budějovice 2008 na straně 87 nebo na stránce

25 25 Explicitní typová konverze 1. příklad Explicitní typová konverze má formu (typ) výraz. –Hodnota výrazu je v čase překladu konvertována na požadovaný typ. –(typ) proměnná není l-hodnota, tudíž do ní nelze přiřazovat.l-hodnota (float) i = 3 je syntaktická chyba. #include main(void) { int i = 10; /* Takto je možné jedním příkazem deklarovat a zároveň inicializovat proměnnou. */ double f; f = sqrt((double) i); printf("%f\n", f); return 0; } Výrazem (double) i je hodnota z proměnné i explicitně převedena na typ double, protože funkce sqrt() z knihovny math.h je definována tak, že její vstup má být typu double (tj. její formální parametr je typu double). Konverze ovlivnila jenom proměnnou f, nikoliv i.formální parametr

26 26 Explicitní typová konverze 2. příklad #include main(void) { int soucet_znamek = 18; int pocet_zaku = 10; float prumer_znamek; prumer_znamek = soucet_znamek / pocet_zaku; printf("%f\n", prumer_znamek); return 0; } Výraz soucet_znamek / pocet_zaku má výsledek typu int o hodnotě 1 (= 1.8 po odříznutí 0.8), protože obě jeho proměnné jsou stejného typu, a proto při dělení nedochází k implicitní typové konverzi. Při přiřazení výsledku do proměnné prumer_znamek dojde k implicitní typové konverzi z typu int do typu float tak, že výsledná hodnota 1 se změní na hodnotu 1.0. Řešením je explicitní typová konverze: –prumer_znamek = (float) soucet_znamek / (float) pocet_zaku; Stačí explicitně konvertovat jen libovolnou jednu z proměnných, protože typová konverze má přednost před dělením, viz operátor (type):Stačí explicitně konvertovat jen libovolnou jednu z proměnnýchtypová konverze má přednost před dělením, viz operátor (type) –prumer_znamek = (float) soucet_znamek / pocet_zaku; Následující příklad má za výsledek 1.0, protože došlo nejdřív k dělení: –prumer_znamek = (float) (soucet_znamek / pocet_zaku); Proč rovnou nedeklarovat zúčastněné proměnné s potřebným typem? –Datový typ by měl být logický např. proměnná s počtem něčeho by měla být int a ne float. –Původní datový typ zabírá méně bitů (je úspornější).Původní datový typ zabírá méně bitů (je úspornější).

27 27 Explicitní typová konverze 3. příklad #include main(void) { double pi = , vysledek; vysledek = 4 / 3 * pi; printf("%f\n", vysledek); return 0; } Výsledek bude , ale my jsme chtěli 4/3 pí. Nejdříve se spočítá výraz 4 / 3 a ten má výsledek typu int o hodnotě 1. Výsledek 1 je při násobení s pi konvertován na double, ale to už nepomůže. 1. řešení –Využívá implicitní typovou konverzi a pravidla pro asociativitu operátorů.asociativitu –vysledek = pi * 4 / 3; /* Také je možný příkaz vysledek = 4 * pi / 3; Nejdříve se spočítá výraz pi * 4 (nebo 4 * pi) a ten je typu double. Konstanta 4 byla přitom konvertována implicitně, stejně jako bude následně konvertována konstanta řešení –Využívá explicitní typovou konverzi. –vysledek = (double) 4 / 3 * pi; Konstanta 4 byla konvertována explicitně, konstanta 3 byla konvertována implicitně, její explicitní konverze by také byla možná. 3. řešení –Je nejlepší. –Místo typové konverze napíšeme rovnou správný typ. –vysledek = 4.0 / 3.0 * pi;

28 28 Terminálový vstup a výstup Terminál je historický termín pro zařízení určené ke vstupu (pomocí klávesnice) a výstupu (na obrazovku) dat. #include –V knihovně stdio.h jsou všechny funkce pro vstupní a výstupní operace. Vstup a výstup jednoho znaku –Funkce getchar() a putchar() Formátovaný vstup a výstup –Funkce scanf() a printf()

29 29 Vstup a výstup znaku #include int main(void) /* Možno také main () */ { int c; /* c nesmí být typu char. */nesmí c = getchar(); /* Do c se uloží ASCII kód znaku. */ putchar(c); /* Znak s kódem v c se vytiskne. */ return 0; } Po spuštění programu napíšeme posloupnost znaků a potom stiskneme klávesu Enter. Do c se uloží první znak z posloupnosti.

30 30 Formátovaný vstup a výstup #include int main(void) { int i, j; scanf("%d%d", &i, &j); printf("%d + %d = %d.\n", i, j, i + j); return 0; } Zadáme 2 mezera/tabelátor/Enter 3 Enter. Program napíše = 5. %d určuje datový typ vstupu/výstupu.datový typ vstupu/výstupu Před proměnnými i a j ve scanf() je nutné napsat &.nutné \n indikuje přesun na nový řádek a je to jedna z escape sekvencí.escape sekvencí Pro výpis znaku „%“ se tento znak zdvojí: %. –Stejně tak zpětné lomítko \\. Řídící řetězec formátu

31 31 Řídící struktury Řídící struktury jsou podstatné pro zápis algoritmů, řídí běh programu. Představují je podmínky, cykly a skoky. Podmínky a cykly mohou být do sebe vnořeny. Každé vnoření indikujte odsazením řádku programu 2 mezerami doprava. Každý elementární nestrukturovaný příkaz pište (až na výjimky, kdy příkazy jsou krátké a patří k sobě) na samostatný řádek. Dodržujte další zásady formátování ukázané na následujících příkladech. Správné formátování je nutné pro uznání programu pro zápočet nebo klasifikaci!!!

32 32 Podmínka: příkaz if – else if (i > 3) j = 5; if (i > 3) j = 5; else j = 1; if (i > 3) { i = 7; j = 5; } else { i = 1; j = 1; } i > 3 j = 5 ano ne i > 3 j = 5 ano ne j = 1 i > 3 i = 7 j = 5 ano ne i = 1 j = 1

33 33 Příkaz if if (i > 3) j = 5; Syntaktická pravidla –if a další klíčová slova musí být psána malými písmeny. –Po if je podmínka (výraz s hodnotou typu boolean) a ta musí být v závorce.boolean –Když podmínka platí nebo je výraz v závorce vyhodnocen jako číslo jiné než 0, tak se vykoná následující příkaz. Štábní kultura –Její nedodržení nevadí překladači, ale vadí programátorům. –Příkaz if (i > 3) Mezi if a závorkou s podmínkou musí být jedna mezera. Operátor > (a všechny ostatní možné operátory) musí být obklopen jednou mezerou po každé straně. –Příkaz j = 5; musí být na dalším řádku, musí být odsazen 2 mezerami od svého if. Operátor = musí být obklopen jednou mezerou po každé straně.

34 34 Příkaz if – else if (i > 3) { i = 7; j = 5; } /* Po této závorce nesmí následovat středník. */ else { i = 1; j = 1; } /* Po této závorce může následovat středník, ale není nutný. */ Syntaktická pravidla –Když podmínka platí nebo je výraz v závorce vyhodnocen jako číslo jiné než 0, tak se vykoná příkaz po if (), jinak se vykoná příkaz po else. –Má-li se po if nebo po else vykonat víc než jeden příkaz, musí být tato skupina příkazů uzavřena ve složené závorce {}. –Obsah závorky tvoří takzvaný blok.blok –Středník musí být na konci každého příkazu (i toho posledního). Tak se příkaz pozná od výrazu. –Středník nesmí být mezi } a else. Štábní kultura –Příkaz j = 1; musí být na dalším řádku, musí být odsazen 2 mezerami od svého else. –Závorka { je na konci řádku s if nebo else a je před ní jedna mezera. –Závorka } je na samostatném řádku po posledním příkazu v bloku, je odsazena stejně daleko od začátku řádku jako její if nebo else.

35 35 Vnořený příkaz if if (x == 5) { if (y == 6) z = 7; } Syntaktická pravidla –Nezaměňujte operátor pro test rovnosti == za operátor pro přiřazení = !!! –else se vztahuje vždy k nejbližšímu if. Štábní kultura –Vnořený příkaz if (y == 6) musí být odsazen. –Závorka {}, ve které je vnořené if, není syntakticky nutná, ale zvyšuje přehlednost. x = 5 ano ne z = 7 y = 6 ano ne

36 36 Štábní kultura zápisu podmínek Nemusíme zapisovat porovnání výrazu s hodnotou 0, protože výraz má sám o sobě hodnotu TRUE nebo FALSE. –if (výraz != 0) se často píše jako if (výraz). výraz != 0 → výraz <> 0 → Výraz má hodnotu TRUE. –if (výraz == 0) se často píše jako if (!výraz). výraz == 0 → výraz = 0 → Výraz má hodnotu FALSE. Při testování na nulu porovnávat s konstantou 0 zapsanou tak, aby byla stejného typu, jako je typ porovnávaného výrazu. –if (c == 0) neboli if (!c) pište jako if (c == '\0'). Test, zda má znak c kód 0.znak –if (p_i == 0) neboli if (!p_i) pište jako if (p_i == NULL). Test, zda je pointer p_i nulový. Obsahuje-li testovaný výraz operátor přiřazení a je testován na nulu, je vhodné ji explicitně uvést. –if (c = getchar()) pište jako if ((c = getchar()) != 0). Výraz s operátorem přiřazení má výslednou hodnotu z pravé strany. Testy uvádějte v aserci (kladné znění tvrzení) a ne v negaci. –Viz De Morganova pravidla.De Morganova pravidla –if (!(c == '\0' || c == ' ' || c == '1')) /* Nevhodné. */ Není pravda, že platí c == '\0' nebo c == ' ' nebo c == '1'. –if (c != '\0' && c != ' ' && c != '1') /* Lepší. */ Neplatí c == '\0' ani c == ' ' ani c == '1'.

37 37 Booleovské výrazy Výrazy s hodnotou typu Boolean v závorce po příkazu if Jsou reprezentovány pomocí typu int. –0 = FALSE, jinak = TRUE. Jazyk C vyhodnocuje logický součin (a) && a logický součet (nebo) || zkráceně (short circuit).short circuit –Argumenty jsou vyhodnocovány zleva doprava a jakmile je možno určit konečný výsledek, vyhodnocování okamžitě končí. –Výhody: stručnost zápisu, rychlost vykonání kódu if (y != 0 && x / y < z) –Testujeme, zda x / y < z, ale přitom může dojít k běhové chybě dělení nulou, takže to nejdřív musíme otestovat. Když je y rovno 0 tak se x / y neprovede. –Nevýhody: Neprovede se přiřazovací příkaz v testu. if (x < 0 && (c = getchar()) != '\0') –Chtěli jsme, aby se něco provedlo, když x < 0 a načtený znak je jiný než '\0', ale když x není záporné, tak se znak nenačte. Když si neuvědomíme, že vyhodnocování je zkrácené, tak se můžeme divit, proč se někdy znak nenačte. –Když to právě takto chceme, je vhodné to okomentovat, aby bylo jasné, že to není nedopatření.

38 38 Booleovské výrazy na příkladu Program čte znak a, je-li to velké písmeno, vypíše jeho ordinální číslo (kód v ASCII tabulce). Toto je jeho 1. verze: #include int main(void) { int c; c = getchar(); if (c >= 'A' && c <= 'Z') printf("%d\n", c); /* Viz formátová specifikace %d. */Viz formátová specifikace %d. return 0; } Výraz c >= 'A' && c <= 'Z' je založen na faktu, že v ASCII tabulce tvoří velká (i malá) písmena anglické abecedy, stejně tak číslice 0 až 9, souvislou řadu.ASCII

39 39 Booleovské výrazy na příkladu Program čte znak a, je-li to velké písmeno, vypíše jeho ordinální číslo (kód v ASCII tabulce). Toto je jeho 2. verze, která je více v céčkovském stylu: #include int main(void) { int c; if ((c = getchar()) >= 'A' && c <= 'Z') printf("%d\n", c); return 0; } Přiřazení c = getchar() je výraz a výraz může být součástí dalšího výrazu. Jak by to fungovalo, kdybychom neobalili výraz c = getchar() závorkou: –if (c = getchar() >= 'A' && c <= 'Z') –getchar() přečte znak a porovná ho se znakem 'A'. –Je-li výsledek testu FALSE, test končí a do proměnné c je přiřazena hodnota 0. –Je-li výsledek testu TRUE (načetlo se 'A'), porovnává se stále ještě nedefinovaná hodnota proměnné c se znakem 'Z'. –Výsledkem porovnání je opět FALSE nebo TRUE reprezentované hodnotami 0 nebo 1. –Obě logické hodnoty se vynásobí logickým součinem && a výsledkem je opět hodnota 0 nebo 1, která se přiřadí do proměnné c. –Závorka byla nutná díky pravidlům pro pořadí operátorů.pravidlům pro pořadí operátorů

40 40 Podmíněný výraz – ternární operátor Ternární operátor je operátor, který má 3 argumenty. Má stejný význam jako příkaz if – else. –Příkaz if – else je velice častý a proto byl zaveden konstrukt, který jej zestručňuje.konstrukt –V některých případech je výraz strukturovaným příkazem nenahraditelný.některých případech j = (i > 3) ? 5 : 1; /* Viz 2. příklad zde. */zde k = (i > j) ? i : j; /* k bude maximum z i a j. */ (i == 1) ? i++ : j++; /* Inkrementuje i nebo j. */ c = (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; –konverze znaku na malé písmeno –Pokud je v proměnné c velké písmeno, zvýší se jeho kód tak, aby odpovídal malému písmenu. –Kód všech velkých a malých písmen anglické abecedy se liší o stejnou hodnotu rovnou kódu znaku 'a' - kód znaku 'A', protože velká a malá písmena tvoří v ASCII tabulce dva souvislé úseky. –Je lepší tuto hodnotu zjišťovat jako 'a' - 'A' místo uvedení konstanty 32, protože ASCII tabulka závisí na operačním systému. Chceme nezávislost programu na platformě. Štábní kultura –Závorky kolem podmínky nejsou nutné, ale uvádějí se pro zvýšení čitelnosti. –Příkaz if – else je čitelnější, proto se používá přednostně pro složitější a nestandardní případy.

41 41 Příkaz switch přepínač, neboli příkaz pro mnohonásobné větvení programu Má podobný význam jako několikrát vnořovaný příkaz if – else. –Příkaz switch by měl být přehlednější. Používá se pro zpracování příkazů menu.zpracování příkazů menu Syntaktická pravidla –Nelze jednoduše napsat prostý výčet několika hodnot pro jeden příkaz. –Výraz, podle kterého se rozhoduje, musí být typu int nebo char. –Na rozdíl od if lze testovat jen rovnost výrazu s hodnotou. –Každá větev přepínače musí být ukončena příkazem break. Není-li větev přepínače ukončena pomocí příkazu break, program zpracovává následující větev v pořadí a v této činnosti pokračuje do dosažení nejbližšího příkazu break nebo do ukončení příkazu switch. –Je podporována větev default, která se provádí, když žádná z větví nevyhovuje. –Pokud všechna porovnání selžou a default chybí, neprovede se žádná činnost. –V každé větvi může být více příkazů, které není nutno uzavírat do závorek. Štábní kultura –Související větve (nejsou odděleny příkazem break) se neoddělují prázdnou řádkou. –Příkazy větve jsou na nové řádce a odsazeny o 2 mezery od kraje. –Větev default se většinou píše, i když je prázdná. –Příkaz break za posledním příkazem poslední větve není nutný, ale z konvence se píše. –Větev default je z konvence uvedena vždy jako poslední větev přepínače. Pokud toto není dodrženo, potom je u default větve nutný ukončující příkaz break.

42 42 Příkaz switch – příklady #include int main(void) { int i; printf("Zadejte číslo mezi 1 a 4: "); scanf("%d", &i); switch (i) { case 1: printf("jedna"); break; case 2: printf("dvě"); break; case 3: printf("tři"); break; case 4: printf("čtyři"); break; default: printf("neznámé číslo"); break; } return 0; } #include int main(void) { printf("Zadejte písmeno: "); switch (getchar()) { case 'a': case 'e': case 'i': case 'o': case 'u': case 'y': printf(" je samohláska\n"); break; default: printf(" je souhláska\n"); break; } return 0; }

43 43 Cykly: Iterační příkazy V jazyce C jsou 3 příkazy pro iteraci. –while Testuje podmínku cyklu před průchodem cyklu. Cyklus tedy nemusí proběhnout ani jednou. –do – while Testuje podmínku cyklu až po průchodu cyklem. Cyklus tedy proběhne nejméně jednou. Je výhodný pro testování zadané hodnoty. –for Známe předem počet průchodů cyklem. Je výhodný pro práci s vektory a maticemi.

44 44 Příkaz while a do – while while (x > y) x -= y; printf("Zadej \"A\" nebo \"N\": "); do { c = getchar(); } while (c != 'A' && c != 'N'); Štábní kultura –Pokud se provádí v do – while cyklu jen jeden příkaz, nejsou složené závorky zapotřebí. Většina programátorů je však používá, aby mohli snáze poznat, že while ukončující do je součástí cyklu do, a ne začátek cyklu while. –Je-li tělo příkazu while prázdné, je středník „;“ odsazen vždy na nové řádce. while ((c = getchar()) == ' ' || c == '\t' || c == '\n') ; –přeskočení všech bílých znaků na vstupubílých znaků x > y ano ne x = x - y c <> ‚A‘ a c <> ‚N‘ ano ne Napiš „Zadej A nebo N: “ Čti c

45 45 Příkaz for for (i = 1; i <= 10; i++) printf("%d\n", i); Štábní kultura –Snažíme se mít pouze jednu řídící proměnnou cyklu (zde i). –Řídící proměnná cyklu nesmí být typu float nebo double. U typů s pohyblivou řádovou čárkou nemusí být přesná inkrementace. –Řídící proměnná smí být ovlivňována pouze v řídící části cyklu a ne v jeho těle. U vnořených cyklů je třeba dát pozor, zda identifikátor řídící proměnné vnořeného cyklu nebyl už použit pro vnější cyklus. –Je-li tělo příkazu for prázdné, je středník „;“ odsazen vždy na nové řádce. –Cykly while a for preferujte před cyklem do – while, protože jsou přehlednější. i <= 10 ano ne i = i + 1 i = 1 Piš i

46 46 Operátor čárky for (i = 1, faktorial = 1; i <= 5; i++) faktorial *= i; Odděluje 2 výrazy. 1. výraz se vyhodnotí nejdříve a je po vyhodnocení zapomenut a proto má smysl jen, když má vedlejší efekt, tedy obsahuje přiřazení. 2. výraz je výsledkem. int i = 2, j = 4; /* Toto není operátor čárky. */ j = (i++, i – j); /* i bude 3 a j bude -1. */ –Kdybychom zapomněli závorky, vyhodnocení by bylo (j = i++), i – j; /* i bude 3 a j bude 2. */ Výsledek je i – j a ten se nikam nepřiřadil. Následující program se úspěšně zkompiluje a vypíše #include int main(void) { float f; f = 3,987; printf("%f\n", f); return 0; } Štábní kultura –Operátor čárky používejte jen v řídících částech příkazů for a while. i <= 5 ano ne i = i + 1 i = 1 faktorial = 1 faktorial = faktorial * i

47 47 Skoky Teoreticky skoky nejsou nutné, prakticky někdy program velmi zjednoduší.Teoreticky skoky nejsou nutné V jazyce C jsou 4 příkazy pro skoky. –break Ukončuje nejvnitřnější neuzavřenou smyčku – opouští okamžitě cyklus. –continue Skáče na konec nejvnitřnější neuzavřené smyčky a tím vynutí další iteraci smyčky – cyklus neopouští. –goto Skáče na návěští. –return Ukončí provádění funkce, která tento příkaz obsahuje. Ve funkci main() ukončí příkaz return celý program. Funkce exit() z knihovny stdlib.h ukončí ihned celý program bez návratu do volající funkce.exit() –Některé překladače vyžadují, aby měla parametr. »Například exit(1);

48 48 Příkaz break Příkaz break se obvykle používá v cyklech, ve kterých může nějaká zvláštní událost způsobit jejich ukončení. V tomto případě může být provádění programu ukončeno stisknutím klávesy „N“. #include int main (void) { int i; char c; for (i = 1; ; i++) { /* Výraz pro ukončení se ve for cyklu nemusí uvádět. */ if (!(i % 6)) { /* Vypisuje všechna čísla, která jsou násobky 6. */ printf("%d, Další? (Y/N)", i); c = getchar(); if (c == 'N') break; /* Ukončení cyklu */ printf("\n"); } return 0; } Štábní kultura –Příkaz break by se měl v těle cyklu vyskytnout pouze v nezbytných případech a pouze na jednom místě. –Vícenásobný výskyt break zhoršuje srozumitelnost a čitelnost programu.

49 49 Příkaz continue Jeden z vhodných případů použití continue je nové spuštění posloupnosti příkazů, když nastane chyba. Například tento program počítá průběžný součet všech čísel zadaných uživatelem. Před přičtením hodnoty k průběžnému součtu otestuje správnost zadaného čísla tak, že ji uživatel musí zadat znovu. Pokud se tato dvě čísla liší, program použije continue na další iteraci cyklu. #include int main (void) { int i, j, soucet; soucet = 0; do { printf("Zadejte další číslo (0 = Konec): "); scanf("%d", &i); printf("Zadejte číslo znovu: "); scanf("%d", &j); if (i != j) { printf("Čísla nesouhlasí.\n"); continue; } soucet += i; } while (i); printf("Součet je %d.\n", soucet); return 0; } Štábní kultura –Příkaz continue je vhodné nahradit čitelnější konstrukcí if – else. #include int main (void) { int i, j, soucet; soucet = 0; do { printf("Zadejte další číslo (0 = Konec): "); scanf("%d", &i); printf("Zadejte číslo znovu: "); scanf("%d", &j); if (i != j) printf("Čísla nesouhlasí.\n"); else soucet += i; } while (i); printf("Součet je %d.\n", soucet); return 0; }

50 50 Příkaz goto Návěští pro goto se nemusí předem definovat. Skočit se dá jen v rámci jedné funkce. Nelze skákat z jedné funkce do druhé funkce. V rámci funkce lze skákat z libovolného místa na libovolné místo, což činí příkaz goto zvláště nebezpečným. V následujícím příkladu si jinak užitečný příkaz goto vynutil užití dvou dalších příkazů goto. –Do příkazů goto je snadné se zamotat!!! –Návěští nenesou informaci, odkud se do nich skáče, což ztěžuje možnost kontroly správnosti programu. … for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { for (k = 0; k < 10; k++) { if (x[k] == 0) goto error; /* V případě výskoku z vnořených cyklů goto zjednodušuje a zpřehledňuje program. */ a[i][j][k] = a[i][j][k] + b[j] / x[k]; } goto dalsi_vypocet; error: /* Návěští pro příkaz goto */ printf("Nulový dělitel!\n"); goto konec; dalsi_vypocet: … /* Příkazy, které se mají vykonat, když nenastane error */ konec: … /* Příkazy, které se mají vykonat v každém případě v závěru funkce */ Štábní kultura –Nepoužívejte goto pro výskok z jednoduché smyčky. V tomto případě je mnohem vhodnější použít break.

51 51 Příkaz return Často se pomocí return vrací nějaká hodnota, jejíž typ záleží na typu funkce. Byla-li by operace s poli z předchozího příkladu provedena voláním funkce, je pak vhodnější použít příkaz return a ne goto.předchozího příkladu Pro zjištění, zda funkce splnila svoji úlohu správně, nám poslouží návratová hodnota. Její hodnota 0 bude znamenat provedení bez chyb a hodnota 1 provedení s chybou. Vedlejším efektem funkce je změněné pole a[i][j][k]. int nasobeni() { int i, j, k; … for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { for (k = 0; k < 10; k++) { if (x[k] == 0) return 1; /* neúspěch */ a[i][j][k] = a[i][j][k] + b[j] / x[k]; } return 0; /* úspěch */ }

52 52 Soubory – 1. část Soubor je posloupnost bajtů uložených na disku. Bajty souboru se sdružují do stejně velkých bloků. Soubor je vytvářen podle pravidel daných operačním systémem. Vstupní a výstupní operace jsou bufferované. –Data jdou vždy přes vyrovnávací paměť. –Čtení a zápis se provádí po celých blocích. Soubory se dělí na binární a textové. –V 1. části se budeme zabývat jen textovými soubory. Funkce pro práci se soubory jsou ve standardní knihovně stdio.h.

53 53 Deklarace proměnných pro práci se souborem FILE *fr, *fw; –FILE * je pointer na objekt typu FILE. –FILE je datový typ reprezentující soubor. –fr a fw jsou proměnné reprezentující soubor. Syntaktická pravidla –Identifikátor FILE musí být velkými písmeny. –Chceme-li definovat více proměnných, čili pracovat s více soubory najednou, musí se znak * opakovat před identifikátorem každé z proměnných. Štábní kultura –Je vhodné použít identifikátor fr pro soubor, který čteme a identifikátor fw pro soubor, do něhož zapisujeme. –Tentýž identifikátor (např. f) by neměl být používán v jednom programu jak pro čtení souboru tak pro zápis do něho.

54 54 Otevření souboru fr = fopen("CISLA.TXT", "r"); fw = fopen("SOUCET.TXT", "w"); "r" (jako read) a "w" (jako write) jsou režimy otevření souboru. –Existují ještě další režimy.Existují ještě další režimy. Cesta k souboru ve Windows –Příkaz fr = fopen("D:\CISLA.TXT", "r"); je špatně, protože obsahuje znak „\“, který uvozuje escape sekvenci.escape sekvenci –Příkaz fr = fopen("D:\\CISLA.TXT", "r"); je správně, protože je znak „\“ zdvojený.

55 55 Základní operace se souborem c = getc(fr); –čtení znaku ze souboru putc(c, fw); –zápis znaku do souboru putc(getc(fr), fw); –čtení a zápis znaku jedním příkazem fscanf(fr, "%d", &i); –formátované čtení ze souboru fr v decimálním režimu do proměnné i fprintf(fw, "%d\n", j); –Formátovaný zápis proměnné j do souboru fw v decimálním režimu Syntaktická pravidla –Pořadí parametru pro soubor fw ve funkci putc() je jiný než ve funkci fprintf(). –Práce se soubory s příliš neliší od práce s terminálem.terminálem

56 56 Ukončení práce se souborem fclose(fr); –příkaz pro zavření souboru fr Soubor, který už nepotřebujete, okamžitě uzavřete. –Počet současně otevřených souborů je omezený. Při opomenutí uzavřít soubor by výsledek mohl zůstat jen v bufferu, čímž by se po skončení programu ztratil.

57 57 Zápis do souboru #include int main(void) { FILE *fw; int i; fw = fopen("CISLA.TXT", "w"); for (i = 1; i <= 10; i++) fprintf(fw, "%d\n", i); fclose(fw); return 0; }

58 58 Čtení ze souboru #include int main(void) { FILE *fw, *fr; int i, j = 0; fr = fopen("CISLA.TXT", "r"); while (fscanf(fr, "%d", &i) != EOF) j += i; fclose(fr); fw = fopen("SOUCET.TXT", "w"); fprintf(fw, "%d\n", j); fclose(fw); return 0; }

59 59 Test zda jsou v souboru data Funkce fscanf() –vrací počet úspěšně přečtených položek, –v případě čtení na konci souboru vrací hodnotu EOF.čtení na konci souboru #include int main(void) { FILE *fr; int x, y, z; fr = fopen("CISLA.TXT", "r"); if (fscanf(fr, "%d %d %d", &x, &y, &z) == 3) printf("%d\n", x + y + z); else printf("Soubor CISLA.TXT neobsahuje 3 čísla.\n"); fclose(fr); return 0; }

60 60 Testování konce řádky V textových souborech existují 3 typy konce řádků. –Carriage return,, tj. posun na začátek téže řádky '\r' = '\x0D' = 13 –Linefeed, tj. posun na novou řádku (typické pro Unix) '\n' = '\x0A' = 10 – (typické pro Windows), zřídkakdy i v obráceném pořadí Jakákoliv varianta konce řádku se v jazyce C rovná '\n'. Následující program přečte první řádku ze souboru DOPIS.TXT a zapíše ji do souboru RADEK.TXT. Pokud konec řádku chybí, program se zacyklí a zapisuje opakovaně hodnotu -1, což je znak '\xFF'. Rychle program ukončete, protože vzniká velký soubor!!! #include int main(void) { FILE *fr, *fw; int c; fr = fopen("DOPIS.TXT", "r"); fw = fopen("RADEK.TXT", "w"); while ((c = getc(fr)) != '\n') /* Čtení až do konce řádky */ putc(c, fw); /* Znaky řádku */ putc(c, fw); /* Konec řádku */ fclose(fr); fclose(fw); return 0; }

61 61 Testování konce souboru pomocí symbolické konstanty EOF Při čtení na konci souboru se automaticky vrací konstanta EOF, která je většinou definována v souboru stdio.h a má většinou hodnotu -1. –Protože hodnotu -1 mít nemusí, je třeba využívat EOF. V předchozím programu nahradíme řídící část cyklu pro čtení až do konce řádku takto: while ((c = getc(fr)) != EOF) a vynecháme poslední příkaz putc(c, fw); Proměnná c nesmí být definována jako char, protože konstanta EOF je reprezentována často int hodnotou -1. Ta by byla konvertována na char a tedy něco jiného než je -1. –Je-li char implicitně unsigned, pak se -1 konvertuje na 255. Pro typy short int, int a long int je implicitní typ signed. Pro typ char to záleží na implementaci. –Například, pokud v předchozím programu nahradíme deklaraci int c; za unsigned char c; tak se program zacyklí, protože -1 != 255.

62 62 Testování konce souboru pomocí funkce feof() Funkce feof() ze standardní knihovny stdio.h vrací hodnotu –TRUE (nenulovou), pokud poslední čtení bylo již za koncem souboru, –FALSE (nulu), pokud ještě nejsme na konci souboru. Je vhodná pro binární soubory, protože se v nich může objevit bajt s hodnotou 0xFF kdekoliv uprostřed souboru. #include int main(void) { FILE *fr, *fw; int c; fr = fopen("ORIG.TXT", "r"); fw = fopen("KOPIE.TXT", "w"); while (c = getc(fr), feof(fr) == 0) /* feof(fr) == 0 lze nahradit za !feof(fr) */ /* while (feof(fr) == 0, c = getc(fr)) způsobí zacyklení, viz operátor čárky. */operátor čárky putc(c, fw); fclose(fr); fclose(fw); return 0; }

63 63 Testování správnosti otevření a uzavření souboru Proč se nepovede otevření souboru? –chybné jméno souboru nebo cesta k němu –překročení limitu počtu současně otevřených souborů Proč se nepovede uzavření souboru? –málo místa na disku –Obsah proměnné, pomocí níž k souboru přistupujeme (například *fr nebo *fw), byl nesprávnou činností programu změněn. využívání návratových hodnot funkcí fopen() a fclose()

64 64 Testování správnosti otevření a uzavření souboru Program vypíše soubor DOPIS.TXT na obrazovku. #include int main(void) { FILE *fr; int c; if ((fr = fopen("DOPIS.TXT", "r")) == NULL) { /* Závorkovat! */Závorkovat! printf("Soubor DOPIS.TXT se nepodařilo otevřít.\n"); return 1; /* 1 znamená konec kvůli chybě. */ } while ((c = getc(fr)) != EOF) putchar(c); if (fclose(fr) == EOF) { printf("Soubor DOPIS.TXT se nepodařilo uzavřít.\n"); return 1; /* 1 znamená konec kvůli chybě. */ } return 0; /* 0 znamená konec bez chyby. */ } Název souboru by vždy měl být jako pojmenovaná konstanta, viz příklad.příklad

65 65 Testování správnosti otevření a uzavření souboru Při nesprávně provedeném –otevření souboru vrací funkce fopen() konstantu NULL. Konstanta NULL je definována v stdio.h a má většinou hodnotu 0. –uzavření souboru vrací funkce fclose() konstantu EOF. EOF Platí konvence, že pokud program vrátí příkazem return hodnotu 0, tak proběhl v pořádku. Pokud vrátí jinou hodnotu, pak skončil detekovanou chybou a čím větší číslo, tím horší chyba.return Pokud testujeme vstupní a výstupní soubory, správně napsaný program zajistí, že před výskokem z něj kvůli chybě jsou soubory, které se podařilo otevřít, i zavřeny pomocí fclose().

66 66 Standardní vstup a výstup Jazyk C pracuje s klávesnicí a obrazovkou jako se souborem. V souboru stdio.h jsou definovány tři konstantní pointery, které představují tři proudy (vstupní/výstupní – I/O a chybový) otevřené operačním systémem při spuštění programu.pointery Jsou to: FILE *stdin; FILE *stdout; FILE *stderr; Tyto pointery se označují jako standardní vstupní / výstupní / chybový proud (standard input / output / error stream) a většinou představují vstup z klávesnice a výstup na obrazovku. Konstanty stdin, stdout a stderr mohou být použity v programu jako argumenty operací se soubory, například –getc(stdin) je ekvivalentem getchar() –putc(c, stdout) je ekvivalentem putchar(c) Příklad: printf("Stiskněte O pro výpis na Obrazovku.\n"); c = getchar(); while (getchar() != '\n') ; /* Vyprázdnění vstupního bufferu je nutné vždy, když víckrát po sobě čteme znak jako odpověď na otázku. Lepší je funkce fflush().*/fflush() if (c == 'o' || c == 'O') fw = stdout; else fw = fopen("VYSTUP.TXT", "r");

67 67 Redirekce I/O proudy je možno v mnoha operačních systémech (UNIX, Windows) změnit pomocí příkazu přesměrování (redirekce) například na vstup ze souboru nebo výstup do souboru. Zkompilujte tento program a nazvěte jej SOUCET.EXE. #include int main(void) { int a, b; scanf("%d %d", &a, &b); /* Vstup z klávesnice */ printf("%d\n", a + b); /* Výstup na obrazovku */ perror("Chyba"); /* Výstup na standardní chybový proud */perror return 0; } Vytvořte textový soubor VSTUP.TXT a napište do něj 2 čísla. Potom je možné v příkazovém řádku ve Windows psát toto: soucetchyba.txt 2>&1 Vytvoří se soubor CHYBA.TXT s chybovým hlášením a případným výstupem programu. soucet>vystup.txt 2>chyba.txt Redirekce výstupního proudu do souboru VYSTUP.TXT a chybového proudu do souboru CHYBA.TXTRedirekce soucet vystup.txt 2>chyba.txt Redirekce výstupního proudu do souboru VYSTUP.TXT a chybového proudu do souboru CHYBA.TXTRedirekce Vstupní hodnoty jsou v souboru VSTUP.TXT.

68 68 Vrácení přečteného znaku zpět do vstupního bufferu Při programování reálných aplikací se v mnoha případech při čtení znaků dozvídáme, že máme přestat číst, až když přečteme jeden znak navíc. Tento znak ale není možné vždy „zahodit“, protože je součástí – začátkem – další informace. Funkce ungetc(c, fr) vrátí znak c zpět do vstupního bufferu. –Po úspěšném vrácení znaku vrací znak c. –Po neúspěšném vrácení znaku vrací EOF. –Obvykle lze do vstupního bufferu vrátit pouze jeden znak. Potom se musí z bufferu číst.

69 69 Vrácení přečteného znaku zpět do vstupního bufferu – 1. příklad #include int main(void) { /* Vstup je typu „123abc“. Chceme přečíst číslo jako int. */ int c, i = 0; while ((c = getchar()) >= '0' && c <= '9') { i = i * 10 + (c - '0'); } printf("%d ", i); ungetc(c, stdin); /* Místo toho by mohlo být putchar(c); */ while ((c = getchar()) != '\n') { putchar(c); } putchar(c); /* Konec řádku */ return 0; }

70 70 Vrácení přečteného znaku zpět do vstupního bufferu – 2. příklad #include int main(void) { /* Vstup je typu „abc123“. */ int c, i; while (!((c = getchar()) >= '0' && c <= '9')) { putchar(c); } ungetc(c, stdin); scanf("%d", &i); printf(" %d\n", i); return 0; }

71 71 Vrácení přečteného znaku zpět do vstupního bufferu – 3. příklad #include int main(void) { int a, b, c; printf("Zadej znak a: "); a = getchar(); while (getchar() != '\n') ; printf("Zadej znak b: "); b = getchar(); while (getchar() != '\n') ; printf("Zadej znak c: "); c = getchar(); while (getchar() != '\n') ; ungetc(a, stdin); c = getchar(); putchar(c); putchar('\n'); return 0; } Zpátky do vstupního bufferu lze vrátit i jiný znak než znak naposledy přečtený. Tohoto triku lze využít například pro předvolení klávesy, která bude později jakoby stisknuta. Funkce getchar() čte znak vrácený do vstupního bufferu bez nutnosti odeslat ho klávesou Enter.

72 72 Alokace paměti Každá proměnná musí mít během své existence přidělen paměťový prostor, který velikostí odpovídá typu proměnné. Jméno proměnné (identifikátor) je vlastně symbolická adresa tohoto paměťového prostoru. Alokace –akce, která vyhrazuje paměťový prostor –statická –dynamická

73 73 Statická alokace paměti Je nejčastější. Vymezuje místo v datové oblasti.datové oblasti Je jedinou možnou alokací pro globální proměnné. Globální proměnná je definována vně jakékoliv funkce.Globální proměnná Používá se, když umíme překladači předem přesně říci, jaké budeme mít v programu paměťové nároky. Překladač určí požadavky na paměť. Zavaděč (systémový program loader) alokuje tuto paměť v čase začátku spuštění programu. Během provádění programu se neprovádí žádná manipulace s přidělováním paměti. Operační systém alokaci paměti po ukončení programu zruší. Kdy statická alokace paměti nestačí? –rekurzívní volání funkcí –načtení celého souboru do paměti

74 74 Dynamická alokace paměti Dynamická alokace paměti na hromadě –Hromada, halda (heap) je omezena velikostí RAM a pevného disku. –Alokaci provádíme voláním knihovní funkce za běhu programu. –Tato paměť nemá identifikátor a přistupuje se do ní pomocí pointeru. Dynamická alokace paměti v zásobníku –Zásobník (stack) má omezenou velikost. –Alokaci provádíme jako běžnou definici lokální proměnné. –Lokální proměnná je definována staticky v rámci funkce a má identifikátor. –Existence lokálních proměnných začíná při vstupu do funkce a končí při výstupu z této funkce. Pak může být paměť využita pro jiné účely, například pro jinou proměnnou definovanou v jiné, právě probíhající, funkci. –Jestliže je funkce volána znovu, proměnná má při vstupu do funkce nedefinovanou hodnotu. –Z toho vyplývá, že proměnná, která si musí ponechávat svoji hodnotu mezi voláními funkce (statická lokální proměnná), nemůže mít paměť alokovanou v zásobníku. Statická lokální proměnná začíná existovat při vstupu do funkce, v níž je definována, a končí s ukončením programu, přičemž je dostupná pouze z funkce, kde je definována. –Překladač ji uloží do datové oblasti paměti.datové oblasti

75 75 Funkce Program v C obsahuje jednu nebo více definic funkcí, z nichž jedna se musí vždy jmenovat main(). Zpracování programu začíná voláním funkce main() a končí opuštěním této funkce. Funkce nemohou být vhnízděné (nested). –Jedna funkce nemůže obsahovat ve svém těle definici druhé funkce. –Formální parametry a lokální proměnné jsou tedy přístupné pouze ve funkci, v níž byly definovány.Formální parametry Všechny funkce v jazyce C vrací hodnotu, dají se však použít i jako procedury.

76 76 Definice funkce s parametry Definice funkce určuje jak hlavičku funkce, tak i její tělo. –Definují se i proměnné. Výsledkem je příkaz, který přidělí proměnné určitého typu jméno a paměť. Deklarace funkce specifikuje pouze hlavičku funkce, tj. jméno funkce, typ návratové hodnoty a případně typ a počet jejích parametrů. –Nepřiděluje paměť. Slouží ke kontrole počtu a typů formálních a skutečných parametrů. int max(int a, int b) /* Hlavička funkce. int max(int a, b) je syntaktická chyba!*/ { return (a > b ? a : b); /* Tělo funkce */ } Hlavička funkce není ukončena středníkem. Všechny parametry jsou volány hodnotou. Neexistuje přímo volání odkazem. Proměnné a a b se nazývají formální parametry funkce. Mezi jménem funkce a levou závorkou se nedělá mezera, aby nebylo nutno odlišovat volání funkce od volání makra s parametry. Vždy definujte typ návratové hodnoty funkce (zde int). Implicitní typ je int. Tělo funkce je uzavřeno do závorek {} stejně jako u funkce main() a může obsahovat jak příkazy, tak i definice proměnných. Na rozdíl od Pascalu a VBA, kde se výstupní hodnota funkce předává přiřazením jménu funkce, se v jazyce C používá příkaz return vyraz; nebo return (rozsahlejsi_vyraz); Volání funkce: x = max(10 * i, j – 15); –Parametry 10 * i a j – 15 jsou skutečné parametry funkce.

77 77 Definice funkce bez parametrů Funkce, která nemá žádné parametry, musí být definována i volána včetně obou kulatých závorek. int secti(void) { int a, b; scanf("%d %d", &a, &b); return (a + b); } Volání funkce: j = secti(); Chybné volání funkce j = secti; bez závorky není syntaktickou chybou. Překladač může vypsat varování „assignment makes integer from pointer without a cast“. Takto se do j přiřadí pointer na funkci, což je adresa, na které je výsledek funkce.pointer na funkci

78 78 Procedury a datový typ void Funkce musí v jazyce C plnit také úkoly, které v jiných programovacích jazycích plní procedury. Tyto funkce mají 2 formy: –Funkce návratovou hodnotu sice vrací, ale nikdo ji nechce. Například getchar(); Novější překladače vyžadují v této situaci explicitní přetypování na typ void („prázdný“), aby bylo jasné, že programátor návratovou hodnotu skutečně nepotřebuje, tedy: (void) getchar(); –Funkce se definuje jako funkce vracející typ void, například: void tisk_int(int i) { printf("%d", i); } Volání funkce: tisk_int(a + b); Příkaz return pak není nutný a používá se pouze pro nucené předčasné ukončení funkce, například po nějaké podmínce.

79 79 Funkce bez formálních parametrů a datový typ void Funkce bez parametrů: int secti(void) { int a, b; scanf("%d %d", &a, &b); return (a + b); } Volání funkce: c = secti(); Procedura (neboli funkce hrající roli procedury) bez parametrů: void ahoj(void) { printf("ahoj\n"); } Volání procedury: ahoj();

80 80 Rekurzivní funkce Funkce volající samy sebe #include int fakt(int n) { return ((n <= 0) ? 1 : n * fakt(n - 1)); /* Podmíněný výraz */Podmíněný výraz } int main(void) { int i; printf("Zadej celé číslo: "); scanf("%d", &i); printf("Faktoriál je %d.\n", fakt(i)); return 0; }

81 81 Umístění definice funkcí Volá-li funkce A funkci B, musí být funkce B deklarována před funkcí A. To není možné v případě nepřímé rekurze. –Funkce A volá funkci B. –Funkce B volá funkci A. Je to obtížné dodržet v případě mnoha funkcí se složitými vztahy. Řešení –deklarace návratového typu a jména Deklarace volané funkce se provede –ve funkci volající nebo ve funkci main(), –na globální úrovni mezi direktivami a hlavičkou funkce main(). V současné době se nepoužívá, protože nepodává žádnou informaci o parametrech funkce. –funkční prototyp dle ANSI verze jazyka C

82 82 Funkční prototyp #include double pikrat(double x); /* Úplný funkční prototyp */ /* double pikrat(double); Neúplný funkční prototyp neboli anonymní specifikace */ /* extern double pikrat(double x); se použije, když je funkce pikrat() definována v jiném modulu. */ int main(void) { double r; printf("Zadej poloměr: "); scanf("%lf", &r); printf("Obvod kruhu je %f.\n", 2 * pikrat(r)); return 0; } double pikrat(double x) { return (x * 3.14); } Štábní kultura –Dáváme přednost úplnému funkčnímu prototypu před neúplným. Úplný funkční prototyp je kopie hlavičky funkce, za kterou se přidá středník. Pokud nejsou uvedeny funkční prototypy, neprovádí jazyk C žádnou kontrolu parametrů, co se týče jejich shody a návratových typů. –Na začátku programu se uvede seznam všech funkčních prototypů. Ještě lepší je zapsat prototypy do speciálního.h souboru, aby mohly být využívány i v jiných souborech..h soubory viz kniha Pavla Herouta Učebnice jazyka C, 1. díl, páté vydání, KOPP České Budějovice 2008 na straně 97 a 133. –U funkcí ze standardní knihovny nemusí být v programu uváděny jejich funkční prototypy. To je již učiněno v příslušném.h souboru a připojením pomocí například příkazu #include se tyto funkční prototypy stanou součástí našeho programu.

83 83 Předávání parametrů funkcí Hodnotou (by value) –Parametry se vyhodnotí a do se funkce zkopírují na nové paměťové místo. –Pokud je funkce změní, po opuštění funkce se jejich výsledná hodnota ztrácí. –Takto se to provádí v jazyce C a téměř ve všech dalších. Odkazem (by reference) –Funkce pracuje přímo s paměťovými místy, ve kterých jsou uloženy originální hodnoty parametrů. –Pokud funkce parametry změní, po opuštění funkce jejich výsledná hodnota zůstává. –Některé jazyky tento způsob podporují vedle předávání hodnotou, například VBA nebo C++.

84 84 Parametry funkcí Parametry funkce jsou v jazyce C předávány hodnotou (call by value). Výhoda –Skutečným parametrem může být i výraz. Nevýhoda –Skutečné parametry nemohou být ve funkci změněny, ale pouze čteny. Volání odkazem se řeší pointery.

85 85 Oblast platnosti identifikátorů #include int i, j; /* Globální proměnné viditelné ve všech podprogramech, pokud tam nejsou zastíněny */ int f1(void) { int i; /* Lokální proměnná překrývající (zastiňující) globální proměnnou i viditelná jen ve funkci f1() */ /* Ve funkci f1() je vidět lokální proměnná i a globální proměnná j. */ … } int main(void) { int k; /* Lokální proměnná viditelná jen ve funkci main() */ … k = f1(); … return 0; } Inicializace –lokálních neboli automatických proměnných je náhodná,lokálníchautomatických –globálních proměnných je nula (0, 0.0, '\0' dle typu proměnné).globálních –U všech proměnných, které mají být inicializovány, by se měla inicializace výslovně uvést, například int i = 1; Další možnosti (například statická lokální proměnná) viz kniha Pavla Herouta Učebnice jazyka C, 1. díl, páté vydání, KOPP České Budějovice 2008, str. 121 – 143.statická lokální proměnná

86 86 Definice proměnných v blocích #include int main(void) { int i; scanf("%d", &i); if (i > 0) { int j; /* j je definováno uvnitř bloku. */ scanf("%d", &j); printf("Součet je %d.\n", i + j); } else { double f; /* f je definováno uvnitř bloku. */ scanf("%lf", &f); printf("Součin je %f.\n", i * f); } return 0; } Kdekoli v programu bezprostředně za { mohou být definovány lokální proměnné. Proč definovat proměnné ve vnitřním bloku? –šetření pamětí Teoreticky se paměť alokuje jen, když se vykonává kód daného bloku. Prakticky to záleží na implementaci. Šetření pamětí není nutné, pokud jde o jednotlivé proměnné. Má význam, pokud by se jednalo o větší pole. –čistota a přehlednost kódu Proměnná by se měla definovat pouze tam, kde je používána.

87 87 Pointery Pointer (ukazatel, směrník) je proměnná uchovávající paměťovou adresu. 25→ 18hodnota p_i→ *p_isymbolická adresa 87→ 25absolutní paměťová adresa p_i je pointer neboli proměnná s hodnotou 25. –Aby překladač věděl, že hodnota této proměnné je paměťová adresa, doplňuje se identifikátor p_i vpředu operátorem *. 25 se nevyužije přímo k výpočtu, ale představuje absolutní adresu v paměti. 18, kterou je možné využít k výpočtu, je hodnota uložená na absolutní adrese je absolutní adresa symbolické proměnné p_i a jejím obsahem je hodnota 25. *p_i je symbolická adresa pro hodnotu 18. * je dereferenční operátor. Jeho první význam je násobení. i = *p_i; /* Do i se uloží obsah adresy, na niž ukazuje pointer *p_i. */ *p_i = 5; /* Na adresu, na niž ukazuje pointer *p_i, se uloží hodnota 5. */ & (ampersand) je referenční operátor. Jeho první význam je bitový součin. –Je užíván například ve funkci scanf().

88 88 Definice dat typu pointer na typ Štábní kultura –Všechny identifikátory pointerů začínají znaky p_. int *p_i, i; –p_i obsahuje adresu pro typ int. –Ve stejné definici je možné uvést i proměnnou typu int. int *p_i, p_j; –Pouze p_i je pointer na int a p_j je proměnná typu int, což je častá chyba, viz například FILE *fr, *fw;. FILE *fr, *fw;

89 89 Referenční operátor & int i, *p_i = &i; /* Definice proměnných */ –Adresa uložená v proměnné p_i je inicializována adresou, na které je uložena hodnota proměnné i. –Proměnná i musí být definována před proměnnou *p_i. p_i = &i; /* Příkaz v programu */ –Pointer p_i začne mířit na stejnou paměťovou adresu, na které je uložena proměnná i. –Výsledek je stejný jako po definici proměnných uvedené výše. Protože to není definice proměnných ale příkaz, je nutné vynechat operátor * před p_i, viz chyba *p_i = &i; uvedená níže. *p_i = 5; –Po předchozím příkazu p_i = &i; je to to samé jako i = 5;. *p_i = &i; /* Příkaz v programu */ –Na adrese uložené v pointeru p_i je adresa, na které je uložena proměnná i. –To je chybný příkaz.

90 90 Pointery a přiřazovací příkazy Každá pointerová proměnná je l-hodnota, tedy může stát na levé straně přiřazovacího příkazu.l-hodnota *p_i = 5; –Zcela v pořádku. *(p_i + 3) = 5; –Podezřelé, pokud p_i neukazuje na pole. *(i + 3) = 5; –Podezřelé, protože i nemá předponu p_. p_i = &i; –Správně, protože proměnná i má vždy adresu. p_i = &(i + 3); –Chyba, protože výraz i + 3 nemá adresu. p_i = &3; –Chyba, protože konstanta 3 nemá adresu.

91 91 Statická a dynamická správnost přiřazení Statická správnost –Přiřazení je správné v době překladu. –Levá strana musí být stejného typu jako pravá strana. Dynamická správnost –Přiřazení je správné v době překladu i při běhu programu. –Do pointerů se mají přiřazovat jen pointery na stejné datové typy. –Pointery musí být správně inicializovány. Pro všechny příklady platí definice int i, *p_i; Staticky správné i = 5; *p_i = 5; i = *p_i; *p_i = i; p_i = &i; Staticky nesprávné p_i = 5; /* Místo adresy je do p_i dána hodnota 5. */ i = p_i; /* Do i se dá adresa místo int hodnoty. */ i = &p_i; /* Do i se dá adresa p_i místo int hodnoty. */ Dynamicky správné p_i = &i; *p_i = 5; /* p_i byla inicializována. Je to totéž jako i = 5; */ Dynamicky nesprávné *p_i = 5; /* p_i nebyla inicializována. 5 je přiřazena na náhodnou adresu. */ Toto je nejčastější chyba!

92 92 Příklady s pointery Program čte dvě celá čísla a zobrazí větší z nich. #include int main(void) { int i, j, *p_i; scanf("%d %d", &i, &j); p_i = (i > j) ? &i : &j; printf("Větší je %d.\n", *p_i); return 0; } Zobrazení adresy, která je uložena v pointeru. –Používáme při ladění, když si nejsme jisti, zda pointer ukazuje tam, kam má. #include int main(void) { int i, *p_i = &i; printf("Adresa proměnné i je %p, hodnota p_i je %p.\n", &i, p_i); return 0; }

93 93 Nulový pointer NULL; konverze pointerů a zarovnávání v paměti Hodnotu NULL je možné přiřadit bez přetypování pointerům na libovolný typ dat a používá se pro označení, že tento pointer neukazuje momentálně na nic, to znamená, že nemá přidělenou paměť. NULL je symbolická konstanta definovaná v stdlib.h jako napříkladsymbolická konstanta #define NULL 0 nebo #define NULL 0L nebo jako #define NULL ((void *) 0) Konverze pointerů –Standardně používáme při přidělování dynamické paměti funkcí malloc(). –Následující použití se běžně nepotřebuje. char *p_c; int *p_i; p_c = p_i; /* Nevhodné implicitní přetypování */ p_c = (char *) p_i; /* Lepší explicitní přetypování */ Zarovnávání v paměti –Pokud při konverzi pointerů dochází k neočekávané chybě, je vhodné zkusit přetypování tam a zpět a vypsat hodnoty pointerů, například printf("p_c před konverzí: %p.\n", p_c); p_i = (int *) p_c; p_c = (char *) p_i; printf("p_c po konverzi: %p.\n", p_c); –Odlišné výsledky, které můžeme dostat, jsou způsobeny díky tomu, že některé kompilátory používají taktiku, že určité datové typy, například int, jsou uloženy v paměti od sudých adres (memory alignment).memory alignment –Bez problémů je pointerová konverze pouze z delších datových typů na kratší.

94 94 Volání parametrů funkce odkazem Funkce vymen() vymění dvě celá čísla. #include void vymen(int *p_x, int *p_y) { int pom; pom = *p_x; *p_x = *p_y; *p_y = pom; } void vymen_komplikovane(int **p_x, int **p_y) { int pom; pom = **p_x; **p_x = **p_y; **p_y = pom; } int main(void) { int i = 5, j = 3; vymen(&i, &j); printf("i je %d a j je %d.\n", i, j); int *p_i = &i, *p_j = &j; vymen(p_i, p_j); printf("i je %d a j je %d.\n", *p_i, *p_j); vymen_komplikovane(&p_i, &p_j); printf("i je %d a j je %d.\n", *p_i, *p_j); return 0; } Chyby při volání funkce vymen(): vymen(i, j); Zapisuje se na adresy 3 a 5, což vede většinou ke zhroucení programu. vymen(*i, *j); Zapisuje se na adresy, které jsou na adresách 3 a 5, což opět vede většinou ke zhroucení programu.

95 95 Pointer na typ void void *p_void; Pointer *p_void neukazuje na žádný konkrétní typ, čili dá se využít pro ukazování na zcela libovolný typ, ovšem po důsledném přetypování. Generický pointer Pointer na typ void jako pointer na několik různých typů int main(void) { int i; float f; void *p_void = &i;/* p_void ukazuje na i */ *(int *) p_void = 2;/* Přetypování a nastavení i na 2 */ p_void = &f;/* p_void ukazuje na f */ *(float *) p_void = 1.1;/* Přetypování a nastavení f na 1.1 */ return 0; } Pointer na typ void jako formální parametr funkce #include void vymen_pointery(void **p_x, void **p_y) { void *p_pom; p_pom = *p_x; *p_x = *p_y; *p_y = p_pom; } int main(void) { char c = 1, *p_c = &c, d = 2, *p_d = &d; FILE *fin = stdout, *fout = stdin; /* Záměrné prohození */ fprintf(fin, "c = %d, d = %d.\n", *p_c, *p_d); vymen_pointery((void **)&p_c, (void **)&p_d); /* Přetypování na (void **) je z důvodu zamezení varovnému hlášení o nestejných typech parametrů. */ vymen_pointery((void **)&fin, (void **)&fout); fprintf(fout, "c = %d, d = %d.\n", *p_c, *p_d); return 0; }

96 96 Pointer na funkci #include int i = 1, j = 2, k = 3; void f1(int **p_x) /* Procedura inicializuje pointer. */ { *p_x = &i; } int *f2(void) /* Funkce vrací pointer. */ { return &j; } int f3(void) /* Funkce vrací hodnotu. */ { return k; } int main(void) { int *p_i, *p_j; int (*p_k)(); /* Pointer na funkci vracející int */ /* int (*p_k); by znamenalo totéž co int *p_k; */ /* int *p_k(); by znamenalo deklaraci funkce p_k() vracející pointer na int. */ f1(&p_i); p_j = f2(); p_k = f3; /* Přiřazení adresy funkce pointeru. Místo f3 může být &f3. */ printf("%d %d %d\n", *p_i, *p_j, p_k()); /* Místo p_k() může být (*p_k)() nebo f3() nebo (*f3)(). */ return 0; } AdresaProměnnáHodnotapo příkazu 101i1i = 1; 102j2j = 2; 103k3k = 3; 104p_x105f1(&p_i); 105p_i101*p_x = &i; 106p_j102p_j = f2(); 107p_k108p_k = f3; 108f33p_k()

97 97 Funkce jako parametry funkcí Program vypíše tabulku hodnot polynomů x a x 3 – 3 v intervalu s krokem 0.2. #include double pol1(double x) { return (x * x + 8); } double pol2(double x) { return (x * x * x - 3); } void tabulace(double d, double h, double k, double (*p_f)()) { double x; for (x = d; x <= h; x += k) printf("%5.1f %8.3f\n", x, p_f(x)); } int main(void) { const double DOLNI = -1.0, HORNI = 1.0, KROK = 0.2; tabulace(DOLNI, HORNI, KROK, pol1); tabulace(DOLNI, HORNI, KROK, pol2); return 0; }

98 98 Pointerová aritmetika Používá se v datových strukturách typu pole a řetězec. Pole nebo řetězec zabírá v paměti souvislý úsek (interval) adres a jeho prvky jsou stejného typu. Velikost prvků určujeme operátorem sizeof. Výraz sizeof(*p) vrací počet bajtů nutný pro uložení prvku, na který ukazuje pointer p. Hodnota adresy n-tého prvku za prvkem, na který ukazuje pointer p, je p + n. Na jednotlivých adresách je uložen vždy jeden byte. Výraz p + n se dá přepsat jako (char *) p + sizeof(*p) * n. Příklad pole 3 hodnot datového typu zabírajícího 4 byty: Index sizeof(*p) = 4 0 = p + 01 = p + 12 = p + 2 Adresa (char *) p pp+1p+2p+3p+4p+5p+6p+7p+8p+9p+10p+11 Byte

99 99 Pointerová aritmetika – přičtení celého čísla k pointeru Pomocí pointerové aritmetiky můžeme polem nebo řetězcem procházet po jednotlivých prvcích. Program načte double číslo a zobrazí odpovídající bajty z adresy v paměti, na níž je toto číslo uloženo. #include int main(void) { int i; double f; unsigned char *p_byte; printf("Zadej reálné číslo: "); scanf("%lf", &f); p_byte = (unsigned char *) &f; /* p_byte bude mířit na první byte proměnné f. */ /* Adresa proměnné f je adresa jejího prvního bytu a my chceme f procházet po bytech.*/ for (i = 0; i < sizeof(double); p_byte++, i++) /* p_byte++ znamená přechod na adresu následujícího bytu. */ printf("%d. byte = %02Xh\n", i, *p_byte); return 0; }

100 100 Pointerová aritmetika – porovnávání pointerů Lze použít operátory, >=, ==, !=. Porovnávané pointery musí být stejného typu a musí ukazovat na totéž pole nebo řetězec. Paměť může být segmentovaná a potom různá pole nebo řetězce mohou být uloženy v různých segmentech. Porovnávání pointerů (adres) z různých segmentů nedává smysl. Například, jsou-li p_c a p_d pointery na char, přičemž p_c ukazuje na začátek bloku dat délky max (to znamená, že tam je max položek), pak je možné zjistit, zda p_d ukazuje dovnitř tohoto bloku takto: (p_d >= p_c && p_d < p_c + max) Znaky z tohoto pole bychom tiskli takto: for (p_d = p_c; p_d < p_c + max; p_d++) printf("%c", *p_d); Rychlé kopírování bloku délky max z adresy p_c na adresu p_d: for (p_t = p_c; p_t < p_c + max; ) *p_d++ = *p_t++; –Tento příkaz se dá rozepsat na tři příkazy: *p_d = *p_t; p_d++; p_t++; –Po ukončení kopírování ukazuje p_d na první bajt za zkopírovaným blokem, takže potom je vhodný příkaz p_d -= max;, který nastaví p_d na začátek zkopírovaného bloku dat.

101 101 Pointerová aritmetika – odečítání pointerů Výraz p_d - p_c má smysl pouze ukazují-li pointery na stejné pole dat. V tomto případě slouží ke zjištění počtu položek pole mezi těmito pointery. Porovnávané pointery musí být stejného typu a musí ukazovat na totéž pole nebo řetězec. Výraz p_d - p_c se dá přepsat jako ((char *) p_d - (* char) p_c) / sizeof(*p_d); Například, jsou-li p_c a p_d pointery na char, přičemž p_c ukazuje na začátek bloku dat délky max, potom následující část programu nalezne v tomto bloku znak „?“ a vytiskne jeho pozici. Pokud není v bloku dat znak „?“ nalezen, vytiskne se „-1“. for (p_d = p_c; *p_d != '?' && p_d < p_c + max; p_d++) ; printf("%d\n", (p_d < p_c + max) ? p_d - p_c + 1 : -1); Sčítání pointerů –Výsledkem by byla nesmyslná hodnota.

102 102 Dynamické přidělování a navracení paměti Termín „dynamické“ znamená „za chodu programu“. ze zásobníku –lokální proměnné definované v programu z hromady –definice pointerů –datové struktury, u kterých předem neznáme jejich konečnou velikost dynamická pole, stromy –funkce malloc() z knihovny stdlib.h Přidělí z hromady blok paměti potřebné velikosti a vrátí jeho adresu. Měla by zajistit, aby přidělená paměť nekolidovala s ostatními daty. –funkce free() z knihovny stdlib.h Uvolní blok paměti.

103 103 Funkce malloc() Její parametr je typu unsigned int a udává počet bajtů, které chceme alokovat. Vrací pointer na void, který představuje adresu prvního přiděleného prvku. –Tento pointer je velmi vhodné přetypovat na pointer na odpovídající typ. Není-li v paměti dost místa pro přidělení požadovaného úseku, vrací hodnotu NULL. V následující ukázce se alokuje paměť pro 1000 hodnot typu int a otestuje se úspěšnost alokace. int *p_i; /* První varianta */ if ((p_i = (int *) malloc(1000 * sizeof(int))) == NULL) { printf("Málo paměti!\n"); exit(1); } /* Druhá varianta */ p_i = (int *) malloc(1000 * sizeof(int)); if (p_i == NULL) { printf("Málo paměti!\n"); exit(1); } Kolik bytů se skutečně zabere? –Operační systém obvykle zabere víc bytů, než je programem alokováno, a byty navíc používá ke správě paměti (například evidenci, že tento kousek paměti je obsazený). –Například ve Windows se přiděluje paměť po takzvaných paragrafech, což jsou násobky 16 bytů. –Je výhodnější přidělovat méně delších úseků než více kratších úseků.

104 104 Funkce free() a calloc() Funkce free() –Je dobré dodržovat zásadu, že již nepotřebnou paměť je dobré okamžitě vrátit a nečekat až na konec programu, kdy se uvolní automaticky. –Její parametr je pointer na typ void, který ukazuje na začátek dříve přiděleného bloku. –Vrací již nepotřebnou paměť zpět na hromadu, čili uvolní ji pro další libovolné použití. –Nemění hodnotu svého parametru. To znamená, že pointer stále ukazuje na totéž místo v paměti. S touto pamětí se dá tedy dále pracovat, ale ve skutečnosti už programu nepatří. Takové využívání uvolněné paměti může způsobit množství problémů. –Po příkazu free((void *) p_c); je tedy vhodné uvést bezprostředně i příkaz p_c = NULL; –Nestačí tedy pointer definovat, ale před přidělením hodnoty paměťovému místu je nutné paměť přidělit funkcí malloc(). –Přidělujeme-li do pointeru novou paměť pomocí funkce malloc(), je nutné předchozí paměť přiřazenou tomuto pointeru funkcí malloc() uvolnit funkcí free(). p_c = malloc(1); Alokace paměti pro typ char. V jazyce C je zajištěno, že char zabírá ve všech implementacích 1 byte. *p_c = 'a'; Na adrese v p_c je teď znak 'a'. p_c = malloc(20); Alokace paměti pro 20 bytů, na které bude mířit pointer, který předtím mířil na znak 'a'. Od této chvíle už nejsme schopni do skončení programu uvolnit paměť, na které je uložen znak 'a'. Měli jsme ji předtím uvolnit funkcí free(). –Tato chyba se nazývá únik paměti (memory leakage).memory leakage Funkce calloc() –Od funkce malloc() se liší syntaxí a tím, že přidělenou paměť vynuluje, což je výhodné u řetězců. řetězců p_i = (int *) malloc(1000 * sizeof(int)); p_i = (int *) calloc(1000, sizeof(int));

105 105 Pointer jako skutečný parametr funkce Program přečte z klávesnice 10 double čísel, uloží je do paměti a vypočítá jejich součin. #include const int SIZE = 10; void init(double **p_f) /* Adresa alokované paměti je uložena do parametru funkce. */ { *p_f = (double *) malloc(SIZE * sizeof(double)); /* Viz procedura inicializuje pointer. */procedura inicializuje pointer } void cteni(double *p_f) { int i; for (i = 0; i < SIZE; i++) { printf("Zadejte %d. číslo: ", i + 1); scanf("%lf", p_f + i); } void nasob(double *p_f, int velikost, double *p_soucin) { for (velikost--, *p_soucin = *(p_f + velikost); --velikost >= 0; ) *p_soucin *= *(p_f + velikost); } int main(void) { double *p_dbl, souc; init(&p_dbl); cteni(p_dbl); nasob(p_dbl, SIZE, &souc); printf("Součin čísel je: %.3f.\n", souc); return 0; }

106 106 Jednorozměrná pole Patří mezi strukturované datové typy. Je to posloupnost stejných prvků. –vektor Pole v jazyce C nemají volitelnou dolní mez. –Pole vždycky začíná prvkem s indexem 0. –Je tak zvýšena efektivnost přístupu do pole. –Souvisí to s pointery, které s poli spolupracují.

107 107 Statická pole const int MAX = 10; int x[MAX] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; Byla definována symbolická konstanta MAX.symbolická konstanta Bylo definováno pole x o 10 prvcích typu int. Pole x má potom platné indexy 0 až 9. Co se stane, když použijeme index mimo rozsah, například x[10] = 1;? –Nic nebo bude program fungovat nějak špatně. –Jazyk C zásadně nekontroluje meze polí. –Kompilátor dokonce neposkytuje varovná hlášení. –Kontroly by totiž byly časově náročné. –Typickou chybou je překročení rozsahu pole o jedničku. –Nedoporučeníhodný typ příkazu: a[i] = i++; –Abychom pořád nemuseli myslet na to, že musíme od indexu odečítat jedničku, můžeme definovat pole o jeden prvek větší: int x[MAX + 1];

108 108 Příklad se statickým polem Program zjistí počet jednotlivých písmen v souboru TEXT.TXT. Malá písmena konvertuje na velká. #include #define POCET ('Z' - 'A' + 1) /* const int POCET = 'Z' - 'A' + 1; V některých překladačích nefunguje, protože hodnota konstanty POCET není známa při překladu ale až při běhu programu. */V některých překladačích nefunguje /* Viz též Rozdíl mezi #define a const */Rozdíl mezi #define a const int main(void) { FILE *fr; int c, i, pole[POCET]; for (i = 0; i < POCET; i++) pole[i] = 0; /* Nulování pole */ fr = fopen("TEXT.TXT", "r"); while ((c = getc(fr)) != EOF) { if (isalpha(c)) /* if (c >= 'A' && c = 'a' && c <= 'z') */ pole[toupper(c) - 'A']++; /* pole [(c >= 'a' && c <= 'z') ? c - ('a' - 'A') - 'A': c - 'A']++; */ } printf("V souboru byl tento počet jednotlivých písmen:\n"); for (i = 0; i < POCET; i++) printf("%c - %d\n", i + 'A', pole[i]); fclose(fr); return 0; }

109 109 Vztah mezi poli a pointery Začíná-li v jazyce C každé pole indexem 0, pak se dá lehce vypočítat adresa libovolného prvku podle vztahu: –&x[i] = adresa prvku x[0] + i * sizeof(typ); V jazyce C je jméno pole, v našem případě x, adresou paměti, neboli ukazatelem na začátek pole, stejně jako jméno funkce je adresou, na které je výsledek funkce.stejně jako jméno funkce je adresou, na které je výsledek funkce –Výraz x je totožný s výrazem &x. x je konstantní pointer, tedy není to l-hodnota. Výraz x[i] je totožný s výrazem *(x + i) a tedy i i[x].a tedy i x + i je adresa daná součtem bázové adresy pole představované hodnotou x a indexu představovaného hodnotou i. Operátor * pak umožňuje získat obsah na této adrese. Příkaz *x = 2; je totožný s příkazem x[0] = 2;

110 110 Dynamická pole #include int main(void) { int *p_i, i, pocet = 4; p_i = (int *) malloc(pocet * sizeof(int)); for (i = 0; i < pocet; i++) p_i[i] = i; /* Inicializace pole pomocí indexů */ for (i = 0; i < pocet; i++) printf("%d\n", *(p_i + i)); /* Výpis pole pomocí pointerů */ return 0; } Přístup k prvkům pole pomocí pointerů bývá obyčejně mnohem efektivnější než přístup pomocí indexace. V tomto případě se totiž pro získání dalšího prvku pole pouze připočítává konstanta, tj. velikost prvku pole, k aktuální adrese současného prvku, kdežto při indexaci je nutné nejdříve touto konstantou vynásobit index a výsledek pak přičíst k bázové adrese, viz předchozí snímek.efektivnější předchozí snímek Záleží na implementaci a na tom, jak překladač optimalizuje. U dobrých překladačů a v jednoduchých příkladech by tyto časy měly být stejné, protože kompilátor by měl přístup pomocí indexů převést na přístup pomocí pointerů.

111 111 Práce s poli Často se vyskytne definice s inicializací: int x[10], *p_x = x, y[10], *p_y = y; *p_z; –statické pole a pointer, který se bude používat pro rychlejší práci s polem Práce s celým polem najednou není možná, je třeba projít celým polem pomocí cyklu. –kopírování for (i = 0; i < 10; i++) x[i] = y[i]; /* Pomocí indexů */ for ( ; p_x < x + 10; ) /* Inicializaci už máme v definici, inkrementace je v těle cyklu. */ *p_x++ = *p_y++; /* Pomocí pointerů, viz pointerová aritmetika */pointerová aritmetika –porovnávání for (i = 0; i < 10; i++) if (x[i] != y[i]) /* Pomocí indexů */ break; if (i < 10) printf("Pole nejsou stejná.\n"); for ( ; p_x < x + 10; ) if (*p_x++ != *p_y++) /* Pomocí pointerů */ break; Zjištění velikosti pole –Operátor sizeof dává odlišné výsledky pro statická a dynamická pole. *p_z = (int *) malloc(10 * sizeof(int)); sizeof(x) == 10 * sizeof(int); /* Velikost statického pole */ velikost celého pole sizeof(p_z) == sizeof(int *) /* Velikost dynamického pole */ velikost pointeru neboli adresy, na které je uložena hodnota typu int

112 112 Pole měnící svoji velikost Účelem pole měnícího svoji velikost je, aby využívalo jen tolik paměti, kolik jí skutečně potřebuje. Vždy, když potřebujeme změnit velikost pole, alokujeme nové pole, původní pole do něj překopírujeme a pak toto původní pole uvolníme. int *x, pocet = 10, *p_pom1, *p_pom2, *p_nove; x = (int *) malloc(pocet * sizeof(int)); /* Bylo by také dobré otestovat úspěšnost alokace. */Bylo by také dobré otestovat úspěšnost alokace. … p_nove = (int *) malloc((pocet + 10) * sizeof(int)); /* Alokace paměti pro větší pole */ p_pom1 = x; p_pom2 = p_nove; while (p_pom1 < x + pocet) /* Kopírování starého pole na novou adresu */ *p_pom2++ = *p_pom1++; pocet += 10; free((void *) x); /* Uvolnění starého pole */Uvolnění starého pole x = p_nove; /* Nové pole se teď jmenuje stejně jako dříve. /* Překopírování starého pole je časově náročné, takže než se do této strategie pustíme, je nutné se rozhodnout, zda potřebujeme spíše rychlost nebo šetření pamětí. Knihovna stdlib.h obsahuje funkci realloc(). void *realloc(void *pole, unsigned int size); kde pole je pointer na již dříve alokovanou oblast paměti a size je počet bajtů nově požadovaného pole. realloc() upravuje velikost alokované paměti na hodnotu size při zmenšování nebo alokuje jinou větší oblast paměti a původní paměť do ní překopíruje při zvětšování a pak uvolní. realloc() vrací pointer na nově alokovanou paměť nebo nulový pointer NULL, pokud nelze pole realokovat.

113 113 Pole jako parametry funkcí Funkce nalezne největší prvek z pole o počtu prvků rovných ROZSAH. ROZSAH je symbolická konstanta.symbolická konstanta double maxim(double pole[]) /* Pole jako formální parametr */ { double *p_max = pole, *p_pom; for (p_pom = pole + 1; p_pom < pole + ROZSAH; p_pom++) { if (*p_pom > *p_max) p_max = p_pom; } return *p_max; } Hlavičku této funkce lze ekvivalentně zapsat double maxim(double *pole) Prvnímu způsobu se dává přednost, protože je tak jasnější, že parametr je použit ve smyslu pole typu double. Hlavička double *pole má význam „parametr typu double je předáván odkazem“. Volání funkce max = maxim(pole_a); kdy skutečný parametr pole_a říká pouze: „od symbolické adresy pole_a začíná pole s prvky typu double“. Z výše uvedeného vyplývá fakt, že pokud je ve funkci pracující s polem nutno znát jeho velikost, pak se tato velikost musí předat jako další formální parametr. Z pouhého skutečného parametru jména pole není překladač schopen velikost pole zjistit. Platí to pro pole dynamické i statické. Hlavička funkce by v tomto případě vypadala například takto: double maxim(double pole[], int pocet) –Maximum ze třetího až sedmého prvku pole je volání max = maxim(f + 2, 5); max = maxim(&f[2], 5); Častý, ale chybný, pokus o předání pole a současně jeho délky jako jediného formálního parametru je: double maxim(double pole[10]) Hodnota 10 zde nemá žádný význam a překladač ji ignoruje.

114 114 Pole pointerů na funkce a operátor typedef Pomocí operátoru typedef lze vytvořit nový datový typ. To je dobré využívat zvláště pro strukturované datové typy a pointery. typedef int *P_INT; Vytvoří nový typ jako pointer na int a pojmenuje tento typ identifikátorem P_INT. P_INT p_i; /* Je potom stejné jako int *p_i; */ Prvky pole mohou být pointery. Pokud jsou to pointery opět na jednoduché proměnné, pak se většinou jedná o vícerozměrná pole, která budou probírána později. Zvláštním a občas využívaným polem pointerů je pole pointerů na funkce stejného typu. –typedef void (* P_FCE)() /* Definice pointeru na funkci vracející typ void */ –P_FCE funkce[10]; /* Definice pole 10 pointerů */ Toto pole je pak nutné naplnit adresami existujících funkcí, což se dělá naprosto stejně jako při přiřazování adresy funkce do pointeru na funkci.pointeru na funkci Možná praktická aplikace pole pointerů na funkce je program řízený pomocí menu. Adresy jednotlivých funkcí provádějících příslušné příkazy menu jsou uloženy v poli a odtud mohou být přímo volány pomocí indexu. P_FCE funkce[] = { file, edit, search, compile, run }; definice pole pointerů na funkce včetně jeho inicializace char prikaz[] = { "FESCR" }; definice pole přístupových znaků včetně jeho inicializace funkce[index](); volání funkce, kde index je výsledek hledání jednopísmenového příkazu v řetězci prikaz Je to alternativa k častějšímu způsobu zpracování menu promocí příkazu switch.switch

115 115 Řetězce Řetězec je speciální typ jednorozměrného pole složeného vždy z prvků typu char. Jazyk C má pro práci s ním standardní funkce v knihovně string.h. Řetězec je vždy ukončen znakem '\0'. –Znak, který má v ASCII tabulce kód 0. –Doporučuje se nepoužívat pro jeho zápis konstantu 0, protože použití znakové nuly '\0' zvyšuje čitelnost programu. –Podle znaku '\0' se pozná aktuální délka řetězce. Řetězec může mít libovolnou délku omezenou pouze velikostí paměti. Z této celkové přidělené paměti je využívaná jen její část od začátku až do prvního znaku '\0'. Veškeré další informace uložené až za '\0' jsou při standardním zpracování řetězců nedostupné, protože práce s řetězcem končí vždy dosažením prvního znaku '\0'. –Lze je ale využít, pokud se na řetězec díváme jako na jednorozměrné pole prvků typu char. Při definování řetězce musíme alokovat o jeden bajt více pro ukončující znak '\0'. Pokud zapomeneme na konec řetězce dát znak '\0' nebo tento znak omylem přepíšeme, považuje se za řetězec celá následující oblast paměti tak dlouho, dokud se někde dále v paměti tento znak neobjeví. To samozřejmě většinou vede k chybné funkci programu, zvlášť pokud do této paměti zapisujeme.

116 116 Definování a inicializace řetězce Staticky char s1_stat[10] = "Ahoj"; řetězec pro 9 znaků s indexy 0 až 8 char s2_stat[] = "Nazdar"; řetězec pro přesně tolik znaků, kolik má řetězcová konstanta "Nazdar" –Překladač si ukončovací znak '\0' přidá sám. –Statickému řetězci není možné přiřadit konstantu, protože adresa řetězce není l-hodnota. char str[10] = { '\0' }; /* inicializace na nulovou hodnotu, stackoverflow */inicializace na nulovou hodnotustackoverflow str = "Ahoj"; /* Nelze */ –řetězec jako pointer na typ char inicializovaný adresou řetězcové konstanty char *str = "Ahoj"; –Na rozdíl od předchozích způsobů je tento způsob dovolený v nejstarší verzi jazyka K&R C.K&R C Dynamicky #include char *s_dyn; s_dyn = (char *) calloc(10, sizeof(char)); /* Alokace 10 bajtů */ U řetězců je lepší použít funkci calloc() než malloc(), protože je inicializuje na nulu.calloc() strcpy(s_dyn, "Ahoj"); správné přiřazení hodnoty řetězci –zkopírování řetězcové konstanty do alokované paměti znak po znaku s_dyn = "Ahoj"; nesprávné přiřazení hodnoty řetězci –změna adresy v s_dyn na adresu řetězcové konstanty, čímž se nenávratně ztratila adresa původně alokované dynamické paměti a již nemáme 10 bajtů ale jen 5 »Memory leakageMemory leakage

117 117 Symbolické řetězcové konstanty #define JMENO_SOUBORU "DOPIS.TXT" const char *JMENO_SOUBORU = "DOPIS.TXT"; –V proměnné JMENO_SOUBORU je adresa prvního znaku řetězce "DOPIS.TXT". –Možno porovnávat operátorem „==“ a kopírovat operátorem „=“, protože se pracuje jen s adresou začátku řetězce. Při porovnání řetězců nezáleží na jejich obsahu ale na tom, jsou-li na stejné adrese, což záleží na překladači, tudíž je bezpečnější používat knihovní funkce strcmp() a strcpy().strcmp()strcpy() –I když je proměnná JMENO_SOUBORU deklarována bez const, nelze měnit její znaky: JMENO_SOUBORU[1] = 'a'; Některé překladače to však povolují a to může vést k havárii programu nebo přepisu shodného řetězce v jiné proměnné, proto se doporučuje používat modifikátor const. –Překladač může umisťovat shodné řetězce na jediné místo v paměti a různé proměnné typu pointer na char mají adresu tohoto místa jako svoji hodnotu. const char JMENO_SOUBORU[] = "DOPIS.TXT"; –Po spuštění programu se alokuje paměť pro proměnnou JMENO_SOUBORU a zkopíruje se do ní řetězec "DOPIS.TXT". –Porovnávání a kopírování je možné jen pomocí knihovních funkcí strcmp() a strcpy() znak po znaku.strcmp() strcpy() –Je-li proměnná JMENO_SOUBORU deklarována bez const, lze měnit její znaky: JMENO_SOUBORU[1] = 'a'; Konstantní pole řetězcůpole řetězců const char *VETY[] = { "První věta", "Druhá věta", "Třetí věta" }; /* Počet vět lze vypočítat. /*Počet vět lze vypočítat. Viz kniha C Primer Plus: Representing Strings.C Primer PlusRepresenting Strings

118 118 Statická inicializace řetězce #include void zmen_retezec(char x[]) /* (char *x) je pro překladač stejné. */ { strcpy(x, "Te buch"); x[7] = '!'; x[8] = '\0'; /* Jakmile nepoužíváme knihovní funkce, nemůžeme spoléhat na to, že za posledním znakem bude znak s kódem nula, proto ho přidáme sami. */ } int main() { char text1[20] = "Ahoj"; /* text1 je názvem adresy se začátkem řetězce. */ char *text2 = "Nazdar"; /* text2 je název pro místo paměti s adresou začátku řetězce. */ strcpy(text1, "Nazdarek"); /* V pořádku, pokud je nový řetězec kratší než 20 znaků. */ /* strcpy(text2, "Cau"); Přeloží se v pořádku, ale skončí běhovou chybou. */Přeloží se v pořádku, ale skončí běhovou chybou. /* text2[1] = 's'; Přeloží se v pořádku, ale skončí běhovou chybou. */Přeloží se v pořádku, ale skončí běhovou chybou. /* text2 = text1; Možné, protože do pointeru mohu přiřadit jinou adresu. */ /* text1 = text2; Nemožné, protože adresa statického řetězce není l-hodnota. */Nemožné, protože adresa statického řetězce není l-hodnota. printf("%p %p %s %s %c\n", text1, &text1, text1, &text1[0], *text1); /* Adresa text1 a &text1 je stejná. */ printf("%p %p %s %s %c\n", text2, &text2, text2, &text2[0], *text2); /* Adresa text2 a &text2 je jiná. */ zmen_retezec(text1); /* zmen_retezec(&text1); Špatně, protože text1 je už sám o sobě adresou. */ printf("%p %p %s %s %c\n", text1, &text1, text1, &text1[0], *text1); /* Adresa text1 a &text1 je stejná. */ return 0; }

119 119 Dlouhé řetězce #include int main(void) { printf("Here's one way to print a "); printf("long string.\n"); printf("Here's another way to print a \ long string.\n"); /* Další řádek nesmí být odsazen. */ printf("Here's the newest way to print a " "long string.\n"); /* ANSI C */ return 0; } Viz kniha C Primer Plus: Printing Long Strings.C Primer PlusPrinting Long Strings

120 120 Čtení a tisk řetězce Čtení scanf("%4s", s); Je-li řetězec staticky nebo dynamicky alokován pro 4 znaky, je vhodnější číst jej v omezeném formátu, aby program nepřepisoval nepřidělenou paměť. Řetězcová proměnná s se píše bez znaku &. Proč? –s je pole neboli pointer neboli proměnná, která obsahuje adresu. –Funkce scanf() má mnoho pravidel umožňujících čtení proměnných různých typů z textu, kde jsou určitým způsobem kombinovány, viz kniha Pavla Herouta Učebnice jazyka C, 1. díl, páté vydání, KOPP České Budějovice 2008 na straně 197 a 204. –Chceme-li přečíst řetězec z klávesnice bez předzpracování až do ukončujícího znaku '\n' nevčetně, použijeme standardní funkci gets(). Funkce fgets() se liší od gets() tím, že je pro textový soubor, hlídá přetečení řetězce a čte do ukončujícího znaku '\n' včetně. Funkce fgets() se liší od scanf() tím, že neukončuje čtení na mezeře nebo tabelátoru. Funkce gets() se nedoporučuje používat, protože uživatelský vstup může být delší než velikost bufferu, což se dá zneužít.zneužít Funkce gets() se doporučuje nahradit funkcí fgets() způsobem fgets(s, max, stdin);fgets(s, max, stdin); Tisk printf("%s", s); Řetězec se tiskne stejně jako jakýkoli jiný typ proměnné. –Funkce printf() má mnoho pravidel umožňujících formátovaný tisk různých typů proměnných, viz kniha Pavla Herouta Učebnice jazyka C, 1. díl, páté vydání, KOPP České Budějovice 2008 na straně 209. –Chceme-li tisknout řetězec efektivněji bez předzpracování, použijeme standardní funkci puts(), která zadaný řetězec vytiskne a sama odřádkuje. Funkce fputs() se od ní liší tím, že je pro textový soubor a po zapsání řetězce neodřádkuje a nezapisuje do souboru ukončovací znak řetězce '\0'.

121 121 Funkce pro napsání řetězce pozpátku Funkce s prototypem char *strrev(char *str); není standardní. #include #define DELKA_RETEZCE 32 void pozpatku(char str[]) { int zacatek, konec; char temp; for (zacatek = 0, konec = strlen(str) - 2; zacatek < konec; zacatek++, konec--) { temp = str[zacatek]; str[zacatek] = str[konec]; str[konec] = temp; } int main() { char s[DELKA_RETEZCE]; printf("Zadej retezec kratsi nez %d znaku: ", DELKA_RETEZCE - 1); fgets(s, DELKA_RETEZCE, stdin); pozpatku(s); /* Řetězec předaný funkci jako parametr bude změněn. */ printf("Retezec pozpatku: "); puts(s); return 0; }

122 122 Funkce pro napsání řetězce pozpátku #include #define DELKA_RETEZCE 32 char *pozpatku(char *str, char *str_pozpatku) { char *konec, *znak_v_str_pozpatku; konec = str + strlen(str) - 2; znak_v_str_pozpatku = str_pozpatku; while (konec >= str) { *znak_v_str_pozpatku++ = *konec--; } return str_pozpatku; } int main() { char s[DELKA_RETEZCE], s_pozpatku[DELKA_RETEZCE] = { '\0' }; printf("Zadej retezec kratsi nez %d znaku: ", DELKA_RETEZCE - 1); fgets(s, DELKA_RETEZCE, stdin); printf("Retezec pozpatku: %s\n", pozpatku(s, s_pozpatku)); puts(s_pozpatku); puts(s); /* Původní řetězec se nemění. */ return 0; } char *pozpatku(char *str, char *str_pozpatku) { int zacatek, konec; zacatek = 0; konec = strlen(str) - 2; while (konec >= 0) { str_pozpatku[konec--] = str[zacatek++]; } return str_pozpatku; }

123 123 Formátované čtení a zápis z a do řetězce čtení –Funkce sscanf() čte do specifikovaných proměnných ze zadaného řetězce. tisk –Funkce sprintf() zapisuje výsledek své práce do řetězce. Tyto funkce dokážou –převádět číslo do a z osmičkové a šestnáctkové soustavy, scanf("%s", s1);/* Čtení z klávesnice do s1 */ sscanf(s1, "%x", &i);/* Čtení z s1 do celočíselné proměnné i */ sprintf(s2, "%o", i);/* Tisk do s2 z i */ –zaokrouhlovat mezivýsledky, sprintf(s, "%.2f", f);/* Tisk zaokrouhleného f do s z f */ sscanf(s, "%lf", &f);/* Čtení z s do f */ –opatřovat názvy souborů s čísly nulami na začátku, for (i = 1; i < 20; i++) { sprintf(jmeno, "obr%02d.jpg", i); printf("%s\n", jmeno); } –generovat řídící řetězec formátu pro funkce scanf() a printf(), abychom se vyhnuli nutnosti dávat do nich literály.generovat řídící řetězec formátu

124 124 Přístup k jednotlivým znakům řetězce S řetězcem pracujeme jako s normálním polem. Je-li řetězec definován jako char s[] = "ahoj"; potom výraz s[3] == 'j' ale i 3[s] nebo 3["ahoj"].ale i Následující část programu vyplní řetězec s definovaný jako char s[max]; hvězdičkami: for (i = 0; i < max - 1; i++) s[i] = '*'; s[max – 1] = '\0'; Na ukončovací znak '\0' se nesmí nikdy zapomenout. Je nutné hlídat meze, abychom nezapisovali do paměti, která nám už nepatří. –Ukončovací znak musí být v prvku s indexem max - 1 nebo nižším.

125 125 Výpis řetězce po jednotlivých znacích char s[] = "Ahoj"; /* společná definice */ char *s_pom = s; while (*s_pom != '\0') putchar(*s_pom++); –Nejdřív se provede výpis znaku a potom inkrementace adresy, viz tabulka precedence operátorů. tabulka precedence operátorů while (*s != '\0') /* To samé bez pomocného pointeru nelze. */ putchar(*s++); /* lvalue required as increment operand */lvalue required as increment operand int i, delka; for (i = 0; i < strlen(s); i++) printf("%c", s[i]); /* místo toho může být putchar(s[i]); */ –Při každé iteraci se volá funkce strlen() i když se její výsledek nemění. –Délku bychom měli spočítat před cyklem nebo použít operátor čárky:operátor čárky for (i = 0, delka = strlen(s); i < delka; i++) Proč funguje i cyklus s řídící částí for (i = 0; s[i]; i++) ?

126 126 Standardní funkce pro práci s řetězci Chceme-li tyto funkce využívat, je nutné připojit do našeho programu standardní hlavičkový soubor string.h příkazem #include. Zde jsou uvedeny jen ty nejdůležitější funkce formou úplného funkčního prototypu: int strlen(char *s); Vrací délku řetězce s bez ukončovacího znaku '\0'. char *strcpy(char *s1, char *s2); Zkopíruje obsah řetězce s2 do s1 a vrátí pointer na první znak řetězce s1. char *strcat(char *s1, char *s2); Připojí řetězec s2 k řetězci s1 a vrátí pointer na první znak řetězce s1. char *strchr(char *s, char c); Pokud se znak v proměnné c vyskytuje v řetězci s, pak je vrácen pointer na jeho první výskyt, jinak je vrácena hodnota NULL. příklad použití int strcmp(char *s1, char *s2); Vrátí 0, jsou-li řetězce s1 a s2 stejné. Vrátí záporné číslo, je-li s1 lexikograficky menší než s2 a kladné číslo v opačném případě. char *strstr(char *s1, char *s2); Nalezne první výskyt řetězce s2 v řetězci s1 a vrátí pointer na tento výskyt nebo vrátí NULL v případě neúspěchu.

127 127 Další standardní funkce pro práci s řetězci Knihovna string.h –Práce s omezenou částí řetězce varianty předchozích standardních funkcí s písmenem n (number) v názvu například char *strncpy(char *s1, char *s2, int max); –Po příkazu strncpy(s, "alkoholické", 7); bude v řetězci s "alkohol" neukončený znakem '\0'. –Znak '\0' přidá jen pro max > strlen(s2). –Práce s řetězcem pozpátku varianty předchozích standardních funkcí s písmenem r (reverse) v názvu například char *strrchr(char *str, char c); –Pokud se znak v proměnné c vyskytuje v řetězci str, pak je vrácen pointer na jeho poslední výskyt, jinak je vráceno NULL. Knihovna stdlib.h –Převody řetězců na čísla v desítkové soustavě (ASCII to Integer, Long, Long Long, Float) int atoi(), long int atol(), long long int atoll() (new in C99), double atof() společné formální parametry: (const char* str) Mají nedefinované chování, když je konvertovaná hodnota mimo interval reprezentovatelných hodnot. –Převody řetězců na čísla Pointer end je funkcemi nastaven na první znak za platnou součástí čísla. Při přetečení datového typu pro číslo je konstanta errno nastavena na ERANGE.errno Celá čísla v binární až třicetšestkové soustavě (parametr base) long int strtol(), long long int strtoll(), unsigned long int strtoul(), unsigned long long int strtoull() –společné formální parametry: (const char* str, char** end, int base) –Když je konvertovaná hodnota mimo interval reprezentovatelných hodnot, funkce vrací příslušné mezní hodnoty z knihovny limits.h.limits.h Reálná čísla v desítkové soustavě float strtof() (new in C99), double strtod(), long double strtold() (new in C99) –společné formální parametry: (const char* str, char** end) –Když je konvertovaná hodnota mimo interval reprezentovatelných hodnot, funkce vrací varianty konstanty HUGE_VAL z knihovny math.h.math.h Knihovna stdio.h –Převody čísel na řetězce nebo zpět viz funkce sscanf() a sprintf().sscanf() a sprintf()

128 128 Vícerozměrná pole Definice statického vícerozměrného pole int x[2][3]; /* Matice s 2 krát 3 hodnotami typu int */ Budeme-li definovat více zcela stejných polí, vyplatí se vytvořit nový datový typ pomocí operátoru typedef:typedef typedef int M[2][3]; M x, y, z; Přístup k prvkům pomocí indexů je stejný jako přístup do jednorozměrného pole: x[1][0] = 5; /* Správně */ x[1, 0] = 5; /* Chybně */ Tří a vícerozměrná pole jsou analogická dvourozměrným: int kostka[3][3][3]; kostka[0][0][0] = 10;

129 129 Uložení statických vícerozměrných polí v paměti Tato znalost je nutná pro efektivní práci s poli. int x[2][3]; /* Alokuje v paměti 2 * 3 * sizeof(int) bytů. */ x[1][2] = 0; je to to samé jako *(*(x + 1) + 2) = 0;je to to samé jako Dvourozměrné pole je uloženo v paměti po řádcích. Situace v paměti, pokud sizeof(int) = 2: x = pointer Index sizeof(*x) = 6 = 2 * 3 x[0] = pointer s adresou x + 0 a hodnotou *(x + 0) x[1] = pointer s adresou x + 1 a hodnotou *(x + 1) Index sizeof(*x[0]) = 2 [0][0][0][1][0][2][1][0][1][1][1][2] Adresax[0] + 0x[0] + 1x[0] + 2x[0] + 3x[0] + 4x[0] + 5 Byte Dvourozměrné pole v jazyce C je jednorozměrné pole, které má prvky pointery na další jednorozměrná pole. Obsahem prvního prvku x[0] jednorozměrného pole je pointer na první řádku dvourozměrného pole x. Výhody –Není potřeba paměť pro pomocné pointery. –Umožňuje to asi nejefektivnější přístup k jednotlivým položkám pole, protože ty jsou v zásobníku (u lokální definice) nebo v datové oblasti (u globální definice).lokální definiceglobální definice Nevýhody –Takto lze vytvořit pouze obdélníkové pole.

130 130 Dynamické vícerozměrné pole 1. varianta Definujeme jednorozměrné pole 2 pointerů na typ int: int *x[2]; Tyto pointery dále využijeme jako ukazatele na jednotlivé řádky pole: x[0] = (int *) malloc(3 * sizeof(int)); x[1] = (int *) malloc(3 * sizeof(int)); –Pro každou řádku jednotlivě alokujeme paměť pomocí funkce malloc(). –Je nutné testovat úspěšnost přidělení, která ve výše uvedeném příkladu není.úspěšnost přidělení Po této alokaci je pak možné teď už dvourozměrné dynamické pole x normálně používat, například x[0][2] = 5; Je to velmi často využívaný typ pole Řádky pole neleží v paměti bezprostředně za sebou, protože jsou alokovány zvlášť. –Chybný příkaz x[0][3] = 8; Přiřazujeme do 1. řádky a 4. sloupečku, ale v poli jsou jen 3 sloupečky. Kdyby pole bylo statické, tento příkaz by přiřadil hodnotu 8 do prvku x[1][0]. Když je pole dynamické, tak tímto příkazem zapisujeme pravděpodobně do paměti, která do pole nepatří, a může dojít ke zhroucení programu. Každý řádek může mít jiný (i nulový) počet sloupců.

131 131 Dynamické vícerozměrné pole 2. varianta Definujeme jeden pointer na pole 3 prvků typu int: int (*x)[3]; Celé dvourozměrné pole potom můžeme alokovat jediným příkazem: x = (int (*)[3]) malloc(2 * 3 * sizeof(int)); x nyní ukazuje na blok 6 prvků typu int sdružených díky definici x po trojicích, které leží v paměti za sebou. Je to tedy prakticky obdoba statického pole jen s tím rozdílem, že je celé uloženo v dynamické paměti. Přístup k prvkům je opět shodný: x[0][2] = 5; Tento typ pole se využívá ve speciálních případech. Použijeme-li chybný příkaz x[0][3] = 8; pak se hodnota 8 zapíše do prvku x[1][0]. Výhody: –Pole může mít proměnlivý počet řádků. –Přístup do pole bude téměř stejně rychlý jako u statického pole.statického pole Nevýhody: –Pole může být pouze obdélníkové.

132 132 Dynamické vícerozměrné pole 3. varianta Když uděláme definici int **x; pak jsme vytvořili pointer na pointer a platí, že x je pointer na pointer na typ int, *x je pointer na typ int, **x je prvek typu int. Pro vytvoření dvourozměrného pole je nutné učinit dva kroky: –alokovat paměť pro 2 pointery na řádky příkazem x = (int **) malloc(2 * sizeof(int *)); Čili nyní je možno využívat 2 prvky pole x[0] a x[1]. –alokovat paměť pro 3 prvky typu int na řádce příkazy x[0] = (int *) malloc(3 * sizeof(int)); x[1] = (int *) malloc(3 * sizeof(int)); Pro výsledné pole platí stejná pravidla jako pro pole definované v 1. variantě.1. variantě Výhody –Je možný proměnlivý počet řádků i sloupků tak, že pole nemusí být obdélníkové. Nevýhody –Je nutná paměť pro 3 pointery navíc. –Dá se předpokládat, že přístup do pole x bude pravděpodobně nejpomalejší ze všech předchozích variant.

133 133 Dynamické vícerozměrné pole 3. varianta jako funkce #include int **vytvor_pole(int radky, int sloupce) { int **p_p_x, i; p_p_x = (int **) malloc(radky * sizeof(int *)); for (i = 0; i < radky; i++) p_p_x[i] = (int *) malloc(sloupce * sizeof(int)); return p_p_x; } void uvolni_pole(int **p_p_x, int radky) { int i; for (i = 0; i < radky; i++) free((void *) p_p_x[i]); } int main(void) { int **a, **b; a = vytvor_pole(3, 11); b = vytvor_pole(8, 4); uvolni_pole(a, 3); uvolni_pole(b, 8); return 0; }

134 134 Dynamické vícerozměrné pole 4. varianta – Method 4Method 4 Definujeme x jako pointer na pointer: int **x; Definujeme p_i jako pointer na typ int: int *p_i; Alokujeme paměť pro pole: p_i = (int *) malloc(2 * 3 * sizeof(int)); Alokujeme paměť pro pointery na začátky řádků: x = (int **) malloc(2 * sizeof(int *)); Nasměrujeme pointery na začátky řádků: x[0] = p_i + (0 * 3); x[1] = p_i + (1 * 3); Výhody –Je možný proměnlivý počet řádků i sloupků tak, že pole nemusí být obdélníkové. Nevýhody –Je nutná paměť pro 3 pointery navíc.

135 135 Dvourozměrné pole s různou délkou řádek Pole pro část matice pod diagonálou (zde včetně diagonály) se může definovat pomocí varianty č. 1, 3 nebo Dle 1. varianty: int *x[3]; for (i = 0; i < 3; i++) x[i] = (int *) malloc((i + 1) * sizeof(int)); Z dvourozměrného pole x jsou pak dostupné prvky: x[0]→x[0][0] x[1]→x[1][0]x[1][1] x[2]→x[2][0]x[2][1]x[2][2]

136 136 Dvourozměrné pole jako parametr funkce Následující funkce vrátí největší prvek z dvourozměrného pole, jehož každá řádka má 4 prvky typu double. #define SLOUPCE 4 double maxim(double pole[][SLOUPCE], int radky) /* možno též double maxim(double (*pole)[SLOUPCE], int radky) */ { double pom = pole[0][0]; int i, j; for (i = 0; i < radky; i++) { for (j = 0; j < SLOUPCE; j++) { if (pole[i][j] > pom) pom = pole[i][j]; } return pom; } Volání funkce pro pole definované jako double x[r][SLOUPCE]: nejvetsi = maxim(x, r); Velikost první dimenze se vynechává stejně jako u jednorozměrného pole.jako u jednorozměrného pole Velikost druhé dimenze, zde SLOUPCE, musí být uvedena ve formálních parametrech funkce, protože do funkce se předává pointer a k němu překladač potřebuje informaci o velikosti řádku, což je SLOUPCE * sizeof(double), aby mohl určit adresu x + 1.adresu x + 1 U vícerozměrných polí musí být ve formálních parametrech funkce uvedeny všechny dimenze kromě první.

137 137 Pole řetězců Je to nejčastěji využívané dvourozměrné pole s různou délkou jednotlivých řádek. Využívají jej programy pracující s textem. Nejčastější způsob definice char *s[] = { "ahoj", "nazdar" }; Jiný způsob definice –Definujeme pole dvou pointerů na řetězce: char *s[2]; –Tomuto poli pointerů můžeme přiřadit hodnoty – adresy řetězců. s[0] = "ahoj"; /* Staticky */ s[1] = (char *) malloc(7); strcpy(s[1], "nazdar"); /* Dynamicky */ Situace v paměti s→s[0] adresas[1] adresa s[0]→s[0][0] = 'a's[0][1] = 'h's[0][2] = 'o's[0][3] = 'j's[0][4] = '\0' s[1]→s[1][0] = 'n's[1][1] = 'a's[1][2] = 'z's[1][3] = 'd's[1][4] = 'a's[1][5] = 'r's[1][6] = '\0'

138 138 Tisk pole řetězců Tisk řetězce po znacích pomocí pointeru char *p_pom = s[0]; while (*p_pom != '\0') putchar(*p_pom++); Tisk řetězce najednou pomocí funkce printf() printf("%s\n", s[0]); Tisk řetězce najednou pomocí funkce puts()puts() puts(s[0]); Tisk pole řetězců pomocí funkce puts() –Nestandardní postup char **p_pom = s; pocet_s = sizeof(s) / sizeof(char *); /* Viz zjištění velikosti pole. */zjištění velikosti pole for (i = 0; i < pocet_s; i++) puts(*p_pom++); –Dle precedence operátorů se nejdříve (díky postfix verzi inkrementu až po vytisknutí řetězce s[0]) zvýší hodnota adresy p_pom z s[0] na s[1].precedence operátorůpostfix verzi inkrementu –puts() vytiskne řetězec začínající na této adrese do znaku '\0'. Co by se stalo po tomto příkazu? char **p_pom = s; puts(++*p_pom); –V pointeru *p_pom je adresa prvního znaku řetězce s[0]. –Tato adresa se zvýší o jednu, takže p_pom i s teď míří na druhý znak řetězce s[0] a vytiskne se „hoj“.

139 139 Inicializace polí všech rozměrů Inicializace polí se provádí nejčastěji u řetězců.řetězců Inicializace pole typu double double f[3] = { 1.5, 3.0, 7.6 }; Není-li uveden počet prvků pole, kompilátor si ho určí sám podle počtu inicializačních hodnot. double f[] = { 1.5, 3.0, 7.6 }; Je-li uveden počet prvků pole a inicializačních hodnot je méně, pak zbývající prvky budou mít nulovou hodnotu a to i v případě inicializace automatických polí.automatických double f[3] = { 1.5, 3.0 }; f[2] bude mít hodnotu 0.0. Není možné uvést více inicializačních hodnot, než je prvků pole. double f[3] = { 1.5, 3.0, 7.6, 9.2 }; /* Chybně */ Dvourozměrné pole se inicializuje takto: double f[][2] = { { 1.5, 3.0 }, { 2.4, 8.7 }, { 7.6, 9.2 } }; –Přičemž počet sloupců musí být uveden a počet řádků může být uveden.

140 140 Zjištění velikosti pole Tento postup nelze využít pro předávání parametrů o velikosti pole do funkce.předávání parametrů o velikosti pole do funkce Pole jednoduchého datového typu int i[] = { 1, 2, 3, 4, 5 }; int pocet_i = sizeof(i) / sizeof(int); Pole pointerů na řetězec char *s[] = { "jedna", "dva", "tři", "čtyři" }; int pocet_s = sizeof(s) / sizeof(char *); Prvkem pole s je pointer na char. int pocet_s = sizeof(s) / sizeof(*s); s je pointer na první prvek s, tudíž *s je první prvek pole s.pointer na první prvek int pocet_s = sizeof(s) / sizeof(s[0]); s[0] je první prvek pole s. Pole statických řetězců neboli dvourozměrné pole znaků char s[][9] = { "jedna", "dva", "tři", "čtyři" }; Maximální počet znaků (sloupců) musí být uveden stejně jako u dvourozměrného pole.dvourozměrného pole int pocet_s = sizeof(s) / sizeof(char *); Chybně: Prvkem pole není pointer na typ char ale statický řetězec. int pocet_s = sizeof(s) / sizeof(*s); s je pointer na první prvek s, tudíž *s je první prvek pole s – statické pole 9 znaků.pointer na první prvek int pocet_s = sizeof(s) / sizeof(s[0]); s[0] je první prvek pole s. Viz též pole struktur.pole struktur

141 141 Návratová hodnota funkce main() V dosud uvedených příkladech byla návratová hodnota funkce main() buďto void nebo int. Pomocí návratové hodnoty lze předat volajícímu – což je v případě funkce main() operační systém, který program spustil – výsledek práce programu. Například v operačním systému MS-DOS se takto předaná hodnota zapsala do systémové proměnné ERRORLEVEL, odkud mohla být přečtena například v dávkovém souboru. Způsob využití návratové hodnoty ANSI C nijak nedefinuje a záleží vždy na konkrétním operačním systému.

142 142 Parametry funkce main() Účelem formálních parametrů funkce main() je předat programu argumenty ze vstupní příkazové řádky. Má-li funkce main() parametry, jsou vždy dva a jsou z historických důvodů pojmenovány vždy jako argc a argv. Takto je možné program nebo víc programů spustit z dávkového souboru (skriptu, *.bat) a po spuštění se program již neptá na další informace. Pokud uživatel nezadá při spuštění programu parametry, je obvyklé, že je program napsaný tak, aby vypsal návod nebo umožnil zadání těchto parametrů standardně z klávesnice. Typickými parametry jsou jména vstupních a výstupních souborů programu.

143 143 Parametry funkce main() Je-li program test.exe spuštěn příkazem test param1 param2 a funkce main() má hlavičku int main(int argc, char *argv[]) pak má parametr –argc hodnotu 3, protože udává počet řetězců na vstupní řádce – tedy „test“, „param1“ a „param2“ a –pole pointerů na řetězce argv ukazuje takto: argv[0] → "test" argv[1] → "param1" argv[2] → "param2" Argument, který je na příkazové řádce uzavřen do uvozovek, se počítá za jeden řetězec. Například po příkazu test "vstup 1.txt" setrid "vystup 1.txt" bude situace následovná: argc == 4 argv[0] → "test" argv[1] → "vstup 1.txt" argv[2] → "setrid" argv[3] → "vystup 1.txt" Pokud chceme v programu změnit parametry zadané na příkazové řádce, musíme je nejdříve překopírovat (pomocí například strcpy()) do řetězců definovaných v našem programu a s těmi dále pracovat. strcpy(pom, argv[1]); Je-li parametrem příkazové řádky číslo, je také předáno jako řetězec a musí se tedy před použitím převést na číslo některou z funkcí atoi(), atof() nebo sscanf().atoi(), atof()sscanf() int pocet = atoi(argv[1]);

144 144 Příklad funkce main() s parametry #include #define POCET_PARAMETRU 3 #define POCET_ZNAKU 32 int main(int argc, char *argv[]) { FILE *fw; char *konec_cisla; long int cislo; if (argc < POCET_PARAMETRU) { printf("Program se musi spustit se %d parametry:\n", POCET_PARAMETRU - 1); printf("1. jmeno vystupniho souboru,\n"); printf("2. cele cislo.\n"); return 1; } if ((fw = fopen(argv[1], "w")) == NULL) { printf("Soubor %s se nepodarilo otevrit.\n", argv[1]); return 1; } cislo = strtol(argv[2], &konec_cisla, 10); fprintf(fw, "%s\n%ld\n%ld\n", argv[0], cislo, cislo * cislo); if (errno || *konec_cisla) { fprintf(fw, "%d %s\nNeni cislo: %s\n", errno, sys_errlist[errno], konec_cisla); } if (fclose(fw) == EOF) { printf("Soubor %s se nepodarilo uzavrit.\n", argv[1]); return 1; } return 0; }

145 145 Struktura Struktura je vedle pole další strukturovaný datový typ. Od pole se liší tím, že jeho prvky mohou být různých datových typů. struct { int vyska; float vaha; } pavel, honza, karel; Při definování je vhodné používat typedef:typedef typedef struct { /* definice typu struktury */ int vyska; float vaha; } MIRY; /* Název struktury pište velkými písmeny. */ MIRY pavel, honza, karel; /* definice proměnných */ Přístup k prvkům struktury je pomocí tečkové notace. pavel.vyska = 186; karel.vaha = 89.5; honza.vyska = pavel.vyska; Je možné pracovat s celou strukturou najednou. honza = pavel; Což je možné využít pro kopírování pole jedním příkazem.kopírování pole typedef struct { int x[10]; /* Pole ve struktuře */ } STRUKTURA_POLE; STRUKTURA_POLE a, b; a.x[1] = 5; b = a; Reprezentace datových tabulek MIRY lide[100]; /* Struktura v poli */Struktura v poli lide[0].vyska = 176;

146 146 Struktury a pointery Pointery na struktury mají dvě velké oblasti použití: –při práci se strukturami v dynamické paměti, –při práci se strukturou ve funkci. Definice pointeru na strukturu typedef struct { char jmeno[30]; int rocnik; } STUDENT; STUDENT s, *p_s; Dynamické přidělení paměti pro strukturu p_s = (STUDENT *) malloc(sizeof(STUDENT)); Nastavení pointeru na již existující strukturu p_s = &s; Přístup k prvkům struktury –pomocí jména struktury s:s.rocnik = 3; –pomocí pointeru p_s komplikovaně:(*p_s).rocnik = 3; –pomocí pointeru p_s jednodušeji:p_s->rocnik = 3; –Příkaz *p_s.rocnik = 3; je chybně. Operátor tečka „.“ má vyšší prioritu než operátor dereference „*“. Na adresu, kam ukazuje obsah proměnné p_s.rocnik, se má zapsat hodnota 3.

147 147 Struktury odkazující samy na sebe Umožňují vytvářet dynamické datové struktury. Dynamické datové struktury umožňují efektivně přidávat a odebírat své prvky. Jednosměrný lineární spojový seznam typedef struct polozka { /* Struktura je pojmenována, aby bylo možné se na ni v ní odkázat. */ int hodnota; struct polozka *p_dalsi; } POLOZKA; /* Typ by se měl jmenovat stejně jako struktura, ale měl by být psán velkými písmeny. */ Strom typedef struct rodokmen { char jmeno[30]; struct rodokmen *p_otec; struct rodokmen *p_matka; } RODOKMEN;

148 148 Struktura v jiné struktuře Struktura je prvkem jiné struktury. –Například struktura ADRESA je položkou struktury OSOBA. typedef struct { char ulice[30]; /* Statický řetězec ve struktuře */Statický řetězec int cislo; } ADRESA; typedef struct { char jmeno[20]; ADRESA adresa; float plat; } OSOBA; –Přístup k prvkům struktury pomocí tečkové notace strcpy(lide[i].adresa.ulice, "Dlouhá"); –Efektivnější přístup k prvkům struktury pomocí pointerůEfektivnější přístup k prvkům struktury pomocí pointerů OSOBA lide[1000], *p_kdo = lide; strcpy((p_kdo + i)->adresa.ulice, "Dlouhá"); Prvkem struktury je dynamický řetězec. –Dynamický řetězec umožňuje šetřit pamětí. typedef struct { char *p_ulice; /* Dynamický řetězec ve struktuře */Dynamický řetězec int cislo; } ADRESA; –Alokace paměti pro řetězec ve struktuře lide[100].adresa.p_ulice = (char *) malloc(strlen("Dlouhá") + 1);

149 149 Alokace paměti pro jednotlivé položky struktury Položky struktury jsou v paměti umístěny za sebou v pořadí definic shora dolů a na řádce zleva doprava. Pointer na strukturu obsahuje adresu prvního bytu struktury. K prvkům struktury je nutné přistupovat vždy jen pomocí operátorů „.“ nebo „->“, protože výpočty pozic jednotlivých položek struktury vzhledem k začátku struktury jsou závislé na implementaci, tj. nejsou portabilní neboli přenositelné na jinou platformu. Velikost struktury se zásadně zjišťuje pomocí operátoru sizeof pro celou strukturu najednou, protože není rovna součtu velikosti prvků struktury díky zarovnávání v paměti.zarovnávání v paměti

150 150 Struktury a funkce Předávání struktury funkci hodnotou typedef struct { double re, im; } KOMP; KOMP secti(KOMP a, KOMP b) { KOMP c; c.re = a.re + b.re; c.im = a.im + b.im; return c; } int main(void) { KOMP x, y, z; x.re = 1.1; x.im = 3.14; y = x; z = secti(x, y); return 0 } Předávání struktury funkci odkazem typedef struct { double re, im; } KOMP; void secti(KOMP *p_a, KOMP *p_b, KOMP *p_c) { p_c->re = p_a->re + p_b->re; p_c->im = p_a->im + p_b->im; } int main(void) { KOMP x, y, z; x.re = 1.1; x.im = 3.14; y = x; secti(&x, &y, &z); return 0 } Návratová hodnota funkce může být struktura a struktura může být předána funkci jako skutečný parametr hodnotou nebo odkazem pomocí pointerů. Předávání parametrů odkazem pomocí pointerů je častější, protože se do funkce nekopíruje celá struktura ale jen jeden pointer.

151 151 Struktura v poli = Pole struktur Pole struktur se alokuje podobně jako vícerozměrná pole. Pole má pocet položek typu STRUKTURA. Statické pole struktur –Je obdobou této deklarace dvourozměrného pole.této deklarace dvourozměrného pole –Definuje se jako globální nebo lokální proměnná. –Jeho nevýhodou je, že velikost pole musí být známa v době překladu. Dynamické pole struktur –Je nejpodobnější této deklaraci dvourozměrného pole.této deklaraci dvourozměrného pole –Definuje se jako pointer na strukturu, kterému se přiřadí prostor pro pole struktury funkcí malloc(). STRUKTURA *p_stru; p_stru = (STRUKTURA *) malloc(pocet * sizeof(STRUKTURA)); –Jeho výhodou je, že velikost pole lze stanovit v době běhu programu. Statické pole pointerů na struktury a dynamické vytváření prvků struktury –Maximální možná velikost pole musí být známa v době překladu. –Je nejpodobnější této deklaraci dvourozměrného pole.této deklaraci dvourozměrného pole Pointer na pointer na struktury –Je nejpodobnější této deklaraci dvourozměrného pole.této deklaraci dvourozměrného pole –Alokuje se pointer na pole pointerů na jednotlivé struktury. STRUKTURA **p_stru; p_stru = (STRUKTURA **) malloc(pocet * sizeof(STRUKTURA *)); –Dále se alokuje paměť pro jednotlivé struktury. p_stru[i] = (STRUKTURA *) malloc(sizeof(STRUKTURA)); –Je to nejuniverzálnější pole, které nejvíce šetří paměť.

152 152 Inicializace struktur Struktury se inicializují podobně jako pole uvedením seznamu inicializačních hodnot. typedef struct { int i, j; float f; } PRIKLAD; PRIKLAD a = {1, 2, 6.4}; Podobně lze inicializovat i pole struktur: PRIKLAD b[] = { { 4, 5, 1.2 }, { 2, 8, 9.6 }, { 1, 1, 1.0 } }; –Počet prvků – jednotlivých struktur – tohoto pole lze zjistit pomocí příkazu pocet = sizeof(b) / sizeof(PRIKLAD);

153 153 Výčtový typ = Enumerated typeEnumerated type Zpřehledňuje program a zvyšuje jeho modularitu. Definuje se jím seznam symbolických konstant, které tvoří posloupnost. typedef enum { CERVENA, ORANZOVA, ZELENA /* Zde nesmí být středník! */ /* Takto byla provedena implicitní inicializace CERVENA = 0; ORANZOVA = 1; ZELENA = 2; */ } SEMAFOR; SEMAFOR barva = CERVENA; Položky výčtového typu nejsou l-hodnoty: CERVENA = 3; /* Chybně */ Štábní kultura –Položky výčtového typu se z konvence píší velkými písmeny. –Máme-li v programu více výčtových typů, například SEMAFOR a BARVY_AUT, tak se položky opatřují předponami, které odlišují členy jednotlivých skupin, např. BA_CERVENA. –První a poslední položka seznamu se může použít jako mez pro for cyklus pro procházení seznamem, viz kniha Code Complete: Visual Basic Example of Very Clear Code. Visual Basic Example of Very Clear Code

154 154 Inicializace výčtového typu Pokud explicitně nepřiřadíme číselné hodnoty jednotlivým prvkům, potom mají implicitní hodnoty 0, 1, 2, atd. Jednotlivé definované proměnné typu enum (např. a a b typu SEMAFOR) jsou vnitřně reprezentovány jako znaménkový celočíselný typ. Výčtový typ se používá pro definici booleovské hodnoty TRUE a FALSE. typedef enum { FALSE, TRUE /* FALSE = 0, TRUE = 1 */ } BOOLEAN; Někdy není nutné definovat proměnnou výčtového typu. Stačí se vzniklou konstantou porovnávat výrazy. if (isdigit(c) == FALSE) Explicitní inicializace –Je možné explicitně inicializovat jen některé prvky a pro zbývající pak platí, že jejich hodnota je vždy o 1 větší než hodnota předchozího prvku. typedef enum { PO = 1, UT, ST, CT, PA, SO, NE } DNY_TYDNE; Dáme-li položkám hodnoty rovné mocninám dvou, bude možné s nimi provádět množinové operace pomocí bitových logických operací.bitových logických operací –Užitečné v situaci, kdy chceme umožnit výběr libovolné podmnožiny prvků. –Prvky kvůli přehlednosti vždy rovnáme podle velikosti. typedef enum { OK = 1, ABORT = 2, RETRY = 4, IGNORE = 8 } TLACITKA;

155 155 Tisk výčtového typu Platí-li definice SEMAFOR barva = CERVENA; potom příkazdefinice printf("Na semaforu svítí %s.\n", a); je chybně. Je možné vytisknout pouze hodnotu položky výčtového typu a je vhodné ji předtím přetypovat na int. printf("Na semaforu svítí barva číslo %d.\n", (int) a); V případě, že jsou položky inicializovány různými hodnotami, je vhodným řešením použití přepínače switch. switch (tlacitko) { case OK : printf("Tlačítko OK"); break; … } V případě implicitní inicializace je vhodné využít pole pointerů na char.pole pointerů na char char *nazvy[] = { "červená", "oranžová", "zelená" }; printf("Na semaforu svítí barva %s.\n", nazvy[barva]); Cyklické střídání prvků výčtového typu lze řešit pomocí operátoru modulo % pro zbytek po celočíselném dělení.modulo % typedef enum { CERVENA, ORANZOVA, ZLUTA, ZELENA, MODRA, FIALOVA, POCET_BAREV } PALETA; PALETA barva; for (i = 0; i < pocet_sloupcu_grafu; i++) { barva = i % POCET_BAREV; kresli_sloupec_grafu(barva); }

156 156 Bitové operace Důvody použití: –nutnost práce na nižších úrovních, než jsou byty, například kvůli nutnosti šetřit pamětí, –zrychlení práce programu. Argumenty bitových operací nesmějí být proměnné typů float, double a long double. Nejméně komplikované je použití argumentů typu unsigned. Výsledky operací s typem signed jsou podle ANSI standardu C závislé na implementaci. Je nutná znalost formátu uložení čísel. –pořadí uložení bitů v pamětipořadí uložení bitů v paměti –Je dobré programovat tak, aby na formátu uložení čísel nezáleželo. Chceme, aby program byl portabilní. Pro účely manipulací s bity poskytuje jazyk C 6 operátorů: &bitový součin AND |bitový součet OR ^bitový exkluzivní součet XOR (nonekvivalence) <>bitový posun doprava ~bitová negace (jedničkový doplněk) unární operátor

157 157 Bitový součin Test, zda je číslo liché –Lichá čísla mají nultý bit nastaven na 1. (1 & (x)) == TRUE, když je x liché. x & x = 0 = FALSE → x je sudé. Převod proměnné typu int na ASCII znak, který má jen 7 nejnižších bitů c = c & 0x7F; nebo c &= 0x7F; c x7F c po konverzi. Změna malých písmen na velká c = c & 0xDF; nebo c &= 0xDF; c = 'a' = 97 = '\x61' 0xDF c = 'A' po konverzi. –Malá písmena mají v ASCII tabulce 5. nejnižší bit (počítáno od 0) roven 1 a velká jej mají roven 0.ASCII tabulce –'A' = 'a' - 32 –32 = Převod bitů na hexadecimální konstanty DecBityHex A B C D E F xyx & y

158 158 Bitový součet Změna velkých písmen na malá c = c | 0x20; nebo c |= 0x20; c = 'Z' = 90 = '\x5A' 0x = 32 c = 'z' po konverzi. Malá písmena mají v ASCII tabulce 5. nejnižší bit (počítáno od 0) roven 1 a velká jej mají roven 0.ASCII tabulce 'z' = 'Z' = Převod bitů na hexadecimální konstanty DecBityHex A B C D E F xyx | y

159 159 Bitový exkluzivní součet Test, zda jsou dvě čísla stejná (x ^ z) == TRUE, když x != y. x y x ^ y = 0 = FALSE → x == y. Šifrování text = (text ^ klíč) ^ klíč klíč text ^ klíč = text po zašifrování. Funkce pro napsání řetězce pozpátku –volání této funkcevolání této funkce –Reverse a string in place using XOR swapping by Bob StoutReverse a string in place using XOR swapping by Bob Stout char *pozpatku(char *str) { char *zacatek, *konec; if (! str || ! *str) return str; for (zacatek = str, konec = str + strlen(str) - 1; konec > zacatek; ++zacatek, --konec) { *zacatek ^= *konec; *konec ^= *zacatek; *zacatek ^= *konec; } return str; } xyx ^ y

160 160 Bitový posun doleva Příkaz x << n; posune bity v x doleva o n pozic. Při tomto posunu se zleva bity ztrácí – jsou vytlačovány – a zprava jsou doplňovány nulou. Rychlé násobení mocninou dvou i = i << 1; nebo i <<= 1; vynásobí i dvěma. i i << = původní i * 2 i << = původní i * 8 Než násobit 80, je rychlejší násobit 64 a 16 a sečíst výsledek. i = j * 80; lze nahradit za i = (j << 6) + (j << 4); Priority operátorů > jsou velmi nízké, takže je nutno téměř vždy závorkovat.

161 161 Bitový posun doprava Příkaz x >> n; posune bity v x doprava o n pozic. Při tomto posunu se zprava bity ztrácí – jsou vytlačovány – a zleva jsou doplňovány nulou. Rychlé celočíselné dělení mocninou dvou i = i >> 1; nebo i >>= 1; dělí i dvěma. i i >> = původní i / 2 i >> = původní i / 8 Umocňování celého čísla na celé číslo Získání hodnoty konkrétního (zde i-tého) bitu int bit(unsigned int x, unsigned int i) /* unsigned, protože */ {/* bitový posun může zachovávat znaménkový bit. */bitový posun může zachovávat znaménkový bit if (i >= sizeof(x) * 8) return (-1); /* Chyba – i-tý bit v x není. */ else return ((x >> i) & 1); /* Logický součin s 1 vynuluje všechny bity na vyšších pozicích, než je nultý bit. */ }

162 162 Bitová negace Potřebujeme například nastavit na nulu nejnižší 4 bity. Příkaz x &= 0xFFF0; bude pracovat správně jen na počítačích, kde platí sizeof(int) == 2. x xFFF x &= 0xFFF x xFFF x &= 0xFFF –To je špatně. Řešením je příkaz x &= ~0xF; který bude pracovat správně na všech typech počítačů. 0xF ~0xF x x &= ~0xF xF ~0xF x x &= ~0xF Převod bitů na hexadecimální konstanty DecBityHex A B C D E F x~x 01 10

163 163 Stavové slovo Celočíselnou proměnnou můžeme použít k uchovávání několika hodnot typu boolean v jejích jednotlivých bitech. Taková proměnná se nazývá stavové slovo (status variable) a její bity příznaky – flags.flags Nejprve se definují konstanty, které určí pozice příznakových bitů ve stavovém slově. Například použijeme 3., 4. a 5. bit pro příznaky číst, psát a vymazat (READ, WRITE, DELETE). #define READ 0x8 /* */ #define WRITE 0x10 /* */ #define DELETE 0x20 /* */ Po této přípravě je možné provést: –nastavení všech příznaků na 1 status |= READ | WRITE | DELETE; –nastavení příznaků READ a WRITE na 1 status |= READ | WRITE; –nastavení všech příznaků na 0 status &= ~(READ | WRITE | DELETE); –nastavení příznaku READ na 0 status &= ~READ; –test, zda jsou oba příznaky WRITE a DELETE nulové if (!(status & (WRITE | DELETE))) = status = WRITE | DELETE = status & (WRITE | DELETE) == FALSE Převod bitů na hexadecimální konstanty DecBityHex A B C D E F

164 164 Bitové pole Bitové pole si lze představit jako strukturu, jejíž velikost je ale pevně omezena velikostí typu int. Bitové pole má dvě základní oblasti použití: –uložení několika celých čísel v jednom slově (typu int) většinou kvůli šetření pamětí, –pro přístup k jednotlivým bitům slova pomocí identifikátorů. Datovým typem položek je buďto int nebo unsigned. unsigned znamená unsigned int. –Když je použit int, pak je nejvyšší bit považován za znaménkový bit. Uložení datumu zhuštěně do jednoho slova o velikosti 2 byty tak, jak to dělal operační systém MS DOS pro práci se soubory typedef struct { /* Datový typ unsigned znamená unsigned int. */ unsigned den : 5; /* bity 0 – 4 nejnižších řádů */ unsigned mesic : 4; /* bity 5 – 8 */ unsigned rok : 7; /* bity 9 – 15 – Máme k dispozici 2 7 = 128 hodnot. */ } DATUM; DATUM dnes, zitra; dnes.den = 25; dnes.mesic = 6; dnes.rok = ; /* K uloženému roku vždy přičteme konstantu */ zitra.den = dnes.den + 1; Realizace stavového slova z předchozího snímkustavového slova z předchozího snímku typedef struct { unsigned zacatek : 3; /* Bity 0 – 2 se musí přeskočit, protože READ má hodnotu 8 = a ostatní jsou větší. */ unsigned read : 1; /* bit 3 */ unsigned write : 1; /* bit 4 */ unsigned delete : 1; /* bit 5 */ } FLAGY; FLAGY status; –Nastavení všech příznaků na 1 status.read = status.write = status.delete = 1; –Nastavení všech příznaků na 0 status.read = status.write = status.delete = 0; –Test, zda jsou oba příznaky WRITE a DELETE nulové if (!(status.write | status.delete)) Pořadí, v jakém jsou ukládány položky bitového pole, tj. zda od vyšších bitů k nižším (od MSB k LSB) nebo obráceně (od LSB k MSB), je implementačně závislé, viz endianness.MSBLSBendianness –Je třeba provést pokus na konkrétním počítači s konkrétním překladačem.

165 165 Union Union (česky někdy unie) je datový typ, který umožňuje uchovávat právě jeden z různých v unionu definovaných datových typů. V paměti vyhradí místo pro největší položku z možných typů. Důvodem pro použití unionu je potřeba šetření pamětí a interpretace dat více způsoby. typedef union { char c; int i; float f; } ZN_INT_FLT; ZN_INT_FLT a, *p_a = &a; K jednotlivým položkám unionu se přistupuje naprosto stejně jako k položkám struktury. a.c = '#'; p_a->i = 1;/* Přemaže znak '#' */ (*p_a).f = 2.3;/* Přemaže číslo 1 */ Union neposkytuje informaci o typu prvku, který do něj byl naposledy uložen. Tento problém se často řeší tak, že se union vloží do struktury, jejíž první položka je výčtový typ a druhá položka union. typedef enum { ZNAK = 'C', CELE = 'I', REALNE = 'F' } TYP; typedef struct { TYP typ; ZN_INT_FLT polozka; } LEPSI_UNION; LEPSI_UNION pole [POCET]; –Program vyzve uživatele, aby zadal „C“, „I“ nebo „F“ a uloží si to do proměnné pole [i].typ, aby podle toho následně správně načetl daný typ následující hodnoty do proměnné pole[i].polozka.c nebo pole[i].polozka.i nebo pole[i].polozka.f. Podle hodnoty proměnné pole [i].typ lze také ve správném formátu vypisovat hodnoty typu ZN_INT_FLT.

166 166 Využití unionu výpis binární reprezentace hodnoty výpis hexadecimální reprezentace hodnoty konverze IP adresy na long int šifrování a dešifrování přehozením bytů čtení a zápis dat pro odlišné systémy –Různé počítače ukládají byty dat v odlišném pořadí, což se nazývá endianness.endianness –Počítač typu big-endian vytvořil binární soubor, který se má správně načíst počítačem typu little-endian, nebo naopak.binární soubor

167 167 Výpis binární reprezentace hodnoty s využitím unionu 1 #include 2 3 typedef struct { 4 unsigned a : 1; 5 unsigned b : 1; 6 unsigned c : 1; 7 unsigned d : 1; 8 unsigned e : 1; 9 unsigned f : 1; 10 unsigned g : 1; 11 unsigned h : 1; 12 } BITY; typedef union { 15 int cislo; 16 BITY bity_cisla; 17 } HODNOTA; int main(void) 20 { 21 HODNOTA moje_cislo; 22 scanf("%d", &moje_cislo.cislo); 23 printf("%d\n", moje_cislo.cislo); 24 printf("%d%d%d%d%d%d%d%d\n", 25 moje_cislo.bity_cisla.h, 26 moje_cislo.bity_cisla.g, 27 moje_cislo.bity_cisla.f, 28 moje_cislo.bity_cisla.e, 29 moje_cislo.bity_cisla.d, 30 moje_cislo.bity_cisla.c, 31 moje_cislo.bity_cisla.b, 32 moje_cislo.bity_cisla.a); 33 return 0; 34 }

168 168 Preprocesor Zpracovává zdrojový text programu před použitím překladače. Nekontroluje syntaktickou správnost programu. Provádí pouze záměnu textů, například identifikátorů konstant za odpovídající číselné hodnoty. –zpracování maker (macro processing) Vkládá do textu programu hlavičkové soubory. Vypustí ze zdrojového textu všechny komentáře. Připravuje podmíněný překlad. Zjištění výstupu preprocesoru pomocí překladače GCCZjištění výstupu preprocesoru pomocí překladače GCC gcc -E main.c > MainPoPreprocesoru.txt gcc -E main.c -o MainPoPreprocesoru.txt –Přehled přepínačů překladače GCCPřehled přepínačů překladače GCC

169 169 Makra bez parametrů #define MAX 1000 /* maximální rozměr pole */ –Jména konstant jsou z konvence psána vždy VELKÝMI PÍSMENY. –Jméno konstanty je od její hodnoty odděleno alespoň jednou mezerou. –Za hodnotu není středník. –Mezi jménem konstanty a hodnotou není „=“ –Za hodnotou by měl být komentář. Pokud je hodnota konstanty delší než řádka, musí být na konci řádky znak „\“, který se ale do makra nerozvine. #define CISLO 12\ 34 /* Další řádek nesmí být odsazen, viz dlouhé řetězce. */dlouhé řetězce Konstanta CISLO má hodnotu –Může se to využít pro příkazy.příkazy Konstanta začíná platit od místa definice a platí až do konce souboru, ve kterém byla definována. Rozdíl mezi #define a const –Datový typ const umožňuje definovat a kontrolovat datový typ stejně jako u proměnných. Datový typ konstant definovaných pomocí #define je určen implicitně.implicitně –Velikost poleVelikost pole const použité pro určení velikosti pole funguje v C99 ale ne v C90. #define funguje ve všech verzích C, viz kniha C Primer Plus.C Primer Plus –Rozsah působnosti konstanty Má-li konstanta platit pro více funkcí (neboli být globální), použijeme #define. Globálně deklarovanou proměnnou s modifikátorem const lze zastínit ve funkcích.modifikátoremzastínit

170 170 Makra bez parametrů s výrazy Je-li symbolickou konstantou výraz, potom je velmi vhodné uzavřít ho do závorek. #include #define MAX_TEPLOTA 50 #define MIN_TEPLOTA -70 #define ROZSAH_TEPLOT (MAX_TEPLOTA - MIN_TEPLOTA) int main(void) { int teplota; double normalizovana_teplota; /* Pro MAX_TEPLOTA vyjde 1 a pro MIN_TEPLOTA vyjde 0.*/ teplota = 15; normalizovana_teplota = (double)(teplota - MIN_TEPLOTA) / ROZSAH_TEPLOT; printf("%f", normalizovana_teplota); return 0; } Kdyby byla konstanta ROZSAH_TEPLOT definována takto #define ROZSAH_TEPLOT MAX_TEPLOTA - MIN_TEPLOTA rozvinul by se příkaz pro výpočet normalizované teploty takto: normalizovana_teplota = (double)(teplota - MIN_TEPLOTA) / MAX_TEPLOTA - MIN_TEPLOTA; a díky precedenci operátorů by byl výpočet chybný.precedenci operátorů

171 171 Makra bez parametrů a řetězce Makro se nerozvine, je-li uzavřeno v uvozovkách, např.: #define JMENO Katka printf("Jmenuji se JMENO."); vytiskne: Jmenuji se JMENO. a ne: Jmenuji se Katka. Řešením může být např.: #define JMENO "Katka" printf("Jmenuji se %s.", JMENO);

172 172 Změna definice makra Je možné měnit hodnotu symbolické konstanty v různých částech programu. Následující řešení ale buďto nefunguje, nebo překladač vydá varování „"JMENO" redefined“: #include int main(void) { #define JMENO "Katka" printf("Jmenuji se %s.\n", JMENO); #define JMENO "Dana" printf("Jmenuji se %s.\n", JMENO); return 0; } V tom případě je nutné napřed starou definici zrušit použitím direktivy #undef, např.: #define JMENO "Katka" printf("Jmenuji se %s.\n", JMENO); #undef JMENO #define JMENO "Dana" printf("Jmenuji se %s.\n", JMENO); return 0;

173 173 Makra bez parametrů a příkazy Makro může být skrytou částí programu: #define ERROR { printf("Chyba v datech\n"); } if (x == 0) ERROR /* Zde nesmí být středník. */Zde nesmí být středník. else y = y / x; Makro může být použito pro převod zdrojového kódu do jiného programovacího jazyka: #define MOD % #define AND &&

174 174 Makra s parametry Každý výpočet, který se v programu opakuje, by měl být umístěn na jednom místě, tedy jako funkce. Funkce má svoji režii: předání parametrů, úschova návratové adresy, skok do funkce, návrat z funkce do místa volání a výběr použitých parametrů. Tato režie je u malých funkcí náročnější než samotný algoritmus funkce, což je neefektivní. Řešením jsou makra s parametry neboli vkládané funkce (in-line functions). Na rozdíl od skutečných funkcí se makra s parametry nevolají, ale před překladem nahradí preprocesor jméno makra konkrétním textem. Praktické použití je jen pro velmi krátké funkce. U maker s parametry na rozdíl od funkcí nelze použít rekurze.

175 175 Syntaxe makra s parametry #define jméno_makra(arg1, …, argN) hodnota_makra Syntaktická pravidla –Mezi jméno_makra a otevírací závorkou „(“ nesmí být mezera! Argumenty by pak byly považovány za hodnotu makra. –Syntaxe volání makra: jméno_makra(par1, …, parN) Štábní kultura –Na rozdíl od maker bez parametrů (symbolických konstant), jejichž jména se píší velkými písmeny, se jména maker s parametry píší malými písmeny, stejně jako jména funkcí.

176 176 Příklad makra s parametry Konverze znaku na malé písmeno #define je_velke(c) ((c) >= 'A' && (c) <= 'Z') Toto makro je voláno jako ch = je_velke(ch) ? ch + ('a' - 'A') : ch; Po zpracování preprocesorem se rozvine jako ch = ((ch) >= 'A' && (ch) <= 'Z') ? ch + ('a' - 'A') : ch; Což je syntakticky stejný příkaz jako zde.zde Tělo makra představuje logický výraz, jehož hodnotou je 0 nebo 1. Pak se dá makro snadno použít v místech, kde je očekáván logický výraz, např. v příkazu if nebo v podmínce ternárního operátoru. příklad makra se dvěma parametry

177 177 Makra s parametry a závorkování #define sqr(x) x * x se po volání sqr(f + g) rozvine do f + g * f + g Správně má být definice #define sqr(x) ((x) * (x)) která se rozvine do ((f + g) * (f + g)) #define cti(c) c = getchar() se po volání if (cti(c) == 'a') rozvine do známé chyby: if (c = getchar() == 'a')známé chyby Správně má být definice #define cti(c) (c = getchar()) která se rozvine do if ((c = getchar()) == 'a')

178 178 Makra s parametry a vedlejší účinky Objeví-li se argument v hodnotě makra vícekrát, pak by makro nemělo být voláno s aktuálním parametrem, který může mít vedlejší účinek, například: #define cislice(x) ((x) >= '0' && (x) <= '9') po volání if (cislice(c++)) způsobí, že proměnná c bude inkrementována dvakrát, což zřejmě není správné. if (cislice(c++)) se rozvine na if (((c++) >= '0' && (c++) <= '9')) Kvůli vedlejším účinkům a možnosti, že argument v makru je funkce, je lepší podobné testy realizovat standardními makry z knihovny ctype.h, která to řeší tabulkami. ctype.h

179 179 Vkládání souborů – příkaz #include Vkládaný soubor je nakopírován (vtažen, inkludován) do volajícího souboru do místa, kde se v něm nachází příkaz #include. Příkaz #include "konstanty.h" –hledá soubor konstanty.h ve stejném adresáři, ve kterém leží volající soubor. Nenajde-li ho tam, je možné, že ho bude hledat dále v dalších adresářích, což už závisí na nastavení překladače. –Tato syntaxe se používá pro práci se soubory, které jsme vytvořili my sami. Příkaz #include –hledá soubor ctype.h v systémovém adresáři. –Tato syntaxe se používá pro práci s již hotovými speciálními soubory, kterým se říká standardní hlavičkové soubory.standardní hlavičkové soubory

180 180 Standardní hlavičkové soubory Popisují funkce, konstanty a datové typy ze standardní knihovny. Standardní knihovna je definována ANSI normou, takže program, který ji využívá, je portabilní. Seznam standardních hlavičkových souborů s odkazy na jejich obsah je například na Wikipedii nebo zde.Wikipediizde Popis toho nejdůležitějšího ze standardních knihoven začíná v této prezentaci zde.zde Nejsou v nich uvedeny celé zdrojové texty příslušných funkcí ale pouze jejich hlavičky, takzvané funkční prototypy.funkční prototypy Jsou to textové soubory uložené nejčastěji v adresáři /include. –Je možné je prohlížet v textových editorech. Celý kód standardních funkcí je v předkompilovaných knihovnách. –Jsou to soubory s příponou.a uložené nejčastěji v adresáři /lib. –Tento kód připojuje k programu linker.

181 181 Oddělený překlad programu Když se program skládá z více zdrojových souborů, může se každý z nich přeložit zvlášť, čímž vznikne několik.obj nebo.o souborů a ty se spojí do jednoho programu až pomocí sestavovacího programu zvaného linker. Při změně jednoho souboru stačí přeložit jenom tento soubor. Je možné si vyměňovat.obj soubory, čímž se zabrání nechtěné modifikaci jejich kódu. Překladače pro prostředí MS Windows pracují s více zdrojovými soubory prostřednictvím projektů. –Do projektu se dají pouze soubory s příponou.c. –V souborech.c jsou příkazy #include připojující pouze hlavičkové soubory.h ale ne zdrojové soubory.c. –Projekt sám řídí oddělený překlad. Při překladu se přeloží jen změněné zdrojové soubory.

182 182 Oddělený překlad a proměnné Když se program skládá z více zdrojových souborů neboli modulů, v jednom modulu je obvykle program main a v ostatních je jedna nebo více funkcí volaných navzájem nebo z programu main. Nastávají 2 problémy s proměnnými a funkcemi: –Některé proměnné a funkce mají být společné. Když v jednom modulu dostane proměnná určitou hodnotu, tak má s touto hodnotou vstoupit jako parametr do funkce v jiném modulu. Tyto proměnné a funkce se deklarují s klíčovým slovem extern. –Některé proměnné a funkce se v různých modulech jmenují stejně, ale mají být na sobě nezávislé. Moduly vytvářejí různí autoři a bylo by složité hlídat, které názvy proměnných kdo používá. Tyto proměnné a funkce se deklarují s klíčovým slovem static. Klíčová slova extern a static se používají především v hlavičkových souborech, viz kniha Pavla Herouta Učebnice jazyka C, 1. díl, páté vydání, KOPP České Budějovice 2008 na straně 130 až 133.

183 183 Paměťové třídy Paměťové třídy určují, ve které části paměti bude proměnná kompilátorem umístěna, a také, kde všude bude proměnná viditelná. Jazyk C rozeznává tyto paměťové třídy: –auto implicitní paměťová třída pro lokální neboli automatické proměnnélokální neboli automatické proměnné –extern Mají ji implicitně globální proměnné. Identifikátory musí být rozlišitelné podle prvních 6 znaků.globální proměnnéIdentifikátory musí být rozlišitelné podle prvních 6 znaků. S tímto klíčovým slovem mohou být deklarovány proměnné uvnitř funkcí, aby se chovaly jako globální. Vytvořte projekt z programů (modulů) hlavni.c a pomocny.c z Učebnice jazyka C Pavla Herouta a zkoušejte různé kombinace deklarací proměnných s klíčovým slovem extern a bez něj a zároveň umisťujte proměnné dovnitř funkcí nebo globálně.Učebnice jazyka C Pavla Herouta –static Globální proměnné deklarované s klíčovým slovem static jsou viditelné jen v rámci svého modulu. –Vytvořte projekt z programů (modulů) hlavni2.c a pomocny2.c z Učebnice jazyka C Pavla Herouta. Lokální proměnné deklarované s klíčovým slovem static uvnitř funkce si zachovávají aktuální hodnotu i mezi jednotlivými voláními této funkce. –program s122.c z Učebnice jazyka C Pavla Herouta –register Používá se pouze pro lokální proměnné, ke kterým má být co nejrychlejší přístup. –např. řídící proměnné cyklu a formální parametry funkce viz program s124.c z Učebnice jazyka C Pavla Heroutařídící proměnné cykluformální parametry funkce Tyto proměnné jsou umístěny v registrech procesoru, ale pouze, pokud se tam dle rozhodnutí překladače vejdou, jinak jsou umístěny jako automatické proměnné třídy auto.registrech procesoru –Překladač při optimalizaci někdy sám nejlépe určí, co do registrů dát a co ne, tudíž je nutné výsledek otestovat. Nelze získat adresu proměnné deklarované s klíčovým slovem register.adresu –Proměnná se takto může chránit před přístupem k její adrese a tedy možností ji takto změnit.možností ji takto změnit Deklarace –Pro všechny paměťové třídy platí, že se musí pro každou proměnnou deklarovat zvlášť. Některé překladače například definici static int i, j; zpracují tak, že statická proměnná bude pouze i a proměnná j bude mít implicitní paměťovou třídu, tedy auto. –Správná deklarace více proměnných static int i; static int j;

184 184 Typové modifikátory Libovolná proměnná určitého datového typu, která je zařazena do určité paměťové třídy, může být navíc ještě modifikována typovým modifikátorem const, volatile nebo restrict.datového typupaměťové třídy const –konstanta s explicitně určeným typemkonstanta –definice formálních parametrů funkce, které budou pouze čteny ale ne měněny volatile –Proměnná může změnit svou hodnotu působením jiných procesů než svým programem. jiný simultánně běžící program nebo adresa, na které je systémový čas –Není-li proměnná deklarována jako volatile, překladač předpokládá, že ji nic jiného než program nemění, a na základě této informace může optimalizovat překládaný program. restrict –Aplikuje se pouze na pointery. –Je-li pointer deklarován jako restrict, překladač předpokládá, že hodnoty v paměťových místech, na které pointer ukazuje, nejsou ničím dalším měněny, protože se k nim přistupuje pouze tímto pointerem, a na základě této informace může optimalizovat překládaný program. Chybné použití klíčového slova restrict může způsobit chybnou funkci programu! Podrobnosti viz kniha C Primer Plus.C Primer Plus

185 185 Podmíněný překlad programu Program by měl obsahovat ladící části. –pomocné výpisy, funkce hlídající meze polí… Po odladění programu je nutné tyto ladící části z programu odstranit. Ruční procházení zdrojového kódu za účelem odstranění ladících částí je náchylné k chybám a při dalším vývoji programu je nutné ty samé ladící části zase přidat. Řešením jsou direktivy pro preprocesor, které zařídí, že při ladění se ladící části dostanou do přeloženého programu a po odladění se ladící části z přeloženého programu vypustí. Ladící části jsou tak trvalou součástí zdrojového kódu ale volitelnou součástí přeloženého programu. Další motivací pro podmíněný překlad je možná závislost programu na platformě. –Pro různé operační systémy má některé svoje části různé.

186 186 Řízení překladu hodnotou konstantního výrazu #if konstantní_výraz část_1 #else část_2 #endif Je-li konstantní_výraz roven 0 (FALSE), překládá se pouze část_2, a v opačném případě (nenulová hodnota – TRUE) se překládá pouze část_1. Štábní kultura –Kód mezi direktivami #if, #else a #endif je odsazen stejně jako mezi příkazy if…else.

187 187 Dočasné vypnutí části kódu Části #else a část_2 mohou být vynechány. #if 0 část programu, která nemá být překládána #endif

188 188 Platformově závislé programy #define WINDOWS 1 /* #define WINDOWS 0 pro newindowsovský systém */ #if WINDOWS #define JMENO "D:\\data\\senzor.txt" /* Lomítka viz příklad. */příklad #else #define JMENO "/data/senzor.txt" #endif

189 189 Řízení překladu definicí makra Mnohem častěji než předchozí řešení se překlad podmiňuje tím, zda byla určitá symbolická konstanta definována či ne.předchozí řešení Společná deklarace #define WINDOWS /* Hodnota nemusí být určena. */ /* #undef WINDOWS pro newindowsovský systém */ 1. varianta následného kódu: #ifdef WINDOWS #define JMENO "D:\\data\\senzor.txt" #else #define JMENO "/data/senzor.txt" #endif 2. varianta následného kódu: #ifndef WINDOWS #define JMENO "/data/senzor.txt" #else #define JMENO "D:\\data\\senzor.txt" #endif

190 190 Operátor defined pro direktivu #ifdef #ifdef TEST #if defined TEST #if defined(TEST) pro direktivu #ifndef #ifndef TEST #if !defined TEST #if !defined(TEST) Ekvivalentní příkazy:

191 191 Direktivy #elif a #error Direktiva #elif má stejný význam jako příkaz else-if v podmíněném příkazu. Direktiva #error umožňuje výpis chybových zpráv již během preprocessingu. –Libovolný text, který následuje za příkazem #error, je vypsán na standardní chybové zařízení (nejčastěji do konzolového okénka) a kompilace je ukončena chybou. –Tato direktiva se typicky používá pro kontrolu hodnot symbolických konstant ovlivňujících podmíněnou kompilaci.

192 192 Použití defined, #elif a #error Následující program vyzkoušejte s různými kombinacemi vypnutých a zapnutých konstant ZAKLADNI, STREDNI a DEBUG v překladači, ve výstupu preprocesoru a ve výstupu preprocesoru přesměrovaného do standardního chybového hlášení.výstupu preprocesorustandardního chybového hlášení #include #if 0 #define ZAKLADNI #define STREDNI #define DEBUG #endif int main(void) { #if defined(ZAKLADNI) && defined(DEBUG) #define VERZE_LADENI 1 printf("%d\n", VERZE_LADENI); #elif defined(STREDNI) && defined(DEBUG) #define VERZE_LADENI 2 printf("%d\n", VERZE_LADENI); #elif !defined(DEBUG) #error Pozor, ladící verzi není možné připravit! #else #define VERZE_LADENI 3 printf("%d\n", VERZE_LADENI); #endif return 0; }

193 193 Standardní hlavičkové soubory bez funkčních prototypů ANSI C definuje 4 standardní hlavičkové soubory, které pouze popisují symbolické konstanty a datové typy nutné pro spolupráci operačního systému s kompilátorem C nebo využívané funkcemi z ostatních hlavičkových souborů. Tyto soubory pracují jen s příkazy #define a typedef a zajišťují přenositelnost na jiné platformy.

194 194 errno.h Deklaruje externí proměnnou errno používanou jinými standardními funkcemi, např. z knihovny math.h, pro předání informace o chybě.externímath.h Do proměnné errno je při každém chybovém stavu uložen kód chyby. Definuje symbolické konstanty, jejichž hodnot může errno nabývat. Má-li errno hodnotu 0, znamená to „bez chyb“, hodnoty 1 až sys_nerr ze stdlib.h znamenají chybu. Kód chyby je indexem do pole chybových hlášení sys_errlist z knihovny stdlib.h. int i; for (i = 0; i < sys_nerr; i++) printf("%d %s\n", i, sys_errlist[i] /* strerror(i) */); Vypíše možné chyby. Proměnná sys_nerr ze stdlib.h udává velikost pole sys_errlist. Příslušné chybové hlášení se dá vypsat pomocí funkce perror() z knihovny stdio.h na standardní chybový proud.standardní chybový proud –Výstup chybového proudu do souboru je možný též redirekcí.redirekcí Text chybového hlášení lze získat pomocí funkce strerror() z knihovny string.h a pak jej libovolně zpracovávat jako řetězec. Do sys_errlist lze přistoupit i přímo, například pomocí funkce strcpy() z knihovny string.h.strcpy()

195 195 limits.h Obsahuje symbolické konstanty popisující charakteristiky celých čísel. Tyto proměnné se dají vhodně využívat pro nejrůznější testování mezí, zjišťování rozsahů jednotlivých typů na konkrétním počítači, atd. –Když například hledáme nejmenší z množiny čísel typu int, tak proměnnou, do které se nakonec uloží minimum, inicializujeme konstantou INT_MAX. Hodnoty jsou závislé na implementaci. –Každá platforma má svoje vlastní instalace vývojových prostředí jazyka C s odpovídajícími soubory limits.h.

196 196 float.h Obsahuje symbolické konstanty popisující charakteristiky reálných čísel. Hodnoty jsou závislé na implementaci. –Některé soubory float.h neobsahují konkrétní hodnoty. Překladač určuje hodnoty automaticky dle systému. Například v hlavičkových souborech překladače MinGW jsou v souboru float.h definice typu #define FLT_DIG__FLT_DIG__ Tato makra mají charakter předdefinovaných maker preprocesoru.předdefinovaných maker preprocesoru

197 197 stddef.h size_t –datový typ bezznaménkového celého čísla pro návratovou hodnotu funkce sizeof –Může se použít pro proměnné a parametry funkcí s počtem prvků pole a velikostí prvku pole.Může se použít pro proměnné a parametry funkcí s počtem prvků pole a velikostí prvku pole. ptrdif_t –datový typ celého čísla pro rozdíl dvou pointerů –Doporučuje se používat v 64-bitových programech.Doporučuje se používat v 64-bitových programech. offsetof(type, member) –makro určující počet bytů pro člena struktury nebo unionu –Členem struktury nesmí být bitové pole.bitové pole –Použití: vestavěné systémy, popis datové struktury.vestavěné systémypopis datové struktury wchar_t –datový typ celého čísla pro široké znaky (širší než 8 bitů)široké znaky NULL –definice hodnoty nulového pointeru

198 198 Předdefinovaná makra preprocesoru neboli předdefinované globální identifikátory __STDC__ –Má-li toto makro hodnotu 1, pak překladač vyhovuje normě ANSI C. Při jiné hodnotě – nejčastěji 0 – nebo spíše při neexistenci tohoto makra (chybu oznámí překladač) je nutno být při programování obezřetnější. –U některých překladačů je norma ANSI C otázkou jen určité volby přepínačů. Například překladač gcc má přepínač -ansi. __FILE__, __LINE__ –zprávy o chybáchzprávy o chybách –makro assert() z knihovny assert.hassert.h __DATE__, __TIME__ –Makro vrací čas překladu ve formě řetězce. –Program tak můžeme opatřit časovou známkou jeho vzniku.

199 199 Předdefinovaná makra preprocesoru - příklad V souboru main.c je: #include #include "myheader.h" int main(void) { if (__STDC__) { printf("Prekladac vyhovuje norme ANSI C.\n"); } else { printf("Prekladac nevyhovuje norme ANSI C.\n"); } printf("V souboru %s je prikaz __LINE__ na radku %d.\n", myheader_name, myheader_line); printf("Hlavni prekladany soubor je %s.\n", __FILE__); printf("Prikaz, ktery tento radek vytiskl, je na radku %d.\n", __LINE__); printf("Program zacal byt prekladan dne %s v case %s.\n", __DATE__, __TIME__); return 0; } V souboru myheader.h patřícího do stejného projektu je: static char myheader_name[] = __FILE__; /* Viz příklad zde. */zde /* Lze i static char *myheader_name = __FILE__; */ static int myheader_line = __LINE__;

200 200 Standardní hlavičkové soubory s funkcemi a makry Každá implementace překladače ANSI C musí obsahovat minimálně dále popsané standardní hlavičkové soubory. Kromě výše a níže probraných knihoven mezi ně patří také knihovny novějších standardů jazyka C: –,,,,,,, a.complex.hfenv.hinttypes.hiso646.hstdbool.hstdint.htgmath.hwchar.hwctype.h Tyto soubory obsahují ochranu vůči několikanásobnému inkludování. Například soubor stdio.h začíná #ifndef _STDIO_H_ /* Zde začíná podmíněný překlad. */ #define _STDIO_H_ vlastní obsah souboru stdio.h #endif /* _STDIO_H_ */ Tedy soubor stdio.h se inkluduje, pokud již nebyl předtím vložen.

201 201 assert.h Knihovna obsahuje jediné makro assert() pro přidání ladicích informací do programu Při běhu programu se mohou vyskytnout chyby po dlouhé době nebo jen někdy. Tyto chyby většinou vznikají čtením a zápisem do nealokovaných částí paměti. Jsou 2 způsoby volání assert(logický_výraz); assert(logický_výraz) příkaz; V obou případech platí, že je-li logický_výraz nulový, pak je vypsána chybová zpráva a program je korektně ukončen.

202 202 Makro assert() Makro assert() v knihovně assert.h je komplikovanější, proto je zde jeho varianta myassert() ukazující princip fungování makra assert(). #ifndef NDEBUG #define myassert(p) {\ if (!(p)) {\ fprintf(stderr, "Assertion failed: %s, file %s, line %d", #p, __FILE__, __LINE__);\ return(1);\ }\ } #else #define myassert(p) /* Když je definováno NDEBUG, tak se myassert() nerozvine. */ #endif Ve výrazu #p je znak # příkaz preprocesoru, který rozvine výraz p do řetězce. –Může se využít jako náhrada za funkci sprintf().Může se využít jako náhrada za funkci sprintf(). Makro assert() by nemělo být náhradou za standardní testy správnosti. –Program na následujícím snímku standardně testuje správnost otevření souboru.následujícím snímku –Kdyby tento test byl nahrazen příkazem myassert((fr = fopen(SOUBOR, "r")) != NULL); neotevřel by se se soubor, kdyby bylo definováno NDEBUG. Pokud makro assert() ze standardní knihovny assert.h používá funkci fprintf(), musí být před příkazem #include příkaz #include.

203 203 Použití makra assert() /* #define NDEBUG */ /* Když máme odladěno, tak můžeme odstranit myassert() z kódu. */ /* Sem zkopírujte definici makra myassert() z předchozího snímku. */předchozího snímku #include #include /* Pokud myassert níže nahradíme za assert. */ #define MAX 10 #define SOUBOR "vstup.txt" int main(void) { FILE *fr; int index, pole[MAX]; if ((fr = fopen(SOUBOR, "r")) == NULL) { printf("Soubor %s se nepodarilo otevrit.\n", SOUBOR); return 1; } fscanf(fr, "%d", &index); myassert(index >= 0 && index < MAX); fscanf(fr, "%d", &pole[index]); printf("pole[%d] = %d.", index, pole[index]); if (fclose(fr) == EOF) { printf("Soubor %s se nepodarilo uzavrit.\n", SOUBOR); return 1; } return 0; }

204 204 ctype.h makra pro testování a konverzi znaků s kódem od 0 do 127 –Znaky s akcentem (diakritikou) mají svůj kód vyšší, tudíž tato makra se na ně nedají aplikovat. Tato makra by se měla preferovat, viz makra s parametry a vedlejší účinky.makra s parametry a vedlejší účinky Makra pro testování znaků ve formě isfunc(c); vrací znak v argumentu (TRUE) nebo nulu (FALSE). isalnumčíslice a malá a velká písmena (alfanumerická) isalphamalá a velká písmena (alfabetická) isasciiASCII znaky (0 ažd 127) isblanktest for blank character (new in C99) iscntrltest for control character (1 ažd 26) isdigitčíslice ('0' – '9') isgraphznak s grafickou podobou (33 až 126), znak, který není mezera islowermalá písmena (lowercase character) ('a' – 'z') isprinttisknutelné znaky včetně mezery (32 až 126) ispunctinterpunkční znaky (punctuation character) – tečka, čárka, lomítko… isspacebílé znaky (whitespace character) – mezera, tabulátor, nový řádek… isuppervelká písmena (uppercase character) ('A' – 'Z') isxdigithexadecimální písmena ('0' – '9', 'a' – 'f', 'A' – 'F') Makra pro konverzi znaků ve formě c = tofunc(c); vrací znak po konverzi nebo stejný znak, není-li argument alfabetický. tolowerkonverze na malá písmena toupperkonverze na velká písmena toasciipřevod na ASCII (ponechání nejnižších 7 bitů)ponechání nejnižších 7 bitů

205 205 Ověřování vstupu čísel Makra v hlavičkovém souboru ctype.h ověřují jednotlivé znaky. ověřování celých čísel (Ověřování reálných čísel je tomu analogické.) scanf("%d", &n) == 1; –Funkce scanf() vrací počet úspěšně přečtených položek.Funkce scanf() vrací počet úspěšně přečtených položek. –Příklad z knihy C Primer PlusPříklad z knihy C Primer Plus int get_int(void) { int input; char ch; printf("Enter an integer value: "); while (scanf("%d", &input) != 1) { while ((ch = getchar()) != '\n') { putchar(ch); /* dispose of bad input */ } printf(" is not an integer.\nPlease enter an integer value, such as 25, -178, or 3: "); } while (getchar() != '\n') /* Když vstup začíná číslem a končí něčím jiným, co chci zahodit, nebo čtu potom znak. */ ; return input; } –Když funkce scanf() nenačte úspěšně data, ponechá je ve vstupní frontě. –Odtud je může vypsat funkce getchar() a putchar(). Volání funkce int main(void) { int cislo; cislo = get_int(); … return 0; } –Jak by se funkce get_int() změnila, kdyby se měla používat způsobem get_int(&cislo);? funkce s parametrem pro text výzvy a kontrolou intervalu

206 206 Ověřování vstupu řetězce funkcí fgets() z klávesnice Musíme zabránit tomu, aby načtený řetězec přesáhl alokovaný paměťový prostor. Příkaz fgets(s, max_delka, stdin); –Když je vstup delší nebo roven max_delka znaků, tak ve vstupním bufferu čeká zbytek znaků a konec řádku. –Když je vstup kratší než MAX znaků, tak ve vstupním bufferu už nic není, ale konec řádku je posledním znakem s, z něhož je obvykle žádoucí jej odstranit:odstranit #include #define MAX 5 void precti_retezec_a_vyprazdni_buffer(char *s, int max_delka) { int delka; printf("Zadej retezec kratsi nez %d znaku: ", max_delka); fgets(s, max_delka, stdin); /* načtení uživatelského vstupu */ delka = strlen(s) - 1; /* zjištění indexu posledního načteného znaku */ if (s[delka] == '\n') s[delka] = '\0'; /* Když s končí koncem řádku, tak konec řádku přepíšeme ukončující nulou. */ else /* V opačném případě můžeme vyprázdnit buffer před dalším možným čtením. */ while (getchar() != '\n') ; } int main(void) { char retezec[MAX]; precti_retezec_a_vyprazdni_buffer(retezec, MAX); printf("%s\n", retezec); return 0; }

207 207 Ověřování vstupu řetězce funkcí scanf() Příkaz scanf("%4s", s); zabraňuje tomu, aby načtený řetězec s přesáhl alokovaný paměťový prostor, literálem 4 v řídícím řetězci formátu.scanf("%4s", s); Literálu v řídícím řetězci formátu se můžeme zbavit tím, že řídící řetězec formátu vygenerujeme funkcí sprintf().řídící řetězec formátu vygenerujeme funkcí sprintf() #include #define DELKA_RIDICIHO_RETEZCE_FORMATU 50 #define MAX 5 void precti_retezec(char *s, int max_delka) { char rrf[DELKA_RIDICIHO_RETEZCE_FORMATU]; printf("Zadej retezec kratsi nez %d znaku: ", max_delka); sprintf(rrf, "%%ds", max_delka - 1); /* Pro MAX == 5 má být rrf == "%4s". */ scanf(rrf, s); } int main(void) { char retezec[MAX]; precti_retezec(retezec, MAX); printf("%s\n", retezec); return 0; } –Kdyby se do rrf tisklo funkcí sprintf() něco delšího, než se tam vejde, došlo by k havárii nebo špatnému fungování programu, proto existuje i bezpečná verze této funkce s omezenou délkou řetězce snprintf().snprintf()

208 208 Ověřování vstupu řetězce funkcí getc() Řetězec je možné načítat po jednotlivých znacích, dokud nedojde k překročení přípustné délky nebo není konec řádku. Přidáme-li && c != ' ' && c != '\t', je možné simulovat funkci scanf(). #include #define MAX 5 void precti_retezec_po_znacich(char *s, int max_delka) { int c, i = 0; printf("Zadej retezec kratsi nez %d znaku: ", max_delka); while ((c = getc(stdin)) != '\n' /* sem další podmínky */ && i < max_delka - 1) { s[i] = c; /* skládání řetězce ze znaků */ i++; } s[i] = '\0'; /* přidání ukončující nuly */ } int main(void) { char retezec[MAX]; precti_retezec_po_znacich(retezec, MAX); printf("%s\n", retezec); return 0; }

209 209 locale.h hlavičkový soubor pro lokalizaci Funkce localeconv() –vrací informaci o nastaveném národním prostředí. Funkce setlocale() –nastavuje národní prostředí. datový typ struct lconv –datový typ pro informaci o národním prostředí –Například zápis času a data, oddělovač celé a desetinné části čísla, rozšířená znaková sada Obvykle je překladačem definováno pouze americké národní prostředí.

210 210 math.h matematické funkce v přesnosti double matematické konstanty Algoritmy záleží na implementaci. spolupráce s externí proměnnou errno.errno –Pokud se v těchto funkcích vyskytne chyba, pak je do errno dosazena jedna z těchto symbolických konstant: EDOM –Vstupní argument má hodnotu mimo definiční obor funkce. ERANGE –Výstupní hodnota přetekla nebo podtekla typ double. –Samotná funkce z math.h v případě přetečení vrátí symbolickou konstantu HUGE_VAL (maximální číslo v double), podtečení vrátí hodnotu 0.0.

211 211 Spolupráce funkcí z knihovny math.h s externí proměnnou errno #include #include /* Pokud bychom chtěli používat názvy konstant hodnot errno. */ int main() { double x = sqrt(-2); if (errno != 0) { perror("Funkce sqrt()"); printf("Funkce sqrt(): %s\n", strerror(errno)); printf("Funkce sqrt(): %s\n", sys_errlist[errno]); printf("Cislo chyby: %d\n", errno); } else printf("%f\n", x); errno = 0; /* Vynulování je nutné, když chceme zachycovat další chyby. */ perror(""); sys_errlist[0] = "Bez chyby"; /* Takto je možné přeložit text chyby. */ perror(""); return 0; }

212 212 Celočíselné matematické funkce Ne všechny jsou součástí standardních knihoven. Celočíselná aritmetika je efektivnější než funkce používající typ double.Celočíselná aritmetika knihovna stdlib.hstdlib.h –absolutní hodnota celého čísla int abs(int), long labs(long), long long llabs(long, long) –celočíselné dělení div_t div(int citatel, int jmenovatel) (analogicky i pro typy long a long long) –Vrací strukturu typu div_t obsahující citalel / jmenovatel a citatel % jmenovatel). test (makro s parametrem), zda je číslo liché #define je_liche(x) (1 & (unsigned int)(x)) umocnění celého čísla na celé číslo –for cyklus –nebo efektivněji viz Exponentiation by squaringExponentiation by squaring int ipow(int base, unsigned int exp) { int result = 1; while (exp) { if (exp & 1) { result *= base; } exp >>= 1; /* Bitový posun doprava */Bitový posun doprava base *= base; } return result; }

213 213 setjmp.h Příkazem goto se nesmí skákat z jedné funkce do druhé.goto V této knihovně jsou funkce a datové typy, které to místo příkazu goto umožňují. Typické užití je pro jednotou reakci na chybu s následným návratem do místa, kde program ještě fungoval správně. návrat z obsluhy signálu v UNIXu viz knihovna signal.hsignal.h Nejdříve se zavolá funkce setjmp(), která uloží aktuální obsah všech potřebných registrů procesoru (stack pointer, program counter, …) do proměnné typu jmp_buf a vrátí nulovou hodnotu. Po volání funkce longjmp() jsou pak obsahy všech registrů obnoveny z této proměnné a je tak proveden návrat zpět do místa volání setjmp(). Druhým parametrem funkce longjmp() je návratová hodnota příštího volání setjmp(), což lze využít například ke zjištění, kolikrát byl již tento skok proveden.

214 214 signal.h funkce pro víceúlohový operační systém V něm mohou jednotlivé úlohy komunikovat pomocí signálů. Knihovna obsahuje pouze omezenou množinu funkcí pracujících se signály z operačního systému UNIX. Signály lze ale využít i v prostředí Windows, například pro zabezpečení reakce na stisk Ctrl+C nebo na běhovou chybu (run-time error). Funkce signal() –nastaví akci, která se má provést, když program obdrží signál. Funkce raise() –vyšle signál programu.

215 215 stdarg.h makra výhodná pro tvorbu funkcí s proměnným počtem parametrů – FPPP Skutečné parametry funkce se ukládají do zásobníku. zásobníku Funkce musí mít minimálně jeden pevný parametr. Makro va_start() –Inicializuje seznam proměnných parametrů. –Dosazuje se do něj jméno posledního pevného formálního parametru. Makro va_arg() –Poskytne hodnotu další položky seznamu. Makro va_end() –Ukončí práci se seznamem.

216 216 Využití FPPP pro jednotný výstup na obrazovku, soubor a řetězec #include void tisk(char *str, FILE *f, char *format,...) { va_list argumenty; va_start(argumenty, format); vprintf(format, argumenty); va_end(argumenty); va_start(argumenty, format); vfprintf(f, format, argumenty); va_end(argumenty); va_start(argumenty, format); vsprintf(str, format, argumenty); va_end(argumenty); } int main(void) { int i = 5; float f = 3.14; char radka[100]; FILE *fw; fw = fopen("VYSTUP.TXT", "w"); tisk(radka, fw, "int = %d, float = %f\n", i, f); puts(radka); fclose(fw); return 0; }

217 217 Využití FPPP pro zpracování řady parametrů v počtu určeném prvním parametrem #include double secti(int kolik,...) { va_list argumenty; double soucet = 0.0; va_start(argumenty, kolik); while (--kolik > 0) { soucet += va_arg(argumenty, double); /* Datovým typem nesmí být float. */ /* Skutečný argument lze přetypovat na double, ale nelze implicitní double přetypovat na float. */implicitní } /* Varování překladače: 'float' is promoted to 'double' when passed through '...' */ va_end(argumenty); return soucet; } int main(void) { printf("Soucet je %f\n", secti(3, 1.0, 3.2, 2.1)); /* 4.2 */ printf("Soucet je %f\n", secti(4, 1.0, 3.2, 2.1)); /* 6.3 */ printf("Soucet je %f\n", secti(5, 1.0, 3.2, 2.1)); /* Do součtu se přidá neinicializovaný 4. double. */ printf("Soucet je %f\n", secti(3, 1, 3.2, 2.1)); /* 2. parametr se předá jako int a tím se vše pomíchá. */ printf("Soucet je %f\n", secti(3, (double)1, 3.2, 2.1)); /* 4.2 */ return 0; }

218 218 Využití FPPP pro zpracování řady parametrů v počtu určeném ukončovací hodnotou – sčítání #include #define KONEC 0 int secti(int cislo,...) { va_list argumenty; int scitanec, soucet; va_start(argumenty, cislo); soucet = cislo; while ((scitanec = va_arg(argumenty, int)) != KONEC) { soucet += scitanec; } va_end(argumenty); return soucet; } int main(void) { printf("Soucet je %d\n", secti(1, 2, 3, KONEC)); /* 6 */ return 0; }

219 219 Využití FPPP pro zpracování řady parametrů v počtu určeném ukončovací hodnotou – zřetězení #include void zretez(char *str,...) { char *p_c; va_list argumenty; va_start(argumenty, str); while ((p_c = va_arg(argumenty, char *)) != NULL) { strcat(str, p_c); } va_end(argumenty); } int main(void) { char retezec[100] = { '\0' }; zretez(retezec, "a", "h", "o", "j", NULL); printf("Retezec je: \"%s\"\n", retezec); return 0; }

220 220 Využití FPPP pro zpracování řady parametrů v počtu určeném ukončovací hodnotou – zřetězení #include void zretez(char *str, int znak,...) { int i = 0; va_list argumenty; va_start(argumenty, znak); str[i++] = znak; while ((znak = va_arg(argumenty, int)) != '\0') { str[i++] = znak; /* warning: 'char' is promoted to 'int' when passed through '...' */ } va_end(argumenty); } int main(void) { char retezec[100] = { '\0' }; zretez(retezec, 'a', 'h', 'o', 'j', '\0'); printf("Retezec je: \"%s\"\n", retezec); return 0; }

221 221 stdio.h již probrané funkce pro vstup a výstup funkce fflush() –Obsah výstupního bufferu bude okamžitě zapsán na výstupní zařízení, soubor zůstává i nadále otevřený. –Využití je vhodné v místech před možnou chybou, po které nechceme přijít o obsah buffferu. –Je možné ji použít pro obousměrný proud (režim otevírání souboru se znakem „+“) za předpokladu, že poslední operací nebyl vstup (čtení ze souboru), viz kniha C Primer Plus.režim otevírání souboruC Primer Plus –Příkaz fflush(stdin); vyprázdní buffer.vyprázdní buffer Je lepší než while cyklus s funkcí getchar(), protože nevadí, když je volána v případě prázdného bufferu. Další dosud neprobrané funkce jsou zde.zde testování chyb –feof(), ferror(), clearerr(), perror()

222 222 stdlib.h obecně užitečné funkce konverze řetězců na čísla generátor pseudonáhodných čísel –int rand(void) a void srand(unsigned int start) funkce pracující s dynamickou pamětí funkce pro spolupráci s operačním systémem –abort(), atexit(), exit() a system() externí proměnné –seznam chyb: sys_errlist, sys_nerrseznam chyb –prostředí systému: environ, funkce getenv() printf("%s\n", getenv("PATH")); funkce pro řazení a hledání –qsort() a bsearch() celočíselné matematické funkce

223 223 Generátor náhodných čísel rand() –Vrací pseudonáhodné celé číslo od 0 do RAND_MAX, ale při každém spuštění programu je posloupnost stejná. rand() % ROZSAH –Vrací celé číslo od 0 do ROZSAH - 1. #define real_rand() ((double) rand() / (RAND_MAX + 1.0)) –makro vracející reálné číslo v rozsahu <0, 1), porovnej s

224 224 Funkce pro spolupráci s operačním systémem z knihovny stdlib.h void abort(void); –Nestandardně ukončí program, tedy nezapisuje buffery, nemaže dočasné soubory, atd. –V UNIXu se snaží zavřít soubory a generuje soubor core. int atexit(void (* stop_funkce)(void)); –Připraví k provádění (zaregistruje) funkci stop_funkce(), která se spustí po ukončení funkce main(). –Výhodné použití je například pro uvolnění dynamické paměti, zápis ladících informací do souboru, apod. –Když na projektu spolupracuje víc lidí, tak si každý zaregistruje svoji vlastní ukončovací funkci, která po skončení programu automaticky uklidí vše, za co je dotyčný programátor zodpovědný. –Je možné mít až 32 těchto funkcí, které jsou volány v obráceném pořadí, než v jakém byly registrovány. void exit_fn1(void) { printf("1. exit funkce\n"); } /* definice stop funkce */ atexit(exit_fn1); /* registrace stop funkce v těle funkce main() */ void exit(int stav); –Standardně ukončí program, tedy zapisuje buffery, uzavře soubory, maže dočasné soubory, volá všechny funkce registrované pomocí atexit(), atd. –Její parametr (0 – bez chyby, čím vyšší, tím horší chyba) je předán operačnímu systému. V MS DOSu se uloží do systémové proměnné ERRORLEVEL. Ve skriptech UNIXu je vrácena nulová hodnota považovaná za true a nenulová za false. –Používá se pro okamžité ukončení programu, když nastane chyba v libovolně vnořené funkci. int system(const char *prikaz_os); –Vyvolá z programu příkaz operačního systému nebo spustí pod sebou jiný program. –Před voláním funkce system() by měly být uzavřeny všechny soubory. system("dir"); /* vyvolání příkazu dir z programu */

225 225 Funkce pro řazení a hledání void qsort(void *pole, size_t pocet_prvku, size_t velikost_prku, int (*porov_fce)(const void*, const void*)); –Seřadí pole. void* bsearch(const void *klic, const void *pole, size_t pocet_prvku, size_t velikost_prku, int (*porov_fce)(const void*, const void*)); –Vrací ukazatel na prvek seřazeného pole, jenž vyhovuje klíči, nebo NULL při neúspěšném hledání. Porovnávací funkce –Vrací záporné číslo, je-li první parametr menší než druhý. –Vrací nulu, jsou-li parametry shodné. –Vrací kladné číslo, je-li první parametr větší než druhý. –Pro porovnání řetězců můžeme použít např. strcmp().porovnání řetězcůstrcmp() –Měla by být stejná pro qsort() i bsearch(). Když hledáme například řádek tabulky (strukturu) s určitým atributem (položkou struktury) v tabulce (poli struktur), tak porovnávací funkce pro qsort() pracuje s pointery na dva prvky pole a porovnávací funkce pro bsearch() dostane jako první parametr pointer na strukturu s příslušnou položkou rovnou hledanému atributu a jako druhý parametr dostane pointer na prvek pole.

226 226 string.h funkce, které zpracovávají řetězec znaků ukončený '\0' –nejdůležitější funkce strcpy(), strstr(), strncpy(), …strcpy()strstr()strncpy() –funkce hledající shodné nebo rozdílné znaky v řetězci zdroj v porovnání s množinou znaků v řetězci set. Na pořadí znaků v řetězci set nezáleží. strspn(), strcspn(), strpbrk() a strtok() funkce, které zpracovávají řetězec znaků neukončený '\0' –užitečné pro rychlou práci s blokem paměti –memchr(), memcmp(), memcpy(), memmove(), a memset()

227 227 Funkce strspn() String span #include int main(void) { char *digits = " "; char *s = "99 ;\t"; printf("Na zacatku retezce \"%s\" ", s); printf("je %d znaku ", strspn(s, digits)); printf("z retezce \"%s\".\n", digits); return 0; }

228 228 Funkce strcspn() String complement span #include int main(void) { char *delimiters = " ;\t"; char *s = "99 ;\t"; printf("Na zacatku retezce \"%s\" je ", s); printf("%d jinych ", strcspn(s, delimiters)); printf("znaku, nez jsou \"%s\".\n", delimiters); return 0; }

229 229 Funkce strpbrk() String pointer break #include int main(void) { char *digits = " "; char *s = " ;\t99;;"; printf("Retezec \"%s\" zkraceny na zacatku ", s); printf("o znaky nepatrici do mnoziny \"%s\" ", digits); printf("je \"%s\".\n\n", strpbrk(s, digits)); char *p_c; char text_s_cisly[] = "Stav uctu cislo / 0300 je "; printf("Text pred nahradou cislic: %s.\n", text_s_cisly); while ((p_c = strpbrk(text_s_cisly, digits)) != NULL) *p_c = '-'; /* hromadná náhrada množiny znaků za jeden */ printf("Text po nahrade cislic: %s.\n", text_s_cisly); return 0; }

230 230 Funkce strtok() String to tokentoken Tato funkce mění řetězec, který má rozřezat tak, že za každý token napíše znak s kódem 0 namísto znaku bezprostředně za tokenem. typické využití –čtení dat z textových souborů po načtení řádku funkcí fgets()fgets() #include int main(void) { char numbers[] = " 123;; 45; \t 67890;"; /* Nesmí být deklarováno jako char *numbers = " 123;; 45; \t 67890;" */Nesmí /* protože strtok() přepisuje numbers. */ char *delimiters = " ;\t"; char *p_c; int i, delka_ret; printf("Retezec, ktery se rozreze: \"%s\"\n", numbers); delka_ret = strlen(numbers); p_c = strtok(numbers, delimiters); if (p_c != NULL) printf("%s\n", p_c); /* první token */ while ((p_c = strtok(NULL, delimiters)) != NULL) printf("%s\n", p_c); /* ostatní tokens */ printf("Retezec po rozrezani: "); for (i = 0; i < delka_ret; i++) putchar(numbers[i]); /* Nelze vše vypsat pomocí printf(), protože jsou tam znaky s kódem 0.*/ return 0; }

231 231 time.h datové typy a funkce umožňující práci s časem a datem datové typy –CLK_TCK nebo CLOCKS_PER_SEC symbolická konstanta původně vyjadřující počet procesorových tiků za sekundu V současné době se již nepoužívá a je nastavena na hodnotu 1000, což nemusí odpovídat skutečnosti. –clock_t čtyřbajtové celé znaménkové číslo (nejčastěji long) sloužící pro funkci clock() –time_t čtyřbajtové celé znaménkové číslo (nejčastěji long) sloužící pro všechny ostatní funkce z knihovny time.h –tm struktura, která slouží pro uložení všech možných složek času obsahující tyto položky: int tm_sec;/* sekundy v minutě[0-59] */ int tm_min;/* minuty v hodině[0-59] */ int tm_hour;/* hodiny v rámci dne[0-23] */ int tm_mday;/* dny v měsíci[1-31] */ int tm_mon;/* měsíce v roce[0-11] Pozor, není to [1-12]! */ int tm_year;/* roky od roku 1900[0-2038] */ int tm_wday;/* dny v týdnu[0-6] Pozor, není to [1-7], 0 je neděle! */ int tm_yday;/* dny v roce[0-366] */ int tm_isdst;/* příznak letního času –+1 letní čas, –0 normální čas –-1 když se to nedá určit */

232 232 Měření času v ticích procesoru #include #define POCET_KROKU int main(void) { unsigned long int i, j; clock_t cas_z, cas_k; cas_z = clock(); for (i = 0; i < POCET_KROKU; i++) { for (j = 0; j < POCET_KROKU; j++) { i++; i--; } cas_k = clock(); printf("Pocet tiku procesoru: %ld.\n", cas_k - cas_z); return 0; } Ve víceúlohových operačních systémech dává funkce clock() nepřesné výsledky. Přesné výsledky jsou možné pomocí knihoven mimo oblast ANSI C. –Viz kniha Pavla Herouta Učebnice jazyka C, 2. díl, třetí vydání, KOPP České Budějovice 2007 na straně 354 až 356.

233 233 Funkce time() Funkční prototyp: time_t time(time_t *cas); Vrací počet sekund od 1. ledna 1970 do okamžiku svého vyvolání. Její hlavní použití je jako primární funkce pro práci se složkami času. –Pro měření doby běhu programu je přesnější funkce clock(). Dá se volat různými způsoby: time_t pocet_sec, n_sec; pocet_sec = time(&n_sec); pocet_sec = time(&pocet_sec); pocet_sec = time(NULL); time(&pocet_sec);

234 234 Funkce difftime() Funkční prototyp: double difftime(time_t cas2, time_t cas1); Vypočte (cas2 - cas1). #include #define POCET_KROKU int main(void) { unsigned long int i, j; time_t pocet_sec_z, pocet_sec_k; time(&pocet_sec_z); for (i = 0; i < POCET_KROKU; i++) { for (j = 0; j < POCET_KROKU; j++) { i++; i--; } time(&pocet_sec_k); printf("Pocet sekund: %f.\n", difftime(pocet_sec_k, pocet_sec_z)); return 0; }

235 235 Funkce localtime() Funkční prototyp: struct tm *localtime(const time_t *p_cas); Rozloží počet sekund od 1. ledna 1970 získaný funkcí time() do jednotlivých složek struktury tm – naplní strukturu tm. Sama si strukturu tm alokuje. #include int main(void) { struct tm tm_promenna, *p_tm; time_t cas; time(&cas); tm_promenna = *localtime(&cas); /* přiřazení celé struktury */ printf("Hodina: %d\n", tm_promenna.tm_hour); p_tm = localtime(&cas); /* přirazení ukazatele */ printf("Hodina: %d\n", p_tm->tm_hour); return 0; }

236 236 Funkce asctime() a ctime() Funkční prototypy char *asctime(const struct tm *p_tm); char *ctime(const time_t *p_cas); Připraví řetězec ve formátu Mon Jun 19 16:00: #include #define POCET_KROKU int main(void) { struct tm *p_tm; time_t cas; time(&cas); p_tm = localtime(&cas); printf("%s", asctime(p_tm)); printf("%s", ctime(&cas)); return 0; }

237 237 Funkce strftime() Umožňuje s velkou variabilitou volit formát času. Vrací počet správně zapsaných znaků.

238 238 Funkce mktime() Inverzní funkce k funkci localtime() Pokud některá položka struktury přesahuje meze, upraví tuto a všechny navazující položky. –To se dá využít k různým trikům, kdy chceme například zjistit, jaký den v týdnu připadá na určité datum.

239 239 Funkce _strdate() a _strtime() Vrací datum jako řetězec. Nejsou součástí ANSI C. Jsou rozšířením knihovny time.h od firmy Microsoft. –http://bytes.com/topic/c/answers/ link- time-error-when-used-_strdate-_strtimehttp://bytes.com/topic/c/answers/ link- time-error-when-used-_strdate-_strtime Měly by se nahradit funkcí strftime().strftime()

240 240 Soubory – 2. část Rozdíly mezi binárním a textovým souborem Binární soubory –jsou pro počítač, –tvoří je program, –čtení dat programem je bez konverze, –jsou posloupností bytů. Textové soubory –jsou pro člověka, –tvoří je člověk nebo počítač, –čtení dat programem je s konverzí z textové na vnitřní reprezentaci datových typů, –jsou členěny na řádky.

241 241 Režimy otevírání souboru základní režimy textových souborů –"r" – otevření existujícího souboru pro čtení Pokud soubor neexistuje, je vrácena hodnota NULL. – Testovat!Testovat! –"w" – vytvoření nového souboru pro zápis Pokud již soubor existuje, je jeho obsah vymazán. –"a" – otevření souboru pro připisování na konec Pokud soubor neexistuje, je vytvořen. rozšířené režimy textových souborů –"r+" – otevření existujícího souboru pro čtení a zápis Pokud soubor neexistuje, je vrácena hodnota NULL. – Testovat!Testovat! Ukazatel aktuální pozice v souboru je nastaven na začátek souboru. –"w+" – vytvoření nového souboru pro čtení a zápis Pokud již soubor existuje, je jeho obsah vymazán. –"a+" – otevření souboru pro čtení a připisování na konec Pokud soubor neexistuje, je vytvořen. Číst se dá v souboru odkudkoliv, ale zapisovat pouze na konec. binární soubory –K režimu pro textové soubory se přidá znak „b“, například "rb+" je binární režim otevření existujícího souboru pro čtení a zápis.

242 242 Textový režim otevírání souboru konce řádků –Čtecí funkce každý znak '\r' bez náhrady ubírají. Někdy hlídají, zda je to '\r' před znakem '\n', a jen tento znak uberou. –Zapisovací funkce ve Windows před každý znak '\n' (0x0A) přidají znak '\r' (0x0D). konec souboru –Čtecí funkce reagují na bajt s hodnotou 255 (0xFF) jako na konec souboru. –Některé textové editory přidávají na konec textového souboru ve Windows bajt s hodnotou 26 (0x1A) – Ctrl-Z, v UNIXu bajt s hodnotou 4 (0x4) – Ctrl-D. Čtecí funkce na ně také reagují jako na konec souboru. Binární soubory otvírejte v binárním režimu a načítejte znaky do proměnné typu int.

243 243 Binární režim otevírání souboru přímý přístup do souboru –Funkce fseek() posun v souboru –Funkce ftell() zjištění pozice v souboru –V textových souborech se tyto funkce dají použít pro návrat na původní místo například po hledání řetězce. zpracování celého bloku dat najednou –Funkce fread() a fwrite() V rámci jednoho proudu (souboru) nesmí nikdy vstupní operace následovat po výstupní operaci nebo naopak bez předchozího volání funkce fseek() nebo fflush()!!!fflush() –situace, kdy chceme jeden blok za druhým editovat »Načteme blok do proměnné, změníme hodnotu proměnné, vrátíme se na začátek bloku, zapíšeme proměnnou, voláme fflush(f) nebo fseek(f, 0L, SEEK_CUR), i když je ukazatel umístěn správně, načteme následující blok, atd.

244 244 Otevření binárního souboru pro čtení a zápis Typicky v databázových programech je potřeba otevřít binární soubor pro čtení a zápis, když existuje, nebo založit nový soubor pro čtení a zápis, když soubor neexistuje. #include #define DATABIN "soub/or.bin" int main(void) { FILE *f; if ((f = fopen(DATABIN, "rb+")) == NULL) { printf("Soubor %s se nepodarilo otevrit.\n", DATABIN); if ((f = fopen(DATABIN, "wb+")) == NULL) { printf("Soubor %s se nepodarilo zalozit.\n", DATABIN); exit(1); /* ukončení celého programu, protože s touto chybou se nedá pokračovat */ } else { printf("Byl zalozen novy soubor %s.\n", DATABIN); } return 0; }

245 245 Funkce pro převod reálného čísla na řetězec s českou čárkou #include #define DELKA_CISLA 32 #define real_rand(min, max) ((min) + ((max) - (min)) * ((double) rand() / (RAND_MAX + 1.0))) void ceske_cislo(char *s, double cislo) { char *p_c; sprintf(s, "%.*g", DELKA_CISLA - 1, cislo); if ((p_c = strchr(s, '.')) != NULL) *p_c = ','; } int main(void) { const int MIN_NC = -20; const int MAX_NC = 20; double cislo; char cislo_ret[DELKA_CISLA] = { '\0' }; srand((unsigned int) time(NULL)); cislo = real_rand(MIN_NC, MAX_NC); ceske_cislo(cislo_ret, cislo); printf("%s\n", cislo_ret); return 0; }

246 246 Vybrané knihovny pro operační systém MS Windows Program s těmito knihovnami nebude fungovat v jiných operačních systémech. knihovna io.h –zjištění informací o položkách v adresářizjištění informací o položkách v adresáři knihovna conio.h –tvorba interaktivních programů prostřednictvím klávesnice –program reagující na stisk kláves ve dvou verzíchprogram reagující na stisk kláves ve dvou verzích příklad s datovým typem long long v 32bitovém operačním systémulong v 32bitovém operačním systému –Následující snímek obsahuje příklad fungující v 32bitových WindowsNásledující snímek –Je vhodné z něj vycházet při tvorbě vlastních funkcí pro zadávání čísel.

247 247 Datový typ long long #include #define MIN #define MAX #define DELKA_RETEZCE 256 long long int zadej_velke_cislo(char *prompt) { long long int input; while (scanf("%I64d", &input) != 1) { fflush(stdin); printf("Nebylo zadano cislo.\n"); printf("%s", prompt); } fflush(stdin); return input; } long long int zadej_velke_cislo_v_intervalu(char *prompt) { long long int input; printf("%s", prompt); while (((input = zadej_velke_cislo(prompt)) MAX)) { printf("%I64d neni v intervalu.\n", input, MIN, MAX); printf("%s", prompt); } return input; } int main(void) { long long int cislo; char prompt[DELKA_RETEZCE]; snprintf(prompt, DELKA_RETEZCE - 1, "Zadej cislo v intervalu : ", MIN, MAX); /* Funkce snprintf() zabrání havárii, kterou by způsobila funkce sprintf() v případě nedostatečné velikosti konstanty DELKA_RETEZCE. */sprintf() cislo = zadej_velke_cislo_v_intervalu(prompt); printf("Cislo o jednu vetsi je %I64d.\n", cislo + 1); return 0; }

248 248 Datový typ long long #include #define STR_HELPER(x) #x /* makro nahrazující funkci sprintf() nebo snprintf() */ #define STR(x) STR_HELPER(x) #define MIN #define MAX long long int zadej_velke_cislo(char *prompt) { long long int input; while (scanf("%I64d", &input) != 1) { fflush(stdin); printf("Nebylo zadano cislo.\n"); printf("%s", prompt); } fflush(stdin); return input; } long long int zadej_velke_cislo_v_intervalu(char *prompt) { long long int input; printf("%s", prompt); while (((input = zadej_velke_cislo(prompt)) MAX)) { printf("%I64d neni v intervalu.\n", input, MIN, MAX); printf("%s", prompt); } return input; } int main(void) { long long int cislo; cislo = zadej_velke_cislo_v_intervalu("Zadej cislo v intervalu : "); printf("Cislo o jednu vetsi je %I64d.\n", cislo + 1); return 0; }

249 249 Pokročilejší funkce pro čtení čísel Funkce zadej_velke_cislo() selhává na číslech vyšších než MAX. –Funkce scanf() přijímá jako číslo vše, co je platným číslem bez ohledu na rozsah, tedy nekontroluje přetečení. Platformově nezávislým řešením jsou funkce strtol() a její varianty.její varianty

250 250 Knihovna conio.h #include int main(void) { int c; printf("Test klavesnice. Escape = konec.\n"); do { if ((c = getch()) == '\0' || c == 224) { printf("Byla stisknuta specialni klavesa s kodem %d nasledovanym ", c); c = getch(); printf("%d.\n", c); } else { printf("Byla stisknuta obycejna klavesa s kodem %d.\n", c); } } while (c != 27); return 0; } int main(void) { int c; printf("Test klavesnice. Escape = konec.\n"); do { switch (c = getch()) { case 0: case 224: printf("Byla stisknuta specialni klavesa s kodem %d nasledovanym ", c); c = getch(); printf("%d.\n", c); break; default: printf("Byla stisknuta obycejna klavesa s kodem %d.\n", c); } } while (c != 27); return 0; }


Stáhnout ppt "1 Programovací jazyk C Dana Nejedlová. 2 Použitá a doporučená literatura Pavel Herout: Učebnice jazyka C, 1. díl, nakladatelství Kopp, České Budějovice,"

Podobné prezentace


Reklamy Google