Distanční opora předmětu: Programování v jazyce C Tématický blok č. 5: Řetězce, parametry programu Autor: RNDr. Jan Lánský, Ph.D. Obsah kapitoly 1 Řetězce 1.1 Deklarace a inicializace řetězcové proměnné 1.2 Knihovna string.h 1.3 Časté chyby 1.4 Funkce printf 2 Parametry programu předávané z příkazové řádky 2.1 Příklad: Vypsání parametrů programu na obrazovku 2.2 Příklad: Zpracování parametrů programu Studijní cíle Cíle nutné k zahájení studia dalšího tématického bloku Znát klíčové pojmy tohoto tématického bloku (alespoň pasivní znalost je podmínkou pro studium dalších bloků). Podle slovního zadání umět napsat a odladit jednoduchý program. Program pracuje se vstupem získaným z parametrů, které byly programu předány z příkazové řádky při jeho zavolání. Schopnost pracovat s řetězci, zejména ovládat zjišťování délky, kopírování, spojování a porovnávání řetězců, přístup k jednotlivým znakům řetězce. Další cíle Znalost funkcí z knihovny string.h Znalost funkce printf
Čas potřebný ke studiu 2 - 4 hodiny na prostudování výukových textů + zodpovězení otázek k rekapitulaci 2 - 6 hodiny na vypracování modelových úloh na PC 1 - 3 hodiny na praktické zopakování učiva na PC ( v jiný den) 30 min - 1 hodina na (znovu)zodpovězení otázek k rekapitulaci (v jiný den) Časy jsou hodně individuální a jsou závislé na míře znalostí z předmětu Úvod do programování a Programování a případných programátorských zkušenostech z jiných jazyků.
Úvod V tomto bloku probereme následující témata. Vysvětlíme si, jak jsou reprezentovány řetězce v jazyku C Naučíme se deklarovat a inicializovat řetězcové proměnné Probereme funkce na zjišťování délky řetězců, kopírování a spojování řetězců, porovnávání řetězců a vyhledávání v řetězcích. Ukážeme si, jaké problémy mohou nastat při práci s řetězci a jak jim předcházet. Vysvětlíme si funkci printf, sloužící k vypisování formátovaného textu na standardní výstup. Vysvětlíme si jakým způsobem lze přistupovat k parametrům programu, které jsou mu při spuštění předány z příkazové řádky. Na dvou příkladech si ukážeme, jak lze tyto parametry zpracovávat. Výkladová část Vysvětlivky Červený text – Porušením nebo opomenutím takto označených pravidel vznikají těžko odladitelné chyby (zejména pro začínající programátory). Modrý text – Doporučení jak programovat v praxi. Často prevence závažných chyb. 1 Řetězce Jazyk C nemá speciální datový typ pro reprezentaci (textového) řetězce. Platí konvence, že řetězec je pole znaků ukončené znakem s hodnotou 0. Řetězec je datového typu char *. S touto konvencí pracují všechny knihovny. Je důležité rozlišovat znaky '0' a 0. Znaková konstanta '0' reprezentuje číslici 0 (tu uvidíme například při vytištění na obrazovku) a má hodnotu 48. Hodnota 0 se dá naopak reprezentovat znakovou konstantou '\0' a po jejím vytištění na obrazovku ji pravděpodobně uvidíme jako mezeru. Při jejím uložení do souboru a pří prohlížení tohoto souboru pomocí hexadecimálního editoru uvidíme černě vyplněný obdélník. Oproti jiným programovacím jazykům přináší tato konvence řadu omezení. Nelze například reprezentovat řetězec, který by obsahoval znak s hodnotou 0. Tento znak nelze rozlišit od konce řetězce a považoval by se proto automaticky za konec řetězce. Toto omezení není nijak zásadní, protože textové řetězce tento znak obvykle neobsahují. Na řetězce lze používat pouze ty samé operátory jako na obyčejné pole. Nejde použít operátor = pro přiřazení (okopírování) hodnoty jednoho řetězce do řetězce druhého. Rovněž nelze používat například operátor + pro spojování řetězců nebo operátory >, <, == pro porovnávání řetězců.. Pro všechny operace s řetězci se používají knihovní funkce. Na slajdu č.61 napravo nahoře vidíme jak je reprezentován řetězec "Ahoj". Přestože tento řetězec obsahuje pouze 4 znaky, kvůli koncové nule musí být uložen v poli o velikosti 5 znaků. K jednotlivým znakům řetězce lze přistupovat pomocí operátoru hranatých závorek.
Nalevo na oranžovém podkladu vidíme kus zdrojového kódu. V něm vytváříme pole znaků buffer o velikosti 4 a následně do něj pomocí funkce strcpy kopírujeme řetězec "Ahoj". Přestože příklad vypadá na první pohled správně, zapomněli jsme na koncovou nulu. Ta se nakopíruje za konec pole buffer a přepíše tak kus paměti, který mu nepatří. Správně mělo být pole buffer velikosti 5 znaků. Příklad je graficky znázorněn napravo. Důležité je umět rozlišovat mezi konstantou znakovou a konstantou řetězcovou. Pokud se ve zdrojovém kódu objeví symbol A, může se jednat například o název proměnné nebo funkce. Pokud bude symbol uzavřený v apostrofech 'A', jedná se o znakovou konstantu (typu char) reprezentující písmeno A. Pokud bude symbol uzavřený v uvozovkách "A", jedná se o řetězcovou konstantu (typu char *) reprezentující řetězec (pole délky dva znaky) obsahující písmeno A a koncovou nulu. Existuje i řetězcová konstanta pro prázdný řetězec "" (prázdné uvozovky). V paměti je prázdný řetězec reprezentovaný jako pole velikosti jeden znak, obsahující pouze koncovou nulu. Jedná se o řetězec, délky nula, neobsahující žádné znaky. Je rozdíl mezi prázdným řetězcem a nulovým řetězcem (nulovým ukazatelem na znak). Nulový řetězec nesmíme předat jako parametr funkcím pro práci s řetězci z knihovny string.h, protože by došlo k dereferenci nulového ukazatele a násilnému ukončení programu operačním systémem. Prázdný řetězec těmto funkcím smíme jako parametr předat. 1.1 Deklarace a inicializace řetězcové proměnné Při deklaraci řetězcové proměnné máme na výběr ze dvou možností. Řetězec můžeme deklarovat jako pole prvků typu char, pak bude mít pevně vyhrazený kus paměti. Druhá alternativa je deklarovat řetězec jako ukazatel na typ char, tato proměnná může ukazovat na různé řetězce. Ani při jednom ze způsobu deklarace řetězcové proměnné nemáme v této proměnné platnou hodnotu. Při deklaraci však můžeme řetězcovou proměnnou inicializovat hodnotou, řetězcovou konstantou. Na slajdu č. 62 vidíme dva řádky zdrojového kódu. Na prvním řádku deklarujeme proměnnou s1 jako pole znaků a inicializujeme jí hodnotou "Uno“. Velikost pole se automaticky dopočítá jako 4 (tři znaky a koncová nula). Situace je graficky znázorněna na horním obrázku napravo. Na druhém řádku deklarujeme proměnou s2 jako ukazatel na znak a inicializujeme jí řetězcovou konstantou "Duo". Situace je graficky znázorněna na dolním obrázku napravo. Pokud by jsme vykonali příkaz s2++, ukazatel bude ukazovat místo 'D' na 'u' a bude reprezentovat řetězec "uo". U proměnné s1 bychom nemohli provést příkaz s1++, protože proměnná je typu pole. Proměnná s2 je navíc deklarována jako ukazatel na typ const char. Toto označení znamená, že hodnoty, ke kterým pomocí ukazatele přistoupíme, můžeme pouze číst, ale nesmíme je modifikovat, nelze provést například příkaz (*s2)++. V obou řádcích zdrojového kódu vidíme použití operátoru = pro inicializaci řetězce nějakou řetězcovou konstantou. Tuto inicializaci lze provést pouze při deklaraci proměnné. Kdekoliv dále ve zdrojovém kódu by se tento zápis vyhodnotil jako použití operátoru přiřazení. V případě proměnné s1, která je deklarovaná jako pole znaků, by se jednalo o syntaktickou chybu. U proměnné s2, která je typu ukazatel, je použití operátoru přiřazení povoleno, proměnná začne ukazovat na jiný kus paměti.
1.2 Knihovna string.h Ve hlavičkovém souboru string.h najdeme deklarace všech základních knihovních funkce pro práci s řetězci. Pokud chceme tyto funkce využívat, je nutné tento hlavičkový soubor do programu vložit pomocí direktivy #include<string.h>. My si v tomto studijním materiálu představíme jen několik nejdůležitějších funkcí, jejichž znalost je vyžadována k úspěšnému zvládnutí učiva tohoto tématického bloku. Funkce strlen slouží ke zjištění délky řetězce, který je funkci předán jako její parametr. Funkce má jeden parametr typu ukazatel na const char. Máme jistotu, že.funkce nemůže zmodifikovat hodnoty jednotlivých znaků v řetězci. Funkce strlen vrací délku řetězce, jako skutečný počet znaků, který tento řetězec obsahuje, ale koncová nula se do délky nepočítá. Na slajdu č. 63 vidíme kus zdrojového kódu, ve kterém deklarujeme pole znaků délky 8 a inicializujeme ho řetězcovou konstantou "Ahoj". Situace je graficky znázorněna na obrázku dole. V poli jsou obsazeny první čtyři znaky písmeny, pátý znak je koncová nula a pak následují tři znaky, které mají neinicializovanou hodnotu. Funkce strlen vrátí počet platných znaků řetězce, číslo 4. Na slajdu č. 64 vidíme sedm různých zdrojových kódů ukazujících, jak by funkce funkce strlen mohla být naprogramována. Princip je jednoduchý. Prochází se pole znaků od začátku do doby, než se narazí na koncovou nulu. Počet projitých prvků se počítá průběžně, nebo se zjistí odečtením adresy prvního a posledního platného prvku. Tento počet je vrácen jako výsledek. Funkce strcpy slouží k okopírování hodnoty jednoho řetězce (znak po znaku, včetně koncové nuly) do řetězce druhého. Funkce má dva parametry první z nich je typu ukazatel na char, druhý z nich je typu ukazatel na const char. Už ze samotných typů parametrů je jasné, že první parametr určuje cílovou adresu, kam se bude kopírovat hodnot provádět, a druhý parametr určuje adresu zdroje, jehož obsah se bude kopírovat. Návratovou hodnotou funkce je ukazatel na cílový řetězec, nicméně tato návratová hodnota nebývá obvykle využívána, protože je jí vlastně první parametr funkce. Na slajdu č. 65 vidíme kus zdrojového kódu. Na prvním řádku deklarujeme neinicializované pole buf velikosti 8 znaků. Na druhém řádku deklarujeme pole pozdrav inicializované řetězcovou konstantou "Ahoj". Na třetím řádku pomocí funkce strcpy okopírujeme řetězec uložený v poli pozdrav do pole buf. Situace je graficky znázorněna na obrázku dole. V poli buf po skončení tohoto zdrojového kódu budou v prvních čtyřech prvcích písmena, pátý prvek bude koncová nula a poslední tři prvky budou neinicializovány. Funkce strcat k cílovému řetězci připojí (nakopíruje za konec řetězce) zdrojový řetězec. Pořadí i typy parametrů, stejně jako návratová hodnota jsou shodné jako u funkce strcpy. Zatímco funkce strcpy cílový řetězec přepíše, funkce strcat z cílového řetězce přepíše pouze koncovou nulu, což je první pozice, kam se zdrojový řetězec začíná kopírovat. Zápis strcat(x,y) je ekvivalentní zápisu strcpy(x+strlen(x),y). V řetězcích lze i vyhledávat. Funkce strchr vyhledá v řetězci, který je jejím prvním parametrem, první výskyt znaku, který je jejím druhým parametrem. Návratovou hodnotou je ukazatel na tuto pozici v řetězci, nebo nulový ukazatel, pokud žádná taková pozice nebyla
nalezena. Funkce strstr hledá v řetězci, který je jejím prvním parametrem, první výskyt jiného řetězce, který je jejím druhým parametrem. Na slajdu č. 67 vidíme kus zdrojového kódu. Na prvním řádku deklarujeme pole znaků buf délky 10. Na druhém řádku deklarujeme ukazatel bp. Na třetím řádku pomocí funkce strcpy okopírujeme do pole buf řetězec "Ahoj". Na čtvrtém řádku pomocí funkce strcat na konec řetězce uloženého v poli buf připojíme řetězec "Babi". V poli buf se nyní nachází řetězec "AhojBabi". Na pátém řádku v řetězci uloženém v poli buf najdeme za pomocí funkce strstr první výskyt podřetězce "oj". Výsledkem je ukazatel na prvek s indexem 2 (třetí prvek) pole buf. Situace je znázorněna na obrázcích napravo slajdu. Pro porovnávání řetězců nelze používat operátory >, < a ==. Tyto operátory porovnají pouze adresy paměti, kde jsou tyto řetězce uloženy. Získané výsledky nevypovídají nic o jednotlivých řetězcích (obsahu paměti na daných adresách). Pro porovnávání řetězců existuje funkce strcmp, která má dva parametry typu ukazatel na const char. Funkce vrací návratovou hodnotu typu celé číslo. Pokud jsou řetězce, které byly funkci předány jako její parametry, shodné, funkce vrací hodnotu 0. Pokud je první parametr lexikograficky menší než druhý parametr, funkce vrací hodnotu -1. Pokud je první parametr lexikograficky větší než druhý parametr, funkce vrací hodnotu 1. Při lexikografickém porovnávání dvou řetězců s1 a s2 se postupně procházejí a porovnávají znaky na stejných pozicích těchto řetězců (s1[0] a s2[0], s1[1] a s2[1], atd…). Najde se nejmenší index i pro který platí s1[i] != s2[i]. Pokud s1[i] < s2[i], potom řetězec s1 je lexikograficky menší než řetězec s2. Pokud s1[i] > s2[i], potom řetězec s1 je lexikograficky větší než řetězec s2. Výsledek celého lexikografického uspořádání je výsledkem porovnání první dvojice rozdílných znaků nebo hodnota 0, pokud oba řetězce jsou stejně dlouhé a mají shodné všechny dvojice znaků až do koncové nuly. Na slajdu č. 68 napravo je celý postup graficky znázorněn. Lexikografické uspořádání připomíná uspořádání slov ve slovníku. Místo klasické abecedy (cca 30 písmen) ale pracujeme s celou znakovou sadou 256 znaků. Písmena anglické abecedy jsou v každé znakové sadě řazeny ve stejném pořadí jako v abecedě, proto toto uspořádání dává na textových řetězcích lidsky očekávané výsledky. Pokud je řetězec s1 vlastním prefixem řetězce s2, je s1 lexikograficky menší než s2, protože koncová nula řetězce s1 je vždy menší než libovolný znak řetězce s2. 1.3 Časté chyby Knihovní funkce pro práci s řetězci (deklarované například ve string.h) předpokládají, že jsou jim při zavolání funkce jako skutečné parametry předány platné řetězce. Dále předpokládají, že pro provedení jejich operací je na cílové adrese vždy dostatek volného prostoru. Platí konvence, že o skutečné parametry, se kterými je funkce zavolána se stará volající funkce, nikoliv funkce volaná. Velký problém nastává při předání neinicializovaného ukazatele. Funkce nemají žádnou možnost neinicializovaný ukazatel rozpoznat. Z adresy paměti dané neinicializovaným ukazatelem (neznámá náhodná adresa) se pokusí číst nebo do ní zapisovat, což vede obvykle k násilnému ukončení programu operačním systémem. Na slajdu č. 66 dole ve zdrojovém kódu vidíme chybný pokus o kopírování hodnoty do neinicializovaného ukazatele ptr. Vlevo je zdrojový kód, vpravo grafické znázornění.
Funkce také předpokládají, že ukazatele jsou nenulové. Neprovádí, žádné testy na nulovost ukazatelů a jejich případný výskyt vede k běhové chybě programu z důvodu pokusu o dereferenci nulového ukazatele. Chybějící testy na nenulovost ukazatelů nejsou chybou daných funkcí, ale záměrem, aby funkce byly rychlejší. Některé z funkcí pro práci s řetězci mají dva parametry, cíl (adresa, kam se budou data zapisovat) a zdroj (adresa, ze které se budou data číst). Při volání takovýchto funkcí záleží v jakém pořadí skutečné parametry uvedeme. Z hlavičky funkce lze většinou určit, který z formálních parametrů je cíl a který zdroj. Zdroj bývá typu ukazatel na const char, cíl bývá typu ukazatel na char. Prohození pořadí skutečných parametrů vede k poškození zpracovávaných dat, občas i k přepsání velkých kusů paměti náhodnými daty. Pokud pro práci s řetězci používáme pouze knihovních funkcí (a v jejich použití neděláme chyby) máme garantováno, že jako výsledky získáme vždy platné řetězce ukončené koncovou nulou. Řetězce můžeme modifikovat i bez použití knihovních funkcí přístupem k jednotlivým znakům řetězce přes operátor hranatých závorek. V tomto případě máme šanci omylem přepsat koncovou nulu jiným znakem (a novou koncovou nulu do řetězce nepřidat). Řetězec po takovémto zásahu pokračuje i za paměť, která mu byla vyhrazena. Ukončení řetězce závisí na náhodě, kdy se v paměti, která následuje za pamětí mu přidělenou, objeví první byte s hodnotou 0, který se bude považovat za koncovou nulu řetězce. Pokud budeme takovýto řetězec pouze vypisovat na obrazovku, po platných znacích řetězce se vypíše i velké množství nesmyslných symbolů. Nejhorší situace nastává při kopírování hodnoty tohoto řetězce do řetězce jiného. V cílovém řetězci nebude pravděpodobně dostatek volné paměti a přepíšeme kus paměti, která cílovému řetězci nepatří. Při jakémkoliv kopírování řetězců si musíme být jistý, že na cílové adrese je dostatek volné paměti k okopírování zdrojového řetězce. V opačném případě může dojít k přepisování paměti, která se nachází za pamětí vyhrazenou cílovému řetězci. Funkce pro zpracování řetězců nemají žádnou možnost jak kontrolovat, zda na cílové adrese je dostatek paměti. Pokud neznáme délku zdrojového řetězce, lze použít funkci strlen k jejímu zjištění. Na slajdu č. 66 nahoře vidíme chybný příklad, ve kterém jsme špatně určili velikost paměti vyhrazené pro cíl kopírování pomocí funkce strcpy. Do pole buf o velikosti 6 znaků kopírujeme řetězec velikosti 10 znaků (včetně koncové nuly). Po provedení kopírování dojde k přepsání 4 bytů paměti následující za koncem pole buf. Situace je graficky znázorněna pod zdrojovým kódem. Řetězce jsou jedním z témat, na kterém se prokáže znalost problematiky ukazatelů a polí. Existuje řada lidí, kteří zdrojové kódy ladí následujícím způsobem. Něco napíšou, zkompilují to, vrátí se jim několik chybových hlášení. A snaží se chyby odstranit: dopíšou hvězdičku, smažou hvězdičku, případně závorku, středník, aniž by tušili co svým zásahem ve zdrojovém kódu dělají. Snaží se pouze zoufale zbavit chyb. Obvykle po několika hodinách a vyzkoušení desítek až stovek vzájemných kombinací získají zdrojový kód, který jde přeložit. K jejich velkému překvapení okamžitě po spuštění dochází k běhové chybě programu, obvykle dereference nulového nebo neinicializovaného ukazatele. Pokud tento styl programování používáte, je třeba se zamyslet, ke kterému tématickému bloku se vrátit. V případě, že náhodně mažete a připisujete pouze hvězdičky či hranaté závorky, stačí návrat k bloku č. 4. V případě náhodného mazání a připisování i dalších syntaktických znaků, je vhodný návrat dokonce až k bloku č. 2 nebo č. 3.
1.4 Funkce printf Funkce printf slouží k vypisování formátovaného textu na standardní výstup. Existují i její varianty fprintf pro výpis do souboru a sprintf pro výpis do paměti., které se liší pouze v přidání jednoho parametru určujícího, kam se bude provádět výpis. Funkce vrací v návratové hodnotě počet znaků, které se skutečně podařilo vytisknout či zapsat. Funkce printf na rozdíl od většiny ostatních funkcí nemá pevně daný počet a typy parametru. Povinný je pouze první parametr funkce (formátovací řetězec), který je textovým řetězcem. Tento řetězec kromě obyčejného textu může obsahovat i různé formátovací symboly, například \n pro odřádkování nebo \t pro tabulátor. Samotný symbol \, který potřebujeme například pro zapsání cesty k souboru v operačním systému Windows, se zapíše jako \\. Ve formátovacím řetězci můžeme také určit, že na jeho daném místě se má vypsat hodnota nějakého výrazu (předem daného datového typu určeného ve formátovacím řetězci), který bude dalším parametrem funkce printf. Například %s zastupuje výraz typu řetězec, %c znak, %d celé číslo a %f reálné číslo. Tento formátovací řetězec určuje s jakými dalšími skutečnými parametry má být funkce printf zavolána. Významu tohoto řetězce ale kompilátor nerozumí, nemůže přítomnost těchto dalších parametrů ověřit. V těle funkce printf se pak počítá s tím, že parametry byly zadány správně, funkce nemá žádnou možnost jejich správnost ověřit. Správnost zadání skutečných parametrů funkce printf (počet a jejich typy) musí kontrolovat programátor sám. Pokud některý ze skutečných parametrů chybí, dojde obvykle k násilnému ukončení běhu programu operačním systémem. Pokud je skutečný parametr pouze špatného datového typu dochází většinou pouze k výpisu nesmyslných dat. Vypsané hodnoty můžeme zarovnávat doprava na daný počet znaků, pokud mezi symbol % a písmeno určujícím typ vypisovaného výrazu vložíme číslo určující, na kolik pozic se vypsaná hodnota zarovnává. Uvedením číslice nula mezi symbol % a číslo udávající na kolik míst se vypisovaná hodnota zarovnává doprava docílíme vytištění znaků '0' místo mezer při tomto výpisu. U čísel můžeme vynutit i povinný výpis znaménka, v případě, že číslo je kladné. U reálných čísel můžeme navíc určit i počet vypisovaných míst za desetinnou čárkou. Na slajdu č. 69 napravo na zeleném podkladu vidíme několik příkladů použití funkce printf. Na pátém řádku zdrojového kódu voláním funkce printf nejprve vypíšeme celé číslo 7 zarovnané na pět znaků doprava, místo mezer budou vypsány znaky '0' a číslo bude mít vynucené znaménko. Vypsáno bude +0007. Odřádkuje se. Dále se vypíše hodnota výrazu r+7 jako reálné číslo s uvedením dvou míst po desetinné čárce. Vypsáno bude 9.60. Příkazem uvedeným na šestém řádku zdrojového kódu vypíšeme text: Retezec Ahoj zacina na A a odřádkujeme. Příkazem uvedeným na šestém řádku zdrojového kódu vypíšeme text: Hodnota znaku A je 65 a odřádkujeme. Na slajdu č. 69 dole na červeném podkladu vidíme dva chybné příklady. Na prvním řádku nesouhlasí typ parametrů uvedený v formátovacím řetězci (řetězec) s typem parametru skutečně zadaným (reálné číslo). Na druhém řádku jsou formátovacím řetězcem požadovány tři parametry typu celé číslo, ale zadány jsou parametry pouze dva.
2 Parametry programu předávané z příkazové řádky Program můžeme spustit z příkazové řádky napsáním a potvrzením jeho názvu, případně.názvu včetně cesty k programu, pokud program pouštíme z jiného adresáře, než ve kterém se tento program nachází. Programu spouštěnému z příkazové řádky můžeme předat i libovolné množství parametrů, pokud je napíšeme za název programu při jeho spouštění, jednotlivé parametry se oddělují mezerou. Na slajdu č. 70 nahoře vidíme příklad volání programu s parametry. Význam a způsob zpracování jednotlivých parametrů určuje autor každého programu sám. Autor programu může parametry zcela ignorovat, pokud se je ale rozhodne zpracovávat, obvykle předpokládá, že parametry splňují nějaká syntaktická pravidla, která uvede například do nápovědy k programu. Ve Visual Studiu můžeme nastavit parametry programu následujícím postupem. V Solution Exploreru (bývá nalevo nahoře na obrazovce) klikneme pravým tlačítkem myši na název aktuálního projektu (projektu, nikoliv celé solution). Objeví se nabídka, ze které vybereme poslední možnost Properties. Vyskočí nám nové okno, ve kterém nalevo v nabídce vybereme položku Configuration Properties, podpoložku Debugging. Napravo nalezneme řádek označený jako Command Arguments a do něj můžeme parametry programu vyplnit. V programu přistupujeme k jeho parametrům, přes parametry funkce main. Funkce main má dva parametry. První parametr se jmenuje argc, je typu int a udává počet parametrů programu. Druhý parametr funkce main se jmenuje argv, je typu pole řetězců a uchovává v poli parametry programu reprezentované jako řetězce. Na parametr argv se můžeme také dívat jako na dvojrozměrné pole znaků. Prvním parametrem programu je vždy název tohoto programu včetně plné cesty. Případné další parametry programu jsou předány z příkazové řádky. Ukazatel argv[0] ukazuje na název programu, argv[1] ukazuje na první parametr, který byl programu přidán z příkazové řádky, argv[argc-1] ukazuje na poslední parametr, který byl programu předán z příkazové řádky. Na konci pole argv se nachází hodnota 0 (argv[argc] je nulový ukazatel). Délku pole argv, lze však zjistit i z parametru argc. Oba parametry funkce main můžeme pojmenovat libovolně, dodržet musíme pouze jejich pořadí a jejich datové typy. Z důvodu přehlednosti programu se doporučuje pojmenovávat parametry funkce main názvy argc a argv. 2.1 Příklad: Vypsání parametrů programu na obrazovku Na slajdu č. 70 vidíme zavolání programu myprog.exe se čtyřmi parametry předanými z příkazové řádky. Hodnota proměnné argc, udávající počet parametrů programu, je 5, protože před čtyři parametry předané z příkazové řádky se přidá jako první parametr název programu včetně plné cesty. Na obrázku dole vidíme graficky znázorněno, jak jsou tyto parametry uloženy v poli argv. Na slajdu č. 71 vidíme zdrojový kód programu, který na standardní výstup vypíše své parametry. Spuštění tohoto programu s parametry z příkazové řádky je vidět nahoře na slajdu. Napravo na slajdu vidíme standardní výstup po skončení běhu programu. Výpis parametrů programu je naprogramován pomoci while cyklu. Cyklus poběží tak dlouho, dokud aktuálně zpracovávaný parametr programu (ukazatel *argv) bude nenulový. Nulová hodnota značí
konec pole parametrů programu. V těle cyklu pak tento parametr vytiskneme na obrazovku a pomocí příkazu argv++ se v poli argv posuneme na další prvek, tedy na další parametr programu. Tento postup mám znemožňuje opakované čtení parametrů programu, nemůžeme se vrátit na původní začátek pole argv. Alternativní postup by byl projít pole argv pomocí for cyklu, poté bychom mohli k jednotlivým argumentům programu přistupovat opakovaně. Na slajdech č. 72 – 74 vidíme graficky znázorněny jednotlivé kroky tohoto programu. Aktuálně vykonávané řádky zdrojového kódu jsou označeny tučně. Pole argv lze považovat za dvojrozměrné pole znaků, nebo jednorozměrné pole řetězců. Na argv se můžeme také dívat jako na dvojnásobný ukazatel na znak nebo jako na ukazatel na řetězec. Slajd č. 72 ukazuje situaci při prvním průchodu cyklem. Dvojnásobný ukazatel argv ukazuje na první prvek pole parametrů, na řetězec (ukazatel na pole znaků) argv[0]. Prvním parametrem programu je jeho název, který se pomocí funkce printf vypíše na obrazovku. Způsob, jakým je na slajdu zobrazeno dvojrozměrné pole argv, nás může překvapit. Jedná se však o jediný možný způsob, jak vytvořit dynamicky alokované dvojrozměrné pole. Protože program může být spouštěn s proměnlivým počtem parametrů, které jsou navíc proměnlivé délky, je vyloučena statická alokace. Dvojrozměrné pole argv je pole ukazatelů na znak, jehož každý prvek ukazuje na první prvek vlastního pole znaků. Na slajdu jsou déle některé prvky označeny bublinou popisující jejich datový typ a způsob, jak zjistit jejich hodnotu. Slajd č. 73 vidíme vykonání příkazu argv++. Proměnnou argv chápeme jako ukazatel, který doteďka ukazoval na první prvek pole parametrů programu (aktuálně na prvek argv[0], což je jméno programu včetně plné cesty). Příkazem argv++ se v ukazateli zvýší hodnota uložené adresy o jedna, ukazatel bude ukazovat na další prvek pole, v našem případě na parametr programu "-n". Slajd č. 74 ukazuje konec cyklu. V poli argv jsme došli (při posledním posunu ukazatelem pomocí příkazu argv++) na prvek obsahující nulový ukazatel. Podmínka cyklu *argv nebude splněna a cyklus končí. 2.2 Příklad: Zpracování parametrů programu Na slajdu č. 75 zpracujeme ty samé parametry programu, jaké jsem použili v minulém příkladu. Nebudeme je tisknout na obrazovku, ale zjistíme z nich s jakým nastavením chce uživatel náš program spustit. Také zjistíme, zda uživatel zadal všechny parametry (a zadal je ve správném pořadí) nutné pro spuštění programu. Nahoře na slajdu vidíme s jakými parametry můžeme program spouštět. Parametry –n a –w jsou uvedeny v hranatých závorkách, jsou to nepovinné parametry. U těchto dvou parametrů nás bude pouze zajímat, zda byl program zavolán s nimi nebo bez nich.. Dále následují dva povinné parametry udávající jména (případně i s cestou) dvou souboru, se kterým bude program pracovat. Může se jednat například o vstupní soubor (ze kterého bude program číst data) a výstupní soubor (do kterého bude data ukládat). Ve zdrojovém kódu jsme pro každý z nepovinných parametrů –n a –w vytvořili jednu proměnnou (n a w) inicializovanou na hodnotu 0. Pokud byl program s daným parametrem zavolán, změníme hodnotu příslušné proměnné na hodnotu 1.
Podobně jako v minulém příkladu procházíme pole parametrů programu while cyklem pomocí posouvání ukazatele argv. Podmínka cyklu je zde složitější, skládá se z logické konjunkce dvou podmínek. V první části podmínky posuneme ukazatel argv na další prvek pole a zároveň otestujeme, zda tento prvek (na který jsme se právě posunuli) není nulový ukazatel (výrazem *++argv). Logická konjunkce zaručuje, že se její první parametr vyhodnotí dříve než její druhý parametr, z tohoto důvodu si můžeme dovolit použít argv i v druhé částí podmínky. V druhé části podmínky testujeme, zda první znak aktuálně zpracovávaného parametru programu (**argv) je pomlčka. Pokud je celá podmínka cyklu splněna, aktuálně zpracovávaný parametr programu je přepínač (začíná na pomlčku). V těle cyklu je jediný příkaz a to vícenásobně větvení switch, kterým zjišťujeme, který konkrétní přepínač zpracováváme. V případě parametrů –n a –w uložíme do příslušné proměnné hodnotu 1. Jakýkoliv jiný parametr znamená chybu a zavolá se funkce error, (nejedná se o knihovní funkci, musíme si její tělo napsat sami), která například může uživateli vypsat s jakými parametry má program volat a skončit program. Po skončení cyklu ze zjistí, zda mezi parametry programu zbývají alespoň dva dosud nezpracované parametry a ty se považují za názvy souborů, aniž by jsme prováděli nějakou kontrolu. Protože jsem se ve while cyklu posouvali s ukazatelem argv, mají tyto zbývající parametry indexy 0 a 1. Následně se všechny zjištěné parametry předají funkci doit (její tělo si musíme napsat sami) k dalšímu zpracování. Ve zpracování parametrů programu, které jsme si nyní představili, je několik chyb, které nám umožňuje volat program s kombinacemi parametrů, které nejsou povolené. Najdete tyto chyby? Uměli byste tyto chyby opravit? Chyba č. 1: Stačí aby parametr začínal na znaky –n nebo –w a poté další znaky jsou v něm ignorovány. Například parametr –n089dWR se vyhodnotí jako –n. Chyba č. 2: Pokud po jménech souborů následují další parametry, jsou ignorovány. Správně by měla být vypsáno chybové hlášení, že žádné další parametry program mít nemá. Chyba č. 3: Parametry –n a –w se mohou opakovat v parametrech programu vícekrát. Nejedná se o chybu, která by zabránila zpracováni programu, ale uživatel by na ní mohl být upozorněn. Často se takovéto chování programu za chybu ani nepovažuje. Klíčové pojmy řetězec (neinicializovaný, nulový, prázdný) znaková a řetězcová konstanta funkce strlen, strcpy, strcat, strcmp, strchr, strstr lexikografické uspořádání parametry argc, argv
Otázky k rekapitulaci Upozornění: odpovědi na některé zde uvedené otázky nelze najít ve studijním textu tohoto tématického bloku. Lze je získat vlastním experimentováním se zdrojovými kódy nebo studiem doporučené literatury. Jakým způsobem je reprezentovaný řetězec? Platí nějaká omezení pro obsah či délku řetězce? Jakým způsobme lze řetězec inicializovat? Jaké operátory lze na řetězcích použít? Jakým způsobem okopírujeme hodnotu jednoho řetězce do jiného řetězce? Jakým způsobem se můžeme porovnat hodnoty dvou řetězců a s jakými možnými výsledky? Jak pracuje algoritmus na porovnávání řetězců? Jakým způsobem zjistíme délku řetězce? Jakým způsobem zjistíme hodnotu prvního znaku řetězce? V jaké knihovně se nacházejí funkce pro práci s řetězci? Jakými způsoby můžeme v řetězcích vyhledávat? Které z funkci pro práci s řetězci, které jsme neprobírali, vypadají užitečně? Co se stane, pokud funkci pro práci s řetězci předáme neinicializovaný ukazatel? Co se stane, pokud funkci pro práci s řetězci předáme nulový ukazatel? Jaké typy parametrů mají obvykle funkce pro práci s řetězci a jak se navzájem tyto typy liší? Kdo se stará o dostatek prostoru při kopírování řetězců a co se stane při jeho případném nedostatku? Co je to prázdný řetězec a k čemu je užitečný? Jaká funkce slouží k formátovanému výpisu na standardní výstup, jaké má parametry? Jakým způsobem lze zadat parametry programu při jeho spuštění? Jakým způsobem lze ve zdrojovém kódu programu přistoupit k parametrům programu zadaným programu při jeho spuštění? Co znamená hodnota parametru funkce main argc? Jakého datového typu je parametr funkce main argv? Co obsahuje? Můžeme zjistit z jakého adresáře byl program spuštěn? Jakým způsobem lze vypsat parametry programu na obrazovku? Jakým způsobem zjistíme, zda byl program zavolán s parametrem –n ? Své odpovědi zdůvodněte. Můžete přidat i syntaktické zápisy tam, kde je to vhodné. Doporučené příklady k naprogramování 1. Implementujte většinu ze standardních knihovních funkcí pro práci s řetězci: strlen, strcpy, strcat, strcmp, strchr, strstr, ... 2. Napište program, který lexikograficky setřídí řetězce, které mu jsou zadány jako parametry programu z příkazové řádky. Výstup se vypisuje na obrazovku. Můžete zkusit použít funkci qsort.
3. Napište program s názvem "zpracuj", který umí zpracovat parametry (ve formátu uvedeném dole), které mu mohou být předány při jeho spuštění. Ve funkci main vytvořte vhodné proměnné a do nich zaznamenejte, s jakými parametry byl program skutečně zavolán. Na vzájemném pořadí parametrů –a, –b, –c a –d nezáleží. V případě chybně zadaných parametrů programu, oznamte, k jaké chybě došlo. zpracuj -a číslo [-b řetězec [číslo1 číslo2]] [-c | -d] file1 file2 Studijní literatura Výklad často odkazuje na slajdy (ve formátu .ppt), které je vhodné si vytisknout. Je vhodné si pořídit nějakou knihu o programování v C nebo C++. Uvedené příklady knih berte pouze jako inspirativní. Miroslav Virius: Programování v C++ (ČVUT, 2. vydání 2004) Jesse Liberty, Bradley L. Jones: Naučte se C++ za 21 dní (Computer Press, 2. vydání, 2007) Knihu je dobré číst postupně a vlastním tempem, můžete mít i mírné zpoždění oproti našemu výkladu. Pořadí kapitol v knize neodpovídá úplně přesně pořadí, v jakém učivo probíráme. Tento tématický blok se zaměřte na řetězce Miroslav Virius: Pasti a propasti jazyka C++ (Brno, 2. vydání 2005) Zopakujte si kapitolu 3 Pole a ukazatele