Lekce 4 - Dynamické textové řetězce a struktury v jazyce C
V minulé lekci, Aritmetika ukazatelů v jazyce C, jsme se věnovali ukazatelové aritmetice.
V dnešním tutoriálu programování v jazyce C se znovu zaměříme na textové řetězce a struktury. Kromě toho, co jsme si ukazovali v prvních lekcích kurzu základů C, s nimi totiž lze pracovat dynamicky.
Dynamické textové řetězce
Když jsme chtěli uložit nějaký textový řetězec, ukládali jsme ho
jako pole znaků (char
ů). U tohoto pole jsme museli pevně zadat
jeho velikost, jak to již u polí v Céčku bývá. A zadali jsme ji buď
explicitně:
char text[5] = "ahoj";
Pamatujte, že velikost je o 1 větší kvůli nulovému
znaku '\0'
.
Nebo jsme její dosazení nechali na Céčku:
char text[] = "ahoj";
Pokud ovšem chceme ukládat text, jehož délku předem neznáme (např. jména uživatelů, která zadají za běhu programu), máme 2 možnosti:
- Použít k ukládání textů pole znaků s pevnou velikostí (např. 21). Pro uživatele s délkou jména pod 20 znaků budeme plýtvat pamětí. Např. Jan Novák využije jen 9 znaků, 11 jich tedy zůstane v paměti neobsazených. Jméno uživatele, které je delší než 20 znaků, naopak musíme oříznout. Bernd Ottovordemgentschenfelde má tedy smůlu.
- Použít k ukládání textů ukazatele na
char
. Díky podobnosti polí a ukazatelů můžeme s takto vytvořenými dynamickými řetězci pracovat jako jsme byli zvyklí doposud a to dokonce i pomocí standardních funkcí. Bohužel se o řetězce budeme muset starat sami a tak přibudou funkcemalloc()
afree()
. K načítání řetězce pomocí standardních funkcí budeme navíc stejně muset použít buffer (viz dále), což situaci zkomplikuje. Vyhnuli jsme se omezení v bodě 1, ale pro nás programátory bude aplikace mnohem pracnější.
V praxi se pro ukládání řetězců používají oba způsoby, každý má své výhody a nevýhody. Vyzkoušejme si je:
1. Vytvoření statického řetězce
Vytvoření statického řetězce se jménem, které uživatel zadá, by vypadalo takto:
char jmeno[21]; printf("Zadej jméno: "); scanf(" %20[^\n]", jmeno); printf("Jmenuješ se %s", jmeno);
Výsledek:
c_textove_retezce
Zadej jméno: Jan Novak
Jmenuješ se Jan Novak
2. Vytvoření dynamického řetězce
Ukažme si, jak by tedy vypadalo vytvoření řetězce, který je přesně
tak dlouhý, jak ho uživatel zadal. Protože budeme k načítání z konzole
využívat funkci scanf()
, musíme si stejně vytvořit pomocný
statický řetězec, do kterého text necháme scanf()
uložit.
Tomuto pomocnému poli se často říká buffer. Ukažme si kód, který si
záhy vysvětlíme:
char buffer[101]; printf("Zadej jméno: "); // Načtení jména do pomocné paměti scanf(" %100[^\n]", buffer); // Vytvoření dynamického řetězce char* jmeno = (char *) malloc(strlen(buffer) + 1); // Nastavení hodnoty strcpy(jmeno, buffer); printf("Jmenuješ se %s", jmeno); // Uvolnění paměti free(jmeno);
Do pomocného pole si načteme řetězec od uživatele, mělo by být
dostatečně dlouhé, aby text pojalo. Podle délky zadaného řetězce
následně vytvoříme nový řetězec dynamický, v paměti alokujeme pole
char
ů. Asi vás nepřekvapí, že o 1 delší než je potřeba,
kvůli nulovému znaku. Hodnotu řetězci nastavíme na tu z bufferu pomocí
funkce strcpy()
. Nyní máme dynamický řetězec hotový a
můžeme ho používat jako jsme byli zvyklí doposud. Jen až ho přestaneme
potřebovat, je ho nutné uvolnit.
Možná se ptáte, v čem je tedy výhoda dynamického řetězce, když nám paměť stejně vždy bude zabírat buffer, který potřebujeme pro načítání. Když budeme načítat a ukládat milion jmen, stačí nám k tomu stále jediný buffer a to jen ve fázi čtení (poté ho můžeme zahodit). Při dynamickém uložení takového počtu jmen ušetříme spoustu paměti, kterou by jinak zabraly nevyužité znaky. Daní za to je delší kód a nutnost myslet na uvolňování takto vytvořených řetězců. Proto toto řešení nelze prohlásit za univerzální, ačkoli ho osobně preferuji nad statickými řetězci.
Odkazy na struktury
Nyní se přesuňme ke slíbeným strukturám. Ty jsme zatím předávali hodnotou. To znamená, že kdykoli jsme ji uložili do nějaké proměnné, struktura se do ni zkopírovala. Vyzkoušejme si to na příkladu.
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char jmeno[51]; int vek; char ulice[51]; } UZIVATEL; int main(int argc, char** argv) { UZIVATEL karel; strcpy(karel.jmeno, "Karel Nový"); strcpy(karel.ulice, "Šikmá 5"); karel.vek = 27; UZIVATEL uzivatel2 = karel; karel.vek = 15; printf("Jméno: %s\nUlice: %s\nVěk: %d", uzivatel2.jmeno, uzivatel2.ulice, uzivatel2.vek); return (EXIT_SUCCESS); }
Kód výše vytvoří uživatele do proměnné karel
a nastaví
mu příslušné hodnoty. Následně vytvoří další strukturu uživatele jako
proměnnou uzivatel2
a do ni uloží Karla. Protože je takto
vytvořená struktura hodnotový typ, můžeme ji jednoduše celou přiřadit a
zkopírovat. Změna věku Karla se uzivatel2
již netýká. Nakonec
druhého uživatele vypíšeme, budou v něm ty samé hodnoty jako měl Karel,
věk bude původní:
c_struktury2
Jméno: Karel Nový
Ulice: Šikmá 5
Věk: 27
Zde by se hodilo zmínit, že takovéto zkopírování celé proměnné najednou nebude fungovat u pole, jelikož je na rozdíl od struktury realizováno ukazatelem.
Předávání hodnotou je poněkud nepraktické a to hlavně když chceme
nějakou strukturu změnit. Přidejme do programu výše funkci, která
přijímá jako parametr strukturu typu UZIVATEL
, kterého nechá
zestárnout o 1 rok:
void zestarni(UZIVATEL uzivatel)
{
uzivatel.vek++;
}
Nyní nechme Karla zestárnout a vypišme ho:
UZIVATEL karel; strcpy(karel.jmeno, "Karel Nový"); strcpy(karel.ulice, "Šikmá 5"); karel.vek = 27; zestarni(karel); printf("Jméno: %s\nUlice: %s\nVěk: %d", karel.jmeno, karel.ulice, karel.vek);
Nic se nestane:
c_struktury2
Jméno: Karel Nový
Ulice: Šikmá 5
Věk: 27
Je to samozřejmě proto, že se Karel zkopíroval do parametru funkce a
změnila se tato kopie, s původním Karlem se nestalo nic. Jedno z řešení by
bylo upraveného Karla zas vrátit a přepsat. Když si ale uvědomíme, že se
kopíruje postupně každá vlastnost struktury, procesor úplně zbytečně
provádí velkou spoustu instrukcí. Navíc v aplikacích struktury stejně
často tvoříme pomocí mallocu()
, pokud s nimi chceme pracovat
mimo funkce, ve kterých byly vytvořené. (Céčko totiž paměť po
lokálních proměnných vytvořených ve funkci uvolňuje po ukončení funkce,
nejsou tedy trvanlivé a nelze je vrátit).
Přepišme si program tak, aby používal ukazatele:
void zestarni(UZIVATEL* uzivatel) { uzivatel->vek++; } int main(int argc, char** argv) { UZIVATEL* p_karel = malloc(sizeof(UZIVATEL)); strcpy(p_karel->jmeno, "Karel Nový"); strcpy(p_karel->ulice, "Šikmá 5"); p_karel->vek = 27; UZIVATEL* p_uzivatel2 = p_karel; p_karel->vek = 15; zestarni(p_uzivatel2); printf("Jméno: %s\nUlice: %s\nVěk: %d", p_uzivatel2->jmeno, p_uzivatel2->ulice, p_uzivatel2->vek); free(p_karel); return (EXIT_SUCCESS); }
Všimněte si, že pokud chceme získat data z ukazatele na strukturu, místo
abychom před něj napsali dereferenční operátor (hvězdičku), tak
zaměníme tečku za operátor šipky (->
).
Výstup programu je následující:
c_struktury2
Jméno: Karel Nový
Ulice: Šikmá 5
Věk: 16
Aplikace nejprve alokuje paměť pro strukturu typu UZIVATEL
a
její adresu uloží do proměnné p_karel
. Nastaví mu hodnoty a
následně na Karla vytvoří ještě jeden ukazatel p_uzivatel2
.
Karlovi změní věk na 15 let a uživatele, na kterého ukazuje
p_uzivatel2
, nechá zestárnout o 1 rok. Karlovi tak bude 16,
jelikož se mu věk předtím nastavil na 15. Vidíme, že oba ukazatele
ukazují na stejného uživatele. Následně vypíšeme Karla pomocí pointeru
p_uzivatel2
. Paměť nezapomeneme uvolnit.
Možná vám to připadá jako zbytečné čarování s ukazateli, je ovšem velmi důležité pochopit jak předávání funguje na těchto malých příkladech, abychom se poté vyznali ve větších aplikacích.
Zdrojové projekty k dnešní lekci jsou níže ke stažení.
Příště, v lekci Dynamická pole (vektory) v jazyce C, se naučíme vytvořit takovou datovou strukturu,
která by nás nijak neomezovala svou velikostí a mohli jsme do ni přidávat
stále nové a nové položky. Půjde o dynamické pole, kterému se někdy
říká vektor. Máte se na co těšit
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 120x (65.43 kB)
Aplikace je včetně zdrojových kódů v jazyce C