Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

4. diel - Dynamické textové reťazce a štruktúry v jazyku C

V minulej lekcii, Aritmetika ukazovateľov v jazyku C , sme sa venovali ukazatelové aritmetike. V dnešnom tutoriále programovaní v jazyku C sa znovu zameriame na textové reťazce a štruktúry. Okrem toho, čo sme si ukazovali v prvých lekciách kurze základov C, s nimi totiž možno pracovať dynamicky.

Dynamické textové reťazce

Keď sme chceli uložiť nejaký textový reťazec, ukladali sme ho ako pole znakov (char ov). U tohto poľa sme museli pevne zadať jeho veľkosť, ako to už u polí v céčka býva. A zadali sme ju buď explicitne:

char text[5] = "ahoj";

Pamätajte, že veľkosť je o 1 väčšia kvôli nulovému znaku '\0'.

Alebo sme jej dosadenie nechali na céčko:

char text[] = "ahoj";

Ak však chceme ukladať text, ktorého dĺžku vopred nevieme (napr. Mená používateľov, ktorá zadajú za behu programu), máme 2 možnosti:

  1. Použiť na ukladanie textov poľa znakov s pevnou veľkosťou (napr. 21). Pre užívateľov s dĺžkou mena pod 20 znakov budeme plytvať pamäťou. Napr. Jan Novák využije len 9 znakov, 11 ich teda zostane v pamäti neobsadených. Meno užívateľa, ktoré je dlhšie ako 20 znakov, naopak musíme orezať. Bernd Ottovordemgen­tschenfelde má teda smolu.
  2. Použiť na ukladanie textov ukazovateľa na char. Vďaka podobnosti polí a ukazovateľov môžeme s takto vytvorenými dynamickými reťazci pracovať ako sme boli zvyknutí doposiaľ a to dokonca aj pomocou štandardných funkcií. Bohužiaľ sa o reťazca budeme musieť starať sami a tak pribudnú funkcie malloc() a free(). K načítanie reťazca pomocou štandardných funkcií budeme navyše rovnako musieť použiť buffer (pozri ďalej), čo situáciu skomplikuje. Vyhli sme sa obmedzenia v bode 1, ale pre nás programátorov bude aplikácia oveľa pracnejšie.

V praxi sa pre ukladanie reťazcov používajú oba spôsoby, každý má svoje výhody a nevýhody. Vyskúšajme si ich:

1. Vytvorenie statického reťazca

Vytvorenie statického reťazca s menom, ktoré užívateľ zadá, by vyzeralo takto:

char jmeno[21];
printf("Zadej jméno: ");
scanf(" %20[^\n]", jmeno);
printf("Jmenuješ se %s", jmeno);

výsledok:

c_textove_retezce
Zadej jméno: Jan Novak
Jmenuješ se Jan Novak

2. Vytvorenie dynamického reťazca

Ukážme si, ako by teda vyzeralo vytvorenie reťazca, ktorý je presne tak dlhý, ako ho používateľ zadal. Pretože budeme k načítanie z konzoly využívať funkciu scanf(), musíme si rovnako vytvoriť pomocný statický reťazec, do ktorého text necháme scanf() uložiť. Tomuto pomocnému poli sa často hovorí buffer. Ukážme si kód, ktorý si vzápätí vysvetlí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 poľa si načítame reťazec od užívateľa, malo by byť dostatočne dlhé, aby text pojalo. Podľa dĺžky zadaného reťazca následne vytvoríme nový reťazec dynamický, v pamäti alokuje pole char ov. Asi vás neprekvapí, že o 1 dlhšia ako je potreba, kvôli nulovému znaku. Hodnotu reťazci nastavíme na tú z bufferu pomocou funkcie strcpy(). Teraz máme dynamický reťazec hotový a môžeme ho používať ako sme boli zvyknutí doteraz. Len až ho prestaneme potrebovať, je ho nutné uvoľniť.

Možno sa pýtate, v čom je teda výhoda dynamického reťazca, keď nám pamäť rovnako vždy bude zaberať buffer, ktorý potrebujeme pre načítanie. Keď budeme načítavať a ukladať milión mien, stačí nám k tomu stále jediný buffer a to len vo fáze čítaní (potom ho môžeme zahodiť). Pri dynamickom uloženie takého počtu mien ušetríme veľa pamäte, ktorú by inak zabrali nevyužité znaky. Daňou za to je dlhší kód a nutnosť myslieť na uvoľňovanie takto vytvorených reťazcov. Preto toto riešenie nemožno vyhlásiť za univerzálne, hoci ho osobne preferujem nad statickými reťazci.

Odkazy na štruktúry

Teraz sa presuňme sľúbené štruktúram. Tie sme zatiaľ odovzdávali hodnotou. To znamená, že kedykoľvek sme ju uložili do nejakej premennej, štruktúra sa do nej skopírovala. Vyskúšajme si to na príklade.

#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 vyššie vytvorí užívateľa do premennej karel a nastaví mu príslušné hodnoty. Následne vytvorí ďalšiu štruktúru používateľa ako premennú uzivatel2 a do nej uloží Karla. Pretože je takto vytvorená štruktúra hodnotový typ, môžeme ju jednoducho celú priradiť a skopírovať. Zmena veku Karla sa uzivatel2 už netýka. Nakoniec druhého užívateľa vypíšeme, budú v ňom tie isté hodnoty ako mal Karel, vek bude pôvodná:

c_struktury2
Jméno: Karel Nový
Ulice: Šikmá 5
Věk: 27

Tu by sa hodilo spomenúť, že takéto skopírovanie celej premenné naraz nebude fungovať u poľa, pretože je na rozdiel od štruktúry realizované ukazovateľom.

Odovzdávanie hodnotou je trochu nepraktické a to hlavne keď chceme nejakú štruktúru zmeniť. Pridajme do programu výšky funkciu, ktorá prijíma ako parameter štruktúru typu UZIVATEL, ktorého nechá zostarnúť o 1 rok:

void zestarni(UZIVATEL uzivatel)
{
    uzivatel.vek++;
}

Teraz nechajme Karla zostarnúť a výpisov 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);

Nič sa nestane:

c_struktury2
Jméno: Karel Nový
Ulice: Šikmá 5
Věk: 27

Je to samozrejme preto, že sa Karel skopíroval do parametra funkcie a zmenila sa táto kópia, s pôvodným Karlom sa nestalo nič. Jedno z riešení by bolo upraveného Karla zas vrátiť a prepísať. Keď si ale uvedomíme, že sa kopíruje postupne každá vlastnosť štruktúry, procesor úplne zbytočne vykonáva veľkú veľa inštrukcií. Navyše v aplikáciách štruktúry rovnako často tvoríme pomocou mallocu(), ak s nimi chceme pracovať mimo funkcie, v ktorých boli vytvorené. (Céčko totiž pamäť po lokálnych premenných vytvorených vo funkcii uvoľňuje po ukončení funkcie, nie sú teda trvanlivé a nemožno ich vrátiť).

Prepíšme si program tak, aby používal ukazovatele:

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šimnite si, že ak chceme získať dáta z ukazovateľa na štruktúru, namiesto aby sme pred neho napísali dereferenční operátor (hviezdičku), tak zameníme bodku za operátor šípky (->).

Výstup programu je nasledujúci:

c_struktury2
Jméno: Karel Nový
Ulice: Šikmá 5
Věk: 16

Aplikácia najprv alokuje pamäť pre štruktúru typu UZIVATEL a jej adresu uloží do premennej p_karel. Nastaví mu hodnoty a následne na Karla vytvorí ešte jeden ukazovateľ p_uzivatel2. Karolovi zmení vek na 15 rokov a používateľa, na ktorého ukazuje p_uzivatel2, nechá zostarnúť o 1 rok. Karolovi tak bude 16, keďže sa mu vek predtým nastavil na 15. Vidíme, že obaja ukazovatele ukazujú na rovnakého užívateľa. Následne vypíšeme Karla pomocou pointera p_uzivatel2. Pamäť nezabudneme uvoľniť.

Možno vám to pripadá ako zbytočné čarovanie s ukazovateľmi, je však veľmi dôležité pochopiť ako odovzdávanie funguje na týchto malých príkladoch, aby sme sa potom vyznali vo väčších aplikáciách.

Zdrojové projekty k dnešnej lekcii sú nižšie k stiahnutiu. Nabudúce, v lekcii Dynamická pole (vektory) v jazyku C , sa naučíme vytvoriť takú dátovú štruktúru, ktorá by nás nijako neobmedzovala svojou veľkosťou a mohli sme do nej pridávať stále nové a nové položky. Pôjde o dynamické pole, ktorému sa niekedy hovorí vektor. Máte sa na čo tešiť :)


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 118x (65.43 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C

 

Predchádzajúci článok
Aritmetika ukazovateľov v jazyku C
Všetky články v sekcii
Dynamická práca s pamäťou v jazyku C
Preskočiť článok
(neodporúčame)
Dynamická pole (vektory) v jazyku C
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 hlasov
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 sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity