4. díl - Dynamické textové řetězce a struktury v jazyce C

C++ Dynamická práce s pamětí Dynamické textové řetězce a struktury v jazyce C

V minulém dílu našeho seriálu o programování v jazyce C jsme se věnovali ukazatelové aritmetice. V dnešním dílu se znovu zaměříme na textové řetězce a struktury. Kromě toho, co jsme si ukazovali v prvních dílech seriálu, 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";

Pozn.: 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:

  1. 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 ořízneme. Bernd Ottovordemgen­tschenfelde má tedy smůlu.
  2. 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 funkce malloc() a free(). 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 je 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:

Dynamické textové řetězce v jazyce C

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í:

Dynamické struktury v jazyce C

Mimochodem, 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:

Dynamické struktury v jazyce C

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í:

Dynamické struktury v jazyce C

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. 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ě 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 :)


 

Stáhnout

Staženo 69x (65.43 kB)
Aplikace je včetně zdrojových kódů v jazyce c

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (1 hlasů) :
55555


 



 

 

Komentáře

Avatar
Libor Šimo (libcosenior):

Chybička
scanf(" %20[^\n]s", &buffer);
;)

Odpovědět 20.11.2014 13:34
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Libor Šimo (libcosenior)
David Čápka:

Díky, měla tam být samozřejmě velikost bufferu, opravil jsem to.

Odpovědět 20.11.2014 15:21
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
tomisoka
Redaktor
Avatar
tomisoka:

Nemělo by se použít toto?

scanf(" %100[^\n]s", buffer);

Sice "&buffer" funguje taky, ale moc bych na to nespoléhal:

main.c:5:1: warning: format ‘%[^
’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[101]’ [-Wformat=]
 scanf(" %100[^\n]s", &buffer);
 
Odpovědět 20.11.2014 17:39
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na tomisoka
David Čápka:

Jasně, pole je ukazatel, takže bez &, opraveno :)

Odpovědět 20.11.2014 21:12
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 4 zpráv z 4.