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:
- 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 Ottovordemgentschenfelde má teda smolu.
- 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ú funkciemalloc()
afree()
. 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