Distanční opora předmětu: Programování v jazyce C Tématický blok č. 8: Dynamické datové struktury, ladění programů Autor: RNDr. Jan Lánský, Ph.D. Obsah kapitoly 1 Dynamické datové struktury 1.1 Příklad: Jednosměrný spojový seznam 1.2 Příklad: Fronta 1.3 Hlavičky funkcí 2 Ladění programu 2.1 Ladění programu v integrovaném vývojovém prostředí 2.2 Ladění programu bez integrovaného vývojového prostředí 2.3 Bezpečné programování 2.4 Udržovatelné zdrojové kódy 2.5 Časté chyby Studijní cíle 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í. Program umí číst data ze souboru a do souboru je zapisovat. Program umí vhodně využít dynamických datových struktur. Zdrojový kód programu je přehledný a doplněný komentáři. Schopnost navrhnout efektivní postup, jak odladit libovolnou chybu ve vlastním programu.
Čas potřebný ke studiu 2 - 4 hodiny na prostudování výukových textů + zodpovězení otázek k rekapitulaci 4 - 16 hodin 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) 1 hodina na vypracování úlohy POT 2, která je formou testu Č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. Připomeneme si, které dynamické datové struktury máme znát z předmětu Programovaní, který jsme absolvovali minulý rok. Na lineárním spojovém seznamu si ukážeme některé programátorské obraty, které lze použít při implementaci funkcí pracujícími s dynamickými datovými strukturami. Vysvětlíme se jak ladit v programech syntaktické chyby. Ukážeme si jak lze ladit program za běhu v integrovaným vývojovém prostředí. Ukážeme si metody ladění programu bez využití integrovaného vývojového prostředí. Seznámíme se zásadami bezpečného programování a metodami pro udržovatelnost zdrojových kódů. Ukážeme si ukázky nejčastějších chyb. 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 Dynamické datové struktury Statické datové struktury (například pole) neumožňují v průběhu práce se strukturou zvyšovat maximální počet prvků v datové struktuře obsažených. Pole má problémy i s vkládáním prvků doprostřed pole, při němž může docházek k rozsáhlým přesunům dat. Dynamické datové struktury jsou oproti statickým datovým strukturám náročnější na implementaci, ale z hlediska časové složitosti algoritmů pro vyhledávání, vkládání a mazání prvků mohou být v některých případech výrazně lepší než struktury statické. Jednotlivé dynamické datové struktury jsme probírali v loňském roce v předmětu Programování. Je očekávána aktivní znalost následujících datových struktur: jednosměrný a obousměrný (lineární) spojový seznam, zásobník, fronta, binární vyhledávací strom, obecný strom a obecný graf. V rámci tohoto tématického bloku si pouze na několika příkladech ukážeme, několik užitečných myšlenek, jak lze implementovat dynamické datové struktury v jazyku C. 1.1 Příklad: Jednosměrný spojový seznam Na slajdu č. 122 vidíme definici struktury osoba, pomocí které budeme implementovat jednosměrný spojový seznam. Struktura osoba obsahuje dvě datové položky (jméno a rok narození) nesoucí informaci o reprezentované osobě. Poslední datová položkou struktury
osoba je pojmenována jako dalsi a je typu ukazatel na strukturu osoba, pomocí kterého
budeme přistupovat k dalšímu prvku spojového seznamu. Všimněme si středníku za definicí struktury. Pod definicí struktury se nachází deklarace proměnné zamestnanci typu ukazatel na strukturu osoba. Dole na slajdu je znázorněn spojový seznam obsahující tři prvky. Na první prvek seznamu ukazuje ukazatel zamestnanci (graficky znázorněno šipkou). Poslední prvek seznamu má položku dalsi nastavenou na hodnotu 0 (graficky znázorněno jako uzemnění). Na slajdu č. 123 vidíme zdrojový kód, který by mohla obsahovat funkce sloužící k nalezení ukazatele na zaměstnance (prvek spojového seznamu zamestnanci) narozeného v roce 1972. Pokud je takových zaměstnanců více, funkce vrátí ukazatel na prvního z nich. Pokud, žádný takový zaměstnanec neexistuje, funkce vrací hodnotu 0 (nulový ukazatel). K průchodu spojovým seznamem jsme využili for cyklus s řídící proměnnou cyklu os, která je typu ukazatel na strukturu osoba. Řídící proměnná cyklu je inicializována prvním prvkem spojového seznamu. Cyklus končí pokud řídící proměnná cyklu je nulový ukazatel (jsme na konci seznamu). Inkrementace řídící proměnné cyklu je provedena posunutím na další prvek seznamu. V případě nalezení hledaného prvku je ukazatel na tento prvek vrácen jako návratová hodnota funkce (cyklus nebude dál pokračovat). V případě nenalezení prvku a projití celého seznamu je vrácen nulový ukazatel posledním řádkem zdrojového kódu. Dole na slajdu je graficky zobrazeno jak se posouvá ukazatel os (řídící proměnná cyklu) po spojovém seznamu. Na slajdu č. 124 vidíme zdrojový kód, pomocí kterého přidáme Nového zaměstnance na začátek spojového seznamu. Nejprve si dynamicky alokujeme paměť pro nového zaměstnance a provedeme test, zda se alokace skutečně zdařila. V případě neúspěchu se zavolá uživatelská funkce error (tělo nutné dopsat). Po provedení alokace nakopírujeme do získané paměti údaje o novém zaměstnanci. Původní začátek spojového seznamu napojíme za nově vzniklého zaměstnance. Nový zaměstnanec se stane novým začátkem seznamu (poslední řádek zdrojového kódu). Situace je opět graficky znázorněna dole na slajdu. Šipky červené barvy ukazují nasměrování ukazatelů. Na slajdu č. 125 vidíme zdrojový kód, pomocí kterého přidáme nového zaměstnance do prostřed spojového seznamu, za prvek, na který ukazuje ukazatel sem. Zajímavé jsou až poslední dva řádky zdrojového kódu, kde přepojujeme ukazatele. Nový prvek nejprve musí svým ukazatelem na další prvek začít ukazovat na prvek, následující za prvkem, na který dosud ukazuje ukazatel sem. Teprve poté může prvek, na který ukazuje ukazatel sem, svým ukazatelem na další prvek začít ukazovat na právě vkládaný prvek. Pokud při vkládání nového prvku prohodíme správné pořadí přepojování ukazatelů, nenávratně ztratíme část spojového seznamu následující za prvkem, na který ukazuje ukazatel sem. Situace je opět graficky znázorněna v dolní části slajdu. 1.2 Příklad: Fronta Na slajdu č. 126 vidíme implementaci dynamické datové struktury zvané fronta. Fronta je tvořena jednosměrným spojovým seznamem, ke kterému lze přistoupit přes ukazatele na jeho začátek a konec. Graficky je struktura znázorněna napravo na slajdu. Pro práci s frontou budeme používat tři funkce, všechny tyto funkce mají jako jeden z parametrů ukazatel na
strukturu fronta, se kterou právě pracují. Funkce init vytvoří novou prázdnou frontu. Funkce put vloží na konec fronty nový prvek. Funkce get odebere prvek ze začátku fronty. Na konci zdrojového kódu vidíme přiklad použití. Vytvoříme proměnnou f typu struktura fronta. Adresu proměnné f získanou operátorem reference předáme funkci init, která frontu inicializuje na prázdnou. Pomocí funkce put vložíme do fronty prvek s hodnotou 1 a následně ho pomocí funkce get z fronty vybere. Funkcím put i get byla předána adresa fronty f pomocí operátoru reference. 1.3 Hlavičky funkcí Při práci s některými dynamicky alokovanými strukturami je potřeba dávat zvýšeny pozor na hlavičky funkcí, které s nimi mají pracovat. Zejména funkcí pro vkládání a mazání prvků. U spojového seznamu nebo binárního vyhledávacího stromu platí obvykle konvence, že nulový ukazatel reprezentuje prázdnou strukturu. Problém tedy nastává v situacích jak do prázdného spojového seznamu (nebo stromu) vložit nový prvek a jak smazat poslední prvek ze spojového seznamu (nebo stromu). Pro jednoduchost si celou situaci popíšeme na funkcí vkládání prvku do spojového seznamu celých čísel, ostatní výše popisované případy se řeší analogicky. Funkce insert pro vkládání nového prvku bude mít dva parametry. Prvním parametrem je ukazatel na první prvek spojového seznamu sez a druhým parametrem je číslo n, které chceme do spojového seznamu vložit. Pokud je tento seznam neprázdný a my do něj vložíme nový prvek kamkoliv za první prvek seznamu, vše je v pořádku. Problém nastane, když do prázdného seznamu budeme vkládat první prvek. Funkci insert jsme zavolali z funkce main a jako první parametr jí předaly ukazatel p na prázdný seznam (mající hodnotu 0 = nulový ukazatel). Tato adresa 0 se okopíruje do parametru sez funkce insert. Pro vložení prvního prvku do prázdného seznamu musíme ukazatel sez nastavit na tento první prvek. Přepíšeme adresu 0 v něm uloženou na adresu první prvku seznamu. Problém spočívá v tom, že pracujeme pouze s kopií adresy uložené v ukazateli p. Adresa uložená v proměnné sez se po skončení funkce insert nenakopíruje nazpět do p. Situaci lze vyřešit dvěmi způsoby. Funkce, která vkládá nebo maže prvek ze spojového seznamu, musí jako návratovou hodnotu vracet ukazatel na první prvek seznamu nebo tato funkce musí mít parametr typu dvojnásobný ukazatel na první prvek seznamu. Řešení s návratovou hodnotou je nepraktické, protože ji musíme po každém zavolání funkce pro vkládání nebo mazání prvků vždy ukládat do ukazatele, který na první prvek daného seznamu ukazuje. Řešení s parametrem typu dvojnásobným ukazatel je pro změnu programátorsky náročnější při psaní těla funkce. 2 Ladění programu Ladění programu lze rozdělit do dvou fází. V první fázi se snažíme odstranit syntaktické chyby, aby program šel vůbec přeložit a spustit. V druhé fázi pak testujeme program, zda na zadané vstupy vrací požadované výstupy. Vytvořit přeložitelný program je snazší část naší práce, přesto i zde mohou nastat problémy. Program je vhodné překládat vždy po dopsání nějakého uceleného kusu zdrojového kódu,
třeba celé funkce. Vyhneme se tak nahromadění různých spolu nesouvisejících chyb. Je dobré si i pamatovat, které kusy zdrojového kódu přibyly od posledního úspěšného překladu. Častou chybou bývá neznámý identifikátor. Může se jednat o použití proměnné, kterou jsme zapomněli definovat. Občas chybí vložení hlavičkového souboru, ve kterém se daná funkce nachází. Velmi těžko odhalitelnou chybou bývají překlepy (třeba prohození písmen) v názvu proměnné. V názvu identifikátorů také záleží na velikosti písmen (je rozdíl mezi Funkce a funkce). Při kopírování zdrojových kódů například ze slajdů nebo textového dokumentu ve Wordu, se můžou okopírovat také různé bílé znaky, které překladač detekuje jako nepovolený znak. Kopírování je proto lepší provádět s mezistupněm poznámkového bloku. Některé znaky zase můžou opticky vypadat podobně, třeba různé typy pomlček se podobají znaménku minus. Lidským okem je tato chyba neodhalitelná, jediné bezpečné řešení je zdrojový kód opsat místo kopírování. Pokud překladač hlásí nesmyslnou chybu na prvním řádku zdrojového kódu, je dobré se podívat i do vložených hlavičkových souborů (jen do uživatelských, ne do standardních), zda chyba není v nich, například zapomenutý středník za definicí struktury nebo hlavičky funkce. Při odstraňování syntaktických chyb nesmíme v programu dělat úpravy, jejichž smysl nechápeme. Pokud překladač ohlásí chybějící středník, nemusí to vždy znamenat, že středník skutečně chybí. Dopsat bez rozmyslu středník na konec řádku, na kterém se chyba objevila může znamenat zavlečení chyby další. Velmi často lidé středníkem takto oddělí hlavičku funkce od jejího těla, nebo oddělí hlavičku cyklu od jeho těla. Náhodné umazávání a připisování operátorů dereference (*) přináší podobné problémy. Důležité je sledovat, zda všechny závorky jsou spárované. Po napsání otevírací závorky je vhodné rovnou napsat i závorku zavírací a postupně prostor mezi nimi naplňovat. Pokud však k chybějící párové závorce dojde i přes naší snahu, nestačí závorku napsat na libovolné místo ve zdrojovém kódu. Program možná půjde přeložit, ale nebude pracovat správně. 2.1 Ladění programu v integrovaném vývojovém prostředí Předpokládejme, že se nám podařilo program přeložit. Většina integrovaných vývojových prostředí nabízí možnost ladit běžící program pomocí nějakého nástroje. Ve Visual Studiu lze program spustit buďto v ladícím (Debug) nebo finálním (Release) módu. Základní nastavení je ladící mód. V ladícím módu jsou do spustitelného kódu programy přidány dodatečné informace například o názvech proměnných a funkcí. Ladící mód také bývá několikanásobně pomalejší než finální mód. Protože programy v ladícím a finálním módu jsou různé, některé chyby (zejména špatná indexace polí) se občas projeví až ve finálním módu, aniž by se objevily v ladícím módu. V ladícím módu můžeme program zastavovat na libovolném řádku s použitím breakpointů. Spuštěný program se zastaví při každém průchodu takto označeném řádku. Je možné nastavit i podmíněné breakpointy, na nich se program zastaví pouze při splnění nějaké dodatečné podmínky, například na hodnotu nějaké proměnné. Program lze i krokovat řádek za řádkem s výběrem, zda chceme vstoupit do těl funkcí, které se volají (Step Into - vstupujeme, Step Over - nevstupujeme).
Pokud je program pozastaven ve svém běhu některým z výše uvedených mechanizmů, můžeme zjišťovat aktuální hodnoty proměnných (obvykle stačí najet myší nad název proměnné). Případně sledovat více hodnot proměnných najednou v nástroji Watch. Sledovat můžeme hodnoty lokálních proměnných funkce, ve které je program zastaven, a hodnoty globálních proměnných. Velice užitečný je i zásobník volání funkcí, pomocí kterého se můžeme podívat do funkce, která zavolala funkci, ve které právě jsme. Takto se můžeme postupně dostat až do funkce main. Zejména při rekurzivním volání jedné a té samé funkce je tento nástroj obtížně nahraditelný. V každé z těchto funkcí můžeme sledovat hodnoty lokálních proměnných. 2.2 Ladění programu bez integrovaného vývojového prostředí Někdy jsme nuceni použít překladač bez integrovaného vývojového prostředí, nebo prostředky integrovaného vývojového prostředí se nám program odladit nepodařilo. Metoda ladících tisků spočívá v obohacení zdrojových kódů o výpisy hodnot lokálních či globálních proměnných na obrazovku, či do souboru. Data z ladících výpisů lze zpracovávat i pomocí databáze, ladící výpisy mohou být přímo ve formě SQL příkazů insert. Největší umění v této technice je rozhodnout jaké proměnné chceme tisknout, na jakých místech programu a případně za jakých dalších podmínek. Nevýhodou této metody je snížení přehlednosti zdrojových kódů. Jestli program funguje správně si můžeme být stoprocentně jistí pouze otestováním všech možných vstupů a zkontrolováním výstupů. Z praktického hlediska to bývá ve většině případů nemožné. Řada lidi prohlásí program za funkční, pokud jim funguje na několika málo vstupech. Kompromisem mezi těmito dvěma přístupy, je metoda automatického testování, která není příliš pracná a dává nám vysokou šanci na odhalení chyby. Vyrobí se velké množství testovacích dat (můžeme je vyrobit i samostatným programem). Testovací data by měla být co nejrozmanitější. Následně pomocí skriptu spouštíme program postupně na jednotlivých testovacích datech a výsledky vyhodnocujeme (pokud to jde, tak zase automatizovaně). Testy můžeme navíc opakovat po každé modifikaci programu. Minimalizace zdrojového kódu spočívá v zakomentování částí zdrojového kódu a pozorování, zda tato změna má vliv na výskyt chyby. Zakomentovaná část zdrojového kódu se postupně zvětšuje a nechává se minimální podmnožina programu, na kterém se chyba ještě projeví. Poté se najde nejmenší část zdrojového kódu, která stačí zakomentovat, aby se chyba neprojevila, a tato část se zkoumá. Tato metoda je velmi užitečná pro ladění chyb, při kterých program provede neplatnou operaci a je násilně ukončen operačním systémem. Minimalizace testovacích dat se používá v případě, že program nepracuje správně na nějakých velkých testovacích datech. Princip je analogický jako u metody minimalizace zdrojového kódu, pouze místo zdrojového kódu upravujeme testovací data. Pokud si nejsme jistí, zda u dynamicky alokovaných polí přistupujeme pouze k prvkům, které do nich patří, lze si napsat vlastní ladící funkce pro alokaci a dealokaci paměti. Každý alokovaný blok paměti je obložen prostorem obsahujícím předem definované značky. Při dealokaci pak kontrolujeme neporušenost těchto značek. Ukázkový zdrojový kód těchto funkci lze nalézt na slajdu č. 145. Tato technika nás neochrání pře dereferencí
neinicializovaného ukazatele. Pokud překročíme hranici pole příliš (třeba o tisíce), je tato technika taká neúčinná. 2.3 Bezpečné programování Bezpečné programování je soubor doporučení, které snižují riziko výskytu chyb ve zdrojových kódech. Jejich dodržování obvykle nepřináší žádné zásadnější ztížení programátory práce, ale naopak může ušetřit velké množství času stráveného hledáním chyb. Překladač hlásí kromě chyb i různá varování. Při nálezu chyby program přeložit nejde, při nálezu varování přeložit jde. Varování jsou upozornění na nějaký potenciálně nebezpečný programátorský obrat ve zdrojovém kódu. Naším cílem by mělo být, psát zdrojové kódy tak, aby při jejich překladu nevznikala žádná varování. Neměli bychom používat globální proměnné, s výjimkou konstant. Funkce smí měnit a číst pouze ta data, která jí byla předána jako parametry. Měli bychom důsledně používat klíčové slovo const, pokud je parametr funkce typu ukazatel a funkce má z něj data pouze číst. Je nutné kontrolovat návratové hodnoty funkcí, které mohou skončit neúspěchem. Jedná se zejména o funkce pro práci s dynamicky alokovanou pamětí (malloc) a pro práci se soubory (fopen). Parametry vstupující do funkce je vhodné na začátku funkce otestovat, jestli splňují požadavky na ně kladené. Například nemusí se nám líbit záporné číslo jako velikost pole nebo nulový ukazatel udávající adresu paměti, kam má proběhnout kopírování dat. Často dochází k chybě, že znovu přistoupíme k bloku paměti, který jsme uvolnili funkcí free. Nejlepší prevence proti této chybě je všem ukazatelům ukazující na tento blok paměti přiřadit hodnotu 0. Pokud mezi parametry funkce ukazatel na první prvek pole, měl by se mezi nimi nacházet i parametr určující velikost pole. V těle funkce bychom měli důsledně kontrolovat, zda přistupujeme k platným indexům daného pole. Největším nepřítelem je chyba, která není vždy a okamžitě smrtelná. Takováto chyba se náhodně objevuje při běhu programu, bez souvislosti s místem ve zdrojovém kódu, na kterém vznikla. Je lepší dereferencovat nulový ukazatel a být okamžitě ukončen od operačního systému, než přepsat náhodnou paměť (třeba jinou proměnnou) a hledat chybu v algoritmu použitém v programu. 2.4 Udržovatelné zdrojové kódy Zdrojový kód programu by měl být přehledný a měl by ho být schopen přečíst a pochopit nejen sám autor, ale i libovolný jiný programátor, který má s autorem lepší nebo srovnatelné znalosti. Nepřehledně napsaný zdrojový kód může být po několika měsících nesrozumitelný i pro samotného autora. Zdrojový kód je třeba rozumně rozdělovat do modulů (modul maximálně pár tisíc řádek zdrojového kódu). Ke každému modulu by měl existovat hlavičkový soubor. V rámci jednoho modulu by zdrojový kód měl být vhodně rozčleněn do funkcí. Spolu související data by měla
být udržována v jedné struktuře. Například jméno, věk a plat zaměstnance je lepší udržovat jako jednu strukturu, než tři samostatné položky. Zdrojový kód by měl obsahovat komentáře. U každé funkce je vhodné napsat co dělá, co vyjadřuje její návratová hodnota a její parametry, případně jaká omezení jsou na ně kladena. Tyto komentáře by měl obsahovat i hlavičkový soubor. Naprostou nezbytností je odsazování zdrojového kódu, které za nás naštěstí obvykle dělá integrované vývojové prostředí. Zdrojový kód s nesprávným odsazením obvykle budí dojem, že jeho jednotlivé bloky jsou zanořeny úplně jinak než ve skutečnosti. Názvy identifikátorů by měly být smysluplné, můžou být i víceslovné. V případě víceslovních identifikátorů se obvykle používá konvence, že jednotlivá.slova jsou oddělena podtržítkem. Je vhodné smysluplně pojmenovávat konstanty, důsledně používat znakové konstanty, pokud pracujeme se znaky. 2.5 Časté chyby Na slajdech č. 128 – 130 jsou shromážděny některé velmi časté chyby, které lze nalézt ve zdrojových kódech. Jednotlivé typy chyb jsou očíslovány. Pod popisem chyby bývá obvykle uvedeno několik příkladů. Chyba č. 1 je chybějící nebo přebývající středník. Obvykle se středník zapomíná za posledním příkazem ve složeném příkazu, za definicí struktury nebo za hlavičkou funkce v hlavičkovém souboru. Naopak se středníkem často chybně oddělí hlavička cyklu od jeho těla, případně hlavička funkci od jejího těla ve zdrojovém souboru. Chyba č. 2 je přehození parametrů funkce (pokud jsou oba prohozené parametry stejného datového typu) nebo části konstrukce. Největší úskalí této chyby spočívá v tom, že překladač jí nemůže odhalit. Nádherným příkladem této chyby je prohození parametrů u funkce strcpy. Po prohození inicializace, podmínky a inkrementu ve for cyklu překladač také žádnou chybu neohlásí. Chyba č. 3 je zapomínání příkazu break pro ukončení bloků příkazů vykonávaných pro dané návěští v příkazu vícenásobného větvení switch. Chyba č. 4 je špatné používání direktivy #define. Tato direktiva funguje jako textová náhrada identifikátoru za nějaký výraz. Pokud chceme¨tímto způsobem definovat konstantu, nesmíme použít ani operátor přiřazení, ani středník. Chyba č. 5 je špatně spárovaná else větev s jiným podmíněným příkazem if než jsme zamýšleli. Více informací lze nalézt v tématickém bloku č. 2, kapitola 4.2. Chyba č. 6 je záměna celočíselného dělení místo dělení reálného. Více informací lze nalézt v tématickém bloku č. 3, kapitola 2.1. Chyba č. 7 je podcenění zaokrouhlovacích chyb při práci s reálnými čísly. Test na rovnost dvou na první pohled stejných reálných čísel, z nich alespoň jedno vzniklo složitějším výpočtem, nebývá téměř nikdy úspěšný.. Více informací lze nalézt v tématickém bloku č. 3, kapitola 1.5.
Chyba č. 8 je volání funkce printf s parametry, které odporují údajům obsažených ve formátovacím řetězci. Více informací lze nalézt v tématickém bloku č. 5, kapitola 1.4. Chyba č. 9 je záměna logických a bitových operátorů. Více informací lze nalézt v tématickém bloku č. 3, kapitola 2.3. Chyba č. 10 je opomenutí zkráceného vyhodnocování výrazů. Více informací lze nalézt v tématickém bloku č. 3, kapitola 2.4. Chyba č. 11 je záměna znaků. Velmi často se zaměňuje středník s čárkou, na spoustě míst toto prohození nevyvolá ani syntaktickou chybu. Dalším problémem bývá použití čárky místo tečky pro oddělení desetinných míst reálného čísla. Chyba č. 12 je neznalost priority operátorů. Více informací lze nalézt v tématickém bloku č. 3, kapitola 2. Chyba č. 13 je spoléhání na pravidla vyhodnocování příkazů, která mohou vypadat logicky, ale ve skutečnosti neplatí. Více informací lze nalézt v tématickém bloku č. 3, kapitola 2.9. Chyba č. 14 je použití hodnoty neinicializované proměnné, zvláště pak dereference neinicializovaného ukazatele. Více informací lze nalézt v tématickém bloku č. 4, kapitola 2.1 Chyba č. 15 je použití ukazatele na proměnnou, která již zanikla. Tato chyba nejčastěji vzniká, pokud z nějaké funkce vrátíme ukazatel na její lokální proměnnou a tento ukazatel dále použijeme. Chyba č. 16 je chybějící nebo přebývající operátor reference u parametru, který chceme předávat jakoby odkazem. Klíčové pojmy lineární spojový seznam, binární vyhledávací strom, zásobník, fronta ladící a finální mód programu automatické testování ladící tisky minimalizace zdrojových kódů udržovatelné zdrojové kódy
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é výhody může mít použití dynamických datových struktur? Vysvětlete na konkrétních příkladech. Jednoznačně popište následující dynamické datové struktury: jednosměrný spojový seznam, obousměrný spojový seznam, fronta, zásobník, binární strom, binární vyhledávací strom, graf. U každé ze struktur uveďte i operace, které lze s ní provádět. V jakých situacích lze mít statickou proměnnou od dynamické datové struktury? Kdy to naopak není možné? Jaké implementační problémy působí práce s prázdnými datovými strukturami? Jaká jsou možná řešení? Jakými způsoby je nejvýhodnější odstraňovat syntaktické chyby a naopak jaké způsoby jsou nevhodné? Jaké nástroje pro ladění programu za jeho běhu obvykle nabízí integrované vývojové prostředí? Jaké jsou klávesové zkratky těchto nástrojů ve Visual Studiu? Jaká pravidla je nutná dodržovat při bezpečném programování? Jaké konvence je nutno dodržovat, aby zdrojové kódy byly čitelné a srozumitelné? Na slajdech č. 128-130 je uveden seznam častých chyb i s příklady výskytu. K jednotlivých příkladům uveďte, kde přesně se v nich chyba nachází a jak by se opravila. 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. Dobrovolný domácí úkol č. 2: Implementujte jednosměrný a obousměrný spojový seznam a binární vyhledávací strom, ve kterém bude klíčem datová položka typu celé číslo. Implementujte funkce Insert, Find a Delete. 2. Upravte příklad (1), aby klíčem byla datová položka typu řetězec. 3. Upravte příklad (1), aby klíčem byla datová položka typu struktura zaměstnanec. 4. Napište gumové pole. To je takové pole, do kterého můžeme vložit libovolný počet prvků a zároveň nemusíme nikde zadávat jeho délku. Implementujte funkce Append, Find a Delete. 5. Implementujte zásobník, implementujte funkce Push a Pop. 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 dynamické datové struktury.