Obsah
V této části si shrneme přednosti použití dynamických datových struktur. Ukážeme si způsob správy, získávání a vracení dynamické paměti. A popíšeme si jednu ze základních dynamických datových struktur - seznam.
Dynamické datové struktury představují jakýsi protiklad ke statickým datovým strukturám.
Statická data mají svou velikost přesně a neměně určenou v okamžiku překladu zdrojového textu.
Dynamická data 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 dynamickými daty. O požadovaný paměťový prostor ovšem musíme požádat OS. Jakmile prostor dostaneme, pracujeme s ním prostřednictvím ukazatelů.
Dynamická data snižují paměťové nároky proto, že požadují právě tolik paměti, kolik je v daný okamžik třeba. Tím umožňují i vícenásobné používání přidělené dynamické paměti, pokud jsou paměťové nároky vhodně rozloženy během činnosti programu. Místo, odkud se dynamické paměť "bere", tedy kam se umisťují dynamická data, se nazývá hromada či halda (heap).
Potřebné znalosti | |
K zvládnutí obsahu této kapitoly je nutné mít znalosti z kapitoly 2 až 11 - tedy rozumět doposud probírané látce. Umět vytvářet struktury a uživatelské typy dat, rozumět složitějším typovým deklaracím. Rozumět pojmům jako struktura, unie, výčtový typ nebo bitové pole. |
Programovou podporu pro přidělování paměti z haldy a její navracení zpět, definuje ISO norma jazyka C pomocí několika funkcí. Jejich deklarace jsou umístěny v stdlib.h.
Tato funkce alokuje paměť potřebnou pro uložení struktury typu adr.
Příklad: | |
#include <stdio.h> #include <stdlib.h> struct adr { char jmeno[40]; char ulice[40]; char mesto[40]; char stat[40]; }; . . . struct adr *get_struct(void) { struct adr *p; if((p = malloc(sizeof(struct adr))) == NULL) { printf("Chyba pri alokaci pameti"); exit(1); } return p; } |
Následující funkce vrací ukazatel na dynamicky alokované pole 100 čísel typu float.
Příklad: | |
#include <stdio.h> #include <stdlib.h> float *get_mem(void) { float *p; p = calloc(100, sizeof(float)); if(!p) { printf("Chyba pri alokaci pameti"); exit(1); } return p; } |
Následující program nejprve alokuje místo pro 10 uživatelem zadaných řetězců a pak je uvolní.
Poslední příklad nejprve alokuje 17 znaků, zkopíruje do tohoto místa řetězec "toto je 16 znaku" a pak použije realloc() pro zvětšení velikosti na 18, aby se dala na konec řetězce vložit tečka.
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. Minimum statických dat, které pro správu takového seznamu potřebujeme, je ukazatel na jeho první prvek. Dále přirozeně potřebujeme podpůrné funkce. Popsaný seznam se rovněž nazývá jednosměrný seznam. 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.
Správné pochopení práce se seznamem si ukážeme na příkladu. Mějme za úkol spočítat četnost výskytu všech slov ve vstupním textu. Na závěr vytiskneme tabulku, v níž bude uvedena četnost výskytu a řetězec, představující slovo. 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 první argument příkazového řádku. Jinak je vstupem standardní vstup.
Za slova považuje program řetězce oddělené specifikovanými znaky. Tuto skutečnost můžeme ovšem měnit úpravou řetězce oddelovace, který obsahuje mezeru a escape sekvence oddělovačů. V cyklu while je funkcí gets() načítán řádek za řádkem, dokud gets() nevrátí NULL a program postupuje dál. Načtený řádek je na slova rozdělen pomocí funkce strtok. Pro ni je prohledávanou oblastí řetězec radek a oddělovači slov jsou znaky z řetězce oddelovace. Poprvé je nutno načíst další slovo hned po načtení řádku a nastavit tak prohledávanou oblast. Další čtení až do konce řádku probíhají tak dlouho, dokud není slovo ukazatel na NULL. Vnitřní cyklus zpracování řádku je ukončen a probíhá načítání dalšího řádku.
Pro každé načtené slovo je zavolána funkce pridej_slovo s argumenty prvni a slovo. Proměnná prvni představuje ukazatel na seznam, přesněji na strukturu námi vytvořeného typu struct_info.
Při prvním volání funkce pridej_slovo je seznam prázdný, funkce obdrží hodnotu NULL, proto je prohledávání seznamu přeskočeno a pomocí funkce malloc je alokována paměť pro položku velikosti sizeof(struct_info). Jakmile je tato dynamická položka vytvořena, je v ní naalokován řetězec délky odpovídající slovu s a řetězcové zarážce, konkrétně malloc(strlen(s)+1). Do vytvořeného prostoru je slovo s nakopírováno pomocí strcpy. Dosud jsme se stále odkazovali na slovo umístěné ve zpracovávaném řádku. Bez vytvoření kopie bychom jej načtením dalšího řádku ztratili. Kromě kopie řetězce musíme nastavit v nové položce seznamu počet výskytů na jedna.
Pak přichází klíčové místo. Připojení položky do seznamu. V našem případě připojíme stávající seznam přes vazební člen prvek->dalsi k nově vytvořenému prvku a ten se následně stává novým prvním prvkem seznamu, *prvni = prvek.
Když je v seznamu nejméně jeden prvek, musí být při přidávání slova nejprve prohlédnut stávající seznam. Provádíme to prvek za prvkem, kdy posouváme pomocný ukazatel prvek na další položku seznamu pomocí prvek = prvek->dalsi, dokud se nedostaneme na konec seznamu, indikovaný hodnotou NULL ve vazebním členu dalsi. Pokud při průchodu narazíme na výskyt stejného řetězce, jako je přidávané slovo, tedy je splněna podmínka strcmp(s, prvek->slovo), zvýšíme počet výskytů slova v položce a opustíme funkci pridej_slovo.
Zkrácený výpis:
1..POSLEDNI 1..PRIDAT 1..JESTE 1..UMIM! 1..SOUBOR pro pokracovani stiskni enter 1..JE 1..TOTO 1..WUER 1..OIEWR 2..83945 2..EWIRIWR
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ěnit 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é, indexy všech prvků zůstávají stejné, jen jich přibylo.