144
http://docs.linux.cz/c_saloun/ Programování v jazyce C Petr Saloun Obsah OSTRAVA http://docs.linux.cz/c_saloun/ [23.2.2004 11:37:16]

Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

http://docs.linux.cz/c_saloun/

Programování v jazyce CPetr Saloun

Obsah

OSTRAVA

http://docs.linux.cz/c_saloun/ [23.2.2004 11:37:16]

Page 2: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Obsah

Konec Následující kapitola

Obsah

PŘEDMLUVA

1. ÚVOD1.1 Historický úvod a normy jazyka1.2. První program v jazyce C.1.3. Jednoduchý vstup a výstup.

2. KONSTANTY, PROMĚNNÉ A DEKLARACE.2.1 Identifikátory, klíčová slova a komentáře.2.2 Základní typy dat2.3 Konstanty a proměnné.

KonstantyCeločíselné konstantyRacionální konstantyZnakové konstantyKonstantní řetězceProměnné

2.4. Ukazatele.

3. OPERÁTORY A VÝRAZY3.1 Operand, operátor, výraz.3.2. Rozdělení operátorů.3.3. Operátor přiřazení, l-hodnota a p-hodnota.3.4. Aritmetické operátory - aditivní a multiplikativní.3.5. Logické operátory.3.6. Relační operátory.3.7. Bitové operátory.3.8. Adresový operátor.3.9. Podmíněný operátor.

http://docs.linux.cz/c_saloun/obsah.htm (1 z 4) [23.2.2004 11:37:21]

Page 3: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Obsah

3.10. Operátor čárka.3.11. Přetypování výrazu.

4. PREPROCESOR4.1. Definice maker.

Symbolické konstantyMakra

4.2. Standardní předdefinovaná makra.

Operátory # a ##.Podmíněný překlad.Zbývající direktivy.

5. FUNKCE5.1. Deklarace a definice funkce.5.2. Návratová hodnota funkce.5.3. Argumenty funkcí a způsob jejich předávání.5.4. Funkce s proměnným počtem argumentů.5.5. Rekurse.

6. ŘÍZENÍ CHODU PROGRAMU6.1. Výrazový příkaz6.2. Prázdný příkaz6.3. Bloky.6.4. Oblast platnosti identifikátoru6.5. Podmíněný příkaz if-else.6.6. Přepínač6.7. Cykly

Cyklus whileCyklus forCyklus do

6.8. Příkaz skoku

7. UKAZATELE, POLE A ŘETĚZCE

http://docs.linux.cz/c_saloun/obsah.htm (2 z 4) [23.2.2004 11:37:21]

Page 4: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Obsah

7.1. Ukazatele7.2. Pole7.3. Aritmetika ukazatelů7.4. Řetězce7.5. Vícerozměrná pole, ukazatele na ukazatele7.6. Ukazatele na ukazatele a pole ukazatelů7.7. Ukazatele na funkce.7.8. Argumenty příkazového řádku

8. VSTUP A VÝSTUP8.1. Standardní vstup a výstup

Standardní vstup a výstup znakůStandardní vstup a výstup řetězcůFormátovaný standardní vstup a výstup

1;2c

8.2. Vstupní a výstupní operace v paměti.8.3. Práce se soubory.8.4. Soubory s přímým voláním8.5. Datové proudy

Otevření a zavření prouduProudy a vstup/výstup znakůProudy a vstup/výstup řetězcůFormátovaný vstup/výstup z/do proudu1;2c Proudy a blokový přenos datDalší užitečné funkcePříklady práce s proudy

9. DYNAMICKÉ DATOVÉ STRUKTURY9.1. Dynamická alokace paměti.9.2. Seznam.9.3. Pole ukazatelů.

10. ODVOZENÉ A STRUKTUROVANÉ TYPY DAT10.1. Uživatelský datový typ.

http://docs.linux.cz/c_saloun/obsah.htm (3 z 4) [23.2.2004 11:37:21]

Page 5: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Obsah

Složitější typové deklarace

10.2. Výčtový typ.10.3. Typ struktura.10.4. Typ union.10.5. Bitová pole.10.6. Klasifikace typů v C

PŘÍLOHA A: TVORBA PROJEKTŮ

PŘÍLOHA B: LITERATURA

PŘÍLOHA C: ZDROJOVÉ SOUBORY

Začátek Následující kapitola

Název: Programování v jazyce C

Autor: Petr SalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 03.12.1996

http://docs.linux.cz/c_saloun/obsah.htm (4 z 4) [23.2.2004 11:37:21]

Page 6: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Předmluva

Obsah Konec Následující kapitola

Předmluva

Tento učební text představuje základ kursu Programování v jazyce C. Kurs má rozsah 2+2 hodiny týdně (přednášky/cvičení). Je určen všem zájemcům o programovací jazyk C, zejména posluchačům oboru Inženýrská informatika FEI VŠB-Technické university Ostrava.

Kurs si klade za cíl seznámit posluchače se základy programovacího jazyka C, o němž se autor domnívá, že patří k základním znalostem informatika. Obsah textu však představuje jen základní pojmy jazyka. Teprve ve cvičeních a zejména soustavným používáním jazyka se student dostane ze stadia pasivní znalosti syntaxe k aktivnímu použití jazyka.

Příklady uvedené jako výpisy zdrojových textů jsou přeloženy jak 16-ti bitovým (Borland C++ 3.1, dále BC31) tak 32 bitovým (Mark Williams C, dále MWC) překladačem jazyka C a odzkoušeny v jednoúlohovém operačním systému MS-DOS 5.0 i ve víceúlohovém a víceuživatelském operačním systému COHERENT 4.0, což je OS třídy Unix (dále jen MS-DOS a COHERENT). Z důvodů jistých komplikací s kódováním češtiny v různých OS a prostředích jsem zvolil cestu nejmenšího odporu. Zdrojové texty jsou psány "cesky", tedy bez háčků a čárek. Jejich následným přepisováním při kompletaci textu jsem do výpisů nechtěl zbytečně vnášet chyby. Na tomto místě je třeba čtenáře upozornit, že příklady jsou napsány tak, aby demonstrovaly právě probíranou látku. Nemají ovšem ošetřeny "všechny možné" vstupy, případně některé možné varianty tak, aby korektně reagovaly na jakékoliv podněty. Tato "strohost" je snadno zdůvodnitelná snahou o co možno největší názornost na straně jedné a snahou neprodlužovat délku výpisů zdrojových textů více, než je nezbytně nutné, na straně druhé.

Chtěl bych poděkovat manželce Daně za četné rady, pečlivé přečtení rukopisu a za podporu, bez níž bych neměl prostor pro sepsání textu.

Autor uvítá všechny podněty a připomínky, které se budou týkat textu.

V Ostravě, září 1994

Autor

Obsah Začátek Následující kapitola

http://docs.linux.cz/c_saloun/uvod.htm (1 z 2) [23.2.2004 11:37:23]

Page 7: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Předmluva

Název: Programování v jazyce C

Autor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/uvod.htm (2 z 2) [23.2.2004 11:37:24]

Page 8: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Úvod

Předchozí kapitola Obsah Konec Následující kapitola

1. Úvod1.1. Historický úvod a normy jazyka1.2. První program v jazyce C1.3. Jednoduchý vstup a výstup

Programovací jazyk C je všeobecně použitelný programovací jazyk známý svou efektivitou, ekonomií a přenositelností. Tato charakteristika jej předurčuje pro prakticky všechny oblasti programování. Obzvláště užitečným je C v systémovém programování, protože umožňuje psaní rychlých, kompaktních programů, které jsou snadno adaptovatelné pro jiné systémy. Dobře napsaný C program je často stejně rychlý, jako program napsaný v assembleru. Navíc je ovšem čitelnější a snadněji udržovatelný.

C je pružný jazyk, který ponechává řadu programátorských rozhodnutí na nás. C nám vnucuje v podstatě jen některá omezení, například konverzi typů. C nám tedy umožňuje snadno vytvářet programy. Abychom ovšem správně věděli, jak se program bude chovat, musíme C dobře poznat. K tomu nám může posloužit předkládané skriptum.

Jazyk C byl navržen jako poměrně malý jazyk, kombinující efektivitu a výkonnost. C v sobě neobsahuje funkce pro provádění vstupu a výstupu, alokaci paměti, práci s obrazovkou a řízení procesů. Pro tyto funkce sahá programátor do systémových knihoven. Díky tomu se můžeme rozhodnout, použijeme-li funkce ze standardních knihoven, nebo napíšeme variantu speciálně pro naše potřeby. Tento návrh pomáhá oddělit vlastnosti jazyka od vlastností spojených s konkrétním procesorem, architekturou, ... . V C tak můžeme snadněji psát přenositelné programy.

1.1. Historický úvod a normy jazykaJazyk C vytvořil v Bellových laboratořích AT&T Denis Ritchie. Záměrem bylo napsat jazyk pro snadnou a přenositelnou implementaci Unixu. Na tomto přenosu (Unixu již existujícího pro PDP-7) se dále podíleli Brian Kernighan a Ken Thompson. Všichni tři tedy "byli u toho".

Jazyk C vznikl jako systémový jazyk pro systémové programátory. Po řadu let byla faktickou normou jazyka byla bible C - kniha Programovací jazyk C autorů K&R. V tomto C bylo možno provádět nejrůznější finty, které ovšem činily zdrojový text značně nečitelným. Nicméně s postupem času se s C a Unixem seznamovali studenti na vysokých školách, odcházeli do praxe a začali používat C i v jiných než systémových oblastech. Uživatelé přirozeně požadovali, aby C bylo nejen mocným prostředkem (tím bylo od svého vzniku), ale i bezpečným a přehledným jazykem. Postupně se

http://docs.linux.cz/c_saloun/kap01.htm (1 z 5) [23.2.2004 11:37:30]

Page 9: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Úvod

upouštělo od fint1. Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž může jazyk opravdu všeobecně rozšířit.

ANSI2 norma jazyka C definuje moderní vyšší programovací jazyk všeobecného použití. Shodný standard definuje ISO3. Proto se někdy uvádí toto spojení jako ANSI/ISO norma. ANSI jazyk C je bezpečnější. Navíc často umožňuje (byť jako archaismy) převzetí zdrojových textů vyvořených ve stylu K&R. Překladače, díky normalizaci jazyka, nabízí řada nezávislých producentů programového vybavení. Podstatné je, že C nabízí prakticky na všech technických platformách. Budeme-li ANSI normu C dodržovat, máme velkou šanci, že budeme schopni přenést zdrojový text na jiný stroj (s jiným procesorem i OS), program přeložit, spojit s knihovnami a spustit. Rozhodně by neměly vyvstat neřešitelné problémy.

ANSI C je navrženo tak, že kodifikuje existující praktiky. Většina zdrojových textů, napsaných před vydáním normy, je novými překladači akceptována. Přesto je ANSI C novým jazykem:

● Přidává některé vlastnosti, jako například prototypy funkcí. Opravuje tím některé nedostatky K&R definice jazyka C.

● Přehodnocuje konfliktní praktiky, jako například rozdílná pravidla pro redeklaraci datových objektů.

● Vyjasňuje dvojznačnosti, například je-li zakázáno překrytí datových objektů zpracovávaných knihovní funkcí.

S postupem doby se nezastavila ani teorie programování. Jestliže C umožňuje psát programy modulárně, dnešní trend vyžaduje i podporu objektového přístupu. V roce 1986 publikoval Stroustrup knihu The C++ Programming Language. Počátkem 90. let pak byl i tento jazyk normalizován. Jedná se o objektové rozšíření jazyka C. Proto čas, který C věnujeme, v případě dalšího zájmu o C++ bohatě zúročíme.

1.2. První program v jazyce CAbychom mohli začít s C co nejrychleji experimentovat, pokusíme se ve zbytku kapitoly ukázat nejdůležitější jazykové konstrukce. Nebudeme si činit nároky na úplnost jejich objasnění či definice. Tuto úlohu splníme v dalších kapitolách. Zde nám půjde spíše o vytvoření základní představy o tom, jak program v C vypadá, případně jak použijeme jednoduchý vstup a výstup.

Každý program v C musí obsahovat alespoň jednu funkci. Ta jediná nepostradatelná funkce se musí jmenovat main. V dalším textu budeme pro zřetelné odlišení jmen funkcí od ostatních identifikátorů používat kulaté závorky. Každý C program tedy obsahuje funkci main(), kterou vykonává při svém spuštění.

Funkce v C má deklarován typ návratové hodnoty a může mít argumenty různých typů. C disponuje datovými typy plně postačujícími pro běžné použití. Pokud to je užitečné, můžeme definovat vlastní nové datové typy. Návratovou hodnotu funkce určuje return příkaz.

http://docs.linux.cz/c_saloun/kap01.htm (2 z 5) [23.2.2004 11:37:30]

Page 10: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Úvod

Další součásti programu si popíšeme na příkladu. Jistě by nám chybělo, kdybychom jako první program napsali něco jiného, než klasické "Hello, world".

/************************//* HELLO.C *//* rychly zacatek *//************************/

#include <stdio.h>

int main(void){ printf("Hello, world.\n"); return 0;} /* main */

O nepostradatelnosti funkce main() již víme. Její argument void nám říká, že jí nepředáváme žádné argumenty. Úvodní int určuje celočíselný typ návratové hodnoty. Tělo funkce je vymezeno složenými závorkami {}. Funkce printf() umožňuje formátovaný výstup. Ještě ji v této kapitole použijeme. Zde zobrazí pozdrav na monitoru.

Ve zdrojovém textu jsou ještě komentáře. Jsou vymezeny dvojicí /* a */. Vše, co se mezi těmito znaky nachází je komentář. Komentáře budeme v našich programech používat zejména pro zvýšení čitelnosti programu.

Zbývá nám příkaz preprocesoru, začleňující hlavičkový soubor: #include <stdio.h>. Zatím jen tolik, že obsahuje všechny údaje potřebné pro správné použití funkce printf().

Tento zdrojový text musíme nyní přeložit překladačem jazyka C. Překladač musí mít k dispozici i začleněný hlavičkový soubor4. Pokud jsme se nedopustili žádné chyby, získáme přeložený tvar. K němu musíme připojit kód související s výstupem. Je uložen ve standardních knihovnách. Spojovací program se jmenuje linker. Často se volá příkazem link. Teprve po spojení modulů a připojení knihovních funkcí můžeme spustit proveditelný tvar našeho prvního zdrojového textu v jazyce C. Teď je jistě zřejmé, proč začínáme tak jednoduchým programem.

V tomto učebním textu nebudeme popisovat podrobněji konfiguraci překladače. Ta je totiž závislá nejen na operačním systému a jeho uživatelském nastavení. Je obvykle závislá i na firemních verzích překladačů a podpůrných programů.

1.3. Jednoduchý vstup a výstup.První program nám umožnil zvládnout překlad zdrojového textu. Teď si ukážeme některé číselné datové typy a jednoduchý vstup a výstup dat. Naším úkolem je načíst celočíselnou a racionální hodnotu zobrazit výsledek jednoduchého početního výkonu, který s těmito hodnotami provedeme.

http://docs.linux.cz/c_saloun/kap01.htm (3 z 5) [23.2.2004 11:37:30]

Page 11: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Úvod

/******************************//* SIMPLEIO.C *//* jednoduchy vstup a vystup *//******************************/

#include <stdio.h>

int main(void){ int i, j; float x, y; printf("zadej dve cela cisla: \n"); scanf("%d %d", &i, &j); printf("zadej racionalni cislo: \n"); scanf("%f", &x);

printf("%5d + %5d = %5d\n", i, j, i+j); y = i * x; printf("%d * %f = %f\n", i, x, y);

return 0;} /* main */

zadej dve cela cisla:4 6zadej racionalni cislo:8.9 4 + 6 = 104 * 8.900000 = 35.599998

Začátek programu je stejný, jako v předchozím případu. V těle funkce main() dále vytvoříme dvě celočíselné int proměnné i, j a pak dvě racionální float proměnné x, y. Poté pomocí známého výstupu printf() zobrazíme výzvu. Na následujícím řádku pomocí formátovaného vstupu načteme dvě celočíselné hodnoty:

scanf("%d %d", &i, &j);

Hodnoty načítáme do proměnných i a j. Nesmíme před ně ovšem zapomenout umístit adresový operátor &. Ve formátovacím řetězci musíme pro celočíselnou hodnotu umístit %d. Obdobně čteme i racionální hodnotu, pak je formát %f.

http://docs.linux.cz/c_saloun/kap01.htm (4 z 5) [23.2.2004 11:37:30]

Page 12: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Úvod

Při formátovaném výstupu používáme stejné formátovací symboly. Navíc můžeme přidat i další specifikace formátu. Podrobněji se formátům vstupu a výstupu budeme věnovat v kapitole Vstup a výstup. V příkladu vidíme i přiřazení výsledku do proměnné. Navíc se jedná o smíšený výraz. Jeden z operandů je typu int, druhý float. Výsledek je přiřazen proměnné typu float.

Možný chod programu máme zobrazen v rámečku. Číselné hodnoty které zadáváme můžeme oddělit nejen klávesou Enter, ale i mezerou či tabelátorem. Výledek násobení racionální hodnoty vidíme zaokrouhlen. Vzniklo omezeným rozsahem platných míst při uložení hodnoty ve vnitřním tvaru.

První seznámení s C máme za sebou. Další výklad povedeme systematičteji.

1 Zřejmě bychom mohli hovořit o nestandardních a nepřenositelných obratech. 2 ANSI je American National Standard Institute. 3 ISO je International Standards Organisation. 4 Musíme mu popsat, kde je tento hlavičkový soubor umístěn.

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce C

Autor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap01.htm (5 z 5) [23.2.2004 11:37:30]

Page 13: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

Předchozí kapitola Obsah Konec Následující kapitola

2. Konstanty, proměnné a deklarace.2.1 Identifikátory, klíčová slova a komentáře.2.2 Základní typy dat2.3 Konstanty a proměnné.

KonstantyCeločíselné konstantyRacionální konstantyZnakové konstantyKonstantní řetězceProměnné

2.4. Ukazatele.

V této kapitole se seznámíme s klíčovými slovy, identifikátory, komentáři. Poznáme rozdíl mezi konstantou a proměnnou a naučíme se nejen základní datové typy, ale i tvorbu nových typů dat.

2.1. Identifikátory, klíčová slova a komentáře.Klíčová slova mají speciální význam pro překladač C. Žádný identifikátor nemůže mít ve fázi

překladu stejné znění jako klíčové slovo. ANSI norma určuje následující klíčová slova:

auto double int struct

break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while

Identifikátory jsou jména, která dáváme například proměnným, funkcím a typům. Identifikátor se

http://docs.linux.cz/c_saloun/kap02.htm (1 z 10) [23.2.2004 11:37:42]

Page 14: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

musí lišit od kteréhokoliv klíčového slova. Nejvyšší počet znaků identifikátoru je implementačně závislý. ANSI říká, že interní identifikátor může být dlouhý 31 znaků, externí 6.

Identifikátor je tvořen posloupností alfanumerických znaků a podtržítka, přičemž musí být splněny následující podmínky:

● prvním symbolem smí být písmeno nebo podtržítko ● následuje libovolná kombinace písmen, číslic a podtržítek (nejvýše však do maximální délky

identifikátoru - viz výše).

Jazyk C v identifikátorech rozlišuje malá a velká písmena1. Následující identifikátory jsou tedy navzájem odlišné:

identifikator Identifikator IDENTIFIKATOR IdEnTiFiKaToR

Komentář je část programu umístěná mezi dvojici párových symbolů /* a */. Komentář může vypadat například takto:

/* Toto je komentář, a toto je jeho pokračování na druhém řádku. */.../* if (uk->chyba) { ts->pom_info++; } else nepripustim chybu, ladim stejne nasucho*/...

Komentáře obvykle umisťujeme do zdrojového textu z důvodu jeho lepší čitelnosti. Často popisujeme některé důležité vlastnosti zdrojového textu právě v komentáři. Komentářem si můžeme rovněž přechodně vypomáhat ve fázi tvorby a ladění programu:

Bílý znak je jeden z následujících symbolů: mezera, tabelátor, nový řádek, posun řádku, návrat vozíku, nová stránka a vertikální tabelátor2. Bílé znaky spolu s operátory a oddělovači stojí mezi identifikátory, klíčovými slovy, řetězci a konstantami3použitými ve zdrojovém textu. Překladač považuje rovněž komentář za bílý znak.

2.2. Základní typy datZákladní typy dat dělíme na celočíselné, racionální4, společně je nazýváme aritmetické datové typy,

http://docs.linux.cz/c_saloun/kap02.htm (2 z 10) [23.2.2004 11:37:42]

Page 15: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

znaky a na ukazatele. Celočíselné datové typy mohou obsahovat modifikátory unsigned respektive signed, čímž můžeme požadovat hodnoty příslušného typu bez znaménka, resp. se znaménkem5. V céčku máme tedy k dispozici všechny potřebné základní typy i s jejich případnými modifikacemi. Jejich přehled spolu s paměťovými nároky a jejich českým významem následuje:

datový typ počet bitů významchar, unsigned char, signed char 8 znakshort, unsigned short, signed short 16 krátké celé čísloint, unsigned int, signed int 16 nebo 32 celé číslo

long, unsigned long, signed long 32 dlouhé celé číslo6

enum 8|16|32 výčtový typ float 32 racionální číslo

double 64 racionální číslo s dvojitou přesností

long double 80 pointer 16|32 ukazatel

Pokud nás překvapí více možných hodnot ve sloupci počet bitů, pak vězme, že tuto hodnotu určuje jak překladač, a případně u některých OS i paměťový model, tak skutečnost zásadnějšího významu. Totiž jedná-li se o překladač generující cílový kód šestnáctibitový či třicetidvoubitový (ovlivní typ int).

Nebudete-li si jisti rozsahem hodnot jednotlivých aritmetických typů, podívejte se do souboru LIMITS.H (pro celočíselné typy), respektive FLOAT.H (pro typy racionální). V nich najdete nejmenší případně i největší možné hodnoty, které příslušný překladač připouští.

Při našich prvních krocích vycházejme z následujících zásad, které jsou součástí ANSI C. Jak celočíselné, tak racionální typy je možno co do počtu obsazených bitů7 (a z toho vyplývajícího rozsahu možných hodnot) uspořádat takto:

short <= int <= long

float <= double <= double float

a dále platí, že char vyžaduje 8 bitů.

Na tomto místě věnujme několik slov konverzím aritmetických typů. Výraz složený z operandů různého aritmetického typu bude vyhodnocen a příslušné typové konverze proběhnou automaticky. Tím ovšem není řečeno, že výsledek bude takový, jaký očekáváme na základě znalostí naší školní matematiky. Konverzím aritmetických typů se podrobněji budeme věnovat později.

2.3. Konstanty a proměnné.http://docs.linux.cz/c_saloun/kap02.htm (3 z 10) [23.2.2004 11:37:42]

Page 16: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

Většina objektů (entit), označených identifikátorem musí být deklarována dříve, než je použita. Konstanty, typy, proměnné a funkce k takovým objektům patří.

S konstantami a proměnnými jsou úzce spojeny dva pojmy, deklarace a definice8. Deklarací určujeme typ objektu. Informace o typu je překladačem používána při typové kontrole, typových konverzích, atd. V místě definice definujeme hodnotu proměnné či posloupnost příkazů funkce. Z uvedeného vyplývá důvod, proč můžeme například funkci vícekrát deklarovat, ale pouze jedenkrát definovat.

Konstanty a proměnné mohou nabývat hodnot jak základních datových typů, tak typů uživatelsky definovaných. Přirozeně mohou tvořit i struktury typu pole. V této části se poli konstantních i proměnných vektorů nebudeme zabývat podrobněji, omezíme se na jejich definice.

Konstanty

Konstanty jsou symboly, reprezentující neměnnou číselnou nebo jinou hodnotu. Překladač jazyka jim přiřadí typ, který této hodnotě odpovídá. Z konstant odpovídajících si typů můžeme vytvářet konstantní výrazy. Tyto výrazy musí být regulární (zjednodušeně musí být snadno vyhodnotitelné během překladu). Nesmí obsahovat žádný z následujících operátorů (nejsou-li použity mezi operandy operátoru sizeof):

● přiřazení ● inkrementace a dekrementace ● funkční volání ● čárka

Konstantám s vhodně zvolenými identifikátory dáváme přednost například před přímým uvedením konstantní hodnoty jako meze cyklu nebo dimenze pole9. Modifikace programu pak probíhá velmi snadno změnou hodnoty konstanty. Odpadá obtížné uvažování, zdali ta či jiná hodnota má býti modifikována či nikoliv. Konstanty mají rovněž určen typ. Tím je umožněna typová kontrola.

Konstanty definujeme po klíčovém slově const následovaném typem konstanty, jejím identifikátorem a po rovnítku její hodnotou ukončenou středníkem. Jedná-li se o vektor, následuje za identifikátorem dvojice hranatých závorek, zpravidla obsahující jeho dimenzi. První prvek pole má vždy index 0. Konstanty můžeme definovat třeba takto:

const int konstanta = 123;const celociselna = -987; const float CPlanck = 6.6256e-34; const char male_a = 'a'; const char *retezec = "Konstantni retezec." const float meze[2] = {-20, 60}; const char rimska_znaky[] = {'I', 'V', 'X', 'L', 'C', 'D', 'M'}; const int rimska_hodn[] = {1, 5, 10, 50, 100, 500, 1000};

http://docs.linux.cz/c_saloun/kap02.htm (4 z 10) [23.2.2004 11:37:42]

Page 17: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

Neuvedeme-li typ, jako v případě druhé konstanty, je implicitně chápán typ int. Můžeme definovat konstanty všech základních datových typů. Proměnná meze představuje dvouprvkové pole konstant typu float. Prvky pole nabývají se vzrůstajícím indexem hodnoty v pořadí, jak jsou v definici zapsány. Poslední dvě konstanty jsou pole konstantních hodnot. Uvedeme-li všechny požadované hodnoty na pravé straně definice, nemusíme v hranatých závorkách uvádět dimenzi pole10.

Celočíselné konstanty

Celočíselné konstanty jsou tvořeny zápisem celého čísla. Mohou být zapsány v desítkové, osmičkové případně v šestnáctkové číselné soustavě. Nejprve tedy jednoduché zápisy:

123 -987 255 4567 -4567

které představují celočíselné konstanty. V pořadí druhá a poslední představují záporné hodnoty, ostatní jsou kladné.

Nyní si uvedeme pravidla, podle nichž určujeme základ číselné soustavy konstanty:

● 0 (číslice nula) uvádí konstanty v osmičkové soustavě ● 0x nebo 0X (číslice nula následovaná znakem x) uvádí konstanty v šestnáctkové soustavě ● libovolná číslice s výjimkou nuly je součástí konstanty v desítkové soustavě (viz příklad výše)

Následuje několik desítkových konstant zapsaných ve třech možných číselných soustavách11:

desítkový osmičkový šestnáctkový12 123 0173 0x7b

-987 0176045 0xfc25

255 0377 0xff

4567 010727 0x11d7

-4567 0167051 0xee29

Jestliže prefix zápisu celočíselné konstanty určoval základ číselné soustavy, pak sufix, pokud je uveden, určuje celočíselný datový typ. A to následovně: u nebo U modifikuje typ na unsigned, zatímco l nebo L říká, že jde o typ long13. Oba sufixy je možno spojit, takže například:

123UL

je (desítková) konstanta 123 typu unsigned long.

http://docs.linux.cz/c_saloun/kap02.htm (5 z 10) [23.2.2004 11:37:42]

Page 18: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

Racionální konstanty

Racionální konstanty umožňují zapsat číselnou konstantu, která nemusí být celočíselná. Vnitřně je reprezentována ve tvaru, který obsahuje mantisu a exponent, obojí s případným znaménkem.

Implicitní typ racionální konstanty je double. Například 12.34e5. Chceme-li, aby konstanta byla typu long double, připojíme k zápisu písmeno L, tedy například

12.34e5L.

Pro lepší představu dává následující tabulka přehled některých vlastností racionálních datových typů:

typ bitů mantisa exponent rozsah absolutních hodnot (přibližně)

float 32 24 8 3.4 10-38 až 3.4 10+38

double 64 53 11 1.7 10-308 až 1.7 10+308

long double 80 64 15 3.4 10-4932 až 1.1 10+4932

Přesně definuje racionální datové typy norma IEEE 754.

Znakové konstanty

Znakové konstanty jsou tvořeny požadovaným znakem, respektive posloupností znaků, uzavřeným mezi apostrofy. Na následujícím řádku je zapsáno několik znakových konstant:

'a' 'A' 'Š' 'ň' '}' '#' '"'

První dvě nás jistě nepřekvapí, další dvě nemusí být nutně k dispozici na všech systémech (byť podpora národního prostředí je čím dál větší samozřejmostí). Další dva znaky zase nejsou k dispozici na standardních českých klávesnicích, ale céčko si bez nich představit nedovedeme. No a poslední znaková konstanta jsou úvozovky. Jimi si připravujeme následující otázku.

Jak zapíšeme znakovou konstantu apostrof? A co případně jiné speciální znaky (řídící symboly, znaky nenacházející se na klávesnici, ...). Zde si pomáháme symbolem opačné lomítko a nejméně jedním dalším znakem. Těmto posloupnostem říkáme escape sequence. Ty mohou být jednoduché, kdy opačné lomítko následuje jediný znak. Nebo následuje osmičkový či (po x) šestnáctkový kód znaku. Tak jsme schopni zadat i znak, který se na klávesnici nenachází, ale jehož kód je nám znám. Přehled escape sequencí obsahuje tabulka:

posloupnost jméno Ctrl-znak význam\a Alert (Bell) G pípnutí

http://docs.linux.cz/c_saloun/kap02.htm (6 z 10) [23.2.2004 11:37:42]

Page 19: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

\b Backspace H návrat o jeden znak\f Formfeed L nová stránka nebo obrazovka\n Newline J přesun na začátek nového řádku\r Carriage return M přesun na začátek aktuálního řádku\t Horizontal tab I přesun na následující tabelační pozici\v Vertical tab K stanovený přesun dolů \\ Backslash obrácené lomítko\' Single quote apostrof\" Double quote úvozovky\? Question mark otazník

\OOO ASCII znak zadaný jako osmičková hodnota

\xHHH ASCII znak zadaný jako šestnáctková hodnota

Konstantní řetězce

Konstantní řetězce - literály jsou na rozdíl od znakových konstant tvořeny více než jedním znakem, zpravidla slovem či větou (tedy znakovou posloupností, řetězcem). Začátek a konec řetězce jsou vymezeny úvozovkami. Následující řetězce jsou úmyslně psány cesky:

"dve slova" "Cela tato veta tvori jeden retezec." "a" "Ahoj!"

Na předposlední řetězec musíme upozornit. Jedná se o řetězcovou konstantu tvořenou písmenem a, tedy řetězcem délky jeden znak. Nesmíme ji zaměňovat se znakovou konstantou. Ta je vymezena dvěma apostrofy. Navíc, znaková konstanta, bez ohledu na její zápis, představuje jeden jediný znak, zatímco řetězcová konstanta může mít i značnou délku.

Pokud konstantní řetězec obsahuje speciální symboly, zapisujeme je obdobně, jako jsme to činili u znakových konstant. Například:

"\tPo uvodnim tabelatoru prejdeme na novy radek\na pipneme\a."

Použili jsme jednoduché escape sequence. Pokud bychom po nich udělali mezeru, byla by tato rovněž obsažena i ve výsledném řetězci. A to nechceme. A ještě jedna ukázka delší řetězcové konstanty:

"Tato delsi retezcova konstanta obsahuje opacne lomitko \\, \ a pokracuje na dalsim radku od jeho zacatku. " "Navic je takto \rozdelena."

http://docs.linux.cz/c_saloun/kap02.htm (7 z 10) [23.2.2004 11:37:42]

Page 20: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

"Jednodussi pokracovani na dalsim radku "

"vypada takto, pak nemusime zacinat hned na zacatku."

Prvni dva řádky jsou ukončeny opačným lomítkem. Tím je řečeno, že řetězec pokračuje na následujícím řádku. Jednodušší je ovšem konstrukce, při níž ukončit řetězec úvozovkami, ukončíme řádek a pokračuneme novým řetězcem kedkoliv na novém řádku. Překladač totiž dva řetězce, oddělené pouze bílými znaky, spojí v řetězec jeden.

Proměnné

Proměnné jsou paměťová místa přístupná prostřednictvím identifikátoru. Hodnotu proměnných můžeme během výpočtu měnit. Tím se proměnné zásadně odlišují od konstant, které mají po celou dobu chodu programu hodnotu neměnnou - konstantní.

Proměnné deklarujeme uvedením datového typu, jež je následován identifikátorem, nebo seznamem identifikátorů, navzájem oddělených čárkami. Deklarace končí středníkem. Současně s deklarací proměnné můžeme, ale nemusíme, definovat i její počáteční hodnotu:

int a, b, c, pocet = 0; float x, prumer = 0.0, odchylka = 0.0; float y;

ANSI C považuje konstanty za proměnné s neměnnou hodnotou. V závislosti na použitém překladači může být tato hodnota umístěna ve výrazu přímo. Pak jí nemusí být vyhrazeno paměťové místo tak, jak by bylo vyhrazeno pro proměnnou.14

2.4. Ukazatelé.Ukazatel představuje adresu paměťového místa. Jeho hodnota říká, kde je uložen nějaký objekt. Součástí deklarace ukazatele je i informace o typu dat, které jsou na získané adrese očekávány.

int *integer_ptr; /* integer_ptr je ukazatel na integer */

Hodnota integer_ptr představuje adresu paměti a *integer_ptr je celočíselná hodnota uložená na této adrese. Často používáme pojem dereference ukazatele. Následující hypotetický příklad pracuje s adresou 1004, kam zapíše hodnotu 123:

int *p; p = 1004; *p = 123;

Paměť by po těchto příkazech vypadala následovně (změněná hodnota je zvýrazněna, ostatní paměťová místa mají náhodný obsah):

http://docs.linux.cz/c_saloun/kap02.htm (8 z 10) [23.2.2004 11:37:42]

Page 21: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

Hodnoty 15 0 123 65

Adresy 1002 1003 1004 1005

Obvyklou chybou začátečníků je použití ukazatele bez jeho předchozí inicializace (alokace paměťového místa). Neinicializovaný ukazatel může ukazovat na kritické oblast paměťi a jeho použití může vést (v operačních systémech jako je MS-DOS) i k havárii systému. Takto tedy ne:

Zatím vystačíme bez alokace paměti. Potřebujeme ovšem prostředek ke získání adresy existující proměnné. Pak se budeme korektně odkazovat na vyhrazené paměťové místo.

int i, *pi;pi = &i;*pi = 123;

Po definici proměnné i typu int, a ukazatele pi na typ int v prvním řádku následuje získání adresy proměnné i. Na tuto adresu se odkazuje ukazatel pi. Jeho dereferencí na levé straně příkazu přiřazení, uložíme příslušnou hodnotu na paměťové místo, na které ukazuje.

Ukazatele, o kterých jsme dosud pojednávali, byly spojené s nějakým konkrétním typem. Tato skutečnost je přínosem, neboť umožňuje typovou kontrolu. Jsou však okamžiky, kdy prostě potřebujeme ukazovat ukazatelem do paměti a nemáme na mysli konkrétní datový typ. ANSI norma pro takovou situaci zavádí prázdný fiktivní typ void.

Výsvětlivky:

1 Tato skutečnost činí obtíže zejména začátečníkům přecházejícím k céčku z většiny jiných jazyků.2 V obvyklém zdrojovém textu se nejčastěji můžeme setkat s prvními třemi představiteli bílých znaků.3 Všechny uvedené pojmy se společně označují jako tokeny. Pro překladač představují dále nedělitelné4 Tento termín nejlépe vystihuje omezenou délku mantisy, kterou mohou čísla s desetinnou tečkou5 Tato varianta je často používána u typu char, kde může být rovněž zdůrazněno jak signed char,6 Celočíselný typ long rovněž můžeme psát jako long int. Obdobně short představuje totéž, co7 Později se dozvíme, že ke zjištění potřebné paměti pro hodnotu příslušného typu je možno použít8 Tyto pojmy budeme později diskutovat i v souvislosti s funkcemi.9 Konstantám uvedeným přímo ve zdrojovém textu se říká magická čísla. Vhodný identifikátor leccos

http://docs.linux.cz/c_saloun/kap02.htm (9 z 10) [23.2.2004 11:37:42]

Page 22: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 2.Konstanty, proměnné a deklarace.

10 Pro hloubavější čtenáře můžeme uvést i možnost, jak později zjistit, kolik prvků takové pole má.11 Nutno podotknout, že se jedná o šestnáctibitový kód, tedy i typ int je šestnáctibitový.12 Osmičkové a šestnáctkové konstanty jsou chápány bez znaménka. Šestnáctkové cifry můžeme13 Často se jako sufix používá velké L, neboť malé l je snadno zaměnitelné s číslicí jedna.

14 Tato skutečnost závisí rovněž na použití konstanty. Odvoláváme-li se na její adresu, je jí paměťový

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce CAutor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap02.htm (10 z 10) [23.2.2004 11:37:42]

Page 23: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

Předchozí kapitola Obsah Konec Následující kapitola

3. Operátory a výrazy3.1 Operand, operátor, výraz.3.2. Rozdělení operátorů.3.3. Operátor přiřazení, l-hodnota a p-hodnota.3.4. Aritmetické operátory - aditivní a multiplikativní.3.5. Logické operátory.3.6. Relační operátory.3.7. Bitové operátory.3.8. Adresový operátor.3.9. Podmíněný operátor.3.10. Operátor čárka.3.11. Přetypování výrazu.

3.1. Operand, operátor, výraz.Zapíšeme-li v matematice například a + b, hovoříme o výrazu. Ten má dva operandy a a b a jeden operátor + . Jedná se sice o výraz velmi jednoduchý, nicméně nám umožnil zopakování potřebných termínů.

3.2. Rozdělení operátorů.Operátory rozdělujeme podle počtu operandů (arity) na operátory unární, binární a ternální. Binární operátory jsou aritmetické, relační, logické, bitové a operátory přiřazení a posuvu. Aritmetické operátory jsou aditivní a multiplikativní. Operátory mají svou prioritu a asociativitu. Priorita určuje, že například násobení se vyhodnotí dříve, než třeba sčítání. Asociativita říká, vyhodnocuje-li se výraz zleva doprava, nebo naopak.

Operátory rovněž dělíme podle pozice jejich zápisu vzhledem k operandu(-ům). Takto rozlišujeme operátory prefixové, infixové a postfixové. Operátory v jednotlivých případech zapisujeme před operandy, mezi operandy, respektive za operandy. Druhá varianta je nám zřejmě nejbližší. Infixový způsob zápisu jsme používali již na základní škole.

Poznamejme, že v C uplatníme všechny zmíněné varianty operátorů. Základní přehled operátorů jazyka C, rozlišených podle arity, následuje:

Unární operátory:

+, - aritmetické plus a mínus & reference (získání adresy objektu)

http://docs.linux.cz/c_saloun/kap03.htm (1 z 11) [23.2.2004 11:37:59]

Page 24: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

* dereference (získání objektu dle adresy) ! logická negace ~ bitová negace

++, -- inkrementace resp. dekrementace hodnoty, prefixový i postfixový zápis

(typ) přetypování na typ uvedený v závorkách sizeof operátor pro získání délky objektu nebo typu

Binární operátory:

=přiřazení, možná je i kombinace s jinými operátory, např. +=, -=, *=, /=, <<=, ^=

+ sčítání- odčítání* násobení/ dělení% zbytek po celočíselném dělení (modulo) <<, >> bitový posun vlevo resp. vpravo & bitový součin (and) | bitový součet (or) ^ bitový vylučovací součet (xor) && logický součin (and) || logický součet (or) . tečka, přímý přístup ke členu struktury -> nepřímý přístup ke členu struktury , čárka, oddělení výrazů < menší než > větší než <= menší nebo rovno >= větší nebo rovno == rovnost!= nerovnost

Ternální operátor:

? : podmíněný operátor

Při podrobnějším pohledu na přehled operátorů podle arity záhy objevíme některé z operátorů, které jsou uvedeny jako unární i binární současně. Příkladem uveďme -, které může vystupovat jako unární mínus i jako

http://docs.linux.cz/c_saloun/kap03.htm (2 z 11) [23.2.2004 11:37:59]

Page 25: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

binární operátor odčítání.

Operátory s uvedením priority (v tabulce jsou řazeny sestupně od priority nejvyšší k prioritě nejnižší) a asociativity:

operátor typ operátoru asociativita[ ] ( ) . -> postfixové ++ postfixové -- výraz zleva doprava

prefixové ++ prefixové -- sizeof & * + - ~ ! unární logické OR

přetypování unární zprava doleva * / % násobení zleva doprava+ - sčítání zleva doprava<< >> bitového posunu zleva doprava< > <= >= relační zleva doprava== != rovnosti zleva doprava& bitové AND zleva doprava^ bitové vylučovací OR (XOR) zleva doprava| bitové OR zleva doprava&& logické AND zleva doprava|| logické OR zleva doprava?: podmíněné vyhodnocení zprava doleva= *= /= %= += -= <<= >>= &= |= ^= jednoduché přiřazení a přiřazení s výpočtem zprava doleva, postupné vyhodnocení zleva doprava

Unární operátory jsou prefixové s možným postfixovým použítím dekrementace a inkrementace. Binární operátory jsou infixové.

Operátory jsou rovněž [], () ohraničující indexy resp. argumenty a #, ##, které zpracovává již preprocesor. Preprocesoru v tomto textu věnujeme celou kapitolu. Užitečným operátorem je sizeof, který v průběhu překladu vyhodnotí paměťové nároky svého argumentu. Tento operátor je nezbytný zejména při dynamické alokaci paměti, případně při operacích čtení/zápis z binárních souborů.

3.3. Operátor přiřazení, l-hodnota a p-hodnota.Výrazy, jak již víme, jsou tvořeny posloupností operátorů a operandů. Výraz předepisuje výpočet adresy nebo hodnoty. Upravíme-li například známý vztah

Pytagorova veta

do syntakticky správného zápisu1 v jazyce C

http://docs.linux.cz/c_saloun/kap03.htm (3 z 11) [23.2.2004 11:37:59]

Page 26: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

c = sqrt(a*a + b*b);

můžeme zřetelně ukázat některé významné vlastnosti operátoru přiřazení =2. Na pravé straně operátoru přiřazení se nachází výraz jehož vyhodnocením získáme hodnotu tohoto výrazu. Ovšem na levé straně se nachází výraz (v našem případě je to "jen" proměnná), jehož vyhodnocením získáme adresu. Na tuto adresu, představující začátek paměťového místa pro umístění hodnoty proměnné c, je umístěna hodnota z pravé strany přiřazovacího operátoru.

Ještě než si zadefinujeme zmíněné pojmy, nesmíme zapomenout na důležitou skutečnost. Výsledkem výrazu3 přiřazení je hodnota. Co to znamená? Například možnost elegantně řešit inicializaci více proměnných stejnou hodnotou, například

int a, b, c;a = b = c = -1;

Nezapomínejme, že přiřazovací operátor je asociativní zprava doleva. Nejprve se tedy vyhodnotí c = -1 , výsledkem je hodnota -1, ta tvoří pravou stranu přiřazení b = , jehož výsledkem je opět -1. A jak se uvedená hodnota dostane do proměnné a není jistě třeba popisovat. Vraťme se však k nastíněným pojmům.

Adresový výraz (lvalue - l-hodnota) je výraz, jehož výpočtem se získá adresa v paměti. Například, je-li P nějaký výraz vyhodnocený jako nenulový ukazatel, pak *P je l-hodnota. V souvislosti s modifikátorem const rozlišujeme modifikovatelnou a nemodifikovatelnou l-hodnotu.

Hodnotový výraz (rvalue - p-hodnota) je výraz, jehož výpočtem se získá hodnota jistého typu. Typ je jednoznačně určen typem operandů. Protože se zejména při číselných výpočtech často setkáváme s operandy různých typů, uveďme si pravidla, jež určují typ výsledku. Poznamenejme, že naplnění pravidel testujeme v uvedeném pořadí:

Alespoň jeden z operandů je racionálního typu, pak

● je-li jeden z operandů typu long double, je rovněž druhý operand konvertován na tento typ. ● je-li jeden z operandů typu double, je rovněž druhý operand konvertován na tento typ. ● je-li jeden z operandů typu float, je rovněž druhý operand konvertován na tento typ. ● V opačném případě (operandy jsou celočíselných typů4): ● je-li jeden z operandů typu unsigned long, je rovněž druhý operand konvertován na tento typ. ● je-li jeden z operandů typu long, a druhý typu unsigned int, je druhý operand konvertován na typ long (16-bitový překladač), nebo jsou oba operandy konvertovány na typ unsigned long (32-bitový překladač).

● je-li jeden z operandů typu long, je rovněž druhý operand konvertován na tento typ. ● je-li jeden z operandů typu unsigned int, je rovněž druhý operand konvertován na tento typ. ● není-li splněna žádná z předchozích podmínek, jsou oba operandy převedeny na typ int.

Výrazy mohou vyvolávat i vedlejší efekty. ANSI norma nedoporučuje používat výrazy, které během vyhodnocení způsobují vícenásobnou změnu obsahu jednoho paměťového místa. Například:

cc = cc++ + 1.

http://docs.linux.cz/c_saloun/kap03.htm (4 z 11) [23.2.2004 11:37:59]

Page 27: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

Některé vedlejší efekty mohou být implementačně závislé, třeba výraz

a[i] = i++.

Operátoru přiřazení jsme se věnovali jako v pořadí prvnímu. Důvod je prostý. Bez tohoto operátoru nemůžeme uchovat výsledky v proměnných. Současně nám tento operátor bude sloužit i v následujícím výkladu látky, včetně popisu dalších operátorů.

3.4. Aritmetické operátory - aditivní a multiplikativní.Aritmetické operátory + - * / % představují základní matematické operace sčítání, odčítání, násobení, dělení a zbytku po (celočíselném) dělení.

Nejlepší ukázkou bude jistě příklad.

/********************************************************************//* program op_int01.c *//* celociselne nasobeni, deleni, zbytek po deleni *//* navic je ukazano celociselne preteceni (jen pro 16-ti bitove int)*//********************************************************************/

#include <stdio.h>

int main(){ int o1 = 123, o2 = 456, o3 = 295, v1, v2, v3; int c1 = 20000, c2 = 20001, vc; v1 = o1 * o2; v2 = o3 / 2; v3 = o3 % 2;

printf("%d * %d = %d\n", o1, o2, v1); printf("%d / %d = %d\n", o3, 2, v2); printf("%d %% %d = %d\n", o3, 2, v3);

vc = c1 + c2; printf("\nnyni pozor:\n\t"); printf("%d + %d = %d\n", c1, c2, vc);

return 0;}

http://docs.linux.cz/c_saloun/kap03.htm (5 z 11) [23.2.2004 11:37:59]

Page 28: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

/* vystup BC31123 * 456 = -9448295 / 2 = 147295 % 2 = 1

nyni pozor:20000 + 20001 = -25535*/

/* vystup MWC + COHERENT123 * 456 = 56088295 / 2 = 147295 % 2 = 1

nyni pozor:20000 + 20001 = 40001*/

Příklad ukazuje nejen inicializaci hodnot proměnných "operandů" o1 o2 a o3, ale po prvních očekávaných výsledcích i neočekávané hodnoty5. Ty jsou způsobeny faktem aritmetického přetečení. Dále je vhodné zdůraznit, že vzhledem k typu operandů int, je i typ výsledku stejného typu (viz pravidla uvedená dříve). Z toho důvodu je i výsledek dělění celočíselný.

Podívejme se nyní na výsledky aritmetických operací, v nich argumenty jsou opět celočíselné, ale levá strana je racionálního typu.

/**********************************************************************//* program op_int_f.c *//* zakladni aritmeticke operace a prirazeni vysledky jsou sice i float*//* ale vypocty jsou provadeny jako int a teprve pote prevedeny *//**********************************************************************/

#include <stdio.h>

int main(){ int i, j; float r, x; j = i = 5; j *= i; r = j / 3; x = j * 3; printf("i=%d\tj=%d\tr=%f\tx=%f\n", i, j, r, x);

return 0;}

/* vystup BC31i=5 j=25 r=8.000000 x=75.000000*/

Rovněž v tomto příkladu vidíme, že výpočet probíhá s hodnotami typů podle zmíněných pravidel a teprve poté

http://docs.linux.cz/c_saloun/kap03.htm (6 z 11) [23.2.2004 11:37:59]

Page 29: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

je získaná p-hodnota konvertována do typu odpovídajícího l-hodnotě. Proto podíl 25/3 dává 8.0 a nikoliv 8.333 .

Stejným způsobem probíhají aritmetické operace s racionálními hodnotami.

Chceme-li změnit pořadí vyhodnocení jednotlivých částí výrazu, použijeme k tomuto "pozměnění priority" kulatých závorek6.

3.5. Logické operátory.Logické operátory představují dvě hodnoty, pravda a nepravda. ANSI norma C říká, že hodnota nepravda je představována 0 (nulou), zatímco pravda 1 (jedničkou)7. Ve druhém případě se ovšem jedná o doporučení. Neboť užívaným anachronismem je považovat jakoukoliv nenulovou hodnotu za pravdu.

Logické operátory jsou && || !, postupně and or a not. Provádí výpočet logických výrazů tvořených jejich operandy. Pravidla pro určení výsledku známe z Booleovy algebry. Logické výrazy často obsahují i stanovení (a ověření) podmínek tvořených relačními operátory.

3.6. Relační operátory.Relační operátory jsou < > <= >= == !=. Pořadě menší, větší, menší nebo rovno, větší nebo rovno, rovno a nerovno. Jsou definovány pro operandy všech základních datových typů8. Jejich výsledkem jsou logické hodnoty pravda a nepravda tak, jak jsou popsány v předchozím odstavci.

3.7. Bitové operátory.Jak sám název napovídá, umožňují provádět operace nad jednotlivými bity. Tuto možnost zdaleka nemají všechny programovací jazyky označované jako vyšší. Jazyk C jí oplývá zejména proto, že byl vytvořen jako nástoj systémového programátora (OS Unix). Použití bitových operátorů vyžaduje znalosti o uložení bitů v paměti, způsobu kódování čísel, ... .

Bitové operátory jsou: << >> & | ~ ^ , tedy posun vlevo, posun vpravo, and, or, not a xor. Bitové operace jsou možné pouze s celočíselnými hodnotami. Podívejme se nyní na jednotlivé zástupce bitových operátorů.

Při bitovém posunu vlevo (vpravo) << , ( >> ) se jednotlivé bity posouvají vlevo (vpravo), tedy do pozice s (binárně) vyšším (nižším) řádem. Na nejpravější (nejlevější) posunem vytvořenou pozici je umístěna nula. Posuny ovšem probíhají aritmeticky. To znamená, že uvedené pravidlo neplatí pro posun vpravo hodnoty celočíselného typu se znaménkem. V takovém případě se nejvyšší bit (znaménkový), zachovává. Takto se při posunu doplňuje do bitového řetězce nový bit. Naopak před posunem nejlevější (nejpravější) bit je odeslán do "říše zapomění".

Bitový posun o jeden (binární) řád vpravo, respektive vlevo, má stejný význam, jako celočíselné dělení, respektive násobení, dvěma. Je-li bitový posun o více než jeden řád, jedná se o násobení (dělění) příslušnou mocninou dvou.

http://docs.linux.cz/c_saloun/kap03.htm (7 z 11) [23.2.2004 11:37:59]

Page 30: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

Bitové and & , or |, a xor ^ provádí příslušnou binární operaci s každým párem odpovídajících si bitů. Výsledek je umístěn do pozice stejného binárního řádu výsledku. Výsledky operací nad jednotlivými bity jsou stejné, jako v Booleově algebře9. Bitové not ~ je operátorem unárním, provádí negaci každého bitu v bitovém řetězci jediného operandu. Tomuto operátoru se často říká bitový doplňek.

/*******************************************************************//* program op_bit01.c *//* ukazuje bitove posuny, a zakladni bitove operace and, or, xor *//* a bitovy doplnek *//*******************************************************************/

#include <stdio.h>

int main(){ printf("1 << 1 = \t%d\t%#x\n", 1 << 1, 1 << 1); printf("1 << 7 = \t%d\t%#x\n", 1 << 7, 1 << 7);

printf("-1 >> 1 = \t%d\t%#x\n", -1 >> 1, -1 >> 1); printf("1024 >> 9 = \t%d\t%#x\n", 1024 >> 9, 1024 >> 9);

printf("13 & 6 = \t%d\t%#x\n", 13 & 6, 13 & 6); printf("13 | 6 = \t%d\t%#x\n", 13 | 6, 13 | 6); printf("13 ^ 6 = \t%d\t%#x\n", 13 ^ 6, 13 ^ 6);

printf("2 & 1 = \t%d\t%#x\n", 2 & 1, 2 & 1); printf("2 | 1 = \t%d\t%#x\n", 2 | 1, 2 | 1); printf("2 ^ 1 = \t%d\t%#x\n", 2 ^ 1, 2 ^ 1);

return 0;}

/* BC31 - 16-ti bitovy kod1 << 1 = 2 0x21 << 7 = 128 0x80-1 >> 1 = -1 0xffff1024 >> 9 = 2 0x213 & 6 = 4 0x413 | 6 = 15 0xf13 ^ 6 = 11 0xb2 & 1 = 0 02 | 1 = 3 0x32 ^ 1 = 3 0x3*/

Jestliže jsme v úvodu k bitovým operátorům naznačili jejich systémovou orientaci, ukažme ji na příkladu. Často popisujeme rozdíly mezi 16-ti a 32-bitovým kódem (překladačem, který kód generuje). Následující program

http://docs.linux.cz/c_saloun/kap03.htm (8 z 11) [23.2.2004 11:37:59]

Page 31: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

nám umožní zjistit, s jakým překladačem máme čest.

/************************************************//* soubor int_size.c *//* zjisti kolika bitove int pouziva prekladac *//************************************************/

#include <stdio.h>

int main(void){ unsigned int ui = ~0; int i = 1; while (ui >>= 1) i++; printf("prekladac pouziva %2d-ti bitovou reprezentaci celeho cisla\n", i);

return 0;}

Povšimněme si použití bitového doplňku ui = ~0. Tak snadno (a zejména přenositelně) získáme bitový řetězec tvořený samými jedničkami.

3.8. Adresový operátor.Tento operátor & je unární. Jak již název adresový operátor napovídá, umožňuje získat adresu objektu, na nějž je aplikován. Adresu objektu můžeme použít v nejrůznějších situacích, obvykle je to ale v souvislosti s ukazateli. Bez tohoto operátoru bychom nebyli schopni pracovat se soubory a ani standardní vstup bychom nebyli schopni číst jinak, než po znacích. Takto například můžeme přečíst hodnoty dvou proměnných jedinou funkcí pro formátovaný vstup:

int i;float f;scanf("%d %f", &i, &f);

3.9. Podmíněný operátor.Podmíněný operátor je poměrně nezvyklý. Proto bude vhodné, objasníme-li si jeho význam. Mějme například výpočet, který potřebujeme provést, v závislosti na nějaké podmínce, jednou ze dvou variant (pochopitelně odlišných). Výsledek výpočtu přiřazujeme vždy stejné proměnné. Pokud navíc je část výrazu, popisující výpočet obou variant, shodná, jedná se o typický příklad využití podmíněného výrazu.

Buďme však raději konkrétnější. Chceme-li vypočíst absolutní hodnotu nějakého čísla, použijeme velmi pravděpodobně podmíněný operátor. Výpis zdrojového textu takového výpočtu následuje:

/**********************************//* soubor op_cond.c *//* ukazuje pouziti podmineneho */

http://docs.linux.cz/c_saloun/kap03.htm (9 z 11) [23.2.2004 11:38:00]

Page 32: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

/* operatoru - absolutni hodnota *//**********************************/

#include <stdio.h>

int main(void){ int i, abs_i;

printf("\nZadej cele cislo: "); scanf("%d", &i);

abs_i = (i < 0) ? -i : i; printf("abs(%d) = %d\n", i, abs_i);

return 0;}

Další použití (ternálního) podmíněného operátoru ukazuje následující řádek.

return((znak == 0x0) ? getch() + 0x100 : znak);

Tento řádek zajišťuje případné načtení a překódování libovolné stisknuté klávesy na tzv. rozšířené klávesnici IBM PC AT. Základní klávesy této klávesnice produkují kód odpovídající jejich pozici v ASCII tabulce. Rozšířené klávesy produkují dvojici celočíselných kódů, z nichž první je nula. Než si popíšeme jeho činnost, doplňme i předcházející příkaz pro načtení znaku:

znak = getch();return((znak == 0x0) ? getch() + 0x100 : znak);

Pomocí podmínky (znak == 0x0) otestujeme, jednalo-li se o rozšířenou klávesu. Jestliže ano, je proveden první příkaz následující za podmíněným operátorem, getch() + 0x100 . Je jím načten tzv. scan kód rozšířené klávesy, který je dále zvětšen o hodnotu 100hex. Jestliže podmínka splněna nebyla, je vykonán příkaz za dvojtečkou. Návratovou hodnotou je pak neupravená hodnota, načtená do proměnné znak těsně před ternálním operátorem.

3.10. Operátor čárka.Čárka je operátorem postupného vyhodnocení. Má nejnižšší prioritu ze všech operátorů a vyhodnocuje se zleva doprava10. Čárkou můžeme oddělit jednotlivé výrazy v místě, kde je očekáván jediný výraz. Například v cyklu for.

3.11. Přetypování výrazu.Jazyk C nám umožňuje přetypovat výrazy podle naší potřeby. Přetypování má přirozeně svá omezení, ta však často plně odpovídají zdravému rozumu. Těžko se například vyskytne potřeba přetypovat znak na typ ukazatel na double.

http://docs.linux.cz/c_saloun/kap03.htm (10 z 11) [23.2.2004 11:38:00]

Page 33: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 3.Operátory a výrazy

Úvodní úvaha postrádá konkrétní ukázku, z níž by vyplýval syntaktický zápis přetypování. Připomeňme si příklad op_int_f.c, v němž se vyskytoval příkaz

r = j / 3;

který byl ovšem vyhodnocen celočíselně a teprve poté konvertován na float. Chceme-li, aby již podíl proběhl v racionálním oboru, musíme pravou stranu upravit. S přetypováním může vypadat pravá strana takto:

r = (float) j / 3;

Přetypování provádíme tak, že před hodnotu, kterou chceme přetypovat, napíšeme typ, který chceme získat, v kulatých závorkách. Syntakticky tedy zapíšeme přetypování takto:

(type) expression

Výsvětlivky:

1 Předpokládejme, že použité proměnné jsou vhodného typu, například double..

2 Nesmíme se nechat svést jinými programovacími jazyky. Jedná se o jeden z nejčastěji se vyskytujících operátorů, proto mu náleží zápis jediným symbolem.3 Podrobněji se výrazům budeme věnovat v kapitole 6. Řízení chodu programu. Zatím tolik, že příkaz je výraz zakončený středníkem.4 Celočíselné typy jsou jak všechny varianty typu int, tak za celočíselný typ považujeme typ char. Jak jsme v předchozí kapitole viděli, můžeme i u měj použít modifikátory unsigned a signed. Ve výrazech je konvertován na typ int plus případné znaménko.5 V případě 16-ti bitového překladače. 32-bitový dává správné výsledky.6 Je-li potřeba více úrovní závorek, používáme vždy jen závorek kulatých. Hranaté a složené závorky představují operátory jazyka C s jiným významem, než je stanovení pořadí vyhodnocení.7 Jedná se o celočíselné hodnoty.8 Ovšem například pro ukazatele mají smysl pouze operátory rovnosti a nerovnosti.9 Připomeňme alespoň, že xor dává výsledek 1 jen v případě různosti hodnot operandů.10 Tento operátor zajišťuje pořadí vyhodnocení zleva doprava. Výsledkem je hodnota nejpravějšího výrazu ze seznamu výrazů oddělených čárkami.

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce CAutor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap03.htm (11 z 11) [23.2.2004 11:38:00]

Page 34: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

Předchozí kapitola Obsah Konec Následující kapitola

4. PREPROCESOR4.1. Definice maker.

Symbolické konstantyMakra

4.2. Standardní předdefinovaná makra.

Operátory # a ##.Podmíněný překlad.Zbývající direktivy.

Název kapitoly napovídá, že se budeme věnovat prostředku, který předchází překladač. Preprocesor zpracovává vstupní text jako text, provádí v něm textové změny a jeho výstupem je opět text. Od preprocesoru tedy nemůžeme čekat kontrolu syntaxe, natož pak typovou kontrolu. Preprocesor zpracovává hlavičkové soubory, rozvíjí makra, nepropouští komentáře a umožňuje provádět podmíněný překlad zdrojového textu.

Při spuštění překladu je nejprve proveden rozvoj maker, teprve výstup zpracovává překladač. Pokud chceme získat výstup, který preprocesor produkuje, můžeme jej zavolat samostatně příkazem cpp (od C Pre Processor). Preprocesor neprovádí rozvoj maker tam, kde nemohou být umístěny ani příkazy jazyka C (například v komentářích a řetězcích).

C preprocesor přijímá tyto direktivy:

#define #elif #else #endif

#error #if #ifdef #ifndef

#include #line #pragma #undef

Jednotlivé direktivy popíšeme v rámci následujících podkapitol.

Direktiva preprocesoru musí být vždy uvozena znakem #. # navíc musí být na řádku prvním jiným znakem než jsou oddělovače. Od direktivy samotné jej opět mohou oddělovat oddělovače1.

Zdůrazněme ještě jednu důležitou skutečnost. Direktiva preprocesoru není příkaz jazyka C.

http://docs.linux.cz/c_saloun/kap04.htm (1 z 8) [23.2.2004 11:38:11]

Page 35: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

Neukončujme ji proto středníkem.

4.1. Definice maker.Definice maker ve významu rozsahů polí je snad typickým příkladem použití preprocesoru2. Ve zdrojovém textu se neodvoláváme na magická čísla, ale na vhodně symbolicky pojmenovaná makra. Program to nejen zpřehlední, ale případnou změnu hodnoty makra provedeme na jednom místě.

Pomocí preprocesoru a maker můžeme vytvářet konstrukce, které zvýší čitelnost programu. Můžeme například označit začátek a konec bloku právě identifikátory začátek a konec, které pomocí preprocesoru správně převedeme na znaky { a }. Poznamejme ovšem, že jsme popsali spíše možnost. Makra mají zvýšit čitelnost programu, nemají za úkol udělat z programu těžko srozumitelný rébus.

Pokud se text makra nevejde na jeden řádek, můžeme jej rozdělit na více následujících řádků. Skutečnost, že makro pokračuje na následujícím řádku, se určí umístěním znaku \ jako posledního znaku na řádku.

Pro větší přehlednost si makra rozdělme na symbolické konstanty a makra. Klíčem nechť je skutečnost, že makro na rozdíl od symbolické konstanty má argumenty.

Symbolické konstanty

Jejich definování a oddefinování můžeme syntakticky popsat takto:

#define macro_id [token_sequence]#undef macro_id

kde

macro_id představuje jméno (identifikátor) makra token_sequence je nepovinný souvislý řetězec

Při své činnosti prohledává preprocesor vstupní text a při výskytu řetězce macro_id3 provádí jeho nahrazení řetězcem token_sequence. Této činnosti se říká rozvoj (expanze) makra. Z tohoto popisu je jasné, proč se preprocesoru někdy zjednodušeně říká makroprocesor.

#define START 2#define PRIRUSTEK 1#define DELKA_RADKU 100...int main(){ int pocet = 0,

http://docs.linux.cz/c_saloun/kap04.htm (2 z 8) [23.2.2004 11:38:11]

Page 36: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

alokovano = START, prirustek = PRIRUSTEK; pole_retezcu p_ret = NULL; ...

Makra

Makra již podle našeho dělení mají argumenty. Definujeme je takto:

#define macro_id([arg_list]) [token_sequence]

kde (ostatní položky jsou stejné, jako jsme již uvedli u symbolických konstant):

arg_list představuje seznam argumentů navzájem oddělených jen čárkou.

Jako klasický příklad makra si uveďme vrácení maximální hodnoty ze dvou:

#define max(a,b) ((a>b)?a:b)

Výhodou i nevýhodou je, že nepracuje s typy. Výhodou proto, že pokud bychom chtěli definovat podobnou funkci, museli bychom napsat tolik jejích verzí, kolik by bylo navzájem neslučitelných variant datových typů argumentů. Nevýhodou je netypovost makra tehdy, uvedeme-li třebas omylem jako argumenty řetězce (pak by se porovnávaly adresy jejich prvních znaků) nebo dva argumenty neporovnatelných typů (struktura a číslo, ...). Takové chyby pak (někdy) odhalí až překladač.

Při definici makra max nás možná překvapí zdánlivě nadbytečné závorky oddělující token_sequence. Musíme jen připomenout, že makra nejsou příkazy jazyka C. Jejich rozvoj probíhá na textové úrovni. Preprocesor tedy nemůže v závislosti na kontextu jednou nadbytečné závorky vypustit, jindy chybějící přidat. Proto raději sami nadbytečné závorky nevypouštíme.

Z textovosti rozvoje makra mohou plynout i nečekané problémy. Porovnejme makro a funkci, počítající druhou mocninu argumentu:

#define SQR(x) (x*x)int sqr(int x){ return x * x;}

a představme si jejich použití:

int x, y, n = 3;x = sqr(n+1); /* sqr(4) -> 4*4 = 16 */y = SQR(n+1); /* (n+1*n+1) t.j. (4+1*4+1) = 9 */

http://docs.linux.cz/c_saloun/kap04.htm (3 z 8) [23.2.2004 11:38:11]

Page 37: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

což nám v případě makra dává zcela jiný (nesprávný) výsledek, než jsme očekávali. Pokud opravíme (x*x) na správnější ((x)*(x)), dostaneme tentokrát sice výsledek správný, ale opět najdeme příklad4, kdy správný nebude.

Jde o skutečnost, že při volání funkce se argument vyhodnotí jen jednou. U makra tomu tak být nemusí. Podívejme (s lepší variantou makra):

int x, y, n = 3;x = sqr(++n); /* sqr(4) -> 4*4 = 16 */y = SQR(++n); /* ((++n)*(++n)) t.j. ((4)*(5)) = 20 */

Opět dostaneme chybný výsledek u makra SQR().

Právě z důvodů popsaných vedlejších efektů a netypovosti maker se nedoporučuje používat makra pro náhradu funkcí. Doporučuje se použití funkcí s případným modifikátorem inline.

4.2. Standardní předdefinovaná makra.Podle ANSI standardu musí preprocesor C identifikovat a v uvedeném významu vyhodnocovat

následující makra (identifikátory maker jsou obklopeny dvěma podtržítky):

__DATE__ datum překladu, mmm dd yyyy, př. Nov 14 1993 __FILE__ jméno zdrojového souboru __LINE__ právě zpracovávaný řádek ve zdrojovém souboru __STDC__ definuje typ (úroveň) překladu (STanDard C) __TIME__ čas překladu, hh:mm:ss, (hh 00-24), př. 16:02:59

Ovšem i výrobci překladačů vybavují své produkty řadou předdefinovaných maker. Zejména takových, která nám umožňují použít speciální vlastnosti jejich produktu. Z důvodů přenositelnosti se jim raději vyhneme.

Na druhé straně jsou předdefinovaná makra popisující operační systém, případně jeho verzi. Pokud píšeme program pro více OS (obvykle se hovoří o platformách), zřejmě se odvoláme na tyto symbolické předdefinované konstanty v místech, kde jsou volané funkce závislé na OS. Tuto možnost popíšeme dále v podkapitole věnované podmíněnému překladu.

Uveďme si alespoň některá nestandardní makrodefinice:

_DECVAX, IAPX286, MWC, COHERENT, _IEEE, _I386

z produktu Coherent, a některé z BC z prostředí MS-DOS:

http://docs.linux.cz/c_saloun/kap04.htm (4 z 8) [23.2.2004 11:38:11]

Page 38: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

__CDECL__, __cplusplus, __MSDOS__, __OVERLAY__, __PASCAL__,

a ještě MS-DOSovská makra pro použitý paměťový model

__TINY__, __SMALL_, __COMPACT__, __MEDIUM__, __LARGE__, __HUGE__.

Operátory # a ##.

ANSI definuje tyto dva operátory a určuje jejich vyhodnocení takto: Operátor # provádí převod argumentu na řetězec (jednoduše řečeno umístí argument mezi pár úvozovek.

Například definujeme-li

#define display(x) show((long)(x), #x)

pak preprocesor rozvine řádek

display(abs(-5));

na řádek

show((long)(abs(-5)), "abs(-5)");

Operátor ## provádí spojování tokenů tak, že argumenty oddělené tímto operátorem po rozvoji makra vytvoří jeden celek (řetězec).

Opět si ukažme činnost popisovaného operátoru.

Definujeme-li

#define printvar(x) printf("%d\n", variable ## x)

pak následující řádek

printvar(3);

přeloží preprocesor na

printf("%d\n", variable3);

Jak je z ukázky patrné, mohou být mezi argumenty a operátorem ## mezery.

http://docs.linux.cz/c_saloun/kap04.htm (5 z 8) [23.2.2004 11:38:11]

Page 39: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

Podmíněný překlad.

Preprocesor může během své činnosti vyhodnocovat, je-li nějaké makro definováno či nikoliv. Při použití klíčového slova preprocesoru defined pak může spojovat taková vyhodnocení do rozsáhlejších logických výrazů. Argument defined nemusí být uzavřen do závorek. Může se však vyskytnout jen za #if nebo #elif. Například si ukažme složitější podmínku:

#if defined LIMIT && defined OSTRA && LIMIT==10

V závislosti na splnění či nesplnění podmínky můžeme určit, bude-li ohraničený úsek programu dále zpracován, nebo bude-li odfiltrován a tak nebude tedy přeložen. Této možnosti použití preprocesoru říkáme podmíněný překlad.

Vždy musí být jasno, kde podmíněná část zdrojového textu začíná a kde končí. Proto nesmíme zapomínat na #endif či #elif. Podmíněné části musí být ukončeny a omezeny v rámci jednoho zdrojového textu. Jinak oznámí preprocesor chybu. Podmínky velmi připomínají konstrukce jazyka C. Navíc je oproti C zavedena i podmínka #elif. Nenechme se však mýlit. Vyhodnocení podmínek provádí již preprocesor.

Ukázka neúplného programu s jednoduchým podmíněným překladem následuje.

#define LADENI#if defined(LADENI) #include <conio.h>#endif#if defined(LADENI) void volno(void) ... void uvolni(pole_retezcu *p_r, int pocet) ...#endifint main(void) ...#if defined(LADENI) volno();#endif if (alokace(&p_ret, alokovano)) ...#if defined(LADENI) uvolni(&p_ret, pocet); volno();#endif ...

http://docs.linux.cz/c_saloun/kap04.htm (6 z 8) [23.2.2004 11:38:11]

Page 40: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

Zbývající direktivy.

Dosud jsme nepopsali čtyři direktivy preprocesoru. Tedy postupně.

#include

je direktivou naprosto nepostradatelnou. Používáme ji pro včlenění zdrojového textu jiného souboru. Tento soubor může být určen více způsoby. Proto má direktiva #include tři možné formy (pro snadnější odkazy je očíslujme):

1. #include <header_name> 2. #include "header_name" 3. #include macro_identifier

které postupně znamenají:

● soubor header_name je hledán ve standardním adresáři pro include. Takto se zpravidla začleňují standardní hlavičkové soubory. Není-li soubor nalezen, je ohlášena chyba.

● soubor header_name je hledán v aktivním (pracovním) adresáři. Není-li tam nalezen, postupuje se podle první možnosti. Takto se zpravidla začleňují naše (uživatelské) hlavičkové soubory.

● macro_identifier je nahrazen. Další činnost podle 1. nebo 2. varianty.

Poznamenejme, že při práci nad velkým projektem se i vlastní hlavičkové soubory umisťují do zvláštního adresáře. Pak se pochopitelně připojují podle 1. varianty. Protože ovšem 2. varianta při neúspěchu přechází do 1., můžeme i v tomto případě popsaným způsobem odlišit vlastní a standardní hlavičkové soubory. Nesmíme ovšem zapomenout definovat více než jeden standardní adresář.

#error

je direktivou, kterou můžeme zajistit výstup námi zadaného chybového hlášení. Nejčastěji se používá v souvislosti s podmíněným překladem. Má formát:

#error chybové hlášení

kde chybové hlášení bude součástí protokolu o překladu.

#line

umožňuje nastavit hodnotu standardního makra __LINE__ a případně i __FILE__. Používá se zejména u strojově generovaných zdrojových textů. Má formát

http://docs.linux.cz/c_saloun/kap04.htm (7 z 8) [23.2.2004 11:38:11]

Page 41: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 4.PREPROCESOR

#line číslo ["jméno"]

kde číslo udává hodnotu uloženou do __LINE__ a platnou pro následující zdrojový řádek.

jméno udává nepovinnou hodnotu uloženou do makra __FILE__.

#pragma

je speciální direktivou, která má uvozovat všechny implementačně závislé direktivy. Pokud jiný překladač speciální direktivu nezná, prostě ji bez chybového stavu ignoruje.

Výsvětlivky:

1 Poznamejme však, že tato možnost je používána jen zřídka.2 Moderní styl C v tomto případě dává přednost konstantám.3 Symbolické konstanty obvykle píšeme velkými písmeny, abychom je v textu snadno odlišili od ostatních identifikátorů jazyka.4 Jde o příklad bezprostředně následující

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce C

Autor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap04.htm (8 z 8) [23.2.2004 11:38:11]

Page 42: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

Předchozí kapitola Obsah Konec Následující kapitola

5. FUNKCE5.1. Deklarace a definice funkce.5.2. Návratová hodnota funkce.5.3. Argumenty funkcí a způsob jejich předávání.5.4. Funkce s proměnným počtem argumentů.5.5. Rekurse.

V této kapitole si podrobněji rozebereme základní stavební kámen jazyka C - funkci. Z předchozího textu víme, že každý C program obsahuje alespoň jednu funkci - main(). A máme co vysvětlovat. Proč je za main napsána dvojice závorek? Tak poznáme funkci od identifikátoru jiného typu (nebo od výrazu představujícího adresu vstupního bodu funkce). Z ANSI C vyplývá ješte jedna povinnost, funkce main() vrací hodnotu typu int.

Funkce v sobě zahrnuje takové příkazy, které se v programu často opakují a proto se vyplatí je vyčlenit, pojmenovat a volat. Takto byla funkce chápána zejména v dobách, kdy byla paměť počítačů malá a muselo se s nízacházet velmi hospodárně. I dnes můžeme používat funkce ze stejných důvodů. Nicméně se na funkce díváme poněkud jinak.

Především rozlišujme standardní funkce, uživatelské funkce a podpůrné a nadstavbové funkce1.

Standardní funkce jsou definovány normou jazyka a výrobce překladače je dodává jako součást programového balíku tvořícího překladač a jeho podpůrné programy a soubory. Tyto standardní funkce zpravidla dostáváme jako součást standardních knihoven (proto se jim někdy říká knihovní funkce) a jejich deklarace je popsána v hlavičkových souborech. Nemáme k dispozici jejich zdrojový kód. Ostatně nás ani moc nezajímá. Tyto funkce se přeci mají chovat tak, jak definuje norma.

Uživatelské funkce jsou ty funkce, které jsme napsali a máme jejich zdrojové texty. Pokud jsme profesionály, vyplatí se nám naše funkce precizně dokumentovat a archivovat. Když už jsme je jednou napsali a odladili, můžemeje příště s důvěrou již jen používat. Může být účelné sdružovat více uživatelských funkcí do jednoho, případně několika, souborů případně z nich vytvořit knihovnu, abychom je při každém použití nemuseli znovu překládat. Hlavičkové soubory (prototypy funkcí, uživatelské typy, definice maker, ...) jsou opět nezbytností.

Podpůrné a nadstavbové funkce jako položka v rozdělení funkcí nám takové pěkné rozdělení okamžitě zničí. Sem totiž zařadíme nejen funkce od tzv. třetích výrobců (podpora pro spolupráci s databázemi, volání modemu, faxu, uživatelské rozhraní, ...) ale i rozšíření překladače o funkce

http://docs.linux.cz/c_saloun/kap05.htm (1 z 10) [23.2.2004 11:38:25]

Page 43: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

nedefinované normou jazyka, funkce implementačně závislé a podobně. Nakonec sem patří i naše funkce, které používáme jako podpůrné. Jako poslední bod si můžeme představit týmovou práci, kdy používáme funkce kolegů. O jejich zdrojový text se zajímáme až v případě, kdy se funkce nechovají tak, jak mají2.

Vraťme se ale k poslání funkcí. Funkce odpovídají strukturovanému programování. Představují základní jednotku, která řeší nějaký problém. Pokud je problém příliš složitý, volá na pomoc jinou (jiné) funkce. Z toho plyne, že by funkce neměla být příliš rozsáhlá. Pokud tomu tak není, nejenže se stává nepřehlednou, ale je i obtížně modifikovatelnou. Jak se například dotkne změna části rozsáhlé funkce její jiné části?

Pokud chceme jít na úroveň modulárního programování, je zde soubor. Ten může obsahovat více funkcí, které navzájem spolupracují. To co ze souboru chceme dát k dispozici jiným jednotkám, umístíme jako deklarace do hlavičkového souboru.

Definované funkce mají status extern. Jsou připojitelné do jiných jednotek, ať chceme nebo ne. ANSI C vyžaduje prototyp každé funkce, kterou chceme použít3. Tento požadavek výrazně zvyšuje bezpečnost - umožňuje typovou kontrolu.

5.1. Deklarace a definice funkce.Po obecném úvodu, v němž jsme si shrnuli význam funkcí, se podívejme, jak se prakticky s funkcemi pracuje. Začněme klasicky, uvedením syntaktického zápisu deklarace funkce:

typ jméno([seznam argumentů]);

kde

- typ představuje typ návratové hodnoty funkce - jméno je identifikátor, který funkci dáváme - () je povinná dvojice omezující deklaraci argumentů (v tomto případě prázdnou)

seznam argumentů je nepovinný - funkce nemusí mít žádné argumenty, může mít jeden nebo více argumentů, nebo také můžeme určit, že funkce má proměnný počet argumentů4

Než se v jednotlivých podkapitolách budeme podrobněji věnovat každé položce z deklarace funkce, popišme si jméno funkce hned.

Identifikátor funkce je prostě jméno, pod kterým se budeme na funkci odvolávat. Závorkami za identifikátorem (toto se týká výskytu identifikátoru funkce mimo deklaraci či definici) dáváme najevo, že funkci voláme. Pokud závorky neuvedeme, jde o adresu funkce. Adresou funkce rozumíme adresu vstupního bodu funkce.

http://docs.linux.cz/c_saloun/kap05.htm (2 z 10) [23.2.2004 11:38:25]

Page 44: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

V této kapitole jsme zatím popsali jen deklaraci funkce. Ta ovšem patří do hlavičkového souboru. Ostatně středník, který ji ukončuje, nám nedává prostor pro samotnou definici seznamu deklarací a příkazů, které tělo funkce obsahuje.

Definice funkce, na rozdíl od její deklarace, nemá za závorkou ukončující seznam argumentů středník, ale blok. Tento blok se nazývá tělo funkce. V úvodu může, jako jakýkoliv jiný blok, obsahovat definice proměnných (jsou v rámci bloku lokální, neuvedeme-li jinak, jsou umístěny v zásobníku). Pak následuje posloupnost příkazů. Ty definují chování (vlastnosti) funkce. Při definici funkce vytváříme (definujeme) její kód, který obsazuje paměť.

Deklarace funkce popisuje vstupy a výstupy, které funkce poskytuje5. Funkce nemá provádět akce s jinými daty, než která jí předáme jako argumenty. Současně výstupy z funkce mají probíhat jen jako její návratová hodnota a nebo (myšleno případně i současně) prostřednictvím argumentů funkce. Pokud se funkce nechová uvedeným způsobem říkáme, že má vedlejší účinky. Těch se vyvarujme. Často vedou k chybám6.

Pokud uvedeme pouze definici funkce, na kterou se později v souboru odvoláváme, slouží tato definice současně jako deklarace.

V případě neshody deklarace a definice funkce ohlásí překladač chybu.

5.2. Návratová hodnota funkce.Pojem datový typ intuitivně známe. Chceme-li získat návratovou hodnotu funkce, musíme určit, jakého datového typu tato hodnota je. Na typ funkce nejsou kladena žádná omezení. Pokud nás návratová hodnota funkce nezajímá, tak ji prostě nepoužijeme7. Pokud ovšem chceme deklarovat, že funkce nevrací žádnou hodnotu, pak použijeme klíčové slovo void, které je určeno právě pro tento účel.

Kromě určení datového typu musíme také určit hodnotu, která bude návratovou. Je to snadné, tato hodnota musí být výsledkem výrazu uvedeného jako argument příkazu return. Typ return výrazu se pochopitelně musí shodovat, nebo být alespoň slučitelný, s deklarovaným typem návratové hodnoty funkce.

Výraz, následující za return, může být uzavřen v závorkách. To je klasický K&R styl. ANSI C norma závorky nevyžaduje:

return výraz_vhodného_typu;

Spojeno s definicí funkce, může funkce celočíselně vracející druhou mocninu svého celočíselného argumentu vypadat takto:

int isqr(int i)

http://docs.linux.cz/c_saloun/kap05.htm (3 z 10) [23.2.2004 11:38:25]

Page 45: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

{return i * i;}

Příkaz return se v těle funkce nemusí vyskytovat pouze jedenkrát. Pokud to odpovídá větvení ve funkci, může se vyskytovat na konci každé větve. Rozhodně však příkaz return ukončí činnost funkce, umístí návratovou hodnotu na specifikované místo8 a předá řízení programu bezprostředně za místem, z něhož byla funkce volána.

5.3. Argumenty funkcí a způsob jejich předávání.Deklarace všech formálních argumentů funkcí musí obsahovat datový typ. Není možné uvést společný typ pro více následujících identifikátorů.

Korespondence mezi skutečnými a formálními argumenty odpovídá jejich pořadí při volání a v definici. To je ostatně stejné i v ostatních vyšších programovacích jazycích.

Pro slučitelnost formálních argumentů se skutečnými musíme dodržet následující pravidla (říkají nám způsob předávání argumentů). Pro zkrácení použijeme FA pro formální a SA pro skutečný argument:

1. FA int -> SA int, char, short 2. FA double -> SA double, float 3. FA pole -> předá se adresa 1. prvku (obsah celého pole nelze předat hodnotou) 4. Struktury (struct, union) se předávají hodnotou jako celek. 5. Funkce nemohou být předávány hodnotou.

Podstatný je způsob předávání argumentů funkci. Hodnoty skutečných argumentů jsou předány do zásobníku. Formální argumenty se odkazují na odpovídající místa v zásobníku (kopie skutečných argumentů). Změna hodnoty formálního argumentu se nepromítne do změny hodnoty argumentu skutečného! Tomuto způsobu předávání argumentů se říká předávání hodnotou. Problém vzniká v okamžiku, kdy potřebujeme, aby funkce vrátila více něž jednu hodnotu9. Řešení pomocí globální proměnné jsme již dříve odmítli (vedlejší účinky) a tvorba nového datového typu jako návratové hodnoty nemusí být vždy přirozeným řešením.

Řešení je předávání nikoli hodnot skutečných argumentů, ale jejich adres. Formální argumenty pak sice budou ukazateli na příslušný datový typ, ale s tím si poradíme. Podstatnější je možnost změny hodnot skutečných argumentů, způsob nazývaný třeba v Pascalu volání odkazem. V C se hovoří o volání adresou, což je zcela jasné a vystihující.

Ukažme si jednoduchý program, který volá různé funkce jak hodnotou, tak adresou.

/****************************************//* soubor fn_argho.c */

http://docs.linux.cz/c_saloun/kap05.htm (4 z 10) [23.2.2004 11:38:25]

Page 46: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

/* pro procviceni predavani argumentu *//* hodnotou i "odkazem" v jazyce C. *//****************************************/

#include <stdio.h>

int nacti(int *a, int *b){ printf("\nzadej dve cela cisla:"); return scanf("%d %d", a, b);} /* void nacti(int *a, int *b) */float dej_podil(int i, int j){ return((float) i / (float) j);} /* float dej_podil(int i, int j) */

int main(void){ int c1, c2; if (nacti(&c1, &c2) == 2) printf("podil je : %f\n", dej_podil(c1, c2)); return 0;} /* main */

Funkce nacti() musí vracet dvě načtené hodnoty. Předává proto argumenty adresou10. Funkce dej_podil() naopak skutečné argumenty změnit nesmí (nemá to ostatně smysl). Proto jsou jí předávány hodnotou. Povšimněme si, že návratový výraz je v ní uzavřen v závorkách. Nevadí to, i když jsou závorky nadbytečné. Výraz je přeci ukončen středníkem.

Povšimněme si ještě jednou pozorněji funkce nacti(). Její návratová hodnota slouží k tomu, aby potvrdila platnost (respektive neplatnost) argumentů, které předává adresou. To je v C velmi praktikovaný obrat (viz scanf()).

5.4. Funkce s proměnným počtem argumentů.Určíme-li typ a identifikátor argumentů funkce, je vše jednoduché. Co však v okamžiku, kdy potřebujeme funkci s proměnným počtem argumentů. Možné to jistě je, neboť jsou standardní funkce, které si s takovou skutečností umí poradit. Například oblíbené I/O funkce scanf()/printf().

Dosud víme, že hodnoty skutečných argumentů jsou umisťovány do zásobníku. Pokud tedy dokážeme to, co díky deklaraci formálních argumentů překladač umí, tedy provést jejich jednoznačné přiřazení, máme vyhráno.

Problémem však je, že díky deklaraci i volající místo ví, jakého typu očekává volaná funkce

http://docs.linux.cz/c_saloun/kap05.htm (5 z 10) [23.2.2004 11:38:25]

Page 47: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

argumenty. Podle toho je předá. Při proměnném počtu argumentů je předání nesnadné. Jak v místě volání (skutečné argumenty), tak v místě přijetí (formální).

ANSI C pamatuje na popsanou situaci několika makry a pravidly, Díky nim si téměř nemusíme uvědomit, že pracujeme se zásobníkem (ukazatelem na něj). Jediné, co k tomu potřebujeme, je mít mezi argumenty funkce nějaký pevný bod (poslední pevný argument pro získání správného ukazatele do zásobníku). Další argumenty budeme deklarovat jako proměnné (tři tečky). Pak použijeme standardní makra. Pro jednoduchost zpracování jsou typy hodnot předávaných skutečných argumentů konvertovány na int a double.

Makra pro proměnný počet argumentů (viděli jsme i pojem výpustky, ale my přece argumenty nevypouštíme, my pouze dopředu neznáme jejich počet) jsou tři, va_start, va_arg a va_end. Co představují? Nejprve uvedeme jejich deklarace, poté popíšeme význam:

void va_start(va_list ap, lastfix);type va_arg(va_list ap, type); void va_end(va_list ap);

kde

- va_list formálně představuje pole proměnných argumentů - va_start nastaví ap na první z proměnných argumentů předaných funkci (probíhá fixace na poslední neproměnný deklarovaný a předaný argument lastfix) - va_arg se jako makro rozvine ve výraz stejného typu a hodnoty, jako je další očekávaný argument (type) - va_end umožní volané funkci provést případné operace pro bezproblémový návrat zpět - return

Poznamenejme, že při použití těchto maker musí být volány v pořadí va_start - před prvním voláním va_arg nebo va_end. Po ukončení načítání by měl být volán va_end.

Dále poznamenejme, že va_end může ap změnit tak, že při jeho dalším použití musíme znovu volat va_start.

Počet skutečně předaných hodnot může být předán jako poslední neproměnný argument, nebo technikou nastavení zarážky.

Program, vyhodnocující pro konkrétní nezávislou proměnnou polynom určený svými koeficienty, následuje. x by mohlo být předáno jako poslední (fixační) argument, pak by nebyl třeba nepotřebný řetězec. Šlo nám však o to, ukázat převzetí argumentů různého typu. Komentář k programu je tentokrát obsažen ve zdrojovém textu. Proto nebudeme program dále rozebírat.

/****************************************/

http://docs.linux.cz/c_saloun/kap05.htm (6 z 10) [23.2.2004 11:38:25]

Page 48: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

/* soubor FN_ARGX.C *//* ukazuje pouziti funkce s promennym *//* poctem argumentu ruznych typu *//****************************************/

#include <stdio.h>#include <stdarg.h>

const int konec = -12345;

double polynom(char *msg, ...)/****************************************//* vycisli polynom zadany jako: *//* an*x^n + an-1*x^n-1 + ... a1*x + a0 *//* nejprve je uvedeno x pak koeficienty *//* kde *//* koeficienty an jsou typu int *//* hodnota x je typu double *//* pro ukonceni je pouzita zarazka konec*//****************************************/{ double hodnota = 0.0, x; va_list ap; int koef; va_start(ap, msg); x = va_arg(ap, double); koef = va_arg(ap, int); while (koef != konec) { hodnota = hodnota * x + koef; koef = va_arg(ap, int); } va_end(ap); return hodnota;}

int main(void){ double f; char *s; s = "polynom stupne 2";f = polynom(s, 2.0, 2, 3, 4, konec); /* 2x^2 + 3x + 4,x=2 -> 18 */ printf("%s = %lf\n", s, f); s = "dalsi polynom";f = polynom(s, 3.0, 1, 0, 0, 0, konec); /* x^3 ,x=3 -> 27 */

http://docs.linux.cz/c_saloun/kap05.htm (7 z 10) [23.2.2004 11:38:26]

Page 49: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

printf("%s = %lf\n", s, f); return 0;}

5.5. Rekurse.Díky umístění kopií skutečných argumentů na zásobník a umístění lokálních proměnných tamtéž se nám nabízí možnost rekurse.

Rekurse znamená volání funkce ze sebe samé, ať přímo, nebo prostřednictvím konečné posloupnosti voláním funkcí dalších. K tomu je nutné, aby programovací jazyk měl výše uvedené vlastnosti.

Předností rekurse je přirozené řešení problémů takovým způsobem, jaký odpovídá realitě či použitému algoritmu. Nevýhodou jsou často vysoké paměťové nároky na zásobník. S předáváním hodnot do zásobníku a následným obnovením jeho stavu souvisí i značná, zejména časová, režie rekurse.

Je ponecháno na naší úvaze, použijeme-li rekursi či problém vyřešíme klasickými postupy. Platí totiž věta, že každá rekurse jde převést na iteraci.

Častým příkladem rekurse je výpočet hodnoty faktoriálu přirozeného čísla či určení hodnoty zvoleného členu Fibonacciho posloupnosti. Podívejme se na první z nich. V úloze je použit úmyslně jiný datový typ pro argument (long) a pro výsledek (double). Rozšíření rozsahu hodnot, pro něž faktoriál počítáme je vedlejší efekt. Chtěli jsme zejména ukázat použití různých datových typů.

/************************//* soubor FACT_R.C *//* faktorial rekursivne *//************************/#include <stdio.h>double fact(long n){ if (n == 0l) return 1.0F; /* 1 jako double konstanta */ else return n * fact(n-1);}int main(void){ static long n; printf("\nPro vypocet faktorialu zadej prirozene cislo n:"); scanf("%ld", &n); printf("%ld! = %lf\n", n, fact(n)); return 0;

http://docs.linux.cz/c_saloun/kap05.htm (8 z 10) [23.2.2004 11:38:26]

Page 50: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

}

Za povšimnutí stojí skutečnost, že je použita jediná proměnná. Ta určuje hodnotu, pro kterou faktoriál počítáme. Při tisku hodnoty výsledku je použito přímo umístění volání fact() jako jednoho z argumentů printf(). Rekursivní funkce fact() má pochopitelně formální argument.

Pokud při rekursi vyžadujeme sdílení nějaké proměnné všemi stejnými rekursivními funkcemi, můžeme pro takovou proměnnou deklarovat paměťovou třídu static. Proměnná pak není umístěna v zásobníku, ale v datovém segmentu programu, kde má vyhrazeno pevné místo. Toto místo je společné všem vnořeným rekursím funkce. Nejjednodušší možností ukázky použití statické lokální proměnné je počítání hloubky rekurse.

Můžeme například rozšířit funkci fact() o statickou celočíselnou proměnnou h. Při každém volání funkce fact() budeme zobrazovat jak hodnotu h, tak i hodnotu n. Počáteční inicializace není explicitně nutná. Statické proměnné inicializuje nulou sám překladač. Uvádíme jen změněnou funkci fact() a výstup programu pro zadanou hodnotu 4. Ostatní je oproti předchozímu příkladu nezměněno.

double fact(long n){ static int h;

double navrat; printf("hloubka=%4d\tn=%4ld\n", ++h, n); if (n == 0l) navrat = 1.0F; else navrat = n * fact(n-1); printf("hloubka=%4d\tn=%4ld\tnavrat=%.0lf\n", h--, n, navrat); return navrat;}

/*Pro vypocet faktorialu zadej prirozene cislo n:4hloubka= 1 n= 4hloubka= 2 n= 3hloubka= 3 n= 2hloubka= 4 n= 1hloubka= 5 n= 0hloubka= 5 n= 0 navrat=1hloubka= 4 n= 1 navrat=1hloubka= 3 n= 2 navrat=2hloubka= 2 n= 3 navrat=6

http://docs.linux.cz/c_saloun/kap05.htm (9 z 10) [23.2.2004 11:38:26]

Page 51: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 5.FUNKCE

hloubka= 1 n= 4 navrat=244! = 24.000000*/

Výsvětlivky:

1 Nejjednodušší možné členění zní - standardní funkce a ostatní funkce.2 Nebo jak jsme z dokumentace usoudili, že se chovat mají, a kolega je zrovna nedosažitelný, aby nám nejasnost vysvětlil nebo funkci opravil.3 To se pochopitelně týká těch funkcí, které dosud nebyly v souboru, obsahujícím zdrojový text, definovány. Přesto nevadí, zahrneme-li shodnou deklaraci funkce vícekrát.4 Později v této kapitole.5 Souhrnně je nazýváme rozhraní funkce.6 Navíc k chybám těžce odhalitelným.7 Děláme to často, například I/O funkce scanf()/printf() hodnotu vrací.8 Do zásobníku, programátor se o umístění obvykle nestará.9 Obdobný problém vzniká při předávání rozsáhlého pole. Představa kopírování jeho obsahu do zásobníku je strašná (viz pravidlo 3. slučitelnosti FA a SA).10 Pozor na scanf()!

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce CAutor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap05.htm (10 z 10) [23.2.2004 11:38:26]

Page 52: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Předchozí kapitola Obsah Konec Následující kapitola

6. Řízení chodu programu6.1. Výrazový příkaz6.2. Prázdný příkaz6.3. Bloky.6.4. Oblast platnosti identifikátoru6.5. Podmíněný příkaz if-else.6.6. Přepínač6.7. Cykly

Cyklus whileCyklus forCyklus do

6.8. Příkaz skoku

V dosavadním výkladu jsme se dozvěděli, že se program skládá z funkce main() a z příkazů, které tato funkce obsahuje. Své intuitivní představy o programu rozšíříme nejen o případné další funkce, ale detailněji1 se podíváme i na obsah funkcí. Tělo funkce obsahuje řadu příkazů. Naším cílem je provádět právě ty příkazy, které odpovídají zvolenému záměru. Výběr z příkazů je určen stavem dosavadního běhu programu, vstupními údaji a řídícími strukturami, které jsme použili.

Program, provádějící příkazy v pevném a neměnném pořadí, které odpovídá jejich umístění ve zdrojovém textu a navíc bez možnosti jejich výběru, jistě není naším ideálem. A to bez ohledu na zvolený vyšší programovací jazyk. Cílem této kapitoly je proto seznámení s řídícími strukturami.

Před podrobným přístupem nejprve uveďme přehledně příkazy, které máme v C k dispozici:

● výrazový příkaz ● blok ● podmíněný příkaz ● přepínač ● cyklus ● skok

6.1. Výrazový příkazVýraz známe z předchozího textu. Výrazem je nejen aritmetický výraz (například a + b, či 5 + 1.23 * a), prostý výskyt konstanty (literálu) či proměnné, ale i funkční volání a přiřazení. Jestliže

http://docs.linux.cz/c_saloun/kap06.htm (1 z 15) [23.2.2004 11:38:50]

Page 53: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

výraz ukončíme symbolem ; (středník), získáme výrazový příkaz.

6.2. Prázdný příkazPrázdný příkaz je výrazový příkaz, v němž není výrazová část. Tato konstrukce není tak nesmyslná, jak se na první pohled může zdát. Dává nám v některých případech možnost umístit nadbytečný středník ; do zdrojového textu. Například i za vnořený blok. Protože se o prázdném příkazu můžeme těžko dozvědět něco dalšího, podívejme se raději na další příkazy jazyka C.

6.3. Bloky.Všude v C, kde se může vyskytovat příkaz, se může vyskytovat i složený příkaz. Složený příkaz je posloupností příkazů2. Konstrukce, která složený příkaz vymezuje, začíná levou a končí pravou složenou závorkou { }, a nazývá se blok. V bloku můžeme, kromě již zmíněné realizace složeného příkazu, provádět lokální deklarace a definice. Ty ovšem pouze na začátku bloku. Jejich platnost je omezena na blok a případné další vnořené bloky. Vnořený blok nemusí být ukončen středníkem. Představuje složený příkaz a jeho konec je jasně určen. Není na škodu si uvědomit, že tělo každé funkce je blokem. Proto jsme mohli v těle funkce main() definovat a používat lokální proměnné. Syntakticky můžeme blok popsat následovně:

{ [declaration_1[; declaration_2 ... ];] [statement_1 [; statement_2 ... ] ]}

Blok tedy může obsahovat žádnou, jednu či více deklarací. Pokud deklaraci obsahuje, musí být od další části bloku oddělena středníkem. Dále blok může obsahovat jednotlivé příkazy, rovněž oddělené středníkem. Povšimněme si, že poslední z příkazů nemusí být od uzavírací složené závorky bloku středníkem oddělen.

6.4. Oblast platnosti identifikátoruIdentifikátor, který deklarujeme či definujeme, si ponechává svou platnost v programu, v němž je deklarován či definován. Jeho jméno je v tomto rozsahu viditelné, není-li maskováno:

● Na úrovni souboru je rozsah platnosti deklarace vymezena místem, kde je deklarace dokončena, a koncem překládané jednotky.

● Deklarace na úrovni argumentu funkce má rozsah od místa deklarace argumentu v rámci definice funkce až do ukončení vnějšího bloku definice funkce. Pokud se nejedná o definici funkce, končí rozsah deklarace argumentu s deklarací funkce.

● V rámci bloku je deklarace platná v rozsahu jejího dokončení až do konce bloku.

Jméno makra je platné od jeho definice (direktivou define) až do místa, kdy je definice odstraněna (direktivou undef, pokud vůbec odstraněna je). Jméno makra nemůže být maskováno3.

6.5. Podmíněný příkaz if-else. http://docs.linux.cz/c_saloun/kap06.htm (2 z 15) [23.2.2004 11:38:50]

Page 54: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Operátor podmíněného výrazu ? : používáme pro výběr části výrazu. Pro výběr z příkazů máme k dispozici podmíněný příkaz. Mohli bychom říci, že se jedná o příkazy dva. Jejich syntaktický zápis je následující

if ( <expression> ) <statement1>;

if ( <expression> ) <statement1>;else <statement2>;

Význam příkazu if je následující. Po vyhodnocení výrazu expression (musí být v závorkách) se v případě jeho nenulové hodnoty provede příkaz statement1. Po jeho provedení pokračuje program za tímto příkazem. V případě nulového výsledku výrazu se řízení programu předá bezprostředně za podmíněný příkaz. Jinak řečeno se příkaz statement1 přeskočí.

Příkaz if můžeme použít i s variantou else. Sémantika takového příkazu if-else je v první části totožná se samotným if. Je-li výslednou hodnotou výrazu expression jedna (nenulová hodnota), provede se příkaz statement1. V opačném případě, kdy výsledkem je nula, se provede příkaz statement2. V obou případech se řízení programu po provedení prvního, respektive druhého, příkazu předá za celý podmíněný výraz.

Někdy se výklad sémantiky předkládá způsobem, kdy se nulové hodnotě říká nepravda a nenulové (jedničce) pravda. Jak ostatně známe z logických výrazů. Pak lze ve druhé variantě říci, že je-li podmínka splněna, vykoná se první příkaz statement1, jinak příkaz druhý statement2.

Zdůrazněme, že oba příkazy jsou ukončeny středníkem4.

Z předchozích odstavců víme, že místo příkazu může být umístěn blok. V takovém případě je jasně příkaz

http://docs.linux.cz/c_saloun/kap06.htm (3 z 15) [23.2.2004 11:38:50]

Page 55: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

vymezen a středník za blokem před else nepíšeme!

Příklad nám ukáže použití podmíněného příkazu if-else. Máme vypočíst a zobrazit podíl dvou zadaných racionálních čísel. Protože je známo, že nulou dělit nelze, využijeme podmíněný příkaz k ošetření tohoto omezení.

/**********************//* soubor if-else1.c *//**********************/

#include <stdio.h>

int main(void){ float a, b; puts("Zadej dve racionalni cisla:"); scanf("%f %f", &a, &b);

if (b == 0.0) puts("\b\nNulou delit nelze!\n"); else { float podil; podil = a / b; printf("Jejich podil je: %10.2f\n", podil); } return 0;}

Jako první příkaz po testu if je jednoduchý výstup řetězce puts(). Je ukončen středníkem a následuje klíčové slovo else. Příkaz v této části je tvořen blokem. Za blokem středník není. Pokud by tam byl, byl by chápán jako prázdný příkaz. Tak je interpretován i středník za posledním příkazem v bloku.

V úvodu této kapitoly jsme si shrnuli příkazy jazyka C. Nepřekvapí nás tedy umístění podmíněného příkazu if jako jednoho z příkazů if-else. Zpravidla se umisťuje na místo druhého příkazu. Výsledná konstrukce bývá často nazývána if-else-if5:

if ( <expression1> ) <statement1>;else if ( <expression2> ) <statement2>;else if ( <expression3> ) <statement3>;...else if ( <expressionN> ) <statementN>;else <statementN+1>;

Její často využívanou vlastností je skutečnost, že se provede právě jeden z příkazů. Pokud není poslední příkaz bez podmínky statementN+1 uveden, nemusí se provést žádný. Jinak řečeno, provede se nejvýše jeden.

http://docs.linux.cz/c_saloun/kap06.htm (4 z 15) [23.2.2004 11:38:50]

Page 56: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Zapamatujme si dobře tuto konstrukci. Nejen pro její užitečnost. Po příkladu ji budeme moci porovnat s konstrukcí zvanou přepínač.

Naším úkolem je programově ošetřit, jaký alfanumerický znak byl zadán. Případně vydat zprávu o zadání znaku jiného. Protože je jisté, že zadaný znak patří právě do jedné z tříd malá písmena, velká písmena, číslice a jiné, použijeme konstrukci podmíněného příkazu. Při použití if-else by pro každou třídu byla znovu testována příslušnost načteného znaku. Bez ohledu, zdali znak již některou z předchozích podmínek splnil. Použitá konstrukce if-else-if případné nadbytečné testy odstraní. Vždy bude vybrán právě jednen z příkazů. Připomeňme si rovněž logický výraz, který tvoří podmínku

if ((znak >= 'a') && (znak <= 'z'))

Vnější závorky jsou nezbytné, neboť ohraničují podmínku příkazu if. Vnitřní závorky naopak uvést nemusíme. Priorita operátoru && je nižší než relačních operátorů <= a >=. Závorky jsme přesto uvedli, neboť zvyšují čitelnost. Celý výpis zdrojového textu následuje.

/**********************//* soubor if-else2.c *//**********************/

#include <stdio.h>

int main(void){ int znak; printf("Zadej alfanumericky znak:"); scanf("%c", &znak);

printf("\nZadal jsi "); if ((znak >= 'a') && (znak <= 'z')) printf("male pismeno\n"); else if ((znak >= 'A') && (znak <= 'Z')) printf("velke pismeno\n"); else if ((znak >= '0') && (znak <= '9')) printf("cislici\n"); else printf("\nNezadal jsi alfanumericky znak!\n");

return 0;}

6.6. PřepínačPřepínač slouží k rozdělení posloupnosti příkazů na části, následné vybrání a provedení některé, či některých z nich. Pokud nám tato formulace přepínač příliš nepřiblížila, pokusme se jinak.

Přepínač slouží k větvení výpočtu podle hodnoty celočíselného výrazu.

http://docs.linux.cz/c_saloun/kap06.htm (5 z 15) [23.2.2004 11:38:50]

Page 57: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Syntakticky zapsaný příkaz přepínač má tento tvar:

switch ( <expression> ) <statement>case <constant expression> :default :

Syntaktickou definici přepínače ovšem musíme upřesnit. Po klíčovém slově switch následuje celočíselný výraz expression uzavřený v závorkách. Za ním je příkaz statement, zpravidla tvořený blokem. Blok představuje posloupnost příkazů, v níž mohou být umístěna návěští, jejichž syntaxi vidíme na druhé řádce definice.

Řídící příkaz tvoří celočíselný konstantní výraz6 constant expression uvozený klíčovým slovem case a ukončený dvoutečkou :. Jedno z návěští může být klíčovým slovem default, přirozeně ukončeným dvoutečkou :.

Sémantika přepínače je poměrně složitá. Popíšeme si jednotlivá pravidla, jimiž se řídí:

Program vyhodnotí konstantní výraz a jeho hodnotu porovnává s každým z case návěští přepínače. Návěští case může být obsaženo uvnitř jiných příkazů (v rámci přepínače), kromě případného vnořeného přepínače.

V jednom přepínači se nesmí používat dvě návěští se stejnou hodnotou.

Nastane-li shoda hodnoty case návěští s hodnotou switch výrazu expression, je přeneseno řízení programu na toto návěští. Jinak je řízení přeneseno na návěští default v rámci příslušného přepínače. Pro návěští default platí stejné zásady jako pro jiná návěští.

Pokud nenapíšeme default návěští a hodnota výrazu switch se nebude shodovat s žádným z návěští, bude řízení přeneseno na příkaz následující za přepínačem.

Pokud program při provádění prěpínače vykoná příkaz break, bude řízení přeneseno na příkaz následující za přepínačem. Příkaz break může být v rámci přepínače umístěn v libovolných příkazech, kromě případných vnořených do, for, switch nebo while příkazech7.

Přepínač switch může mít mnoho forem. Podívejme se na příklad, v němž použijeme jednu z nich. Naším úkolem je podat informaci o tom, jaká hodnota padla při simulovaném hodu kostky. Místo kostky použijeme generátor pseudonáhodných čísel (dále jim budeme říkat jen náhodná). Protože takovou elektronickou kostku budeme používat i v příkladu v následujícím odstavci, věnovaném cyklům, probereme si funkce s ní spojené podrobněji.

Pomocí funkce srand()8nastavíme posloupnost náhodných čísel. Jako počáteční hodnotu nemůžeme zvolit konstantu, neboť pak by byla generovaná posloupnost náhodných veličin vždy stejná. Proto potřebujeme vhodnou hodnotu, která bude při každém spuštění programu jiná. Takto vhodně se mění například čas. Proto použijeme jako argument návratovou hodnotu funkce time()9. Jiné záměry s funkcí time() nemáme, proto použijeme jako její argument NULL. Nesmíme jen zapomenout čas správně přetypovat. A generátor máme připraven:

http://docs.linux.cz/c_saloun/kap06.htm (6 z 15) [23.2.2004 11:38:50]

Page 58: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

srand((unsigned) time(NULL) );

Teď musíme umět kostkou házet. Při volání funkce rand() dostáváme náhodné celé číslo v rozsahu 0 až RAND_MAX. 16-ti bitové BC31 definuje tuto hodnotu jako MAXINT, konkrétně 32767. Pro naši potřebu stačí číslo v rozsahu 0 až 5, k němuž přičteme jedničku. Tuto práci odvede operace modulo šesti.

switch (rand() % 6 + 1)

Získaná hodnota 1 až 6 je výrazem přepínače switch. V závislosti na této hodnotě se řízení programu přenese na konstantní návěští. Problém je v tom, že pokud bychom takto označenou posloupnost příkazů neukončili příkazem break, pokračoval by od návěští program dále, až po konec přepínače. I této vlastnosti se dá využít. Ukázku vidíme ve spojeném hlášení pro hodnoty 2 a 3. Umístění návěští default v přepínači je libovolné. Zpravidla se píše na konec, což je poměrně zakořeněný zvyk. Budeme jej rovněž dodržovat.

/**********************//* soubor switch-1.c *//**********************/

#include <stdlib.h>#include <stdio.h>#include <time.h>

int main(void){ char *s;

printf("\nHazim kostkou...\n"); srand((unsigned) time(NULL) );

switch (rand() % 6 + 1) { case 1: s = "jednicka"; break; case 2: case 3: s = "dvojka nebo trojka"; break; case 4: s = "ctyrka"; break; case 5: s = "petka"; break; default: s = "sestka"; break; } printf("\nPadla %s.\n\n", s);

return 0;}

http://docs.linux.cz/c_saloun/kap06.htm (7 z 15) [23.2.2004 11:38:50]

Page 59: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Srovnáme-li přepínač s konstrukcí if-else-if, vidíme zásadní rozdíly:

1. Rozhodovací výraz if může testovat hodnoty jakéhokoliv typu10, zatímco rozhodovací výraz příkazu switch musí být výhradně celočíselným výrazem.

2. V konstrukci if-else-if se provede nejvýše (či podle použití právě) jeden z příkazů. I u přepínače se nemusí provést žádný z příkazů. Může jich být ovšem provedeno více. Konstantní návěští určuje pouze první z nich. Pokud chceme, oddělíme následující příkazy příkazem break.

3. V přepínači se návěští default může vyskytovat kdekoliv. Odpovídající varianta v konstrukci if­else­if může být umístěna pouze na konci11.

6.7. CyklyCyklus je část programu, která je v závislosti na podmínce prováděna opakovaně. U cyklu obvykle rozlišujeme řídící podmínku cyklu a tělo cyklu. Řídící podmínka cyklu určuje, bude-li provedeno tělo cyklu, či bude-li řízení předáno za příkaz cyklu. Tělo cyklu je příkaz, zpravidla v podobě bloku.

Cykly můžeme rozdělit podle toho, provede-li se tělo alespoň jedenkrát, a cykly, kde tělo nemusí být provedeno vůbec. Rozhodně můžeme říci, že i když v C existují různé typy cyklů, je možné vystačit s jedním z nich. Přesto si v této podkapitole povíme o všech. Jejich výběr ponecháme na vhodnosti použití v dané situaci i na jejich individuální oblibě. V části věnované příkazu while si popíšeme některé vlastnosti společné všem cyklům. Postupně tedy while, for a do.

Cyklus while

Příkaz while, vykonává příkaz statement (tělo cyklu) vícekrát, nebo vůbec, dokud má v daném kontextu testovací výraz expression nenulovou hodnotu. Syntaxe příkazu while je následující:

while ( <expression> ) <statement>

Příkaz je velmi často blokem. Zde se zejména začátečníci mylně domnívají, že pokud se kontext kdykoliv během provádění příkazů bloku těla cyklu změní, a podmínka tak přestane být splněna, je cyklus opuštěn. To ovšem není pravda. Pravidlo říká, že se příkaz statement provede, je-li podmínka expression splněna. Je-li příkazem blok, provede se tedy blok celý. Teprve poté je znovu proveden test. Názornější bude, podívat se na vývojový diagram.

http://docs.linux.cz/c_saloun/kap06.htm (8 z 15) [23.2.2004 11:38:50]

Page 60: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Ve vývojovém diagramu se vyskytují nepovinné příkazy break a continue. S prvním z nich jsme se již setkali u přepínače. Druhý zatím neznáme. Jejich sémantiku, zakreslenou ve vývojovém diagramu, si nyní popíšeme. Předem poznamenejme, že oba jsou pokládány za prostředky strukturovaného programování.

Příkaz break má ve všech cyklech stejný význam. Ukončí provádění příkazů těla cyklu a předá řízení prvnímu příkazu za příkazem while. Tímto způsobem můžeme bezprostředně ukončit průběh cyklu bez ohledu na hodnotu podmínky.

Příkaz continue rovněž ukončí provádění příkazů těla cyklu. Řízení ovšem předá těsně před příkaz cyklu. Proběhne tedy vyhodnocení testovacího výrazu a podle jeho hodnoty pokračuje program stejně, jako by bylo tělo cyklu vykonáno do konce (tedy bez předčasného ukončení vykonáním continue). Podobně jako break, má příkaz continue ve všech cyklech stejný význam.

Použití příkazu while ukazuje příklad. Opět v něm používáme kostku. S ní související funkce jsme popsali v předchozím příkladu. Tentokrát je naším cílem sečíst počet pokusů, potřebných k tomu, abychom hodili šestku POCET-krát. Současně s novou látkou si připomeneme některé poznatky z předchozích kapitol.

Konstanta POCET12, která nemá uveden typ má implicitně typ int. Klíčovým slovem static použitým při deklaraci proměnných celkem a pocet (je to jiný identifikátor než POCET), nejen umisťujeme tyto proměnné do datového segmentu programu, ale zároveň je jim při spuštění programu definována počáteční hodnota nula. Díky tomu je dále nemusíme inicializovat.

Cyklus while samotný probíhá tak dlouho, dokud platí podmínka (pocet < POCET). Za blokem, tvořícím příkaz cyklu, nemusíme psát ukončovací středník. Následuje formátovaný standardní výstup výsledku a závěr funkce main().

/**********************//* soubor while-1.c *//**********************/

#include <stdlib.h>#include <stdio.h>#include <time.h>

const POCET = 10;

int main(void){ static int celkem, pocet;

printf("\nHazim kostkou dokud mi nepadne %d-krat sestka. ...\n", POCET); srand((unsigned) time(NULL) );

while (pocet < POCET) { celkem++; if ((rand() % 6 + 1) == 6)

http://docs.linux.cz/c_saloun/kap06.htm (9 z 15) [23.2.2004 11:38:50]

Page 61: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

pocet++; }

printf("\nA je to! Hodu bylo celkem %d.\n\n", celkem);

return 0;}

Následující příklad ukazuje použití příkazu while spolu s if-else-if a příkazy break a continue. Naším úkolem je porovnání dvou načtených řetězců a zobrazení menšího z nich. Tento příklad používá aritmetiku ukazatelů, probíranou v rámci jedné z dalších kapitol. V tomto okamžiku přijměme skutečnost, že prostřednictvím ukazatelů porovnáváme ASCII hodnoty jednotlivých znaků odpovídajících řetězců. Každý řetězec je ukončen znakem s kódem nula.

Zvolená mnemonika pojmenovává dva řetězce a a b, ukazatele na, respektive do, těchto řetězců pa a pb. Identifikátor pk představuje ukazatel na kratší z porovnávaných řetězců.

Algoritmus úlohy srovnává postupně znaky obou řetězců, dokud jsou si rovny:

else if (*pa == *pb) { pa++; pb++; continue; }

Použití continue není v našem případě nezbytné. Díky if-else-if nemůže v této větvi těla cyklu následovat jiný příkaz. Naopak break v obou zbývajících větvích opouští cyklus. Ukončuje tím srovnávání řetězců a určuje kratší z nich. V prvním ze zmíněných případů je

if (*pa < *pb) { pk = a; break; }

hodnota znaku v řetězci a menší, než v řetězci b. a je tudíž kratší. Cyklus je ukončen. Dále bychom totiž opustili definovaný rozsah řetězce a. A protože je podmínka cyklu napsána tak, že zajišťuje jeho opakování, dokud nenarazíme na konec alespoň jednoho z řetězců:

while ((*pa != 0x0) && (*pb != 0x0)) /* '\0' 0 */

Máme jistotu, že pokud nenastaly předchozí možnosti, musí platit:

else{pk = b;break;

http://docs.linux.cz/c_saloun/kap06.htm (10 z 15) [23.2.2004 11:38:51]

Page 62: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

}

Tedy, že kratší je druhý řetězec. Těsně za tělem cyklu máme ošetřenu variantu, kdy skončí druhý z řetězců dříve, než se jejich obsahy lišily. Pak je kratším z řetězců. Nám nezbývá, než jej zobrazit a ukončit program. Následuje nepřerušený výpis zdrojového textu řešení.

/************************************************//* soubor cmp_str.c - compare two strings *//* porovna dva nactene retezce a zobrazi mensi *//************************************************/

#include <stdio.h>#define DELKA 50

int main(void){ char a[DELKA], b[DELKA], *pa, *pb, *pk; /* pa[], pb[], pk[] */

pk = pa = gets(a); pb = gets(b);

while ((*pa != 0x0) && (*pb != 0x0)) /* '\0' 0 0x0 - totez*/ { if (*pa < *pb) { pk = a; break; } else if (*pa == *pb) { pa++; pb++; continue; } else { pk = b; break; } }

if ((*pa != 0x0) && (*pb == 0x0)) pk = b; puts("\nkratsi je:"); puts(pk); return 0;}

Cyklus for

http://docs.linux.cz/c_saloun/kap06.htm (11 z 15) [23.2.2004 11:38:51]

Page 63: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Příkaz for provádí příkaz statement vícekrát, nebo vůbec, dokud je hodnota nepovinného testovacího výrazu expr2 nenulová. V příkazu for můžeme ještě napsat dva další výrazy s vedlejším efektem, expr1 a expr3. Syntaxe for:

for ( [<expr1>] ; [<expr2>] ; [<expr3>] ) <statement>

Nepovinný výraz expr1 je proveden před prvním vyhodnocením testu. Typicky se používá pro inicializaci proměnných před cyklem. Po každém provedení těla cyklu statement, provede program nepovinný výraz expr3. Typicky se jedná o přípravu další iterace cyklu. Pokud neuvedeme testovací výraz expr2, použije překladač hodnotu 1, a tedy bude provádět nekonečný cyklus. Naštěstí můžeme použít příkazy break a continue se stejným významem, jaký jsme popsali u while.

Vývojový diagram úplné varianty příkazu for je na obrázku, v němž jsme ponechali značení odpovídající výše uvedené syntaxi:

Pro tisk ASCII tabulky znaků s kódy 32 až 127 použijeme příkaz for v úplné variantě:

for (znak = ' '; znak < 128; znak++)

V tomto případě můžeme nazývat proměnnou znak řídící proměnnou cyklu. Před testem ji inicializujeme mezerou13, tedy prvním zobrazitelným ASCII znakem. Následuje test na hodnotu 128. Provádí se před každým průchodem tělem cyklu. Není-li podmínka splněna, pokračuje se za cyklem.

Po každém průchodu tělem cyklu je řídící proměnná cyklu znak zvýšena o jedničku. Tak je připraven další průchod cyklem s hodnotou následného ASCII znaku. Samotné tělo cyklu vlastně jen zobrazuje jednotlivý znak. Je-li jeho ASCII hodnota dělitelná 16 beze zbytku, zajistí vysláním konce řádku tvar tabulky.

/**********************//* soubor for-1.c *//**********************/

#include <stdio.h>

int main(void){

http://docs.linux.cz/c_saloun/kap06.htm (12 z 15) [23.2.2004 11:38:51]

Page 64: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

int znak;

putchar('\n'); for (znak = ' '; znak < 128; znak++) { if (znak % 16 == 0) putchar('\n'); putchar(znak); } putchar('\n'); return 0;}

/*

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~&127

*/

Cyklus do

Příkaz do je jediným z cyklů, který zajišťuje alespoň jedno provedení těla cyklu. Jinak řečeno, jeho testovací příkaz statement je testován až po průchodu tělem cyklu. Pokud je test splněn, provádí se tělo cyklu. Po syntaktické stránce tvoří tělo cyklu opět výraz expression:

do <statement> while ( <expression> );

Vývojový diagram příkazu do tuto vlastnost ukazuje ještě názorněji:

http://docs.linux.cz/c_saloun/kap06.htm (13 z 15) [23.2.2004 11:38:51]

Page 65: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Příkazy break a continue v těle cyklu se chovají stejně, jaako v cyklech ostatních. Po break je cyklus opuštěn, zatímco po continue je proveden výraz expression a podle jeho výsledku se pokračuje dále.

Časté použití příkazu do nalezneme například v těch numerických výpočtech, kdy je alespoň jedna iterace nezbytná. V našem případě se jedná o výpočet přibližné hodnoty Eulerova čísla. V prvním přiblížení položíme hodnotu e rovnu jedné stejně, jako hodnotu přírůstku epsilon. Za upozornění stojí datový typ long double, a tedy i odpovídající vhodné určení typů číselných konstant. I ve formátovaném výstupu jsme museli specifikovat příslušný datový typ. Operátory přiřazení spojené s operací nad svou levou stranou jsou použity spíše pro připomenutí jejich existence.

/**********************//* soubor dowhile2.c *//**********************/

#include <stdio.h>#include <math.h>

#define chyba 1.0e-15L

int main(void){ long double e = 1.0L, epsilon = 1.0L; unsigned long n = 0L;

printf("\n"); do { epsilon /= ++n; e += epsilon; } while (epsilon > chyba); printf("e=%21.18Lf\tpocet diteraci:%ld", e, n);

return 0;}

6.8. Příkaz skokuPříkaz skoku patří k zatracovaným příkazům strukturovaného programování. Skutečně s jeho pomocí dokáže méně zkušený programátor vytvořit zdrojový text, pro nějž se vžilo označení špagetový. Skutečnost, že jsme si příkaz skoku nezatajili má tři příčiny:

1. Jazyk C prostě příkaz skoku obsahuje. Jeho neuvedení by nebylo fér. 2. Jazyk C umožňuje jako systémový prostředek psát programy na velmi nízké úrovni. Často velmi

blízko strojovému kódu. Použití skoku v takovém případě může být velmi žádoucí14. 3. Skokové příkazy break, continue a return15 jsou označovány jako strukturované skoky. Jejich

použití teorie (ani praxe) programování neodmítá.

http://docs.linux.cz/c_saloun/kap06.htm (14 z 15) [23.2.2004 11:38:51]

Page 66: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 6.Řízení chodu programu

Zůstává nám tedy jen příkaz skoku goto. Jeho syntaxe je jednoduchá:

identifier: <statement> ;goto <identifier> ;

Uvedli jsme si nejen syntaxi goto samotného, ale i souvisejícího návěští identifier. Návěští neprovádí žádnou akci a nemá jiný vliv na řízení běhu. Jeho identifikátor jen označuje nějaké místo ve zdrojovém textu. Příkaz goto přenáší řízení na příkaz, označený návěštím. V rámci jedné funkce nesmí být dvě stejná návěští.

Výsvětlivky:

1 V tomto skriptu věnujeme funkcím celou kapitolu.2 Nezapomeňme, jednotlivé příkazy jsou ukončeny středníky.3 To je jasné, uvědomíme-li si, že makra zpracovává preprocesor. Teprve takto zpracovaný text s expandovanými makry je vstupem pro samotný překladač.4 Jinak by se o příkazy nejednalo.5 Některé programovací jazyky kvůli ní dokonce zavádí klíčové slovo elif. 6 Tedy výraz vyčíslitelný během překladu. K celočíselným výrazům patří i znakové výrazy.7 Tyto příkazy mohou break obsahovat. Jeho význam pak ovšem bude spojen s nimi, nikoliv s vnějším příkazem case. 8 Její prototyp je umístěn v hlavičkovém souboru stdlib.h. 9 Její prototyp je umístěn v hlavičkovém souboru time.h. 10 Je vhodné, aby výsledkem testu byla celočíselná hodnota, nejlépe 0 a 1. Jinak automaticky proběhne konverze.11 Případně jen na začátku. To bychom museli neobvykle negovat podmínky.12 Díky tomu, že je definována mimo tělo funkce je rozsahem její platnosti celý soubor (počínaje místem definice).13 ASCII kód mezery je 32.14 Přeložený tvar našich "pěkných strukturovaných programů" skoky ostatně stejně obsahuje.15 První dva jsme již popsali dříve v této kapitole. Příkazu return se věnujeme v souvislosti s funkcemi.

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce CAutor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap06.htm (15 z 15) [23.2.2004 11:38:51]

Page 67: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

Předchozí kapitola Obsah Konec Následující kapitola

7. UKAZATELE, POLE A ŘETĚZCE7.1. Ukazatele7.2. Pole7.3. Aritmetika ukazatelů7.4. Řetězce7.5. Vícerozměrná pole, ukazatele na ukazatele7.6. Ukazatele na ukazatele a pole ukazatelů7.7. Ukazatele na funkce.7.8. Argumenty příkazového řádku

Ukazatele mohou být používány v souvislosti se statickými daty, například s proměnnými. Mnohem elegantnější použití poskytují v souvislosti s poli (a zejména s poli znaků - řetězci), neboť jak zakrátko uvidíme, (zjednodušeně řečeno) ukazatele a pole jedno jsou. Zcela nezastupitelné místo mají ukazatele v souvislosti s dynamickými datovými strukturami.

Protože zmíněné použití ukazatelů je příliš rozsáhlé pro jednu kapitolu, popíšeme jej v kapitolách dvou. Téma této kapitoly napovídá její název. Následující kapitola obsahuje podrobný přehled vstupu a výstupu. V další kapitole se budeme věnovat dynamickým datovým strukturám.

7.1. UkazateleS ukazateli jsme se stručně seznámili již v kapitole věnované základním datovým typům. Proto jistě neuškodí krátká rekapitulace. Ukazatel reprezentuje adresu objektu. Nese sebou současně informaci o datovém typu, který se na této adrese nachází. Deklaraci ukazatele a jeho použití ukazuje příklad:

int x, y, *px, *p2x;px = &x; /* px nyní ukazuje na x */*px = 5; /* totéž co x = 5; */y = *px + 1; /* y = x + 1; */*px += 1; /* x += 1; */(*px)++; /* x++; závorky jsou nutné */p2x = px; /* p2x ukazuje na totéž, co px, t.j. na x */*p2x = *p2x + y; /* x = x + y; */

Mějme na paměti, že při deklaraci ukazatele můžeme použít i modifikaci const. Pak se jedná o konstantní ukazatel, ukazatel na konstantu, nebo obojí, tedy konstantní ukazatel na konstantu. Podívejme se na několik

http://docs.linux.cz/c_saloun/kap07.htm (1 z 19) [23.2.2004 11:39:12]

Page 68: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

příkladů:

int i;int *pi; /* pi je neinicializovaný ukazatel na typ int */int * const cp = &i; /* konstantní ukazatel na int */const int ci = 7; /* celočíselná konstanta */const int *pci;/* neinicializovaný ukazatel na celočíselnou konstantu*/const int * const cpc = &ci; /* konstantní ukazatel na konstantu */

Překladače ANSI C pochopitelně kontrolují program a v případě pokusu o porušení konstantnosti ukazatele, hodnoty nebo obojího (podle deklarace) ohlásí chybu.

7.2. PolePole je kolekce proměnných stejného typu, které mohou být označovány společným jménem. K prvkům pole přistupujeme prostřednictvím identifikátoru pole a indexu1. Pole v C obsazuje spojitou oblast operační paměti tak, že první prvek pole je umístěn na nejnižší přidělené adrese a poslední na nejvyšší přidělené adrese. Pole je v C výhradně jednorozměrné.

Povšimněme si skutečnosti, že na prvky pole neklademe žádné omezení. To nám umožňuje zavést vícerozměrná pole jako pole polí.

Definice pole je jednoduchá:

typ jméno[rozsah];

kde

typ určuje typ prvků pole (bázový typ)jméno představuje identifikátor polerozsah je kladný počet prvků pole (celočíselný konstantní výraz, který musí být vyčíslitelný za překladu)

Na tomto místě zdůrazněme význam položky rozsah. Znamená počet prvků pole. V souvislosti se skutečností, že pole začíná prvkem s indexem nula to znamená, že poslední prvek pole má index rozsah-1.

Ještě jedna důležitá vlastnost C. Překladač nekontroluje rozsah použitého indexu. Pokud se o tuto skutečnost nepostaráme sami, můžeme číst nesmyslné hodnoty při odkazu na neexistující prvky pole.

Proti některým jiným vyšším programovacím jazykům na nás klade C vyšší nároky. Pokud se ovšem zamyslíme, zmíněná kontrola mezí se stejně použije pouze při ladění, hotový program by možnost nedodržení mezí neměl obsahovat.

Ukažme si několik definic polí, v nichž budeme postupně ukazovat možnosti definice proměnných typu pole:

http://docs.linux.cz/c_saloun/kap07.htm (2 z 19) [23.2.2004 11:39:12]

Page 69: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

#define N 10int a[N];

a je deklarováno jako pole o N prvcích, každý typu int. Na jednotlivé prvky se můžeme odkazovat indexy 0 až 9, t.j.

a[0], a[1], ... , a[N-1]

paměťový prostor přidělený poli a můžeme zjistit výpočtem:

počet obsazených byte = N * sizeof(int)

Proč musíme potřebný paměťový prostor počítat právě tímto způsobem? Pouhé sizeof(a) vrátí velikost paměti pro proměnnou typu ukazatel. sizeof(*a) vrátí velikost prvku pole, tedy velikost int.

C sice netestuje indexy polí, ale umožňuje nám současně s definicí pole provést i jeho inicializaci:

b[0] b[1] b[2] b[3] b[4] static double b[5] = { 1.2, 3.4, -1.2, 123.0, 4.0 };

Princip je jednoduchý. Před středník, jímž by definice končila, vložíme do složených závorek seznam hodnot, jimiž chceme inicializovat prvky pole. Hodnoty pochopitelně musí být odpovídajícího typu. Navíc jich nemusí být stejný počet, jako je dimenze pole. Může jich být méně. Přiřazení totiž probíhá následovně: první hodnota ze seznamu je umístěna do prvního prvku pole, druhá hodnota do druhého prvku pole, ... . Pokud je seznam vyčerpán dříve, než je přiřazena hodnota poslednímu prvku pole, zůstávají odpovídající (závěrečné) prvky pole neinicializovány.

Při inicializaci popsané výše jsme museli uvádět dimenzi pole. Tuto práci ovšem můžeme přenechat překladači. Ten poli vymezí tolik místa, kolik odpovídá inicializaci, jako například:

int c[] = { 3, 4, 5};

Abychom předchozí větu uvedli na správnou míru, musíme si říci, jak chápe C identifikátor pole. Identifikátor pole (symbolicky id) představuje konstantní ukazatel na první prvek pole, tedy na id[0]. Někdy také hovoříme, že id představuje bázovou adresu pole. Dříve popsané vlastnosti C nám zaručí, že nezměníme hodnotu konstantního ukazatele id (viz následující Aritmetika ukazatelů).

7.3. Aritmetika ukazatelůNázev tohoto odstavce zřejmě vyvolá zděšení u většiny pascalistů. Jaký to má smysl, provádět aritmetické operace s ukazateli? Jak hned uvidíme, význam spočívá ve zvýšení přehlednosti2, zrychlení chodu programu i v možnostech, které dostáváme k dispozici.

Jak víme, ukazatel ukazuje na hodnotu nějakého typu. Nese sebou informaci o tomto typu. Tato informace

http://docs.linux.cz/c_saloun/kap07.htm (3 z 19) [23.2.2004 11:39:13]

Page 70: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

pochopitelně představuje počet bajtů nutný pro uchování hodnoty typu. A při aritmetice ukazatelů smysluplně používáme obě části zmíněné informace, adresu i velikost položky.

Aritmetika ukazatelů je omezena na tři základní operace, sčítání, odčítání3a porovnání. Podívejme se nejprve na první dvě operace.

Sčítání i odčítání jsou binární operace. Protože se zabýváme aritmetikou ukazatelů, bude jedním z operandů vždy ukazatel. Druhým operandem pak bude buď opět ukazatel4, nebo jím může být celé číslo.

Pokud jsou oba operandy ukazatele, je výsledkem jejich rozdílu počet položek, které se mezi adresami na něž ukazatele ukazují nacházejí. Pokud k ukazateli přičítám, respektive odčítám celé číslo, je výsledkem ukazatel ukazující o příslušný počet prvků výše, respektive níže. Představíme-li si prvky s vyšším indexem nad prvky s vyšším indexem. Tak je ale pole v C definováno. Tato představa je zcela dokonalá, pokud si představíme ukazatel doprostřed nějakého pole.

Podívejme se tedy na ukazatele a pole. Mějme následující deklaraci,

int i, *pi, a[N];

nepřiřazujme pro jednoduchost již žádné konkrétní hodnoty. Co můžeme nyní s jistotou říci. Jen to, co víme o umístění prvků pole5.

adresa prvku a[0] je &a[0] <-->6 a <--> a + 0.

Poslední výraz již představuje součet ukazatele s celočíselnou hodnotou. Ta musí být nula, aby se opravdu jednalo o ukazatel na první prvek pole.

Nyní nás jistě nepřekvapí, že následující výrazy uvedené na stejném řádku

a + i <--> &a[i]*(a+i) <--> a[i]

představují jiné zápisy téhož výrazu. V prvním řádku jde o ukazatel na i+1. prvek, ve druhém řádku pak hodnotu tohoto prvku.

Pro konkrétní N rovno 100 dostáváme,

int i, *pi, a[100];

a následující přiřazení je přiřazení hodnoty jednoho ukazatele ukazateli druhému,

pi = a; /* pi nyní ukazuje na začátek pole a, na 1. prvek */

výrazy uvedené níže jsou po tomto přiřazení zaměnitelné (mají stejný význam, představují hodnotu prvku pole a s indexem i),

http://docs.linux.cz/c_saloun/kap07.htm (4 z 19) [23.2.2004 11:39:13]

Page 71: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

a[i] <--> *(a+i) <--> pi[i] <--> *(pi+i).

Budeme-li nyní potřebovat ukazovat na prostřední prvek pole a, snadno tak můžeme učinit s pomocí našeho pomocného ukazatele pi:

pi = a + 49; /* p nyní ukazuje doprostřed pole a, na 50. prvek */

S tímto ukazatelem se pak můžeme posunout na následující prvek pole třeba pomocí inkrementace, neboť i u ní překladač ví, o kolik bajtů má posunout (zvětšit) adresu, aby ukazoval na následující prvek: ++pi nebo pi++, ale ne ++a ani a++, neboť a je konstantní ukazatel (je pevně spojen se začátkem pole).

Část věnovanou polím a aritmetice ukazatelů ukončeme krátkým programem. V něm si zejména povšimneme skutečnosti, že formální argumenty funkcí nejsou poli, nýbrž ukazateli na typ. Navíc jejich změna neovlivní změnu argumentů skutečných (to by ostatně překladač nedovolil - jsou přece konstantní). Proto není problémem těmto funkcím předat ukazatele jak na začátek pole, tak na libovolný jiný jeho prvek. Opatrnost pak musíme zachovat co se předávaného počtu prvků týče. C neprovádí kontrolu mezí!

/****************************************************************//* soubor CPYARRY.C *//* na funkcich kopirujicich prvky jednoho pole do druheho *//* ukazuje pristup k prvkum pole pomoci indexu i pomoci uka- *//* zatele, tedy pointerovou aritmetiku *//****************************************************************/

#define N 6

#include <stdio.h>

void copy_array1(int *a, int *b, int n)/* a - vstupni pole, b - vystupni pole, n - pocet prvku */{ register int i = 0; for (; i < n; i++) b[i] = a[i];}

void copy_array2(int *a, int *b, int n)/* a - vstupni pole, b - vystupni pole, n - pocet prvku */{ while (n-- > 0) *b++ = *a++;}

void print_array(int *p, int n)/* vytiskne celociselne pole o n prvcich *//* zacne a skonci na novem radku */{

http://docs.linux.cz/c_saloun/kap07.htm (5 z 19) [23.2.2004 11:39:13]

Page 72: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

puts(""); while (n-- > 0) printf("\t%d", *p++); puts("");}

int main(){ int pole1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}, pole2[N], dim1; dim1 = sizeof(pole1) / sizeof(int);

print_array(pole1, dim1); copy_array1(pole1, pole2, N); print_array(pole2, N); copy_array2(pole1 + 3, pole2, N); print_array(pole2, N); return 0;}

/* vystup vypada nasledovne:

1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 4 5 6 7 8 9

*/

7.4. ŘetězceŘetězec je (jednorozměrné) pole znaků ukončené speciálním znakem ve funkci zarážky. Na tuto skutečnost musíme myslet při definici velikosti pole. Pro zarážku musíme rezervovat jeden znak. Nyní se zamysleme nad zarážkou. Ta je součástí řetězce (bez ní by řetězec byl jen pole znaků). Musí tedy být stejného typu, jako ostatní prvky řetězce - char. A při pohledu na ASCII tabulku nám zůstane jediná použitelná hodnota - znak s kódem nula: '\0'.

Podívejme se již na definici pole, které použijeme pro řetězec:

char s[SIZE];

Na identifikátor s se můžeme dívat ze dvou pohledů:

Jednak jako na proměnnou typu řetězec (pole znaků, zarážku přidáváme někdy sami) délky SIZE, jehož

http://docs.linux.cz/c_saloun/kap07.htm (6 z 19) [23.2.2004 11:39:13]

Page 73: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

jednotlivé znaky jsou přístupné pomocí indexů s[0] až s[SIZE-1].

Nebo jako na konstantní ukazatel na znak, t.j. na první prvek pole s, s[0].

Řetězcové konstanty píšeme mezi dvojici úvozovek, úvozovky v řetězcové konstantě musíme uvodit speciálním znakem \ - opačné lomítko. Tak to jsme si připomněli obsah jedné z úvodních kapitol. Pokračujme ještě chvíli v jednoduchých ukázkách:

"abc" je konstanta typu řetězec délky 3+1 znak (zarážka), tedy 3 znaky, 4 bajty paměti (konstantní pole 4 znaků). "a" je rovněž řetězcovou konstantou, má délku 1+1 znak 'a' je znaková konstanta (neplést s řetězcem!)

Nyní si spojíme znalosti řetězcových konstant a polí. Například zápis

char pozdrav[] = "hello";

je ekvivalentní zápisu

char pozdrav[] = {'h','e','l','l','o','\0'};

V obou případech se délka (dimenze) pole použije z inicializační části. Druhý příklad je zejména zajímavý nutností explicitního uvedení zarážky. Jinak lze říci, že se tento způsob zadání řetěce nepoužívá.

Jestliže jsme pracovali s polem znaků, proč to nyní nezkusit s ukazatelem na znak. Následující definice je velmi podobná těm předcházejícím:

char *ps = "retezec";

přesto se liší dosti podstatně. ps je ukazatel na znak, jemuž je přiřazena adresa řetězcové konstanty retezec. Tato konstanta je tedy umístěna v paměti (ostatně, kde jinde), ale proměnná ps na ni ukazuje jen do okamžiku, než její hodnotu třeba změníme. Pak ovšem ztratíme adresu konstantního řetězce "retezec".

Jednoduchý program popisovanou situaci ozřejmí (v něm jsme pochopitelně neměnili hodnotu konstantního ukazatele na pole):

/****************************************//* soubor str_ptr.c *//* ukazuje jednoduchou praci s retezci *//****************************************/

#include <stdio.h>

int main()

http://docs.linux.cz/c_saloun/kap07.htm (7 z 19) [23.2.2004 11:39:14]

Page 74: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

{ char text[] = "world", *new1 = text, *new2 = text; printf("%s\t%s\t%s\n", text, new1, new2); new1 = "hello"; printf("%s\t%s\t%s\n", text, new1, new2); printf("%s\n", "hello world" + 2); return 0;}

/*

world world world

world hello world

llo world

*/

Nejdůležitějším bodem programu je přiřazení

new1 = "hello",

v němž pouze měníme hodnotu ukazatele new1. Nedochází při něm k žádnému kopírování řetězců. To by se musel změnit obsah obou dalších řetězců text i new2, vždyť původně byly umístěny na stejné adrese.

Podobné věci, které můžeme dělat s ukazateli, můžeme dělat i s konstantními řetězci, například:

"ahoj světe" + 5

představuje ukazatel na

"světe"

Prostě jsme použili aritmetiku ukazatelů a zejména fakt, že hodnota řetězcové konstanty je stejná jako jakéhokoliv ukazatele na řetězec (a nejen na řetězec) - ukazatel na první položku.

Nadále si tedy pamatujme přibližně toto. Při práci s řetězci nesmíme zapomínat na skutečnost, že jsou reprezentovány ukazateli. Pouhou změnou či přiřazením ukazatele se samotný řetězec nezmění. Proto s těmito ukazateli nemůžeme provádět operace tak, jak jsme zvyklí třeba v Turbo Pascalu.

http://docs.linux.cz/c_saloun/kap07.htm (8 z 19) [23.2.2004 11:39:14]

Page 75: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

Na závěr odstavce věnovaného popisu práce s řetězci si uveďme modifikaci programu, který kopíroval obsah jednoho celočíselného pole do jiného. Dvě varianty funkce pro kopírování zdrojového řetězce do cílového jsou použity v programu:

/************************************************//* soubor STR_CPY.C *//* ukazuje dve mozne implementace kopirovani *//* retezcu *//************************************************/

#include <stdio.h>#define SIZE 80

void strcpy1(char *d, char *s){ while ((*d++ = *s++) != '\0') ;}

void strcpy2(char d[], char s[]){ int i = 0; while ((d[i] = s[i]) != '\0') i++;}

int main(){ char s1[] = "prvni (1.) retezec", s2[] = "druhy (2.) retezec", d1[SIZE], d2[SIZE], *ps = s2; strcpy1(d1, s1); strcpy2(d2, s2 + 6); ps = s2 + 8; printf("s1[]:%s\t\ts2[]:%s\nd1[]:%s\t\td2[]:%s\n\t\t\t\t\t *ps:%s\n\n", s1, s2, d1, d2, ps); return 0;}

/*s1[]:prvni (1.) retezec s2[]:druhy (2.) retezecd1[]:prvni (1.) retezec d2[]:(2.) retezec *ps:.) retezec*/

http://docs.linux.cz/c_saloun/kap07.htm (9 z 19) [23.2.2004 11:39:14]

Page 76: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

Podívejme se na obě kopírovací funkce. Nemusíme jim předávat počet znaků. Zarážka má skvělou vlastnost. Díky ní poznáme konec pole. Současně však vzniká nebezpečí, že cílový řetězec nebude mít dostatek prostoru pro zapsání znaků z řetězce zdrojového.

Řetězce zřejmě budeme v našich programech používat velmi často. Jistě by nám nebylo milé, kdybychom museli psát pro každou operaci nad řetězci svou vlastní funkci. Jednak by to vyžadovalo jisté úsilí, jednak bychom museli vymýšlet jednoznačné a současně mnemonické identifikátory. Tuto práci naštěstí dělat nemusíme. Zmíněné funkce jsou součástí standardní knihovny funkcí a jejich prototypy jsou obsaženy v hlavičkovém souboru string.h.

Následující řádky dávají přehled o nejpoužívanějších řetězcových funkcích. Protože jsme se řetězcům věnovali dostatečně, uvedeme pouze deklarace těchto funkcí s jejich velmi stručným popisem.

int strcmp(const char *s1, const char *s2); lexikograficky porovnává řetězce, vrací hodnoty

< 0 je-li s1 < s20 s1 == s2> 0 s1 > s2

int strncmp(const char *s1, const char *s2, unsigned int n);jako předchozí s tím, že porovnává nejvýše n znaků

unsigned int strlen(const char *s);vrátí počet významných znaků řetězce (bez zarážky)

char *strcpy(char *dest, const char *src);nakopíruje src do dest

char *strncpy(char *dest, const char *src, unsigned int n);jako předchozí, ale nejvýše n znaků (je-li jich právě n, nepřidá zarážku)

char *strcat(char *s1, const char *s2); s2 přikopíruje za s1

char *strncat(char *s1, const char *s2, unsigned int n); jako předchozí, ale nejvýše n znaků

char *strchr(const char *s, int c);vyhledá první výskyt (zleva) znaku c v řetězci s

char *strrchr(const char *s, int c);vyhledá první výskyt (zprava) znaku c v řetězci s

char *strstr(const char *str, const char *substr);

http://docs.linux.cz/c_saloun/kap07.htm (10 z 19) [23.2.2004 11:39:14]

Page 77: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

vyhledá první výskyt (zleva) podřetězce substr v řetězci str

7.5. Vícerozměrná pole, ukazatele na ukazatelePo delší vsuvce, která ukazovala podrobněji vlastnosti znakových polí - řetězců, se dostáváme k problematice vícerozměrných polí. Nejčastěji nám zřejmě půjde o matice.

Jazyk C umožňuje deklarovat pole pouze jednorozměrné. Jeho prvky ovšem mohou být libovolného typu. Mohou tedy být opět (například) jednorozměrnými poli. To však již dostáváme vektor vektorů, tedy matici. Budeme-li uvedeným způsobem postupovat dále, vytvoříme datovou strukturu prakticky libovolné dimenze. Pak již je třeba mít jen dostatek paměti7.

Definice matice (dvourozměrného pole) vypadá takto:

type jmeno[5][7];

kde

typ určuje datový typ položek polejmeno představuje identifikátor pole[5][7] určuje rozsah jednotlivých vektorů, 5 řádků a 7 sloupců

Pamatujme si zásadu, že poslední index se mění nejrychleji (tak je vícerozměrné pole umístěno v paměti).

S vícerozměrným polem můžeme pracovat stejně, jako s jednorozměrným. To se přirozeně týká i možnosti inicializace. Jen musíme jednotlivé "prvky" - vektory, uzavírat do složených závorek. Tento princip je ovšem stejný (což se opakujeme), jako pro vektor. Proto si přímo uvěďme ukázkový program, převzatý od klasiků - K&R.

Naším úkolem je pro zadaný den, měsíc a rok zjistit pořadové číslo dne v roce. Ve výpisu je uvedena i funkce, mající opačný význam. Pro den v roce a rok (co když je přestupný) určí měsíc a den. Řešení je velmi elegantní.

/****************************************//* soubor yearday.c *//* urci cislo dne v roce a naopak *//* motivace K&R *//****************************************/

static int day_tab[2][13] = {{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};

int day_of_year(int year, int month, int day){ int i, leap;

http://docs.linux.cz/c_saloun/kap07.htm (11 z 19) [23.2.2004 11:39:14]

Page 78: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0; for (i = 1; i < month; i++) day += day_tab[leap][i]; return day;}

int month_day(int year, int yearday, int *pmonth, int *pday){ int i, leap; leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0; for (i = 1; yearday > day_tab[leap][i]; i++) yearday -= day_tab[leap][i]; *pmonth = i; *pday = yearday; return i;}

int main(){ int year = 1993, month = 11, day = 12, yearday, m, d; yearday = day_of_year(year, month, day); month_day(year, yearday, &m, &d); return 0;}

Program nemá vstup ani výstup. Představuje prostě definice funkcí a ukázku jejich volání. Hned v úvodu je definováno statické pole, obsahující počty dní v měsících roku. Prvky s indexem nula mají nulovou hodnotu. Díky tomuto posunu vůči standardnímu C indexování má pátý měsíc index pět. Pole je dvourozměrné. Jeho první vektor odpovídá běžnému roku, druhý vektor odpovídá roku přestupnému. Toto řešení je velmi rychlé a jeho paměťové nároky nejsou přemršťené.

Funkce day_of_year() určí pro vstupní year, month a day číslo dne v roce. Funkce obsahuje rozsáhlejší logický výraz, který určí, je-li rok přestupný či nikoliv. Pak se pouze k zadanému dnu v zadaném měsíci postupně přičítají8 počty dnů v měsících předcházejících.

Funkce month_day() má opačnou úlohu. Ze vstupních údajů year a yearday (číslo dne v roce) vypočte měsíc a den. Problémem je, že výstupní hodnoty jsou dvě. Řešením jsou formální argumenty pmonth a pday, předávané adresou9. Postup algoritmu výpočtu je opačný, než v předchozím případě. Postupně se odečítají délky měsíců (počínaje lednem), dokud není zbytek menší, než je počet dnů měsíce. Tento měsíc je pak výsledným měsícem. Zbytek dnem v měsíci. Návratová hodnota funkce nemá prakticky žádný význam. Je prostě "do počtu".

7.6. Ukazatele na ukazatele a pole ukazatelůUkazatel je proměnná jako každá jiná. Proč bychom tedy na něj nemohli ukazovat nějakým jiným (dalším) ukazatelem. Ostatně, ve vícerozměrném poli provádíme v podstatě totéž, jen jednotlivé vektory nejsou pojmenované10. Lze říci, že pole ukazatelů je v jazyce C nejen velmi oblíbená, ale i velmi často používaná

http://docs.linux.cz/c_saloun/kap07.htm (12 z 19) [23.2.2004 11:39:14]

Page 79: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

konstrukce.

Mějme jednoduchou úlohu. Pole řetězců (t.j. ukazatelů na řetězce), které máme setřídit. Výhodou našeho řešení je, že řetězce nebudou měnit svou pozici v paměti. Zaměňovat se budou jen ukazatele na ně.

Naše pole nechť se jmenuje v a nechť má nějaký počet ukazatelů na char.

char *v[10];

Pokud umístíme do jednotlivých ukazatelů pole například ukazatele na načtené řádky textu (nemusí mít stejnou délku), můžeme princip třídění ukázat na záměně dvou sousedních ukazatelů (na řetězce) v poli. Pro větší názornost jsou řetězce na obrázku v nesprávném pořadí:

ukazatele

Důležité je povšimnout si, že text (řetězce) svou pozici v paměti nezměnily. To by bylo poměrně komplikované, uvědomíme-li si, jak probíhá záměna dvou proměnných. Zaměnily se pouze adresy (hodnoty ukazatelů),

Po vysvětlení principu se můžeme podívat na program, který lexikograficky setřídí jména měsíců v roce. Protože standardní knihovny C netřídí česky, nejsou ani jména měsíců česky, ale jen "cesky". Povšimněme si i inicializace vícerozměrného pole a zjištění počtu jeho prvků.

/************************************************//* soubor str_sort.c *//* ukazuje trideni pole ukazatelu na retezce *//************************************************/

#include <stdio.h>#include <string.h>

void sort(char **v, int n){ int gap, i, j; char *temp; for (gap = n/2; gap > 0; gap /= 2) for (i = gap; i < n; i++) for (j = i - gap; j >= 0; j -= gap) { if (strcmp(v[j], v[j+gap]) <= 0) break; temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp;

http://docs.linux.cz/c_saloun/kap07.htm (13 z 19) [23.2.2004 11:39:15]

Page 80: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

}}

int main(){ char *name[] = { "chyba", "leden", "unor", "brezen", "duben", "kveten", "cerven", "cervenec", "srpen", "zari", "rijen", "listopad", "prosinec" }; int i; puts("jmena mesicu v roce (podle poradi):"); for (i = 0; i < 13; i++) printf("%2d. mesic je : %s\n", i, name[i]); puts("\na ted po setrideni:"); sort(name, sizeof(name)/sizeof(char *)); for (i = 0; i < 13; i++) printf("%2d. mesic je : %s\n", i, name[i]); return 0;}

Nevýhoda zobrazení jmen všech měsíců před i po seřazení se projeví na monitorech s nejvýše 25-ti textovými řádky. Nechtěli jsme však program činit méně přehledným, proto jsme takový výstup ponechali.

7.7. Ukazatele na funkce.Ukazatele na funkce jsme mohli začlenit do předchozí podkapitoly. Jedná se často o ukazatele nebo o pole ukazatelů. Funkce ovšem nejen vrací hodnotu jistého typu, ale mohou mít i různý počet argumentů různého typu. Protože je použití ukazatelů na funkce nejen velmi mocným prostředkem C, nýbrž i prostředkem velmi nebezpečným, rozhodli jsme se popsat ukazatele na funkce v samostatné podkapitole11.

Definujme například ukazatel na funkci, která nemá argumenty a vrací hodnotu zvoleného datového typu:

typ (*jmeno)();

Poznamenejme, že závorky okolo identifikátoru jmeno a úvodní hvězdičky jsou nutné, neboť zápis

typ *jmeno();

představuje funkci vracející ukazatel na typ.

Příkladem použití ukazatelů na funkce může být knihovní funkce qsort(). Její deklarace je následující:

void qsort(void *base, size_t nelem, size_t width, int (*fcmp)(const void *, const void *));

http://docs.linux.cz/c_saloun/kap07.htm (14 z 19) [23.2.2004 11:39:15]

Page 81: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

kde

base je začátek pole, které chceme setříditnelem je počet prvků, které chceme setřídit (rozsah pole)width je počet bajtů, který zabírá jeden prvek

fcmp je ukazatel na funkci, provádějící porovnání, ta má jako argumenty dva konstantní ukazatele na právě porovnávané prvky (nutno přetypovat)

Prototyp funkce qsort() je v hlavičkovém souboru stdlib.h.

Funkce qsort() je napsána obecně a tak nemá žádné konkrétní informace o typech hodnot, které třídí. Tuto záležitost řeší naše uživatelská funkce, která správně porovná hodnoty. Neboť my víme, co třídíme. Ukázku použití funkce spolu s měřením času a použitím generátoru náhodných čísel nám dává program:

/********************************************************//* soubor fn_qsrt.c *//* ukazuje pouziti qsort() a definici a pretypovani *//* uzivatelske srovnavaci funkce *//********************************************************/

#include <time.h>#include <stdio.h>#include <stdlib.h>

#define POCET 1000#define RND_START 1234

int float_sort (const float *a, const float *b){ return (*a - *b); /* <0, ==0, >0 */} /* int float_sort (const float *a, const float *b) */

void test (float *p, unsigned int pocet)/* otestuje zda dane pole je setrideno vzestupne */{ int chyba = 0; for (; !chyba && --pocet > 0; p++) if (*p > *(p+1)) chyba=1; puts ((chyba) ? "\npole neni setrideno\n" : "\npole je setrideno\n");} /* void test (float *p,unsigned int pocet) */

void vypln(float *p, int pocet)/* vypni pole dane adresou a poctem prvku nahodnymi *//* cisly pomoci generatoru nahodnych cisel */{

http://docs.linux.cz/c_saloun/kap07.htm (15 z 19) [23.2.2004 11:39:15]

Page 82: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

srand(RND_START); while (pocet-- > 0) *p++ = (float) rand();} /* void vypln(float *p, int pocet) */

int main (void){ static float pole [POCET]; clock_t start, end; vypln (pole, POCET); start = clock(); qsort(pole, POCET, sizeof(*pole), (int (*) (const void *, const void *)) float_sort); end = clock(); printf ("Trideni qsort trvalo %fs", (end - start) / CLK_TCK); test (pole, POCET); getc (stdin); return 0;} /* int main (void) */

Při práci s ukazateli na ukazatele, a tedy i na funkce, je velmi užitečné zavést nový typ, který definujeme podle našeho záměru. Odkaz na tento nový typ výrazně zpřehlední náš program. Před uvedením další ukázky poznamejme, že odkazy na funkce mohou být i externí. Znamená to, že máme možnost nezávisle na zdrojovém textu definovat funkci s požadovanými vlastnostmi, kterou pak připojíme do výsledného programu. Naše ukázka ovšem používá jen ukazatele na v souboru definované funkce.

/********************************//* soubor PTR_FN01.C *//* pole ukazatelu na funkce *//********************************/

#include <stdio.h>#include <process.h>

typedef void (*menu_fcn) (void); /* definice typu */menu_fcn command[3]; /* pole tri ukazatelu na funkce */

void fn_1(void) /* definice prvni funkce *//**************/{ puts("funkce cislo 1\n"); /*puts() je kratsi, nez printf() */} /* void fn_1(void) */

void fn_2(void) /* definice druhe funkce *//**************/{ puts("funkce cislo 2\n");} /* void fn_2(void) */

http://docs.linux.cz/c_saloun/kap07.htm (16 z 19) [23.2.2004 11:39:15]

Page 83: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

void fn_3(void) /* definice treti funkce *//**************/{ puts("funkce cislo 3\nKONEC\n"); exit(0); /* ukonceni chodu programu */} /* void fn_3(void) */

void assign_fn(void) /* prirazeni ukazatelu na fce *//******************/ /* do pole ukazatelu */{ command[0] = fn_1; command[1] = fn_2; command[2] = fn_3;} /* void assign_fn(void) */

void menu(void) /* funkce s nabidkou *//*************/{ int choice; do { puts("1\tpolozka\n2\tpolozka\n3\tukonceni\n"); putchar('>'); scanf("%d", &choice); if (choice >= 1 && choice <= 3) command[choice - 1](); /* ^ volani funkce pomoci pole ukazatelu, () jsou nutne */ } while (1); /* nekonecny cyklus */} /* void menu(void) */

int main(void) /* hlavni funkce programu *//************/{ assign_fn(); /* volani spravne inicializace pole ukazatelu */ menu(); /* volani nabidky, obsahuje i volbu ukonceni */ return 0; /* tento radek je vlastne zbytecny, program */ /* ukonci exit() ve treti funkci - fn_3 */} /* int main(void) */

7.8. Argumenty příkazového řádkuPokud ještě spouštíme programy z příkazového řádku, asi rovněž víme, že jim můžeme předat argumenty. Často to bývají jména vstupního a výstupního souboru, přepínače ... . Stejná situace je i v případě, kdy spouštíme program z jiného programu.

Aby nám byly argumenty příkazového řádku v programu dostupné, stačí rozšířit definici funkce main o dva argumenty. Situace je podobná, jako u funkce s proměnným počtem argumentů.

První argument funkce main() udává počet argumentů příkazové řádky (1 = jen jméno programu, 2 =

http://docs.linux.cz/c_saloun/kap07.htm (17 z 19) [23.2.2004 11:39:16]

Page 84: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

jméno programu + jeden argument, ...). Je celočíselného typu.

Druhý argument funkce main() představuje hodnoty těchto argumentů. Jeho typ je pochopitelně pole ukazatelů na řetězce, neboť jimi argumenty příkazové řádky skutečně jsou.

Je dobrým zvykem popsané argumenty funkce main() pojmenovat argc a argv, kde c znamená count a v znamená value.

Program zobrazující argumenty, s nimiž byl spuštěn následuje:

/************************************************//* soubor CMD_LN04.C *//* pomoci ukazatelu, dva formaty printf *//************************************************/

#include <stdio.h>

int main(int argc, char **argv){ while (argc-- > 0) printf((argc > 0) ? "%s " : "%s\n", *argv++); return 0;}

Pro lepší představu se podívejme, jak bude vypadat situace s argumenty příkazového řádku při spuštění příkladu. Přiznejme, že první argument, tedy cmd_ln04, bude do prostředí spuštěného programu rozvinut v úplné podobě. Nicméně věřme, že obrázek je, přes drobnou nepřesnost, užitečný.

Na závěr rozsáhlé kapitoly věnované polím a ukazatelům si zopakujme hlavní zásady pro práci s nimi:

● nesmíme se odkazovat na neinicializovaný ukazatel ● je-li p ukazatel na nějaký typ, pak *p je právě ta hodnota, na kterou ukazuje ● identifikátor pole je jen konstantní ukazatel na toto pole

http://docs.linux.cz/c_saloun/kap07.htm (18 z 19) [23.2.2004 11:39:16]

Page 85: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 7.UKAZATELE, POLE A ŘETĚZCE

● nebojme se aritmetiky ukazatelů

Výsvětlivky:

1 Zanedlouho uvidíme, že i prostřednictvím ukazatele.2 Pokud ovšem aritmetice ukazatelů porozumíme.3 Nezapomeňme na unární operace inkrementace a dekrementace. Představují jen jiný zápis přičtení či odečtení jedničky.4 Týká se pouze operace odčítání dvou ukazatelů.5 Proměnné mají po deklaraci nedefinovanou hodnotu.6 Symbol <--> chápejme jako ekvivalenci. Můžeme jej číst například "je totéž".7 A OS který nám ji umí poskytnout.8 S formálním argumentem předaným hodnotou si to můžeme dovolit. Skutečný argument zůstane nezměněn.9 Jinak řečeno, jsou to ukazatele.10 Přistupujeme k nim pomocí bázové adresy základního pole a indexu. Z tohoto pohledu jsou opravdu nepojmenované.11 Tuto podkapitolu jsme nemohli zařadit přímo do kapitoly věnované funkcím, neboť v tom místě jsme ještě dostatečně nepoznali ukazatele.

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce CAutor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap07.htm (19 z 19) [23.2.2004 11:39:16]

Page 86: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

Předchozí kapitola Obsah Konec Následující kapitola

8. Vstup a výstup8.1. Standardní vstup a výstup

Standardní vstup a výstup znakůStandardní vstup a výstup řetězcůFormátovaný standardní vstup a výstup

8.2. Vstupní a výstupní operace v paměti.8.3. Práce se soubory.8.4. Soubory s přímým voláním8.5. Datové proudy

Otevření a zavření prouduProudy a vstup/výstup znakůProudy a vstup/výstup řetězcůFormátovaný vstup/výstup z/do prouduProudy a blokový přenos datDalší užitečné funkcePříklady práce s proudy

Každý program zpracovává nějaká vstupní data a sděluje nám výsledky touto činností získané. Pokud by tomu tak nebylo, neměli bychom zřejmě důvod takový program vůbec aktivovat.

Vstup a výstup probíhá z různých vstupně výstupních zařízení. Jejich nejjednodušší rozdělení je znaková a bloková (blokově orientovaná). Znakové vstupní zařízení typicky představuje klávesnice, výstupní pak monitor či tiskárna. Blokové vstupně/výstupní zařízení je velmi často pružný či pevný disk. Rozdíl mezi nimi spočívá zvláště v možnostech přístupu k datům. Z klávesnice čteme sekvenčně znak po znaku (sekvenční přístup), zatímco u diskového souboru můžeme libovolně přistupovat ke zvolené části dat (používaný termín popisující tuto činnost je náhodný přístup).

Vstup a výstup budeme často zkráceně zapisovat I/O, nebo, nebude-li možnost omylu, jen IO.

Ještě než se pustíme do podrobného výkladu, definujme si některé základní pojmy.

Řádek textu je posloupnost znaků ukončená symbolem (symboly) přechodu na nový řádek. Zdůrazněme, že v uvedené definici není žádná zmínka o délce řádku. Tuto skutečnost je třeba mít na paměti.1

Soubor je posloupnost znaků (bajtů) ukončená nějakou speciální kombinací, která do obsahu souboru nepatří - konec souboru. Tuto hodnotu označujeme symbolicky EOF. Textový soubor obsahuje řádky textu. Binární soubor obsahuje hodnoty v témže tvaru, v jakém jsou uloženy v paměti počítače2. Binární soubor obvykle nemá smysl vypisovat na terminál.

8.1. Standardní vstup a výstupKaždý program v jazyce C má standardně otevřen standardní vstup stdin, standardní výstup stdout a standardní

http://docs.linux.cz/c_saloun/kap08.htm (1 z 26) [23.2.2004 11:39:58]

Page 87: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

chybový výstup stderr. Ty jsou obvykle napojeny na klávesnici a terminál. Na úrovni operačního systému máme ovšem možnost vstup a výstup přesměrovat. Programy, které čtou ze standardního vstupu a zapisují do standardního výstupu se často nazývají filtry.

Připomeňme si užitečné filtry grep, more, find. Na filtrech, krátkých a relativně jednoduchých programech, je založena i jedna ze základních myšlenek Unixu. Snadno zvládnutelné filtry spojené do kolony mají často stejný efekt, jako složité a rozsáhlé programy, které bývají navíc jednostranně orientované3.

Možnost přesměrování vstupu a výstupu je rovněž velmi oblíbená pro tvorbu funkčního jádra programu, který zatím nemá obsažen styk s uživatelem. Takovému programu přesměrováváme data uložená v připraveném testovacím vstupním souboru a výsledek přesměrováváme do jiného výstupního souboru.

Několik poznámek ke standardnímu I/O:

Pro ukončení vstupu z klávesnice použijeme v MS-DOSu kombinaci ctrl-z, v Unixu ctrl-d.

Standardní vstup a výstup používá vyrovnávací paměť obsahující jeden textový řádek.

Při volání funkcí standardního vstupu/výstupu musíme použít hlavičkový soubor stdio.h.

Standardní vstup a výstup znaků

představuje zcela základní možnost I/O. Funkce4

int getchar(void);

přečte ze standardního vstupu jeden znak, který vrátí jako svou návratovou hodnotu. V případě chyby vrátí hodnotu EOF. Funkce

int putchar(int c);

má zcela opačnou úlohu, znak, který je jejím argumentem zapíše na standardní výstup. Zapsaná hodnota je současně návratovou hodnotou, nenastane-li chyba. Pak vrací EOF.

Zdůrazněme podstatný fakt, typ hodnoty, kterou čteme/zapisujeme je int, nikoliv char, jak bychom v první chvíli očekávali. Tuto záležitost nám objasní opětné přečtení definice souboru z úvodu této kapitoly. Soubor obsahuje znaky. To odpovídá naší představě. Ale konec souboru je představován hodnotou, která do souboru nepatří. Tato hodnota ovšem musí být odlišná od ostatních znaků. A proto je typu int.

Čtení znaků ze standardního vstupu a jejich zápis na standardní výstup ukazuje program, představující jednoduchou variantu příkazu kopírování souboru (nesmíme ovšem zapomenout přesměrovat vstup a výstup).

/****************************************//* CPY.C *//* CoPY character *//****************************************/

#include <stdio.h>

int main(void){ int c;

http://docs.linux.cz/c_saloun/kap08.htm (2 z 26) [23.2.2004 11:39:58]

Page 88: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

while ((c = getchar()) != EOF) putchar(c); return 0;}

Standardní vstup a výstup řetězců

je jednoduchou nadstavbou nad čtením znaků. Obě funkce,

char *gets(char *s);int puts(const char *s);

pracují s řetězci. gets načte do znakového pole vstupní řetězec až do konce řádku, symbol '\n' není do znakového pole zapsán. Ukazatel na pole (načtený řetězec) je rovněž návratovou hodnotou. Chybu signalizuje návrat NULL. puts zapíše řetězec na výstup a přidá přechod na nový řádek '\n'. Chybu představuje návratové EOF, jinak vrací kladné celé číslo.

Jednoduchost použití skrývá velké nebezpečí. Funkce gets() nemá informaci o délce oblasti vymezené pro čtený řetězec. Je-li oblast kratší, než vstupní řádek, dojde jeho načtením velmi pravděpodobně k přepsání paměťové oblasti související s vyhrazenou pamětí. A to se všemi důsledky z toho vyplývajícími.

Následující program je modifikací předchozího kopírování souborů. Je ovšem možno jej použít pouze pro textové soubory. Navíc mohou vzniknout odlišnosti mezi originálem a kopií - např. končí-li poslední řádek originálu přímo EOF a ne '\n', je konec řádku '\n' připojen díky vlastnosti funkce puts().

/****************************************//* GETS.C *//* GET String function *//****************************************/

#include <stdio.h>#define MAX_STR 512

int main(void){ char s[MAX_STR];

while (gets(s) != NULL) puts(s); return 0;}

Formátovaný standardní vstup a výstup

V Úvodu jsme se povrchně seznámili s funkcemi pro formátovaný vstup a výstup printf() a scanf(). Tyto funkce jsou základními představiteli funkcí pro formátovaný I/O, které se sice navzájem liší zdrojem či spotřebitelem, nicméně formát argumentů a formátovací řetězce používají stejným způsobem. Proto na tomto místě podrobněji popíšeme vlastnosti (způsob volání), na něž se dále budeme odvolávat, nebo které jsou v textu použity.

Pro formátovaný standardní výstup používáme funkci:

int printf (const char *format [, argument, ...]);

http://docs.linux.cz/c_saloun/kap08.htm (3 z 26) [23.2.2004 11:39:58]

Page 89: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

První parametr format určuje formátovací řetězec. Ten může obsahovat popis formátu pro každý argument, nebo text, který bude zapsán do výstupu. Popis formátu vždy začíná znakem %. Chceme-li znak % použít jako text, zdvojíme jej: %%. Návratová hodnota reprezentuje počet znaků zapsaných do výstupu, nebo EOF v případě chyby.

Specifikace formátu má poměrně velmi rozsáhlé možnosti:

% [flags] [width] [.prec] [F|N|h|l|L] type_char

Každá specifikace formátu začíná symbolem %. Po něm mohou v uvedeném pořadí následovat další položky:

položka význam

flags zarovnání výstupu, zobrazení znaménka a desetinných míst u čísel, úvodní nuly, prefix pro osmičkový a šestnáctkový výstup

width minimální počet znaků na výstupu, mohou být uvedeny mezerami nebo nulami

.prec maximální počet znaků na výstupu, pro celá čísla minimum zobrazených znaků, pro racionální počet míst za desetinnou tečkou

F|N|h|l|L l indikuje dlouhé celé číslo, L long double, ostatní mají význam v MS-DOSu type_char povinný znak, určuje typ konverze

Specifikujme si nyní typ konverze (type_char):

symbol významd, i desítkové celé číslo se znaménkem u desítkové celé číslo se bez znaménka o osmičkové celé číslo x, X šestnáctkové celé číslo, číslice ABCDEF malé (x) nebo velké (X)

f racionální číslo (float, double) bez exponentu, implicitně 6 desetinných míst

e, E racionální číslo (float, double) v desetinném zápisu s exponentem, implicitně jedna pozice před des. Tečkou, 6 za. Exponent uvozuje e, resp. E (dle použitého symbolu).

g, Gracionální číslo (float, double) v desetinném zápisu s exponentem nebo bez něj (podle absolutní hodnoty čísla). Nemusí obsahovat desetinnou tečku (nemá-li desetinnou část). Pokud je exponent menší, než -4, nebo větší, než počet platných číslic, je použit.

c znaks řetězec

Příznak (flag) může být:

příznak význam- výsledek je zarovnán zleva + u čísla bude vždy zobrazeno znaménko (i u kladného) mezera pro kladná čísla vynechá prostor pro znaménko

#pro formáty o, x, X výstup jako konstanty jazyka C, pro formáty e, E, f, g, G vždy zobrazí desetinnou tečku, pro g, G ponechá nevýznamné nuly, pro c, d, i, s, u nemá význam.

http://docs.linux.cz/c_saloun/kap08.htm (4 z 26) [23.2.2004 11:39:58]

Page 90: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

Šířka (width) může být:

šířka význam

n je vytištěno nejméně n znaků zarovnaných zleva či zprava (viz příznak), doplněno mezerami

0n jako předchozí, doplněno zleva nulami * jako šířka pole bude použit následující parametr funkce printf()

Přesnost (.prec) může být:

přesnost význam

.0 pro e, E, f nezobrazí desetinnou tečku, pro d, i, o, u, x nastaví standardní hodnoty

.npro d, i, o, u, x minimální počet číslic, pro e, E, f počet desetinných číslic. Pro g, G počet platných míst, pro s maximální počet znaků

* jako přesnost bude použit následující parametr funkce printf()

Pro formátovaný standardní vstup použijeme funkci:

int scanf (const char *format [, address, ...]);

První argument je opět formátovací řetězec. Ten může obsahovat tři typy zpracování objektů. jsou to:

● přeskok bílých znaků (oddělovačů), t.j. mezery, tabulátoru, nového řádku a nové stránky ● srovnání znaků formátovacího řetězce se vstupními. Je-li na vstupu jiný, než určený znak, je čtení ukončeno. ● specifikace formátu vstupní pro hodnoty, je vždy uvozena znakem %.

Druhý (případně třetí, čtvrtý, ...) argument je úmyslně nazván address. Jde o to, že musí určovat paměťovou oblast, do níž bude odpovídající vstupní hodnota uložena. V praxi jde nejčastěji o adresu proměnné, nebo o ukazatel na pole znaků.

Čtení ze vstupu probíhá tak, že první formátovací popis je použit pro vstup první hodnoty, která je uložena na první adresu, druhý formátovací popis je použit pro vstup druhé hodnoty uložené na druhou adresu, ... .

Návratová hodnota nás informuje kladným celým číslem o počtu bezchybně načtených a do paměti uložených položek (polí), nulou o nulovém počtu uložených položek a hodnotou EOF o pokusu číst další položky po vyčerpání vstupu.

Formát vstupní hodnoty popíšeme následovně:

% [*] [width] [F|N] [h|l|L] type_char

Po znaku % mohou následovat jednotlivé položky v uvedeném pořadí:

položka význam* přeskoč popsaný vstup width maximální počet vstupních znaků F|N blízký nebo vzdálený ukazatel (jen MS-DOS) h|l|L modifikace typu

http://docs.linux.cz/c_saloun/kap08.htm (5 z 26) [23.2.2004 11:39:58]

Page 91: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

type_char (povinný) typ konverze

Hvězdička (*) ve specifikaci formátu umožňuje příslušnou vstupní hodnotu sice ze vstupu načíst, ale do paměti nezapisovat. Šířka (width) určuje maximální počet znaků, které budou použity při vstupu. Modifikátory typu (h|l) týkající se celočíselné konverze (d) určují typ short resp. long, (l|L) modifikuje racionální typ float na double na long double.

Specifikujme si nyní typ konverze (type_char):

symbol významd celé číslo u celé číslo bez znaménka o osmičkové celé číslo x šestnáctkové celé číslo i celé číslo, zápis odpovídá zápisu konstanty jazyka C (např. 0x uvozuje šestnáckové č.) n počet dosud přečtených znaků probíhajícím voláním funkce scanf()

e, f, g racionální číslo typu float, lze modifikovat pomocí l|L

s řetězec (i zde jsou úvodní oddělovače přeskočeny!), v cílovém poli je ukončen '\0', c vstup znaku, je-li určena šířka, je čten řetězec bez přeskočení oddělovačů!

[search_set]jako s, ale se specifikací vstupní množiny znaků (je možný i interval, např. %[0-9], i negace, např. %[^a-c].

Popis formátovaného standardního vstupu a výstupu je značně rozsáhlý. Některé varianty kombinací paramertů formátu se vyskytují poměrně exoticky. Častěji se setkáme s využitím návratové hodnoty funkce scanf().

Příkladem může být cyklus, v němž načteme číselnou hodnotu (zadat můžeme celé i racionální číslo) a vypočteme a zobrazíme její druhou mocninu. Cyklus končí teprve ukončením vstupu - symbolem EOF. Tento symbol je v MS-DOSu ^Z, v Unixu ^D. Program můžeme chápat i jako malé ohlédnutí k cyklům:

/************************************************//* SCANWHIL.C *//* program je ukazkou nacitani pomoci scanf *//* v kombinaci s cyklem while. *//* Ideova motivace K&R. *//************************************************/

#include <stdio.h>

int main(void){ float f;

printf("zadej x:"); while (scanf("%f", &f) != EOF) { printf("x=%10.4f x^2=%15.4f\nzadej x:", f, f*f); } return 0;} /* int main(void) */

http://docs.linux.cz/c_saloun/kap08.htm (6 z 26) [23.2.2004 11:39:58]

Page 92: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

zadej x:2x= 2.0000 x^2= 4.0000zadej x:3x= 3.0000 x^2= 9.0000zadej x:4x= 4.0000 x^2= 16.0000zadej x:^Z

8.2. Vstupní a výstupní operace v paměti.V paměti počítače můžeme provádět prakticky stejné operace, jako při standardním vstupu a výstupu. Jen musíme specifikovat vstupní respektive výstupní řetězec. Tyto operace se provádějí pomocí funkcí sprintf() a sscanf(). Úvodní s v nich znamená string a fakticky definuje rozdíl mezi nimi a rodinou funkcí scanf a printf. Podívejme se na deklarace:

int sprintf (char *buffer, const char *format [, argument, ...]);int sscanf (const char *buffer, const char *format [, address, ...]);

Vstupní a výstupní řetězec je buffer, ostatní argumenty mají stejný význam jako u funkcí pro formátovaný standardní vstup a výstup.

8.3. Práce se soubory.V odstavci věnovaném standardnímu vstupu a výstupu jsme si ukázali, že na této úrovni můžeme vlastně pracovat i se soubory. Stačí jen na úrovni operačního systému přesměrovat vstup a/respektive výstup. Taková práce se soubory má velikou výhodu. Vždy probíhá na textové úrovni, která je implementačně nezávislá5. Zmíněná nezávislost a tím i přenositelnost sebou přináší i neustálou nutnost konverze mezi vnitřním (binárním) a vnějším tvarem hodnoty. Zpravidla se jedná o čísla. Číslo ve vnitřním tvaru má pevný formát a bez ohledu na svou hodnotu zaujímá tedy vždy6 stejnou část paměti. Tuto vlastnost řetězec, představující textový zápis čísla obvykle nesplňuje. Důvody popsané v tomto odstavci ovšem nejsou ty, pro něž je odlišen standardní IO od souborového IO.

Soubor, s nímž můžeme v jazyce C pracovat má své jméno. Tím rozumíme jméno na úrovni operačního systému7. Navíc, program může zjistit jméno souboru například až za chodu při interakci s uživatelem. Programátor může určit, zda obsah souboru bude interpretován textově, nebo binárně. Uvedené vlastnosti odlišují pojmenovaný soubor od standardního vstupu a výstupu.

Jazyk C umožňuje se soubory pracovat na dvou odlišných úrovních.

První z nich je tak zvaný přímé volání . Tím se rozumí přímé volání služeb jádra systému. Poskytuje sice maximální možnou rychlost díky vazbě na jádro systému, současně však může být systémově závislý. Tento způsob práce se soubory je definován v K&R. Je dobré znát jeho základní principy, neboť se v praxi můžeme setkat s mnoha staršími zdrojovými texty používajícími přímé volání. Soubory s přímým voláním nejsou součástí ANSI normy. Jsou však definovány normou POSIX, kde se používají pro volání služeb jádra systému.

Druhým přístupem, je datový proud. Způsob práce s ním definuje jak ANSI norma jazyka C, tak norma definující rozhraní přenositelného operačního systému POSIX (Portable Operating Systems Interface). Pro manipulaci s datovým proudem máme k dispozici řadu funkcí, které nám poskytují vysoký komfort. Navíc, díky citovaným normám, máme zajištěnu nejen existenci těchto funkcí, modifikátorů a definic, ale i jejich definované vlastnosti a rozhraní. To nás vede k použití tohoto

http://docs.linux.cz/c_saloun/kap08.htm (7 z 26) [23.2.2004 11:39:58]

Page 93: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

přístupu v našich programech.

Poznamejme, že počet souborů, které můžeme v programu současně otevřít, je omezen OS (nejčasteji jeho konfigurací). Pro zjištění, jaký limit máme k dispozici, slouží makro FOPEN_MAX. Operační systém rovněž omezuje délku jména souboru. Někdy dokonce na neuvěřitelných 8 + 3 znaky. Rovněž tuto hodnotu můžeme zjistit pomocí makra, tentokrát FILENAME_MAX. Odstavec věnovaný makrům, použitelných jak ve spojení se soubory s přímým přístupem, tak s proudy, ukončeme známou konstantou EOF, představující konec souboru.

Popišme si obě možnosti práce se soubory v pořadí jejich historického vzniku. V úvodu obou pasáží nejprve shrneme nejdůležitější funkce, pak uvedeme příklady, ukazující základní principy jejich použití. Ještě jednou ovšem poznamenejme, že práce s datovými proudy zaručuje přenositelnost.

8.4. Soubory s přímým volánímPro práci se soubory s přímým voláním musíme používat funkční prototypy umístěné v io.h., sys\stat.h a fcntl.h. Jednoznačným popisem souboru v rámci běžícího programu (procesu) je kladné celé číslo, představující číselný popis souboru - manipulační číslo (deskriptor, handle).

int open(const char *path, int access [ , unsigned mode ] );

Slouží k otevření a případnému vytvoření souboru. Vrací kladný deskriptor souboru, v případě neúspěchu -1 a nastaví kód chyby v globální proměnné errno.

Další argumenty open() jsou:

path je řetězec určující jméno souboru na úrovni OS. Může obsahovat i cestu. V případě MS-DOSu je nutné zdvojovat opačná lomítka oddělující navzájem jednotlivé podadresáře cesty - jedná se o normální céčkový řetězec, včetně interpretace znaku \.

access popisuje režim přístupu k otevřenému souboru. S výjimkou prvních tří možností je možné je navzájem kombinovat pomocí bitového součtu | .

mode je nepovinný příznak, definující atributy souboru na úrovni OS. Při jeho určení využíváme předdefinované konstanty S_I... .

příznak významO_RDONLY otevření pouze pro čtení O_WRONLY otevření pouze pro zápis O_RDWR otevření pro čtení i zápis O_NDELAY pro kompatibilitu s UNIXem O_APPEND připojení na konec souboru. Je-li nastaven, probíhá každý zápis na konec souboru O_CREAT vytvoř a otevři soubor. Pokud soubor již existuje, nemá význam. Jinak je soubor vytvořen. O_EXCL výlučné otevření. Použití jen současně s O_CREAT. Pokud soubor existuje, vrátí chybový kód.

O_TRUNC otevření s ořezáním. Pokud soubor existuje, je jeho délka nastavena na 0. Atributy souboru zůstávají nezměněny.

Příznaky pro textový/binární režim. Mohou být použity funkcemi fdopen, fopen, freopen, _fsopen, open a sopen:

http://docs.linux.cz/c_saloun/kap08.htm (8 z 26) [23.2.2004 11:39:59]

Page 94: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

příznak významO_BINARY explicitní otevření souboru v binárním režimu. Nejsou prováděny žádné konverze.

O_TEXT explicitní otevření souboru v textovém režimu. Jsou prováděny konverze CR-LF.

Další symbolické konstanty umožňují nastavení atributů souboru. Jsou definovány v souboru SYS\STAT.H:

příznak významS_IWRITE povolení zápisuS_IREAD povolení čteníS_IREAD|S_IWRITE povolení čtení i zápisu

Popis k jediné funkci open() je poměrně zdlouhavý. Máme však alespoň za sebou výčet předdefinovaných identifikátorů, se kterými se můžeme při práci s funkcemi pro přímý přístup k souborům setkat. Protože soubor chceme jistě nejen otevřít, ale s ním i dále pracovat, nezbývá, než si popsat i další potřebné funkce.

int creat(const char *path, int amode);

Vytváří nový soubor, nebo umožňuje přepsat soubor existující. Hodnoty amode umožňují nastavení atributů souboru podobně, jako u open(). Řetězec path představuje (úplné) jméno souboru. Vrací kladný deskriptor souboru, v případě neúspěchu -1 a nastaví kód chyby v globální proměnné errno.

int close(int handle);

Zavírá soubor, určený hodnotou deskriptoru handle. Současně vyprázdní případné vyrovnávací paměti, uzavře a uvolní související datové oblasti8. Vrací 0, proběhlo-li uzavření souboru správně. V případě neúspěchu vrací hodnotu -1.

int read(int handle, void *buf, unsigned len);

Čte ze souboru určeného manipulačním číslem handle tolik bytů, kolik je určeno hodnotou len do paměťové oblasti určené ukazatelem buf. Návratová hodnota je -1 v případě neúspěchu. Jinak je vrácen počet přečtených bytů9. Funkce neprovádí (a ostatně ani nemůže provádět) žádnou kontrolu velikosti paměťové oblasti buf a požadovaného početu bajtů pro čtení len. Pokud chceme jedním příkazem načíst více, než máme vyhrazeného prostoru, skončí takový příkaz fiaskem.

int write(int handle, void *buf, unsigned len);

Z paměťové oblasti určené ukazatelem buf zapíše do souboru určeného manipulačním číslem handle tolik bytů, kolik je určeno hodnotou len. Návratová hodnota je v případě neúspěchu -1. Jinak je vrácen počet úspěšně zapsaných bytů10. Ten by se měl shodovat s hodnotou len. Jinak může být například plný disk.

long lseek(int handle, long offset, int fromwhere);

Je funkce, která nám umožní přemístit aktuální pozici v souboru na požadované místo. A to jak pro čtení, tak pro zápis11. Hodnota handle je zřejmá. Hodnota offset udává požadovaný počet bajtů posunu. Zdůrazněme, že se jedná o hodnotu typu long int. Pro stanovení pozice fromwhere, vzhledem k níž má být posun proveden, slouží předdefinované hodnoty:

SEEK_SET posun vůči aktuální pozici

http://docs.linux.cz/c_saloun/kap08.htm (9 z 26) [23.2.2004 11:39:59]

Page 95: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

SEEK_CUR posun vzhledem počátku SEEK_END posun vzhledem ke konci

Nedoporučuje se používat jejich skutečné číselné hodnoty (po řadě 0, 1 a 2). Návratová hodnota je opět typu long int. Představuje skutečně nastavenou pozici v souboru, ovšem vždy vzhledem k počátku! V případě chyby vrací -1L.

Pro zkrácení zavedeme pro aktuální pozici v souboru zkratku CP (od Current Pointer). CP si můžeme představit jeho čtecí/záznamovou hlavu, která se nachází nad obsahem souboru. Dále zdůrazněme, že každá operace čtení/zápisu přesune CP o příslušný počet bajtů. Pro jistotu doplňme, že směrem od začátku ke konci souboru. Rovněž musíme připomenout, že při nastaveném O_APPEND probíhá každý zápis na konec souboru. Tedy bez ohledu na původní polohu CP.

Podívejme se postupně na několik příkladů, v nichž si ukážeme základní práci se soubory s přímým přístupem.

Nejprve vytvoříme binární soubor, obsahující hodnoty druhých odmocnin čísel od 1 do 99. Tyto hodnoty budou typu float.

/************************************************//* soubor IO-PV01.C *//* vytvori binarni soubor ODMOC.DTA, ktery *//* obsahuje hodnoty druhych odmocnin od 1 do 99 *//* hodnoty jsou typu float *//************************************************/

#include <stdio.h>#include <io.h>#include <fcntl.h>#include <math.h>#include <sys\stat.h>

int main(void){ const int mez = 100; int handle, i; char *jmeno = "odmoc.dta"; float f;

handle = open(jmeno, O_CREAT | O_BINARY | O_RDWR, S_IREAD | S_IWRITE); if (handle == -1) { printf("Chyba!\nSoubor %s nejde vytvorit.\n", jmeno); return(handle); }

for (i = 1; i < mez; i++) { f = sqrt(i); if (write(handle, &f, sizeof(float)) != sizeof(float)) { printf("Chyba pri zapisu %2d. hodnoty\n", i); return(1); } } /* for (i = 1; i < mez; i++) */ close(handle); return(0);

http://docs.linux.cz/c_saloun/kap08.htm (10 z 26) [23.2.2004 11:39:59]

Page 96: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

} /* int main(void) */

Jelikož nám deklarace a definice proměnných a konstant nečiní potíže, podívejme se na klíčové příkazy. Vcelku přirozeně očekáváme, že nejprve musíme soubor vytvořit12 a otevřít jej pro zápis. Můžeme tak učinit jak funkcí creat(), tak následovně:

handle = open(jmeno, O_CREAT | O_BINARY | O_RDWR, S_IREAD | S_IWRITE);

Vytváříme (O_CREAT) nový soubor s názvem určeným řetězcem jméno (pokud existuje, je zkrácen na délku 0), který bude obsahovat binární (O_BINARY) data (tedy žádné textové konverze CR-LF) a do tohoto souboru budeme zapisovat, případně z něj máme možnost i číst (O_RDWR). Na úrovni OS požadujeme, aby soubor měl atributy umožňující jeho vlastníkovi čtení i zápis (S_IREAD | S_IWRITE). Tento třetí argument je sice nepovinný, ovšem jeho určením se vyvarujeme nedefinovaného chování některých překladačů. Pokud je vše v pořádku, máme nyní manipulační číslo souboru handle, jinak ohlásíme chybu a ukončíme chod programu.

Dále v cyklu do našeho otevřeného souboru - první argument funkce write() obsahuje manipulační číslo souboru handle,

if (write(handle, &f, sizeof(float)) != sizeof(float))

zapisujeme obsah proměnné f. Tento moment je velmi významný. Nestačí totiž jen určit, že obsah proměnné se nachází na adrese &f. Musíme ještě určit, kolik bajtů má býti zapsáno. Činíme tak třetím argumentem sizeof(float) funkce write(). Tím sice máme zápis popsán, my však raději ještě provedeme kontrolu, bylo-li přeneseno právě tolik bytů, kolik jsme požadovali. Vyhodnocení případné nerovnosti je v programu současně chybovou zprávou a následným ukončením programu.

Po patřičném počtu opakování, které zajistí cyklus, musíme před ukončením programu ještě soubor uzavřít:

close(handle);

Teprve poté si můžeme být jisti, že jsme pro zdárný průběh našeho prvního setkání se soubory učinili vše potřebné.

V pracovním adresáři máme vytvořen soubor odmoc.dta. Měl by být dlouhý 396 bajtů ( 99 hodnot typu float - tedy každá 4 bajty). Je-li tomu tak, můžeme s tímto souborem dále pracovat. Potřebujeme si ukázat čtení a přemístění CP v souboru.

V binárním souboru odmoc.dta máme zapsány hodnoty druhých odmocnin čísel od 1 do 99. Podívejme se nyní na program, který podle zadané hodnoty buď zobrazuje hodnoty druhé odmocniny pro zadané celé číslo ve zmíněném intervalu, nebo ukončí svou činnost. Poznamejme, že se hodnoty druhých odmocnin nepočítají, nýbrž čtou ze souboru13. Úloha tedy spočívá v umístění CP na vhodnou pozici v souboru a následném načtení a zobrazení zapsané racionální hodnoty. Celý program může vypadat takto:

/************************************************//* soubor IO_PV02.C *//* cte binarni soubor ODMOC.DTA, ktery obsahuje *//* hodnoty druhych odmocnin od 1 do 99 *//* hodnoty jsou typu float *//* hledanou hodnotu odmocniny najde v souboru *//* pomoci lseek *//************************************************/

#include <stdio.h>

http://docs.linux.cz/c_saloun/kap08.htm (11 z 26) [23.2.2004 11:39:59]

Page 97: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

#include <io.h>#include <fcntl.h>

int main(void){ const int mez = 100; int handle, i; char *jmeno = "odmoc.dta"; float f;

handle = open(jmeno, O_BINARY | O_RDONLY); if (handle == -1) { printf("Chyba!\nSoubor %s nejde cist.\n", jmeno); return(handle); } printf("zadej hodnotu <1..%3d> :", mez - 1); scanf("%d", &i); while ((i >= 1) && (i < mez)) { if (lseek(handle, ((long) i - 1l) * sizeof(float), SEEK_SET) == -1l) { printf("Chyba pri nastaveni na %2d. polozku\n", i); return(1); }

if (read(handle, &f, sizeof(float)) != sizeof(float)) { printf("Chyba pri cteni %2d. hodnoty\n", i); return(1); } printf("2. odmocnina z %3d je %10.4f\n\n", i, f); printf("zadej hodnotu <1..%3d> :", mez - 1); scanf("%d", &i); } /* while ((i >= 1) && (i < mez)) */ close(handle); return(0);} /* int main(void) */

Opět se zaměříme na nové funkce případně na jejich nové použití. Nejprve musíme otevřít existující soubor. Víme, že z něj budeme pouze číst, tedy:

handle = open(jmeno, O_BINARY | O_RDONLY);

Tentokrát testujeme návratovou hodnotu (a tedy úspěšnost otevření souboru) až na následujícím řádku. Dále potřebujeme přemístit CP na správnou pozici v souboru,

if (lseek(handle, ((long) i - 1l) * sizeof(float), SEEK_SET) == -1l).

První argument lseek(), tedy manipulační číslo souboru, získáme při otevření. Následuje zdánlivě složitý výpočet pozice, na níz chceme CP přemístit. Třetí SEEK_SET argument říká, že pozice bude vzhledem k počátku souboru. Odbočme na chvíli. První položka souboru má posun nula. Druhá položka má posun jedna krát délka položky. Třetí ... . Poslední položka, řekněme n-tá, má posun (n - 1) krát délka položky.

Nyní již snadno nahlédneme, že výpočet ((long) i - 1l) * sizeof(float) skutečně určuje správnou pozici CP

http://docs.linux.cz/c_saloun/kap08.htm (12 z 26) [23.2.2004 11:39:59]

Page 98: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

pro načtení hodnoty druhé odmocniny. Ty jsou v souboru přece pro celá čísla 1 až 99. Přetypování proměnné i na long je spíše proto, abychom si uvědomili typ tohoto argumentu.

CP je správně nastaven, nezbývá nám, než načíst potřebný počet bajtů na správné místo,

if (read(handle, &f, sizeof(float)) != sizeof(float))

pro jistotu i s kontrolou na skutečně přečtený počet bajtů. A to je v podstatě vše. Po zadání čísla mimo rozsah je program ukončen. Předtím pochopitelně nezapomeneme uzavřít soubor.

Než přejdeme k ukázce příkladu práce s textovými soubory s přímým přístupem, ukažme si, jak zjistit informaci, která nezávisí na typu dat v souboru uložených. Touto informací je jistě délka souboru (počet bajtů). Můžeme se pustit na tenký led a použít specialisované funkce záviské na konkrétním OS. Nebo si uvědomíme, jakou návratovou hodnotu má funkce lseek(). Je to přece pozice v souboru vzhledem k jeho počátku. Nastavíme-li tedy CP na konec souboru, získáme požadovanou informaci. Jak provedeme nastavení na konec? Snadno,

if ((l = lseek(handle, 0l, SEEK_END)) == -1l)

prostě provedeme v otevřeném souboru přesun o nula bajtů, ovšem vzhledem k jeho konci. Výpis celého programu, s mírným ošetřením některých možných zdrojů chyb, následuje:

/************************************************//* soubor IO-PV03.C *//* Zjisti a vytiskne delku souboru, jehoz jmeno *//* je zadano z klavesnice. *//************************************************/

#include <stdio.h>#include <io.h>#include <fcntl.h>

int main(void){ int handle; char jmeno[80]; long l;

printf("\nzadej jmeno souboru :"); scanf("%s", jmeno);

handle = open(jmeno, O_BINARY | O_RDONLY); if (handle == -1) { printf("Chyba!\nSoubor <%s> nejde cist.\nNeexistuje?\n", jmeno); return(handle); }

if ((l = lseek(handle, 0l, SEEK_END)) == -1l) { printf("Chyba pri nastaveni na konec souboru <%s>\n", jmeno); return(1); } else { printf("Soubor <%s> je dlouhy %ld bajtu.\n", jmeno, l);

http://docs.linux.cz/c_saloun/kap08.htm (13 z 26) [23.2.2004 11:39:59]

Page 99: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

close(handle); return(0); }} /* int main(void) */

Je načase ukázat si i práci s textovými soubory pomocí funkcí pro přímý přístup. Naším úkolem bude vytvořit několikařádkový textový soubor. Ukážeme si, jak do něj zapsat řetězce a co musíme udělat proto, chceme-li do textového souboru zapsat i číselné hodnoty. V podstatě se nejedná o nic převratného. Jen při otevření či vytvoření souboru musíme určit, že jej budeme chápat jako textový,

if ((handle = open(jmeno, O_TRUNC | O_CREAT | O_TEXT | O_WRONLY, S_IWRITE)) == -1)

Proto tedy hodnota O_TEXT. Navíc zde máme kombinaci příznaků O_TRUNC | O_CREAT, která zajistí nejen vytvoření souboru, ale v případě jeho existence i zkrácení na nulovou délku (tedy zrušení starého obsahu). Příkaz je doplněn i příznakem O_WRONLY, dávajícím informaci, že do souboru hodláme výhradně zapisovat. Třetím argumentem funkce pro otevření, příznakem S_IWRITE, sdělujeme prakticky totéž OS.

Vyvstává problém. Při zápisu do souboru musíme znát počet bajtů, které chceme přenést. Máme, jako již tradičně, více možností, jak tuto informaci získat. Postup

write(handle, "Uz to umim!\n", strlen("Uz to umim!\n"));

jistě není příliš vhodný. Řetězec musíme uvést dvakrát. Nejenže tedy je pravděpodobně umístěn dvakrát v paměti (pokud náš překladač při optimalizaci nezjistí shodu literálů). Navíc v případě, kdy chceme řetězec změnit, musíme tuto změnu identicky provést na dvou místech. Zvolené řešení, kdy do vytvořeného prostoru char s[81] řetězec prostě nakopírujeme pomocí strcpy(), vede ve všech případech zápisů ke shodnému a poměrně čitelnému

write(handle, s, strlen(s));

Po krátkém zamyšlení ovšem opět nahlédneme, že řetězec je v paměti opět umístěn dvakrát. Navíc jej ještě kopírujeme! Proč jsme tedy volili tuto na první pohled strašlivou variantu?14 Odpoví nám dvojice následujících řádků

sprintf(s, "%5d", i);write(handle, s, strlen(s));,

které jsou umístěny v těle cyklu. Pro zobrazení číselných hodnot musíme použít u textových souborů s přímým přístupem umělý krok. Číslo nejprve převedeme z vnitřního tvaru na řetězec a teprve poté tento řetězec (reprezentující číslo ve zvoleném formátu) zapíšeme do souboru. A jestliže pro převod potřebujeme vyhrazený prostor, tak jsme tento prostor využili i výše. Odměnou za přece jen nadbývající kopírování nám může být identický zápis příkazů výstupu v obou případech.

Nyní již následuje souvislý výpis programu:

/************************************************//* soubor IO-PV04.C *//* vytvori textovy soubor SOUBOR.TXT *//* verze s primym pristupem - open *//************************************************/

#include <stdio.h>#include <io.h>#include <fcntl.h>#include <process.h>

http://docs.linux.cz/c_saloun/kap08.htm (14 z 26) [23.2.2004 11:40:00]

Page 100: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

#include <string.h>#include <sys\stat.h>

int chyba(int er){ extern int errno; if (er != 0) { perror(strerror(errno)); /* zapise chybove hlaseni do stderr */ exit(errno); /* -> OS; ukonci chod programu */ } return er;} /* int chyba(void) */

int main(void){ int handle, i; char s[81], *jmeno = "soubor.txt";

if ((handle = open(jmeno, O_TRUNC | O_CREAT | O_TEXT | O_WRONLY, S_IWRITE)) == -1) chyba(handle);

strcpy(s, "Toto je textovy soubor vytvoreny v C.\n" "primy pristup - handle\n"); write(handle, s, strlen(s)); for (i = 1; i < 10; i++) { sprintf(s, "%5d", i); write(handle, s, strlen(s)); } /* for (i = 1; i < 10; i++) */

strcpy(s, "\n\n"); write(handle, s, strlen(s));

strcpy(s, "Uz to umim!\n"); write(handle, s, strlen(s));

strcpy(s, "Jeste pridat posledni radek na konec."); write(handle, s, strlen(s));

if (close(handle) == -1) chyba(1);

return 0;} /* int main(void) */

Program obsahuje i funkci chyba(), o níž jsme se dosud nezmínili. Ta pracuje s globální proměnnou errno, a pomocí funkce perror() zobrazí před ukončením programu textový popis chyby. Výstup je směrován na standardní výstupní chybové zařízení, jímž je obvykle terminál. Co se popisu chyby týče, je jemnost jejího rozlišení dána nabídkou hodnot errno. Textové řetězce standardní knihovny jsou zpravidla jednojazyčné (anglické). I to je jeden z důvodů. proč jsme funkci chyba() vůbec vytvořili. Není přece žádný problém dát do těla této funkce (nejlépe) statické pole řetězců s chybovými hlášeními podle našich potřeb. Z úsporných důvodů nebudeme uvádět kód funkce chyba() v dalších výpisech programů, a to až do konce této kapitoly.

Text musíme být schopni do textového souboru nejen zapsat, ale následovně jej pak i zpracovat. Obsah vytvořeného textového soubor zobrazíme pomocí následujícího programu. Podívejme se opět na jeho klíčové části. Na rozdíl od

http://docs.linux.cz/c_saloun/kap08.htm (15 z 26) [23.2.2004 11:40:00]

Page 101: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

binárních souborů, obsahujících třeba jen hodnoty typu float, obsahuje textový soubor (zpravidla) nestejně dlouhé řádky textu ukončené oddělovačem - koncem řádku. Pokud otevřeme soubor jako textový, nemusí nás zajímat, jaký znak, či znaky, konec řádku tvoří. O jeho rozpoznání se postará standardní knihovna. Ovšem přesto před námi stojí problém. Je jím délka řádku. Tu zpravidla dopředu neznáme. Ale již víme, že pouhé čtení znaků do vymezené oblasti paměti, dokud nenarazíme na konec řádku, může být velmi nebezpečné. Proto budeme raději testovat skutečně přečtený počet znaků vzhledem ke stanovenému limitu. Volba limitu je často dána obvyklou délkou řádku znakového terminálu (plus malá rezerva). Často se pohybuje mezi osmdesáti až sto znaky15.

Opusťme úvahy a podívejme se na záhlaví funkce cti_radek():

int cti_radek(int hdl, char *radek, int max)

Obsahuje tři argumenty potřebné pro určení souboru, vyrovnávací paměti a její délky (limitu). Návratová hodnona vypovídá o výsledku čtení. Je popsána ve výpisu programu. Dále si povšimneme nutnosti ukončení načteného řetězce zarážkou:

s[i] = '\x0';

Nyní se podívejme na výpis zdrojového textu:

/********************************//* soubor IO-PV05.C *//* precte a vytiskne obsah *//* textoveho souboru SOUBOR.TXT *//* pouziva UNIXove funkce *//********************************/

#include <stdio.h>#include <io.h>#include <fcntl.h>#include <process.h>#include <sys\stat.h>

#define MAX_DELKA_RADKU 81 /* plus jedna */

int cti_radek(int hdl, char *radek, int max)/************************************************//* Do *radek nacte ze souboru hdl radek textu *//* dlouhy nejvice max znaku. *//* vrati: -1 pri neuspesnem cteni *//* 0 pri konci souboru *//* +n pri uspechu (pocet znaku) *//* vyjimka (prazdny radek) n == 1 *//************************************************/{ int i = 0, kod; char *s = radek, ch = 0;

while ((i < max) /* neprekrocena max. delka */ && ((kod = read(hdl, &ch, sizeof(char))) == sizeof(char)) /* ^ korektne nacteno a dosud neni konec */ && (ch != '\n')) /* neni konec radku */ { s[i] = ch; i++; }

http://docs.linux.cz/c_saloun/kap08.htm (16 z 26) [23.2.2004 11:40:00]

Page 102: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

s[i] = '\x0'; return (kod > 0) ? ((i == 0) ? kod : i) : kod;} /* int cti_radek(int hdl, char *radek, int max) */

int chyba(int er){ extern int errno; if (er != 0) { perror(strerror(errno)); exit(errno); } return(0);} /* int chyba(void) */

int main(void){ int handle, nacteno; char s[MAX_DELKA_RADKU], *jmeno = "soubor.txt";

if ((handle = open(jmeno, O_TEXT, S_IREAD)) == -1) chyba(handle);

printf("\nCteni textoveho souboru <%s> funkcemi s primym pristupem\n", jmeno); while ((nacteno = cti_radek(handle, s, sizeof(s) - 1)) > 0) { printf("%s\n", s); }

if (nacteno == 0) printf("%s\n\n...KONEC...\n", s); else chyba(nacteno);

if (close(handle) == -1) chyba(1);

return(0);} /* int main(void) */

Výstup tohoto programu na obrazovku je následující16:

Cteni textoveho souboru <soubor.txt> funkcemi s primym pristupemToto je textovy soubor vytvoreny v C.primy pristup - handle 1 2 3 4 5 6 7 8 9

Uz to umim!Jeste pridat posledni radek na konec.

...KONEC...

8.5. Datové proudy

http://docs.linux.cz/c_saloun/kap08.htm (17 z 26) [23.2.2004 11:40:00]

Page 103: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

Pro práci s datovými proudy musíme používat funkční prototypy umístěné v stdio.h. Základem pro přístup k proudu je datový typ FILE17. Při každém spuštení programu máme otevřeny proudy, které bychom mohli deklarovat takto:

FILE *stdin; FILE *stdout; FILE *stderr;

Při práci s proudy se doporučuje testovat hodnoru TMP_MAX. Toto makro je rozšířením možností společných pro obě varianty práce se soubory.

ANSI norma definuje dva režimy proudů - textový (rozlišuje řádky) a binární. Režim stanovíme při otevírání souboru. Pro další výklad bude zřejmě opět nejvhodnější, uvedeme-li si nejprve nejdůležitější funkce a konstanty pro práci s proudy, a teprve poté několik příkladů, které výčet osvětlí. Pro přehlednost uvedeme skupiny funkcí, rozdělené podle možnosti jejich použití.

Pro práci s proudy máme k dispozici značné množství funkcí, maker a globálních proměnných. Jejich plný výčet je nad rámec tohoto textu. Proto se omezíme na ty z nich, které považujeme za významné. Ostatní můžeme vyhledat v referenčních příručkách C, nebo v elektronické dokumentaci přímo u počítače.

Otevření a zavření proudu

Každý proud máme možnost otevřít a uzavřít. Při otevření určujeme režim našeho přístupu k datům v proudu. Ve speciálních případech oceníme i možnost proud znovuotevřít či spojit s novým souborem.

FILE *fopen(const char *filename, const char *mode);

Je funkce, vracející ukazatel na strukturu FILE v případě úspěšného otevření proudu. Při neúspěchu vrací hodnotu NULL. Konstantní řetězec filename, označuje jméno souboru podle konvencí příslušného OS. Řetězec mode určuje režim práce se souborem i jeho typ. Základní možnosti jsou uvedeny v tabulce:

řetězec význam (otevření pro:) r čteníw zápisa připojenír+ aktualizace (update) - jako rw w+ jako výše (r+), ale existující proud ořízne na nulovou délku, jinak vytvoří nový a+ pro aktualizaci, pokud neexistuje, vytvoří t textový težimb binární režim

Poznamenejme, že otevření proudu v binárním režimu vyžaduje vždy písmeno b v řetězci mode, například: "a+b", "wb", ... . Obdobně pro textový režim je vhodné uvést jako součást řetězce mode znak t.18

int fclose(FILE *stream);

Je funkce uzavírající určený proud. V případě úspěchu vrátí hodnotu 0, jinak EOF. Uvolní paměť vyhrazenou pro strukturu FILE * a vyprázdní případnou vyrovnávací paměť.

http://docs.linux.cz/c_saloun/kap08.htm (18 z 26) [23.2.2004 11:40:00]

Page 104: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

FILE *freopen(const char *filename, const char *mode, FILE *stream);

Funkce uzavře soubor asociovaný s proudem stream (jako při volání fclose()). Pak otevře soubor jménem filename (jako při fopen(filename, mode)). Návratovou hodnotou je v případě úspěchu otevřený proud, jinak nulový ukazatel NULL.

Proudy a vstup/výstup znaků

Znak je po načtení z proudu konvertován bez znaménka na typ int. Obdobně je při zápisu do proudu konvertován opačným postupem. Tak máme ponechánu možnost rozlišit konec souboru od dalšího načteného znaku.

Připomeňme si čtení a zápis znaku z/do standardního vstupu/výstupu. Při pohledu na následující funkce je význam maker getchar() a putchar() zcela zřejmý. Stačí prostě doplnit druhý argument odpovídající funkce hodnotou stdin či stdout.

int getc(FILE *stream);

V případě úspěšného načtení znaku z proudu jej bez znaménka převede na typ int. Takto získaná hodnota je hodnotou návratovou. V případě chyby, nebo dosažení konce proudu pro getc(), vrací EOF.

int ungetc(int c, FILE *stream);

Je-li c různé od EOF, uloží jej funkce ungetc() do datového objektu s adresou stream a případně zruší příznak konce souboru. Následným čtením z tohoto proudu získáme námi zapsanou hodnotu. Je-li c rovno EOF, nebo nemůže-li zápis proběhnout, vrací funkce EOF. Jinak vrací c (přesněji (unsigned char) c).

int putc(int c, FILE *stream);

Zapíše znak c do proudu stream. Vrátí stejnou hodnotu, jako zapsal. V případě chyby, nebo dosažení konce proudu pro getc(), vrací EOF.

Proudy a vstup/výstup řetězců

Z proudu nemusíme číst pouze jednotlivé znaky. Můžeme načítat i celé řádky19. Ty jsou ukončeny přechodem na nový řádek. Pro vyšší bezpečnost musíme při čtení uvést velikost vyrovnávací paměti. Při zápisu to pochopitelně nutné není. Do proudu se zapíše celý řetězec až po koncovou zarážku (ovšem bez ní).

char *fgets(char *s, int n, FILE *stream);

Načte řetězec (řádek až po jeho konec) z proudu stream do vyrovnávací paměti s, nejvýše dlouhý n-1 znaků. Vrátí ukazatel na tento řetězec (vyrovnávací paměť), nebo, při chybě, NULL.

int fputs(const char *s, FILE *stream);

Zapíše do proudu řetězec ukončený zarážkou. Ani zarážku, ani případný konec řádku (obsažený na konci řetězce) do proudu nezapíše. V případě úspěchu vrátí počet zapsaných znaků (délku řetězce), jinak EOF.

Formátovaný vstup/výstup z/do proudu

Jestliže jsme zvládli standardní formátovaný vstup a výstup, nemůže nám činit formátovaný vstup a výstup z a do proudu

http://docs.linux.cz/c_saloun/kap08.htm (19 z 26) [23.2.2004 11:40:00]

Page 105: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

potíže. Funkce se navzájem liší pouze úvodním f a identifikací proudu jako prvního argumentu. Právě srovnání je důvodem, proč uvádíme odpovídající funkce ve dvojici.

int fprintf (FILE *stream, const char *format [, argument, ...]);int printf ( const char *format [, argument, ...]);

int fscanf (FILE *stream, const char *format [, address, ...]);int scanf ( const char *format [, address, ...]);

Proudy a blokový přenos dat

Blokový přenos dat je nezbytný při práci s binárním proudem. Pokud srovnáme tyto funkce s odpovídajícími funkcemi pro soubory s přímým přístupem, pochopíme, jak se navzájem liší. Funkcí pro přímý přístup je poměrně málo. Představují základní operace, které při práci programátor potřebuje. A to je vše. Funkce pro práci s proudy umí všelijaké jemnůstky. Je jich díky tomu poměrně mnoho a programátor se musí umět rozhodnout, kterou z nich pro daný účel použije. Některé jejich argumenty můžeme dokonce považovat za nadbytečné. Zmiňujeme se o tom právě zde, neboť to můžeme názorně dokumentovat. Při blokovém proudovém IO musím určit, kolik položek chci přenést a jaká je velikost jedné položky. Prostý součin těchto hodnot by jistě každý zvládnul20. Počet argumentů by se o jeden snížil.

Typ size_t, použitý v následujících funkcích, je zaveden pro určení velikosti paměťových objektů a případně počtu. Dává nám rovněž najevo, že je implementačně závislý. Kdyby místo něj bylo pouze unsigned int, nemuseli bychom si hned uvědomit, že v 16-ti bitovém systému je velikost položky omezena na 65535 bajtů.

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

Přečte z proudu stream položky o velikosti size v počtu n jednotek do paměťové oblasti určené ukazatelem ptr. V případě úspěchu vrátí počet načtených položek. Jinak vrátí počet menší (pravděpodobně to bude nula). Menší návratovou hodnotu, než je zadaný počet položek, můžeme získat například při dosažení konce proudu před načtením požadovaného počtu položek. Načtené položky jsou platné.

size_t fwrite(const void *ptr, size_t size, size_t n, FILE*stream);

Tato funkce má argumenty obdobného významu, jako funkce předchozí. Pochopitelně s tím, že provádí zápis položek do proudu.

Další užitečné funkce

Protože je počet dosud neuvedených funkcí pro práci s proudy velmi značný, shrneme pod názvem Další užitečné funkce alespoň některé z nich.

int feof(FILE *stream);

Je funkce, umožňující zjistit dosažení konce proudu. Její návratová hodnota je - true (t.j. různá od nuly), nacházíme-li se na konci proudu, nebo - false (nula) - jinak.

int fflush(FILE *stream);

Má-li proud přiřazenu vyrovnávací paměť, provede její vyprázdnění (zápis) do souboru. Jinak neprovede nic. Návratová nula svědčí o úspěchu, EOF signalizuje chybu.

int fseek(FILE *stream, long offset, int whence);

http://docs.linux.cz/c_saloun/kap08.htm (20 z 26) [23.2.2004 11:40:00]

Page 106: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

Přenese aktuální pozici CP v proudu stream na stanovené místo. To je určeno posunem offset vzhledem k počátku, aktuální pozici, nebo konci souboru. Vztažný bod určuje argument whence. Předdefinované konstanty jsou stejné, jako v případě přímého přístupu - tj. lseek() (jsou to SEEK_SET, SEEK_CUR, SEEK_END). Poznamejme, že použití této funkce zruší účinek bezprostředního předešlého ungetc().

long ftell(FILE *stream);

Vrátí aktuální pozici v proudu. To je pro binární proud počet bajtů vzhledem k začátku. V případě chyby vrátí -1L a nastaví globální proměnnou errno na kladné celé číslo.

Jen zkratkovitě uvedeme ještě některé další užitečné funkce:

ferror() informuje o případné chybě při práci s proudem.

clearerr() ruší nastavení příznaku chyby a konce proudu.

perror() zobrazí řetězec chybového hlášení na standardní chybové zařízení (obvykle je to konsola).

tmpfile() otevře přechodný soubor v binárním režimu pro aktualizaci. S přechodnými soubory jsou spjaty ještě funkce tmpnam() a tempnam().

Dvojice fgetpos() a fsetpos() umožňuje uchovat (získat) pozici v proudu a pak ji (opětně) nastavit.

setbuf() a setvbuf() umožňují nastavit a případně modifikovat velikost vyrovnávací paměti pro určený proud.

Příklady práce s proudy

Následující příklad vychází ze stejného zadání, jako příklad obsažený ve zdrojovém textu io-pv04.c. Naším úkolem je vytvořit několikařádkový textový soubor. Ukážeme si, jak do něj zapíšeme řetězce i číselné hodnoty.

Nejprve musíme vytvořit datový proud

if ((soubor = fopen(jmeno, "wt")) == NULL) chyba(1);

Řetězec jmeno udává jeho jméno pod OS. Další argument funkce fopen(), řetězec "wt", určuje způsob otevření a režim práce s proudem (proud vytvoříme a otevřeme pro zápis v textovém režimu). Ošetřujeme současně chybový stav, indikovaný návratovým NULL.

Při zápisu řetězce do proudu nemáme žádné problémy. Díky existenci zarážky nemusíme ani udávat jeho délku, stačí napsat

fputs(s, soubor);

Při zápisu čísla do proudu ovšem stejnou funkci použít nemůžeme. Víme totiž, že číslo je v paměti v uloženo v binárním tvaru, zatímco proud má textový režim. S výhodou použijeme funkci pro formátovaný výstup do proudu,

fprintf(soubor, "%5d", i);

její použití je obdobné, jako při formátovaném standardním výstupu. Pouze je navíc uvedeno určení proudu, jehož se výstup týká.

Po ukončení práce nesmíme zapomenout proud uzavřít,

http://docs.linux.cz/c_saloun/kap08.htm (21 z 26) [23.2.2004 11:40:00]

Page 107: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

if (fclose(soubor) == EOF) chyba(1);

Po popisu klíčových řádků se podívejme na souvislý výpis zdrojového textu programu:

/************************************************//* soubor IO-DP01.C *//* vytvori textovy soubor SOUBOR.TXT *//* verze se streamem *//************************************************/

#include <stdio.h>#include <io.h>#include <fcntl.h>#include <process.h>#include <string.h>#include <sys\stat.h>

int chyba(int er){ extern int errno; if (er != 0) { perror(strerror(errno)); /* zapise chybove hlaseni do stderr */ exit(errno); /* -> DOS; ukonci chod programu */ } return er;} /* int chyba(void) */

int main(void){ FILE *soubor; int i; char *s, *jmeno = "soubor.txt";

if ((soubor = fopen(jmeno, "wt")) == NULL) chyba(1);

s = "Toto je textovy soubor vytvoreny v C.\n" "pristup pomoci proudu - FILE *\n"; fputs(s, soubor); for (i = 1; i < 10; i++) { fprintf(soubor, "%5d", i); } /* for (i = 1; i < 10; i++) */

fputs("\n\n", soubor);

s = "Uz to umim!\n"; fputs(s, soubor);

s = "Jeste pridat posledni radek na konec."; fputs(s, soubor);

if (fclose(soubor) == EOF) chyba(1);

http://docs.linux.cz/c_saloun/kap08.htm (22 z 26) [23.2.2004 11:40:00]

Page 108: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

return 0;} /* int main(void) */

Povšimněme si ještě zajímavé skutečnosti. Posledním příkazem fputs() zapíšeme do proudu řetězec, nikoli ovšem symbol konce řádku. Protože se jedná o poslední text, umístěný do souboru, bude užitečné si říci, že konec řádku je buď přechod na řádek nový, nebo konec souboru.

Opačnou úlohou, než je předchozí tvorba a zápis do textového souboru, může být jeho načtení a zobrazení obsahu. Otevření textového proudu pro čtení nám jistě vrásky na čele neudělá. Pro určení režimu zadáme "rt" - otevíráme existující textový soubor pro čtení. Dále je úloha zdánlivě rovněž jednoduchá. Stačí přeci v cyklu číst celé řádky a zobrazovat jejich obsah. Například takto:

while (fgets( s, sizeof(s) - 1, soubor) != NULL)

Vše bude fungovat. Jen poslední informace, zapsaná do souboru, nebude zobrazena. Naopak uzříme hlášení o chybě. Problém tkví v tom, že fgets() při posledním čtení (správně) vrátí NULL a my nezískáme závěrečný text. Naše řešení je následující:

while ((nacteno = cti_radek(soubor, s, sizeof(s) - 1)) != EOF)

I když je na první pohled zřejmé, nemůžeme vynechat popis funkce cti_radek(). Její záhlaví je díky mnemonicky pojmenovaným argumentům samopopisné.

int cti_radek(FILE *f, char *radek, int max);

Prakticky (až na pořadí) je shodné s fgets(). Vzhledem k našemu problému se musí lišit ve způsobu reakce na konec souboru a v návratové hodnotě. Nejprve návratová hodnota. Vnořené (dvojité) použití podmíněného operátoru by pro nás mělo být čitelné. Možné návratové hodnoty tedy jsou:

EOF při chybě při čtení

n - (kladné celé číslo) - počet načtených znaků řetězce

výjimka: prázdný řádek (pouze ukončení řádku) n == 1.

Kód této funkce nám ukáže, kolik toho za nás provádí standardní knihovní funkce fgets(). Celý zdrojový text řešení našeho příkladu následuje.

/*******************************//* soubor IO-DP02.C *//* precte a vytiskne obsah *//* textoveho souboru SOUBOR.TXT*//* verze pouziva stream *//* pouziva UNIXove funkce *//*******************************/

#include <stdio.h>#include <io.h>#include <fcntl.h>#include <process.h>#include <sys\stat.h>#include <string.h>

http://docs.linux.cz/c_saloun/kap08.htm (23 z 26) [23.2.2004 11:40:00]

Page 109: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

#define MAX_DELKA_RADKU 81 /* plus jedna */

int cti_radek(FILE *f, char *radek, int max)/************************************************//* Do *radek nacte ze streamu f radek textu *//* dlouhy nejvice max znaku. *//* vrati: EOF pri neuspesnem cteni *//* +n pri uspechu (pocet znaku) *//* vyjimka - prazdny radek - n == 1 *//************************************************/{ int i = 0, ch = 0; char *s = radek; while ((i < max) /* neprekrocena max. delka */ && ((ch = getc(f)) != EOF) /* ^ korektne nacteno a dosud neni konec */ && (ch != '\n')) /* neni konec radku */ { s[i] = ch; i++; }

s[i] = '\x0'; return (ch == EOF) ? ch : ((i == 0) ? 1 : i);} /* int cti_radek(int hdl, char *radek, int max) */

int main(void){ int nacteno; FILE *soubor; char s[MAX_DELKA_RADKU], *jmeno = "soubor.txt";

if ((soubor = fopen(jmeno, "rt")) == NULL) chyba(1);

printf("\nCteni textoveho souboru <%s> funkcemi pro datovy proud\n", jmeno);

while ((nacteno = cti_radek(soubor, s, sizeof(s) - 1)) != EOF) { printf("%s\n", s); *s = 0x0; }

if ((nacteno == EOF) && (strlen(s) != 0)) printf("%s\n\n...KONEC...\n", s); else chyba(nacteno);

if (fclose(soubor) == -1) chyba(1);

return 0;} /* int main(void) */

A takto vypadá výstup programu IO-DP01.C zobrazený pomocí programu IO-PV02.C:

Cteni textoveho souboru <soubor.txt> funkcemi pro datovy proud

http://docs.linux.cz/c_saloun/kap08.htm (24 z 26) [23.2.2004 11:40:01]

Page 110: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

Toto je textovy soubor vytvoreny v C.pristup pomoci proudu - FILE * 1 2 3 4 5 6 7 8 9

Uz to umim!Jeste pridat posledni radek na konec.

...KONEC...

Náměty pro další možné pokusy při práci s proudy můžeme čerpat z příkladů napsaných pro ukázky práce se soubory s přímým přístupem. I bez jejich přepsání a komentování je tato kapitola velmi rozsáhlá.

Výsvětlivky:

1 Naše programy by se měly rozumně chovat pro vstupní řádky libovolné délky. Nicméně za slušné chování se obvykle pokládá přijetí řádku do nejvýše 512 znaků, jak činí mnohé Unixové programy.2 OS nezná typ souboru. Přípony jmén souborů jsou většinou pouze doporučeními. Proto záleží jen na nás, v jakém režimu chceme k souboru přistupovat. 3 Tím máme na mysli, že výběr a vzájemné spojení funkcí je dáno autorem takového programu. Unixovské filtry si propojíme sami.4 Ve skutečnosti jsou jak getchar tak putchar makra, představující volání odpovídajících funkcí pracujících se standardním vstupním a výstupním proudem - getc(stdin) a putc(c, stdout). 5 Pokud nebereme v úvahu dvě možná kódováni ASCII a EBCDIC, neboť druhé se v MS-DOSu ani v Unixu nepoužívá.6 Máme pochopitelně na mysli libovolný pevně zvolený datový typ.7 Někdy se v této souvislosti zavádí pojmy vnější a vnitřní jméno souboru. Tím se rozumí jméno souboru na úrovni OS a jednoznačná identifikace souboru v rámci programu (procesu).8 Návod na ověření uvolňování hodnot manipulačních čísel souboru je následující. Otevíráním nových souborů se zvyšuje jejich manipulační číslo. Pokud uzavřeme některý z otevřených souborů a posléze otevřeme soubor nový, může dostat stejné manipulační číslo, jaké měl soubor uzavřený.9 Chceme-li například přečíst posledních 100 bajtů ze souboru, kde však zbývá již jen 50 bajtů, není to chyba. Funkce read() prostě vrátí hodnotu 50. Skutečně přečtený počet bajtů. Z jejího pohledu se o chybu nejedná.10 Nezapomínejme, že naše data jsou zřejmě uložena ve vyrovnávací paměti. Teprve po close() máme jistotu, že vyrovnávací paměť pro příslušný soubor je vyprázdněna. 11 Pokud například potřebujeme vytvořit soubor dlouhý 500000 bajtů, máme dvě možnosti. Buď budeme otrocky zapisovat například 1000krát 500 bajtů. Nebo po otevření souboru použijeme lseek(handle, 500000L, SEEK_SET) a je to. V prvním případě ovšem máme jistotu, co soubor obsahuje. Ve druhém případě je to pravděpodobně náhodný pozůstatek předchozích dat. Rychlost provedení je ovšem velmi rozdílná.12 V právě vytvořeném (či otevřeném) souboru se nacházíme na jeho počátku. 13 Takový přístup můžeme provést například tehdy, trvá-li výpočet hodnot příliš dlouho. Pak je výhodnější hodnoty jedenkrát vypočíst a uložit do souboru.. Dále budeme data pouze číst.14 Vždyť jistě stačí char *s a dále jen s = "Uz to umim!\n". Tedy bez nutnosti kopírování. Pouze nastavíme ukazatel s na počátek řetězce. Navíc bychom ušetřili i místo pro vyrovnávací paměť.15 Chceme-li být velmi opatrní, zvolíme jako maximální délku řádku 512 znaků. Mnoho unixovských programů má právě toto omezení.16 Nesmíme zapomenout soubor.txt nejprve předchozím spuštěním dříve uvedeného programu vytvořit. 17 Jeho definici můžeme nalézt i v kapitole Odvozené a strukturované typy dat.18 Při startu programu je jako default hodnota aktivní režim textový. Tento stav je ovšem možno změnit nastavením hodnot O_TEXT, případně O_BINARY v globální proměnné _fmode (je deklarována v stdlib.h a v fcntl.h (zde se rovněž nacházejí O_... konstanty).19 Máme-li dostatečně velikou vyrovnávací paměť.20 Tím spíše, když napíšeme pouze operátor násobení mezi operandy. Práci za nás vykoná stroj.

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce CAutor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

http://docs.linux.cz/c_saloun/kap08.htm (25 z 26) [23.2.2004 11:40:01]

Page 111: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 8.Vstup a výstup

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap08.htm (26 z 26) [23.2.2004 11:40:01]

Page 112: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

Předchozí kapitola Obsah Konec Následující kapitola

9. DYNAMICKÉ DATOVÉ STRUKTURY9.1. Dynamická alokace paměti.9.2. Seznam.9.3. Pole ukazatelů.

Dynamické datové struktury představují jakýsi protiklad ke statickým datovým strukturám. Povězme si tedy nejprve něco o nich. Porovnáním jejich vlastností lépe vyniknou přednosti těch či oněch pro jisté třídy úloh.

Statická data (SD) mají svou velikost přesně a neměnně určenou v okamžiku překladu zdrojového textu. Proto se s nimi pracuje jednoduše. Odpadá možnost práce s nepřidělenou pamětí. Potud klady. Nyní zápory. Ať náš program potřebuje dat méně či více, nedá se s tím nic dělat. Proto často raději volíme SD poněkud naddimenzovaná, aby náš program zvládl "většinu" úloh, jejichž třídu je schopen řešit. Pro příklady nemusíme chodit daleko. Napíšeme-li program pro práci s maticemi postavený na SD, musíme určit jejich dimenzi. Dimenze 3 x 3 asi nepostačí, tak raději sáhneme k dimenzi 10 x 10, nebo raději k 20 x 20. A budeme mít dobrý pocit, že naše úloha vyřeší vše. Pocit je pochopitelně mylný. Úloha si neporadí už s maticí 21 x 21. Navíc klademe na OS vždy stejné, a to většinou přemrštěné, požadavky na operační paměť. V jednoúlohovém systému to obvykle nevadí. Ve víceúlohovém, natož ještě víceuživatelském prostředí je takové plýtvání téměř neodpustitelné. SD jsou pochopitelně statická ve smyslu velikosti paměti, kterou mají přidělenu. Jejich hodnoty se během programu mohou měnit.

Dynamická data (DD) nemají velikost při překladu určenou. Při překladu jsou vytvořeny pouze proměnné vhodného typu ukazatel na, které nám za chodu slouží jako pevné body pro práci s DD. O požadovaný paměťový prostor ovšem musíme požádat OS. Ten naši žádost uspokojí, nebo nikoliv. Rozhodně však žádáme vždy jen tolik paměti, kolik se za chodu ukázalo být potřeba. Popravdě řečeno, požadujeme jí trošku víc. Ta trocha navíc je dána jednak nutnou režií, jednak našimi schopnostmi. Záleží jen na nás, jak vhodné dynamické datové struktury vybereme (nebo si oblíbíme). Pochopitelně vznikají mnohá nebezpečí, zejména při opomenutí požádání o paměť a zápisu do nepřidělených prostor.

DD mají ještě jednu přednost proti SD. Pokud požadavky na DD vystupují v na sobě nezávislých částech programu, může paměťová oblast být menší, než prostý součet těchto požadavků. Jakmile totiž končí část programu, vrátí přidělenou část DD zpět. Naopak nový úsek programu podle potřeby o paměť požádá.

DD tedy snižují paměťové nároky ze dvou důvodů. Jednak proto, že požadují tolik paměti, kolik je třeba. Jednak díky možnému vícenásobnému používání přidělěné dynamické paměti.

SD jsou umístěna v datovém segmentu. Klasický program je totiž rozdělen na datový segment a kódový segment.

Kódový segment obsahuje kód, který překladač vytvoří na základě našeho zdrojového textu. Tento kód by se za chodu programu tedy neměl měnit. Neměnnost kódového segmentu operační systémy obvykle kontrolují a zaručují. MS-DOS ovšem nikoli. Právě z toho plynou prakticky jisté havárie dokonce celého OS při chybné práci s ukazateli.

Datový segment je opět vytvořen překladačem. Jsou v něm umístěna všechna statická data programu. Tato část programu se během chodu programu mění.

Kromě kódového a datového segmentu přiděluje při spuštění (zavádění) programu OS ještě jisté prostředí. To má buď nějakou standardní velikost, nebo je tato velikost určena na základě požadavků zaváděného programu. Zde je umístěn zásobník. O něm víme, že je nezbytný při předávání argumentů funkcím, návratové hodnoty od funkcí a také pro

http://docs.linux.cz/c_saloun/kap09.htm (1 z 9) [23.2.2004 11:40:12]

Page 113: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

nalezení návratové adresy po ukončení funkce. Tato návratová adresa je na zásobník zapsána při volání funkce.

Zásobník však zpravidla neobsadí celou zbývající paměť, kterou má program (proces) k dispozici. Část přidělené paměti zůstává volná. Té se zpravidla říká hromada či halda (heap). Její velikost můžeme při tvorbě programu určit. A právě halda představuje oblast paměti, do které se umisťují DD.

9.1. Dynamická alokace paměti.Aby části programu mohly jak žádat o přidělení dynamické paměti, tak již nepotřebnou paměť vracet, musí existovat alespoň základní programová podpora. Tu si ovšem nebudeme vytvářet sami. Je definována ANSI normou jazyka a tudíž ji dostáváme spolu s překladačem.

Souhrně se programová podpora pro dynamickou alokaci paměťi nazývá podle oblasti, jíž se přidělování týká, správce haldy (heap manager). Součástí správce haldy jsou nejen potřebné funkce, ale i datové struktury. Jako uživatele nás pochopitelně zajímají především funkce správce haldy. Popišme si tedy některé z nich. Náš výběr určen zejména jejich přenositelností. Deklarace funkcí správce haldy jsou umístěny v alloc.h, případně ve stdlib.h.

void *malloc(size_t size);

představuje požadavek o přidělení souvislého bloku paměti o velikosti size. Je-li úspěšný, dostáváme ukazatel na jeho začátek, jinak NULL.

void *calloc(size_t nitems, size_t size);

jako předchozí s tím, že náš požadavek je rozložen na nitems položek, každá o size bytech. Navíc je přidělená paměť vyplněna nulami.

void free(void *block);

je naopak vrácení dříve alokované paměti, na kterou ukazuje block1.

void *realloc(void *block, size_t size);

umožňuje změnit velikost alokované paměti, na kterou ukazuje block na novou velikost určenou hodnotou size. V případě potřeby (požadavek je větší, než původní blok) je obsah původního bloku překopírován. Vrací ukazatel na nový blok.

UNIX a MS-DOS definují pro dynamickou změnu velikosti haldy dvojici funkcí. První z nich,

int brk(void *addr);

nastaví hranici haldy programu na hodnotu danou addr. Druhá v dvojice,

void *sbrk(int incr);

umožní zvýšit tuto hodnotu o incr bajtů.

MS-DOS přidává ještě funkci, která vrací počet volných bajtů na hromadě (v závislosti na paměťovém modelu vrací unsigned int nebo unsigned long):

http://docs.linux.cz/c_saloun/kap09.htm (2 z 9) [23.2.2004 11:40:12]

Page 114: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

unsigned coreleft(void);

Krátkou ukázku přidělení a navrácení paměti pro řetězec představují dvě následující funkce. Rozsáhlejší a úplné programy jsou součástí každé z následujících podkapitol.

char *newstr(char *p){ register char *t; t = malloc(strlen(p) + 1); strcpy(t, p); return t;}

void freestr(char *p){ free(p);}

První funkce vytvoří na hromadě dostatečný prostor a nakopíruje do něj předávaný řetězec. Ukazatel na tuto kopii vrací jako svou funkční hodnotu. Druhá funkce provádí činnost opačnou. Oblast určenou ukazatelem vrátí správci haldy.

Detailní vlastnosti správce haldy mohou být různé. Čemu se však obvykle nevyhneme je situace zvaná segmentace haldy. Nejsou-li úseky z haldy vráceny v opačném pořadí, než byly přidělovány (LIFO), vzniknou na hromadě střídavě úseky volné a obsazené paměti. Celková velikost volné paměti, daná součtem všech volných úseků, je pak větší než velikost největšího souvislého volného bloku. Může pak nastat situace, kdy volné paměti je sice dost, ale náš požadavek na její přidělení není uspokojen, protože není k dispozici dostatečně velká souvislá oblast.

9.2. Seznam.Je dynamická datová struktura, která mezi svými členy obsahuje kromě datové části i vazebný člen, který ukazuje na následující prvek seznamu, nebo NULL, je-li posledním prvkem. Datové minimum2, které pro správu seznamu potřebujeme, je ukazatel na jeho první prvek. Popsaný seznam se rovněž nazývá jednosměrný. Postupně totiž můžeme projít od začátku seznamu až k jeho konci, nikoliv však naopak. Existuje ještě jedna varianta seznamu, mající vazebné členy dva. Jeden ukazuje opět na následníka, druhý na předchůdce.

Ukázku použití seznamu přináší následující úloha (a program). Mějme za úkol spočítat četnost výskytu všech slov ve vstupním textu. Pro jednoduchost budeme rozlišovat malá a velká písmena. Na závěr vytiskneme tabulku, v níž bude uvedena četnost výskytu a řetězec, představující slovo. Za slova považuje program řetězce písmen a číslic. Tuto skutečnost můžeme ovšem měnit úpravou vlastností funkce odstran_neslova. Jména funkcí i proměnných v programu jsou volena s ohledem na jejich maximální popisnost. Vstupem může být soubor, je-li zadán jako 1. argument příkazového řádku. Jinak je vstupem standardní vstup.

http://docs.linux.cz/c_saloun/kap09.htm (3 z 9) [23.2.2004 11:40:12]

Page 115: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

Obrázek odpovídá datové struktuře vytvořené v programu. Hodnoty jsou pouze ilustrativní. Jediný nedynamický objekt seznamu je ukazatel prvni na začátek seznamu. Ostatní objekty jsou dynamické. V obrázku je tato skutečnost odlišena tvarem rohů obdélníčků.

Obrázek může být i návodem, jak dynamické objekty rušit. Tato činnost totiž v programu obsažena není. Rozhodně je zřejmé, že musíme uvolnit jak paměť vyčleněnou pro hodnoty typu struct_info, tak i paměť, kterou obsazují řetězce, na něž je ze struktury ukazováno.

/****************************************************************//* soubor SLOVA.C *//* provede nacteni vstupniho textu, jeho rozlozeni na slova *//* a z techto slov vytvori seznam s jejich cetnosti vyskytu. *//* Na zaver vytiskne slova a jejich cetnosti. *//****************************************************************/

#include <stdio.h>#include <string.h>#include <alloc.h>#include <ctype.h>#include <conio.h>

#define DELKA_RADKU 500#define PAGE 24

/* typedef struct info; */typedef struct struct_info { int pocet; char *slovo; struct struct_info *dalsi; } info;

void odstran_neslova(char *ptr)/* odstrani mezery, tabelatory a znaky CR, LF ze zacatku retezce */{char *pom; pom = ptr; if (strlen(ptr) == 0) return; while ((*pom != '\0') && ((*pom == 0x20) || (*pom == '\t') || (*pom == '\n') || (*pom == '\r') || (!isalnum(*pom)))) {pom++;} strcpy(ptr, pom);} /* void odstran_neslova(char *ptr) */

void vrat_slovo(char *r, char *s){ int i = 0; while ((r[i] != 0x0) && (isalnum(r[i]) || (r[i] == '_'))) {i++;} /* while */ if (i == 0) {*s = 0x0;} else {strncpy(s, r, i); s[i] = 0x0; strcpy(r, r + i);

http://docs.linux.cz/c_saloun/kap09.htm (4 z 9) [23.2.2004 11:40:12]

Page 116: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

}} /* void vrat_slovo(char *r, char *s) */

void pridej_slovo(info **prvni, char *s){ info *prvek; prvek = *prvni; while (prvek != NULL) { if (strcmp(s, prvek->slovo) == 0) {(prvek->pocet)++; return; } prvek = prvek->dalsi; } prvek = malloc(sizeof(info)); prvek->slovo = malloc(strlen(s) + 1); strcpy(prvek->slovo, s); prvek->pocet = 1; prvek->dalsi = *prvni; *prvni = prvek;} /* void pridej_slovo(info **prvni, char *s) */

void tiskni(info *prvni){ int vytisknuto = 0; while (prvni != NULL) { printf("%4d..%s\n", prvni->pocet, prvni->slovo); prvni = prvni->dalsi; vytisknuto++; if ((vytisknuto % PAGE) == 0) { printf("pro pokracovani stiskni klavesu"); getch(); printf("\n"); } }} /* void tiskni(info *prvni) */

int main(int argc, char **argv){ info *prvni = NULL; char radek[DELKA_RADKU + 1], slovo[DELKA_RADKU + 1]; FILE *f; if (argc != 1) f = fopen(argv[1], "rt"); else f = stdin; if (f == NULL) return(1); while (fgets(radek, DELKA_RADKU, f) != NULL) { odstran_neslova(radek); while (strlen(radek) != 0)

http://docs.linux.cz/c_saloun/kap09.htm (5 z 9) [23.2.2004 11:40:12]

Page 117: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

{vrat_slovo(radek, slovo); odstran_neslova(radek); pridej_slovo(&prvni, slovo); } } tiskni(prvni); return 0;} /* int main(int argc, char **argv) */

9.3. Pole ukazatelů.Velmi pohodlnou dynamickou datovou strukturou je dynamické pole ukazatelů. Princip je jednoduchý. Požádáme o paměť pro dostatečně veliké pole ukazatelů. Dále již pracujeme stejně, jako by pole bylo statické (až na ten ukazatel na pole navíc). K jednotlivým prvkům můžeme přistupovat pomocí indexu. Nesmíme zapomenout, že prvky jsou ukazatele, a že tedy musíme paměť, na kterou ukazují, také alokovat. Díky funkci realloc() máme možnost snadno měnit3 počet prvků našeho pole ukazatelů. Navíc s tím, že naše dynamická data svou adresu nemění a funkce realloc() přenese správné (stále platné) adresy do nového (většího) bloku ukazatelů. My k nim přistupujeme pomocí stejné proměnné4, indexy všech prvků zůstavají stejné, jen jich přibylo. Skvělé.

Ukázková úloha, kterou řešíme, načítá vstupní řádky textu, ukládá je na hromadu s přístupem pomocí pole ukazatelů a nakonec řádky vytiskne ve stejném pořadí, v jakém byly načteny.

Hodnoty START a PRIRUSTEK jsou úmyslně voleny malé, aby se ukázala možnost realokace pole i při zadávání vstupu z klávesnice. Pro rozsáhlejší pokusy doporučujeme přesměrovat vstup.

/************************************************//* soubor STR_ARRY.C *//* ze standardniho vstupu cte radky, alokuje *//* pro ne pamet a ukazatele na tuto kopii *//* nacteneho radku uklada do pole ukazatelu *//* pole ukazatelu ma pocatecni velikost, ktera *//* se ovsem v pripade potreby zmeni - realloc() *//* po ukonceni vstupu nactene radky vytiskne *//************************************************/

/********************************//* obvykle navratove hodnoty: *//* O.K. 0 *//* error !0 (casto -1)*//********************************/

#include <stdio.h>#include <alloc.h>#include <string.h>

#define LADENI

#define START 2#define PRIRUSTEK 1#define DELKA_RADKU 100

typedef char * retezec;typedef retezec * pole_retezcu;

http://docs.linux.cz/c_saloun/kap09.htm (6 z 9) [23.2.2004 11:40:12]

Page 118: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

#if defined(LADENI) void volno(void) { printf("\nje volnych %10lu bajtu\n", coreleft()); /* LARGE */ } /* void volno(void) */

void uvolni(pole_retezcu *p_r, int pocet) { int i; for (i = 0; i < pocet; i++) { free((*p_r)[i]); } free(*p_r); } /* void uvolni(pole_retezcu *p_r, int pocet) */#endif /* defined(LADENI) */

int alokace(pole_retezcu *p_r, int pocet){ *p_r = malloc(pocet * sizeof(retezec)); return (*p_r == NULL) ? -1 : 0;} /* int alokace(pole_retezcu *p_r, int pocet) */

int re_alokace(pole_retezcu *p_r, int novy_pocet){ pole_retezcu pom; if (*p_r == NULL) if (alokace(p_r, novy_pocet)) return -1; /* chyba */ pom = realloc(*p_r, sizeof(retezec) * novy_pocet); if (pom == NULL) return -1; else { *p_r = pom; return 0; }} /* int re_alokace(pole_retezcu *p_r, int novy_pocet) */

int pridej_radek(pole_retezcu *p_r, retezec s, int index){ int delka = strlen(s) + 1; if (((*p_r)[index] = malloc(delka)) == NULL) return -1; strcpy((*p_r)[index], s); return 0;} /* int pridej_radek(pole_retezcu *p_r, retezec s, int index) */

int cti_a_pridavej(pole_retezcu *p_r, int *pocet, int *alokovano, int prir){ char radek[DELKA_RADKU], *pom; puts("Zadavej retezce, posledni CTRL-Z na novem radku"); do {

http://docs.linux.cz/c_saloun/kap09.htm (7 z 9) [23.2.2004 11:40:12]

Page 119: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

if ((pom = gets(radek)) != NULL) { if (*pocet + 1 > *alokovano) { if (re_alokace(p_r, *alokovano + prir)) { puts("nedostatek pameti"); return -1; } *alokovano += prir; } if (pridej_radek(p_r, radek, *pocet)) { puts("nedostatek pameti"); return 1; } (*pocet)++; } } while (pom != NULL); return 0;} /*int cti_a_pridavej(pole_retezcu *p_r, int *pocet, int *alokovano, int prir)*/

void zobrazuj(pole_retezcu p_r, int pocet) { while (pocet--) { puts(*p_r++); }} /* void zobrazuj(pole_retezcu p_r, int pocet) */

int main(void){ int pocet = 0, alokovano = START, prirustek = PRIRUSTEK; pole_retezcu p_ret = NULL;

#if defined(LADENI) volno();#endif /* defined(LADENI) */ if (alokace(&p_ret, alokovano)) { puts("nedostatek pameti"); return 1; } if (cti_a_pridavej(&p_ret, &pocet, &alokovano, prirustek)) return 1; zobrazuj(p_ret, pocet);

#if defined(LADENI) uvolni(&p_ret, pocet); volno();#endif /* defined(LADENI) */

return 0;} /* int main(void) */

http://docs.linux.cz/c_saloun/kap09.htm (8 z 9) [23.2.2004 11:40:12]

Page 120: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 9.DYNAMICKÉ DATOVÉ STRUKTURY

V programu je funkce volno() definována v závislosti na skutečnosti, je-li definováno makro LADENI. Jde tedy o podmíněný překlad. Proč jsme jej zaváděli je nasnadě. Ve fázi ladění potřebujeme mít jistotu, že paměť, kterou jsme alokovali, později správně vracíme. Jakmile je program odladěn, není tato pomocná funkce potřeba. Nemusíme ale vnášet chyby tím, že ji i její volání bezhlavě smažeme. Výhodnější je, promyslet si takovou situaci již při návrhu programu. Pak ji, stejně jako v příkladu, budeme překládat podmíněně.

Poznamenejme, že obě popsané dynamické datové struktury lze modifikovat přidáním dalších funkcí na zásobník, frontu, ... .

Pro podrobnější popis dynamických datových struktur doporučujeme skvělé dílo profesora Niclause Wirtha Algoritmy a struktury údajů.

Výsvětlivky:

1 Ze skutečnosti, že k uvolnění alokované oblasti paměti stačí ukazatel na tuto oblast, můžeme odhadovat i některé skryté vlastnosti správce haldy. Potřebné informace o velikosti alokovaného bloku musí mít správce interně k dispozici. I to je součástí celkové režie použití DD.2 Dále potřebujeme podpůrné funkce.3 V popisu jsme předpokládali, že naše paměťové nároky stoupají. Pokud je tomu naopak, je naše činnost obdobná. Jen se počet použitelných indexů snižuje.4 Pokud si ještě vzpomeneme na funkci qsort(), tak její použití pro uspořádání pole ukazatelů se přímo nabízí. Předchozí dynamická datová struktura (seznam) by pro uspořádání vyžadovala zcela jiné techniky.

Předchozí kapitola Obsah Začátek Následující kapitola

Název: Programování v jazyce C

Autor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap09.htm (9 z 9) [23.2.2004 11:40:12]

Page 121: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

Předchozí kapitola Obsah Konec Příloha A

10. Odvozené a strukturované typy dat10.1. Uživatelský datový typ.

Složitější typové deklarace

10.2. Výčtový typ.10.3. Typ struktura.10.4. Typ union.10.5. Bitová pole.10.6. Klasifikace typů v C

Dosud jsme se seznámili jen se základními datovými typy. To jest takovými typy, které vyhovují pro jednoduché výpočty či (programové) zpracování textu. Tyto typy máme v C přímo k dispozici (jsou součástí normy jazyka). Také známe preprocesor a víme, že použitím symbolických konstant program zpřehledníme. V této kapitole si ukážeme tvorbu a použití takových strukturovaných datových typů, jaké nám přináší život. Také si ukážeme definici výčtových typů, která umožní hodnoty nejen pojmenovat, ale provádět při překladu i jejich typovou kontrolu. Na úvod kapitoly si necháváme popis definice vlastních datových typů s prakticky libovolnou strukturou.

10.1. Uživatelský datový typ.Vyšší programovací jazyk má k dispozici takové základní datové typy, které pokryjí většinu potřeb. S jejich pomocí můžeme vytvářet rozsáhlejší homogenní datové struktury, jako například pole, či heterogenní, jako strukturu či unii (struct a union). Programátor ovšem musí mít k dispozici mechanismus, kterým si vytvoří datový typ podle svých potřeb. Tento mechanismus se nazývá typedef a jeho syntaxe je na první pohled velmi jednoduchá:

typedef <type definition> <identifier> ;

Po klíčovém slově typedef následuje definice typu type definition. Poté je novému typu určen identifikátor identifier.

Skalní zastánce nějakého datového typu či jazyka si spojením maker a uživatelských typů může C přetvořit k obrazu svému:

http://docs.linux.cz/c_saloun/kap10.htm (1 z 12) [23.2.2004 11:40:24]

Page 122: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

/**************//* TYPEDEF0.C *//**************/

#include <stdio.h>

int main(){ typedef float real; real x = 2.5, y = 2.0;

printf("%5.1f * %5.1f = %5.1f\n", x, y, x * y);

return 0;}

Získá tím ovšem jistotu, že jeho programy bude číst pouze on sám.

Složitější typové deklarace

Jestliže se uvedené jednoduché typové konstrukce1 prakticky nepoužívají, podívejme se naopak na některé konstrukce složitější. Nejprve si shrňme definice, které bychom měli zvládnout poměrně snadno:

deklarace typ identifikátoru jméno typ jméno; typtyp jméno[]; (otevřené) pole typu typ jméno[3]; pole (pevné velikosti) tří položek typu (jméno[0], jméno[1],jméno[2]) typ *jméno; ukazatel na typ typ *jméno[]; (otevřené) pole ukazatelů na typ typ *(jméno[]); (otevřené) pole ukazatelů na typ typ (*jméno)[]; ukazatel na (otevřené) pole typu typ jméno(); funkce vracející hodnotu typu typ *jméno(); funkce vracející ukazatel na hodnotu typu typ *(jméno()); funkce vracející ukazatel na hodnotu typu

typ (*jméno)(); ukazatel na funkci, vracející typ2

Může se stát, že si u některých definic přestáváme být jisti. Uvedeme si proto zásady, podle nichž musíme pro správnou interpretaci definice postupovat. Obecně se držíme postupu zevnitř ven. Podrobněji lze zásady shrnout do čtyř kroků:

http://docs.linux.cz/c_saloun/kap10.htm (2 z 12) [23.2.2004 11:40:24]

Page 123: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

1. Začněme u identifikátoru a hledejme vpravo kulaté nebo hranaté zívorky (jsou-li nějaké). 2. Interpretujme tyto závorky a hledejme vlevo hvězdičku. 3. Pokud narazíme na pravou závorku (libovolného stupně vnoření), vraťme se a aplikujme

pravidla 1 a 2 pro vše mezi závorkami. 4. Aplikujme specifikaci typu.

Celý postup si ukážeme na příkladu:

char *( *( *var) ()) [10];7 6 4 2 1 3 5

Označené kroky nám říkají:

1. Identifikátor var je deklarován jako 2. ukazatel na 3. funkci vracející 4. ukazatel na 5. pole 10-ti prvků, které jsou 6. ukazateli na 7. hodnoty typu char.

Raději další příklad. Tentokrát již popíšeme jen výsledek. Jednotlivé kroky nebudeme značit:

unsigned int *( * const *name[5][10]) (void);

Identifikátor name je dvourozměrným polem o celkem 50-ti prvcích. Prvky tohoto pole jsou ukazateli na ukazatele, které jsou konstantní. Tyto konstantní ukazatele ukazují na typ funkce, která nemá argumenty a vrací ukazatel na hodnotu typu unsigned int.

Následující funkce vrací ukazatel na pole tří hodnot typu double:

double ( *var (double (*)[3])) [3];

Její argument, stejně jako návratová hodnota, je ukazatel na pole tří prvků typu double.

Argument předchozí funkce je konstrukce, která se nazývá abstraktní deklarace. Obecně se jedná o deklaraci bez identifikátoru. Deklarace obsahuje jeden či více ukazatelů, polí nebo modifikací funkcí. Pro zjednodušení a zpřehlednění abstraktních deklarací se používá konstrukce typedef.

Abstraktní deklarace my nás neměly zaskočit ani v případě, kdy typedef použito není. Raději si několik abstraktních deklarací uvedeme:

int * ukazatel na typ int

http://docs.linux.cz/c_saloun/kap10.htm (3 z 12) [23.2.2004 11:40:24]

Page 124: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

int *[3] pole tří ukazatelů na int int (*)[5] ukazatel na pole pěti prvků typu int int *() funkce bez specifikace argumentů vracející ukazatel na int int (*) (void) ukazatel na funkci nemající argumenty vracející int

int (*const []) (unsigned int, ..)ukazatel na nespecifikovaný počet konstantních ukazatelů na funkce, z nichž každá má první argument unsigned int a nespecifikovaný počet dalších argumentů

10.2. Výčtový typ.Výčtový typ nám umožňuje definovet konstanty výčtového typu. To je výhodné například v okamžiku, kdy přiřadíme hodnotě výčtového typu identifikátor. Pak se ve zdrojovém textu nesetkáme například s hodnotou 13, či dokonce 0x0d, ale například CR, či Enter. Takový text je mnohem čitelnější. Jestliže později zjistíme, že je třeba hodnotu změnit, nemusíme v textu vyhledávat řetězec 13, který se navíc může vyskytovat i jako podřetězec řady jiných řetězců, ale na jediném místě změníme hodnotu výčtové konstanty. Že se jedná o klasické konstanty (či dokonce konstantní makra), které jsme poznali prakticky na začátku textu? Téměř to tak vypadá, ale výčtové konstanty mohou mít navíc pojmenován typ, který reprezentuje všechny jeho výčtové hodnoty. I tím se zvýší přehlednost3.

Podívejme se nejprve na příklad. Naším úkolem je zpracovat stisknuté klávesy na standardní 101 tlačítkové klávesnici PC-AT. Pokud bychom do jednotlivých větví umístili pro porovnávání celočíselné konstanty, zřejme bychom sami brzy ztratili přehled. Použijeme-li výčtové konstanty, je situace zcela jiná. Ostatně podívejme:

/************************************************//* soubor enum_use.c *//* definice a naznak pouziti vyctovych konstant *//* pro jednoduchy editor *//************************************************/

typedef enum { Back = 8, Tab = 9, Esc = 27, Enter = 13, Down = 0x0150, Left = 0x014b, Right = 0x014d, Up = 0x0148, NUL = 0x0103, Shift_Tab = 0x010f, Del = 0x0153, End = 0x014f, Home = 0x0147, Ins = 0x0152, PgDn = 0x0151, PgUp = 0x0149} key_t; ... int znak; ... else if ((znak == Left) || (znak == Back)) ... else if (znak == Enter)

http://docs.linux.cz/c_saloun/kap10.htm (4 z 12) [23.2.2004 11:40:25]

Page 125: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

... else if (znak == Esc) ... else if ... ...

Je zřejmé, že výpis zdrojového textu je krácen. Jde nám o ukázku. Přesto, že je zřejmě průhledná, podívejme se na syntaxi definice výčtového typu:

enum [<type_tag>] {<constant_name> [= <value>], ...} [var_list];

Klíčové slovo enum definici uvádí. Nepovinné označení type_tag umožňuje pojmenování hodnot výčtového typu bez použití konstrukce typedef. Poté následuje seznam výčtových konstant ve složených závorkách. Na závěr definice můžeme (nepovinný parametr) přímo uvést proměnné4, které mohou definovaných výčtových hodnot nabývat. Vraťme se ještě k obsahu bloku. Seznam identifikátorů je důležitý i pořadím jejich definice. Pokud nepoužijeme nepovinnou konstrukci = <value>, je první výčtové konstantě přiřazena hodnota nula. Následník pak má hodnotu o jedničku vyšší, než předchůdce. Jak jsme si ovšem ukázali v příkladu, můžeme přiřadit i první konstantě hodnotu jinou, než nulovou, rovněž může mít následník hodnotu nesouvisející s předchůdcem. Tak mohou vzniknout "díry" v číslování, případně i synonyma. Díky této možnosti (příklad nás jistě přesvědčil, že je užitečná), nemůže překladač kontrolovat, zdali nabývá proměnná hodnoty korektní či nikoliv. To je přijatelná cena, kterou platíme za popsané možnosti.

Poznamenejme, že hodnoty výčtových typů nelze posílat na výstup ve tvaru, v jakém jsme je definovali. Můžeme je zobrazit pouze jako odpovídající celočíselné ekvivalenty. Obdobně je můžeme číst ze vstupu. Výčtové konstanty se tedy ve své textové podobě nacházejí pouze ve zdrojovém tvaru programu. Přeložený program pracuje již jen číselnými hodnotami výčtových konstant.

Vrátíme-li se k příkladu, povšimneme si skutečnosti, že nepoužíváme type_tag. Tato možnost byla nutná ještě před zavedením konstrukce typedef. Dnes je obvyklejší pracovat naznačeným stylem. Přinejmenším pokaždé při deklaraci argumentů ušetříme ono klíčové slovo enum.

10.3. Typ struktura.Dosud jsme v C obvykle vystačili se základními datovými typy. Realita, kterou se ve svých programech často neuměle pokoušíme popsat, zřejmě tuto jednoduchost postrádá. Nezřídka se setkáváme se skutečnostmi, k jejichž popisu potřebujeme více souvisejících údajů. Programátor navíc dodá, že různého typu. Užitečnou možností je konstrukce, která takovou konstrukci dovolí a pro její snadné další použití i pojmenuje. Směřujeme k definici struktury. Její korektní syntaktický předpis je následující:

struct [<struct type name>] { [<type> <variable-name[, variable-name, ...]>] ; [<type> <variable-name[, variable-name, ...]>] ;

http://docs.linux.cz/c_saloun/kap10.htm (5 z 12) [23.2.2004 11:40:25]

Page 126: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

... } [<structure variables>] ;

Konstrukci uvádí klíčové slovo struct. Následuje nepovinné pojmenování struct type name, které jako v případě výčtového typu obvykle nepoužíváme. Zůstalo zachováno spíše kvůli starší K&R definici C. Následuje blok definic položek struktury. Po něm opět můžeme definovat proměnné nově definovaného typu. Položky jsou odděleny středníkem. Jsou popsány identifikátorem typu type, následovaným jedním, nebo více identifikátory prvků struktury variable­name. Ty jsou navzájem odděleny čárkami.

Pro přístup k prvkům struktury používáme selektor struktury (záznamu) . (je jím tečka). Tu umístíme mezi identifikátory proměnné typu struktura a identifikátor položky, s níž chceme pracovat. V případě, kdy máme ukazatel na strukturu, použijeme místo hvězdičky a nezbytných5 závorek raději operátor ->.

Podívejme se na příklad. Definujeme v něm nové typy complex a vyrobek. S použitím druhého z nich definujeme další typ zbozi. Typ zbozi představuje pole mající POLOZEK_ZBOZI prvků, každý z nich je typu vyrobek. Typ vyrobek je struktura, sdružující položky ev_cislo typu int, nazev typu znakové pole délky ZNAKU_NAZEV+16. Teprve takové definice nových typů, někdy se jim říká uživatelské, používáme při deklaraci proměnných.

typedef struct {float re, im;} complex;typedef struct { int ev_cislo; char nazev[ZNAKU_NAZEV + 1]; int na_sklade; float cena; } vyrobek;typedef vyrobek zbozi[POLOZEK_ZBOZI];

Syntaxe struct sice nabízí snadnější definice proměnných použitých v programu, otázkou zní, jak čitelné by pak bylo například deklarování argumentu nějaké funkce jako ukazatel na typ zboží. Jinak řečeno, Konstrukci struktury pomocí typedef oceníme spíše u rozsáhlejších zdrojových textů. U jednoúčelových krátkých programů se obvykle na eleganci příliš nehledí.

Dále se podívejme na přiřazení hodnoty strukturované proměnné při její definici.

vyrobek *ppolozky, a = {8765, "nazev zbozi na sklade", 100, 123.99};

Konstrukce značně připomíná obdobnou inicializaci pole. Zde jsou navíc jednotlivé prvky různých typů.

http://docs.linux.cz/c_saloun/kap10.htm (6 z 12) [23.2.2004 11:40:25]

Page 127: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

Následují ukázky přiřazení hodnot prvkům struktury. Nejzajímavější je srovnání přístupu do struktury přes ukazatel. Čitelnost zavedení odlišného operátoru v tomto případě je zřejmá. Můžeme porovnat s druhou variantou uvedenou jako komentář:

ppolozky->ev_cislo = 1;/* (*ppolozky).ev_cislo = 1; */

Nyní se podívejme na souvislý zdrojový text.

/************************//* soubor STRUCT01.C *//* ukazka struct *//************************/

#include <stdio.h>#include <string.h>

#define ZNAKU_NAZEV 25#define POLOZEK_ZBOZI 10#define FORMAT_VYROBEK "cislo:%5d pocet:%5d cena:%10.2f nazev:%s\n"

typedef struct {float re, im;} complex;

typedef struct { int ev_cislo; char nazev[ZNAKU_NAZEV + 1]; int na_sklade; float cena; } vyrobek;typedef vyrobek zbozi[POLOZEK_ZBOZI];

int main(void){ complex cislo, im_jednotka = {0, 1}; zbozi polozky; vyrobek *ppolozky, a = {8765, "nazev zbozi na sklade", 100, 123.99};

cislo.re = 12.3456; cislo.im = -987.654;

polozky[0].ev_cislo = 0; strcpy(polozky[0].nazev, "polozka cislo 0");

http://docs.linux.cz/c_saloun/kap10.htm (7 z 12) [23.2.2004 11:40:25]

Page 128: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

polozky[0].na_sklade = 20; polozky[0].cena = 45.15;

ppolozky = polozky + 1; ppolozky->ev_cislo = 1;/* (*ppolozky).ev_cislo = 1; */ strcpy(ppolozky->nazev, "polozka cislo 1"); ppolozky->na_sklade = 123; ppolozky->cena = 9945.15;

printf("re = %10.5f im = %10.5f\n", im_jednotka.re, im_jednotka.im); printf("re = %10.5f im = %10.5f\n", cislo.re, cislo.im); printf(FORMAT_VYROBEK, a.ev_cislo, a.na_sklade, a.cena, a.nazev); printf(FORMAT_VYROBEK, polozky[0].ev_cislo, polozky[0].na_sklade, polozky[0].cena, polozky[0].nazev); printf(FORMAT_VYROBEK, ppolozky->ev_cislo, ppolozky->na_sklade, ppolozky->cena, ppolozky->nazev); return 0;}

Tento výstup získáme spuštěním programu.

re = 0.00000 im = 1.00000re = 12.34560 im = -987.65399cislo: 8765 pocet: 100 cena: 123.99 nazev:nazev zbozi na skladecislo: 0 pocet: 20 cena: 45.15 nazev:polozka cislo 0cislo: 1 pocet: 123 cena: 9945.15 nazev:polozka cislo 1

O užitečnosti struktur nás dále přesvědčí detailní pohled na typ, který jsme dosud používali, aniž bychom si jej blíže popsali. Je to typ FILE. Jeho definice v hlavičkovém souboru STDIO.H je:

typedef struct { short level; unsigned flags; char fd; unsigned char hold; short bsize; unsigned char *buffer, *curp; unsigned istemp; short token;} FILE;

Pokud se rozpomeneme na vše, co jsme se dosud o proudech dozvěděli, naznačí nám některé identifikátory, k jakému účelu jsou nezbytné. Výhoda definice FILE spočívá mimo jiné i v tom, že jsme tento datový typ běžně používali, aniž bychom měli ponětí o jeho definici. O implementaci souvisejících funkcí, majících FILE * jako jeden ze svých argumentů či jako návratový typ, ani

http://docs.linux.cz/c_saloun/kap10.htm (8 z 12) [23.2.2004 11:40:25]

Page 129: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

nemluvě.

Pro základní použití struktur již máme dostatečné informace. Intuitivně jsme schopni odhadnout, jak přistupovat k prvku struktury, který je rovněž strukturou (prostě umístíme mezi identifikátory prvků další tečku).

Problém nastane v okamžiku, kdy potřebujeme definovat dvě struktury, které spolu navzájem souvisí. Přesněji řečeno, jedna obsahuje prvek typu té druhé. A naopak. Pravdou sice, je, že se nejedná o častou situaci, nicméně se můžeme podívat na použití neúplné deklarace. Nebudeme si vymýšlet nějaké příliš smysluplné struktury. Princip je následující:

struct A; /* incomplete */struct B {struct A *pa};struct A {struct B *pb};

Vidíme, že u neúplné deklarace určíme identifikátoru A třídu struct. V těle struktury B se ovšem může vyskytovat pouze ukazatel na takto neúplně deklarovanou strukturu A. Její velikost totiž ještě není známa7.

10.4. Typ union.Syntakticky vypadá konstrukce union následovně:

union [<union type name>] { <type> <variable names> ; ...} [<union variables>] ;

Již na první pohled je velmi podobná strukturám. S jedním podstatným rozdílem, který není zřejmý ze syntaxe, ale je dán sémantikou. Z položek unie lze používat v jednom okamžiku pouze jednu. Ostatní mají nedefinovanou hodnotu. Realizace je jednoduchá. Paměťové místo, vyhrazené pro unii je tak veliké, aby obsáhlo jedinou (paměťově největší) položku. Tím je zajištěno splnění vlastnosí unie. Překladač C ponechává na programátorovi, pracuje-li s prvkem unie, který je určen správně8 či nikoliv. Ostatně, v okamžiku překladu nejsou potřebné údaje stejně k dispozici.

Každý z prvků unie začíná na jejím začátku. Můžeme si představit, že paměťově delší prvky překrývají ty kratší. Této skutečnosti můžeme někdy využít. Nevíme-li, jakého typu bude návratový argument, definujeme unii, mající položky všech požadovaných typů. Dalším argumentem předáme informaci o skutečném typu hodnoty. Pak podle ní provedeme přístup k správnému členu unie9.

10.5. Bitová pole.K současným trendům programování patří i oprostění se od nutnosti šetřit každým bajtem, v

http://docs.linux.cz/c_saloun/kap10.htm (9 z 12) [23.2.2004 11:40:25]

Page 130: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

extrémních případech až bitem, paměti. Plýtvání zdroji (pamětí, diskovou kapacitou, komunikací) je stále častěji skutečností. Přesto jsou i dnes oblasti, v nichž je úsporné uložení dat ne-li nezbytné, tedy alespoň vhodné. Ano, jedná se mimo jiné o operační systémy. Jednou z možností, jak úsporně využít paměť jsou právě bitová pole.

Bitové pole je celé číslo, umístěné na určeném počtu bitů. Tyto bity tvoří souvislou oblast paměti. Bitové pole může obsahovat více celočíselných položek. Můžeme vytvořit bitové pole tří tříd:

1. prosté bitové pole 2. bitové pole se znaménkem 3. bitové pole bez znaménka

Bitová pole můžeme deklarovat pouze jako členy struktury či unie. Výraz, který napíšeme za identifikátorem položky a dvoutečkou, představuje velikost pole v bitech. Nemůžeme definovat přenositelné bitové pole, které je rozsáhlejší, než typ int.

Způsob umístění jednotlivých položek deklarace do celočíselného typu je implementačně závislý.

Podívejme se nyní na příklad bitového pole. Jak můžeme z předchozího textu usuzovat, je spjat s konkrétním operačním systémem. V hlavičkovém souboru IO.H překladače BC3.1 je definována struktura ftime, která popisuje datum a čas vzniku (poslední modifikace) souboru v OS MS-DOS (řekněme včetně verze 6):

struct ftime { unsigned ft_tsec : 5; /* Two seconds */ unsigned ft_min : 6; /* Minutes */ unsigned ft_hour : 5; /* Hours */ unsigned ft_day : 5; /* Days */ unsigned ft_month : 4; /* Months */ unsigned ft_year : 7; /* Year - 1980 */ };

Prvky struktury jsou bitová pole. Výsledkem je výborné využití 32 bitů. Jediným omezením je skutečnost, že sekundy jsou uloženy v pěti bitech a pro rok zůstává bitů sedm. Jinak řečeno, pro sekundy můžeme použít 32 hodnot. Proto jsou uloženy zaokrouhleny na násobek dvou. Rok je uložen jako hodnota, kterou musíme přičíst k počátku, roku 1980. Umístění jednotlivých položek v bitovém poli ukazuje tabulka (z důvodů umístění na stránce je rozdělena do dvou částí):

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 ft_hour ft_min ft_sechodiny minuty sekundy/2

http://docs.linux.cz/c_saloun/kap10.htm (10 z 12) [23.2.2004 11:40:25]

Page 131: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 ft_year ft_month ft_dayrok - 1980 měsíc den

Jelikož se snažíme nepoužívat systémově závislé funkce, nebudeme si uvádět příklad použití bitových polí. Výše uvedená ukázka nám postačí.

10.6. Klasifikace typů v CV této kapitole jsme dokončili výklad typů, které máme v C k dispozici. Proto jsme zahrnuli obrázek, který typy v C klasifikuje. Pro větší výrazové schopnosti angličtiny, umožňující popsat na nenší ploše potřebné skutečnosti, jsme v tomto obrázku nepoužili české termíny.

http://docs.linux.cz/c_saloun/kap10.htm (11 z 12) [23.2.2004 11:40:25]

Page 132: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - 10.Odvozené a strukturované typy dat

Výsvětlivky:

1 Prakticky se jedná o přejmenování.2 Při definicích (*jméno)[] a (*jméno)() jsou závorky nezbytné, v případě *(jméno[]) jsou nadbytečné - zvyšují pouze čitelnost.3 A tedy i bezpečnost. Přehledný program neskrývá myšlenku použitím fint a nejasných konstrukcí. Někdy snad není tak efektivní (i když i o tom lze pochybovat, přirozeně musíme brát případ od případu), ale zřejmě bude obsahovat méně chyb. Pokud tam přece jen nějaké budou (Murphy ...), pravděpodobně je dříve odhalíme.4 Jedná se tedy o definici proměnných, neboť jim nejen deklarujeme typ, ale přidělujeme jim i paměťové místo.5 Nevíme-li proč, je na čase nalistovat tabulku s prioritou a asociativitou operátorů.6 Tedy řetězec délky ZNAKU_NAZEV. Znak, který je v poli navíc, je zarážkou řetězce.7 Velikost nutná pro uložení ukazatele ovšem známa je. Proto je taková konstrukce možná.8 Přesněji, jehož hodnota je definována.9 Méně čitelné řešení předá vždy argument nejdelšího typu a poté jej podle potřeby přetypuje.

Předchozí kapitola Obsah Začátek Příloha A

Název: Programování v jazyce CAutor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 03.12.1996

http://docs.linux.cz/c_saloun/kap10.htm (12 z 12) [23.2.2004 11:40:25]

Page 133: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

Předchozí kapitola Obsah Konec Příloha B

Příloha A: Tvorba projektů

Prošli jsme výukou jazyka C až do konce. Přesto, že jsme jazyk zvládli, stále nám chybí něco, co ovšem není přímou součástí jazyka. Schopnost vytvářet a spravovat rozsáhlejší projekty1.

V čem je vlastně problém? C již známe. Celý projekt tedy můžeme napsat do jednoho souboru. A je to. Ano, tento přístup je jistě možný. Nejspíš ovšem jen teoreticky. Představme si program dlouhý deset, dvacet, nebo sto tisíc řádků. Naše orientace v tak dlouhém zdrojovém textu by byla velmi nesnadná. Navíc je málo provděpodobné, že by tak dlouhý kód vytvářel jediný člověk2. Pokud jich bude více, vznikl by problém se sdílením takového souboru. Teď vezměme v úvahu i specializaci. Nejen programátorů, ale i zdrojového kódu.

Podobné úvahy bychom mohli dále rozvíjet. Naštěstí nezačínáme na zelené louce. Programy v C psaly zástupy programátorů před námi. Teoretici posouvali hranici poznání. V C můžeme úspěšně pracovat modulárním způsobem. Projekt rozdělíme vhodným3 způsobem na menší části. Inspirací nám mohou být hlavičkové soubory.

Máme-li projekt rozdělen, musíme být schopni definovat závislosti jednotlivých jeho částí. Co se stane, když opravíme v jednom ze souborů hlavičku funkce. Musíme znovu přeložit celý projekt, nebo stačí přeložit jen jeho část. Povšimněme si, jakou výhodu nám rozdělení projektu může přinést. Nemusíme vždy překládat všechny části projektu. Stačí přeložit jen ty změněné, dále pak jen soubory, které změna ovlivní.

Pro usnadnění správy projektů se používá program make. Program je řízen textovým souborem, definujícím vzájemné závislosti souborů projektu, konfiguraci překladače, informace o adresářích. Je zřejmé, že možnosti jazyka, popisujícího zmíněné závislosti musí být poměrně značné. Myšlenka sama zcela zapadá do konstrukce Unixu (v něm C vzniklo). Nepsat jeden rozsáhlý komplexní program, ale více malých. make prostě čte řídící informace a podle stavu, v němž se právě nachází, vyvolává pro vykonání potřebných akcí odpovídající programy. Informace jim předává jako argumenty. Výhodou je značná pružnost. A rovněž jistá nezávislost na použitém překladači. Teoreticky i na operačním systému.

Projekt umožňuje nezávislý překlad svých částí. Nakonec tyto přeložené části spojíme ve výsledný spustitelný tvar programu. Háček je v tom, že části sice můžeme překládat nezávisle, ovšem musíme být schopni předávat z nich informace, potřebné při překladu v částech jiných. Definujme například ... . Ale raději opusťme teoretické úvahy. Vždyť metologii návrhu a analýzy je věnováno mnoho rozsáhlých publikací. Nebudeme se snažit jejich obsah shrnout v části malého dodatku skripta. Ukažme si raději základní myšlenky na příkladu.

Projekt: Naším úkolem je umožnit převod vnitřního tvaru informací (výčtové hodnoty) na vnější

http://docs.linux.cz/c_saloun/kap0a.htm (1 z 8) [23.2.2004 11:40:33]

Page 134: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

(řetězce). Dále je naším úkolem odladit některé matematické funkce a umístit je odděleně. Tím umožníme jejich nezávislé použití4.

Po obecném zadání poznamenejme, že v praxi pochopitelně víme, co se od nás očekává. Zvolme si proto konkrétní funkce.

1. Převod výčtového typu, určujícího den v týdnu na řetězec. Řekněme, že si ukážeme vícejazyčnou variantu.

2. Pro celočíselný argument funkce pro výpočet faktoriálu a hodnoty členu Fibonacciho posloupnosti.

Náš projekt je evidentně ryze výukový. Je na první pohled zřejmé rozdělení funkcí do souborů. Funkci, související s prvním bodem, umístíme do jednoho souboru, funkce pokrývající bod druhý do druhého souboru. Třetí soubor bude obsahovat funkci main(). Z něj budeme celý program řídit.

Protože ANSI C vyžaduje deklarace pro všechny použité funkce, vytvoříme k prvnímu i druhému souboru deklarace těch funkcí, které budeme chtít používat vně těchto souborů5. Schéma našeho projektu vidíme na obrázku.

Na obrázku vidíme tři základní soubory funkce.c, prj_demo.c a kalendar.c a hlavičkové soubory kalendar.h a funkce.h. Ty deklarují nejen funkce, ale v případě kalendar.h definují i nové datové typy (viz výpis zdrojového textu dále). Proto je začleňujeme jak do prj_demo.c, ale i do "jejich" zdrojových souborů. Ve zdrojových souborech jsou jen definice funkcí. Případně definice těch objektů (typů, proměnných, funkcí, ... ), které nechceme dát vně k dispozici. Do prj_demo.c musíme ještě připojit stdio.h, neboť obsahuje i vstupní a výstupní operace.

Obrázek obsahuje mnohem více informací, než jsme dosud popsali. Vodorovná přerušovaná čára

http://docs.linux.cz/c_saloun/kap0a.htm (2 z 8) [23.2.2004 11:40:33]

Page 135: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

odděluje textové informace, které do projektu vstupují, od jejich překladu.

Každý ze zdrojových textů *.c může být samostatně přeložen. Postačují mu informace z hlavičkových souborů (viz obrázek). Jejich překladem jsou *.obj soubory6.

Spojením *.obj souborů a připojením kódu ze standardních knihoven (v obrázku jsou označeny symbolicky), vytvoříme spustitelný tvar programu. Jeho začátek (vstupní bod) je jednoznačně určen. Funkce main() musí být v celém projektu právě jediná. Ve fázi spojování se mohou odhalit chyby při návrhu projektu. Pokud například definujeme funkci nebo deklarujeme proměnnou na více místech. To je častá chyba začátečníků - definují funkci v hlavičkovém souboru. Díky jeho začlenění do více modulů získají nechtěně více definic téže funkce. Jak se pak má zachovat spojovací program, když narazí na týž identifikátor ve více modulech? Jednoznačně, oznámí chybu a ukončí svou činnost. Všechny moduly jsou si z tohoto pohledu rovnocenné.

Vysvětlili jsme si závislosti v ukázkovém projektu. Podívejme se nyní na (mírně zkrácenou) ukázku make souboru, vytvořeného pro tento projekt. Jeho tvorba proběhla automaticky programem prj2mak z balíku podpůrných programů BC31.

.AUTODEPEND

.PATH.obj = C:\BIN

# *Translator Definitions*CC = bcc +PRJ_DEMO.CFGTASM = TASMTLIB = tlibTLINK = tlinkLIBPATH = C:\BC\LIBINCLUDEPATH = C:\BC\INCLUDE

# *Implicit Rules*.c.obj: $(CC) -c {$< }

.cpp.obj: $(CC) -c {$< }

# *List Macros*

EXE_dependencies = \ funkce.obj \ prj_demo.obj \ kalendar.obj

# *Explicit Rules*c:\bin\prj_demo.exe: prj_demo.cfg $(EXE_dependencies) $(TLINK) /v/x/c/P-/L$(LIBPATH) @&&|

http://docs.linux.cz/c_saloun/kap0a.htm (3 z 8) [23.2.2004 11:40:33]

Page 136: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

c0s.obj+c:\bin\funkce.obj+c:\bin\prj_demo.obj+c:\bin\kalendar.objc:\bin\prj_demo # no map fileemu.lib+maths.lib+cs.lib|

# *Individual File Dependencies*funkce.obj: prj_demo.cfg funkce.c

prj_demo.obj: prj_demo.cfg prj_demo.c

kalendar.obj: prj_demo.cfg kalendar.c

# *Compiler Configuration File*prj_demo.cfg: prj_demo.mak copy &&| ... zkráceno-nC:\BIN-I$(INCLUDEPATH)-L$(LIBPATH)-P-.C| prj_demo.cfg

Po podrobném rozboru projektu se již podíváme na jeho zdrojové texty. Začneme hlavním z nich. Obsahuje funkci main() a volá ostatní funkce definované vně něj. Najdeme v něm dva cykly, které by měly zajistit správné načtení vstupních hodnot. Ty jsou pak převedeny či přepočteny a zobrazeny. Odpovídající volání funkcí jsou umístěna přímo ve funkci printf(). Jejich výsledky tedy jsou použity jako argumenty formátovaného výstupu.

/******************************//* soubor PRJ_DEMO.C *//* ukazka maleho projektu *//* pouziva funkce ze souboru: *//* FUNKCE.C, KALENDAR.C *//******************************/

#include <stdio.h>#include "funkce.h"#include "kalendar.h"

int main(void){

http://docs.linux.cz/c_saloun/kap0a.htm (4 z 8) [23.2.2004 11:40:33]

Page 137: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

int i = -1; tDen eden; do { printf("\nZadej den v tydnu jako cislo < 0, 6> :"); scanf("%d", &i); } while (i < 0 || i > 6); eden = (tDen) i; printf("\nZadal jsi: %s (anglicky: %s)\n", eden_sden(eden, eCZ), eden_sden(eden, eBE));

do { printf("\nZadej cele cislo do 20:"); scanf("%d", &i); } while (i < 0 || i > 20); printf("\n%d! = %0.0lf\t fib(%d) = %0.0ld\n", i, fact(i), i, fib(i));

return 0;} /* int main(void) */

Soubor kalendar.c definuje funkci eden_sden(). Zajímavá je definice statického dvourozměrného pole řetězců v těle této funkce. Touto konstrukcí vytváříme rozhraní pro pole sdny. Pole pak není na globální úrovni viditelné, tedy není ani dostupné. Je odstíněno definovanými službami, které zajišťuje právě funkce eden_sden().

/******************************//* soubor KALENDAR.C *//* vyzaduje soubor KALENDAR.H *//* definuje: *//* - eden_sden() *//******************************/

#include "kalendar.h"

const char *eden_sden(tDen d, tLang l)/********************************************************//* prevede vyctovy den v tydnu na odpovidajici retezec *//* ve zvolenem jazyce *//********************************************************/{ static char *sdny[eBE+1][eNE+1] = {{"pondeli", "utery", "streda", "ctvrtek", "patek", "sobota", "nedele"}, {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}};

http://docs.linux.cz/c_saloun/kap0a.htm (5 z 8) [23.2.2004 11:40:33]

Page 138: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

return sdny[l][d];} /* const char *eden_sden(tDen d, tLang l) */

Hlavičkový soubor kalendar.h obsahuje deklaraci funkce eden_sden(). Jsou v něm definovány i dva výčtové typy a řada výčtových konstant, náležejících k těmto typům.

/************************************//* soubor KALENDAR.H *//* hlavickovy soubor pro KALENDAR.C *//* deklaruje a definuje: *//* - eden_sden() *//* - tDen, tLang *//************************************/

typedef enum {ePO, eUT, eST, eCT, ePA, eSO, eNE} tDen;/****************************************************//* vyctovy typ pro den v tydnu *//* PONDELI..NEDELE -> ePO..eNE (0..6) *//****************************************************/

typedef enum {eCZ, eBE} tLang;/****************************//* vyctovy typ pro jazyk: *//* CZ - cestina *//* BE - British English *//****************************/

const char *eden_sden(tDen d, tLang l);/********************************************************//* prevede vyctovy den v tydnu na odpovidajici retezec *//* ve zvolenem jazyce *//********************************************************/

Soubor funkce.c definuje funkce pro výpočet faktoriálu a Fibonacciho posloupnosti. První z funkcí je založena na iteraci. K tomu má cyklus for. Druhá funkce je postavena na eleganci rekurse. Její jednoduchost je ovšem draze vykoupena výrazně nižší výpočetní rychlostí a vysokými nároky na velikost zásobníku. Z tohoto důvodu je ve funkci main() omezen vstup na hodnoty do dvaceti.

/****************************//* soubor FUNKCE.C *//* vyzaduje soubor FUNKCE.H *//* definuje funkce: *//* - fact() *//* - fib() *//****************************/

http://docs.linux.cz/c_saloun/kap0a.htm (6 z 8) [23.2.2004 11:40:33]

Page 139: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

#include "funkce.h"

double fact(int n)/***********************//* vypocte n faktorial *//* pro n < 0 vrati -1 *//***********************/{ double f = 1.0L; for ( ; n > 0; n--) f *= n; return f;} /* double fact(int n) */

long fib(long n)/******************************//* vrati hodnotu n-teho clenu *//* Fibbonaciho posloupnosti *//******************************/{ if (n == 1) return 1; else if (n == 2) return 2; else return fib(n - 1) + fib(n - 2);} /* long fib(long n) */

Poslední soubor projektu, funkce.h, obsahuje hlavičky (deklarace) funkcí fact() a fib(). Připomeňme si, že deklarace funkcí neobsahují tělo funkce. Mají však popsány argumenty a jejich typy, stejně jako návratový typ. Jsou ukončeny středníkem7.

/****************************//* soubor FUNKCE.H *//* deklaruje funkce: *//* - fact() *//* - fib() *//****************************/

double fact(int n);/***********************//* vypocte n faktorial *//* pro n < 0 vrati -1 *//***********************/

long fib(long n);/******************************/

http://docs.linux.cz/c_saloun/kap0a.htm (7 z 8) [23.2.2004 11:40:33]

Page 140: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha A: Tvorba projektů

/* vrati hodnotu n-teho clenu *//* Fibbonaciho posloupnosti *//******************************/

Vysvětlivky:

1 Pravdou je, že tuto schopnost získáme teprve tehdy, když nějaký projekt úspěšně ukončíme. A raději ne hned ten první.2 V reálném projektu je významný i časový faktor. Kolik kódu je schopen jedinec zvládnout?3 Tím jsme si situaci pěkně zjednodušili. Otázkou zůstává, co je to vhodný způsob. Pro začátek můžeme například oddělit uživatelské rozhraní od zbylého kódu. Výkonnou část programu rozdělíme do souborů podle příbuzných skupin funkcí.4 Dokonce po jejich přeložení i bez nutnosti následného použití zdrojového textu.5 V našem případě budeme předávat informace o všech objektech. Tímto způsobem se ovšem můžeme obecně chovat modulárně. Ze zdrojového souboru zveřejníme v hlavičkovém souboru pouze informace, které chceme dovolit exportovat.6 Nebudeme popisovat detailně více OS. Pro názornost nám postačí popis v prostředí MS-DOSu.7 Pokud na středník zapomeneme, díky začlenění hlavičkového souboru do jiných zdrojových textů se syntaktická chyba pravděpodobně projeví na místě, kde ji zpravidla nečekáme. V jazyce C prostě musíme být pozorní.

Předchozí kapitola Obsah Začátek Příloha B

Název: Programování v jazyce C

Autor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap0a.htm (8 z 8) [23.2.2004 11:40:33]

Page 141: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Programování v jazyce C - Příloha B:  literatura

Příloha A Obsah Konec

Příloha B: literatura

[BS89] Brodský, J. - Skočovský, L. : Operační systém Unix a jazyk C. ISNB 80-03-00049-1, KVT SNTL Praha 1989.

[KR78] Kernighan, B.W. - Ritchie, D.M. : The C Programming Language. Englewood Cliffs, Prentice-Hall 1978. (Slovenský překlad ALFA 1986)

[Wirth75] Wirth, N. : Algorithms + Data Structures = Programs. Prentice-Hall, Englewood Cliffs, New Jersey, 1975. (Slovenský překlad EVT ALFA 1988.)

[Herout92] Herout, P. : Učebnice jazyka C. ISBN 80-901342-1-1. Nakladatelství Kopp, České Budějovice 1992.

[RiBr91] Richta, K. - Brůha, I. : Programovací jazyk C. ISBN 80-01-00704-9. Ediční středisko ČVUT, Praha 1991.

[PlBr92] Plauger, P.J. - Brodie, J. : ANSI and ISO Standard C, Programmer's Reference. ISBN 1-55615-359-7. Microsoft Press, 1992.

[Micr91] Microsoft C/C++ 7.0, C Language Reference.

[HeRuŠm] Herout, P. - Rudolf, V. - Šmrha, P. : ABC programátora v jazyce C. ISBN 80-901051-2-2. Nakladatelství Kopp. České Budějovice 1992

[MW92] Coherent. Mark Wiliams Company. 1992.

Příloha A Obsah Začátek

Název: Programování v jazyce C

Autor: Petr ŠalounDo HTML převedl: Kristian Wiglasz

Poslední úprava: 29.11.1996

http://docs.linux.cz/c_saloun/kap0b.htm [23.2.2004 11:40:34]

Page 142: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Index of /FAKULTY/FEI/KINFO/STUDENTS/c/sources

Index of /FAKULTY/FEI/KINFO/STUDENTS/c/sources

Name Last modified Size Description

Parent Directory 13-Mar-97 10:32 -

ccopy.c 04-Dec-96 15:14 1k

cetno_ch.c 04-Dec-96 15:14 1k

cmd_ln04.c 04-Dec-96 15:14 1k

cmp_str.c 04-Dec-96 15:14 1k

con_int.c 04-Dec-96 15:14 1k

cpy.c 04-Dec-96 15:14 1k

cpyarry.c 04-Dec-96 15:14 1k

dowhile1.c 04-Dec-96 15:14 1k

dowhile2.c 04-Dec-96 15:14 1k

enum_use.c 04-Dec-96 15:14 1k

fact_r.c 04-Dec-96 15:14 1k

fact_rst.c 04-Dec-96 15:14 1k

fn_argho.c 04-Dec-96 15:14 1k

fn_argx.c 04-Dec-96 15:14 1k

fn_qsrt.c 04-Dec-96 15:14 1k

for-1.c 04-Dec-96 15:14 1k

funkce.c 04-Dec-96 15:14 1k

funkce.h 04-Dec-96 15:14 1k

gets.c 04-Dec-96 15:14 1k

hello.c 04-Dec-96 15:14 1k

histslov.c 04-Dec-96 15:14 1k

http://docs.linux.cz/c_saloun/sources/ (1 z 3) [23.2.2004 11:40:38]

Page 143: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Index of /FAKULTY/FEI/KINFO/STUDENTS/c/sources

if-else1.c 04-Dec-96 15:14 1k

if-else2.c 04-Dec-96 15:14 1k

int_size.c 04-Dec-96 15:15 1k

io-dp01.c 04-Dec-96 15:15 1k

io-dp02.c 04-Dec-96 15:15 1k

io-dp02.out 04-Dec-96 15:15 1k

io-pv01.c 04-Dec-96 15:15 1k

io-pv02.c 04-Dec-96 15:15 1k

io-pv03.c 04-Dec-96 15:15 1k

io-pv04.c 04-Dec-96 15:15 1k

io-pv05.c 04-Dec-96 15:15 1k

io-pv05.out 04-Dec-96 15:15 1k

jmeno_me.c 04-Dec-96 15:15 1k

kalendar.c 04-Dec-96 15:15 1k

kalendar.h 04-Dec-96 15:15 1k

op_bit01.c 04-Dec-96 15:15 1k

op_cond.c 04-Dec-96 15:15 1k

op_int01.c 04-Dec-96 15:15 1k

op_int_f.c 04-Dec-96 15:15 1k

out 04-Dec-96 15:15 1k

prj_demo.c 04-Dec-96 15:15 1k

prj_demo.mak 04-Dec-96 15:15 1k

prj_dmob.c 04-Dec-96 15:15 1k

ptr_fn01.c 04-Dec-96 15:15 1k

rims_tab.c 04-Dec-96 15:15 1k

scanwhil.c 04-Dec-96 15:15 1k

scanwhil.out 04-Dec-96 15:15 1k

http://docs.linux.cz/c_saloun/sources/ (2 z 3) [23.2.2004 11:40:38]

Page 144: Petr Saloun - zcu.czhome.zcu.cz/~lsroubov/ZPE/Cecko.pdfProgramování v jazyce C - Úvod upouštělo od fint1.Rovněž vznikl trend vedoucí k normalizaci jazyka. Teprve pak se totiž

Index of /FAKULTY/FEI/KINFO/STUDENTS/c/sources

simpleio.c 04-Dec-96 15:15 1k

slova.c 04-Dec-96 15:15 2k

str_arry.c 04-Dec-96 15:15 3k

str_cpy.c 04-Dec-96 15:15 1k

str_cpy2.c 04-Dec-96 15:15 1k

str_ptr.c 04-Dec-96 15:15 1k

str_sort.c 04-Dec-96 15:15 1k

struct01.c 04-Dec-96 15:15 1k

struct01.out 04-Dec-96 15:15 1k

sumslova.c 04-Dec-96 15:15 1k

sumznaky.c 04-Dec-96 15:15 1k

switch-1.c 04-Dec-96 15:15 1k

typedef0.c 04-Dec-96 15:15 1k

while-1.c 04-Dec-96 15:15 1k

yearday.c 04-Dec-96 15:15 1k

http://docs.linux.cz/c_saloun/sources/ (3 z 3) [23.2.2004 11:40:38]