Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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:

  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 musíme oříznout. 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 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 116x (65.43 kB)
Aplikace je včetně zdrojových kódů v jazyce C

 

Předchozí článek
Aritmetika ukazatelů v jazyce C
Všechny články v sekci
Dynamická práce s pamětí v jazyce C
Přeskočit článek
(nedoporučujeme)
Dynamická pole (vektory) v jazyce C
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
19 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity