Vytváření typů
Zatím jste pracovali jen s typy, které byly definovány jazykem C (např. typ int, float atp.). V této kapitole se naučíte používat nástroje, pomocí nichž můžete vytvářet složitější datové typy a konstrukce. Přispěje to nejenom k zefektivnění psaní zdrojového kódu, ale také k jeho čitelnosti. Mimoto, takové vytváření struktur a vlastních datových typů může být i zábava :-).
Struktury
Struktura vám umožní spojit několik datových typů do jednoho a pracovat s nimi jako s celkem. Nejdříve se podívejte, jak strukturu definovat.
Jméno struktury je nepovinné. Za definicí struktury mohou být hned definovány proměnné nového typu. Když struktuře nedáte jméno, stanou se jedinými proměnnými tohoto nového (nepojmenovaného) typu.
Podívejte se na příklad struktury
polozka
. S definicí struktury
rovnou vytvořím dvě proměnné typu
struct polozka
.
Struktura vždy začíná klíčovým slovem struct. Pak může, ale také nemusí, být definováno jméno struktury. Pokud není definováno jméno, není možné později vytvořit další proměnné této struktury, ani ji použít jako parametr funkce.
V těle struktury jsou deklarovány
proměnné, které bude struktura obsahovat. Mohou to být i jiné
struktury, nebo ukazatel na sebe sama. Struktura však nemůže
obsahovat sebe samu (zkuste si představit, že jste překladač a
chtěli byste něco takového interpretovat). Struktury nelze
sčítat, odčítat atd. U struktur lze použít pouze operátor přiřazení
=
. Tím se obsah jedné struktury zkopíruje do jiné.
Jazyk C++ umožňuje tzv. přetěžování operátorů, díky čemuž pak můžete definovat sčítání, odčítání atp. vlastních datových typů. Jazyk C nic takového neumí.
Pro přístup ke členům struktury se používají operátory
.
(tečka) a ->
(šipka).
Druhý operátor se používá v případě,
že pracujete s ukazatelem na strukturu.
ukazatel = &p;
/* prirazeni 20 do casti struktury p pojmenovane rok_narozeni */
p.rok_narozeni = 1920;
/* stejne prirazeni, jen pres ukazatel */
(*ukazatel).rok_narozeni = 1940;
/* stejne prirazeni pres ukazatel, jen jiny (hezci) zapis */
ukazatel->rok_narozeni = 1960;
Použité závorky jsou nutné, aby bylo jasné že operátor dereference
*
(hvězdička) pracuje s proměnnou
ukazatel, a ne s
proměnnou rok_narozeni ve struktuře
(což, mimochodem, není ukazatel, takže by to byla blbost). Přiřazení
se šipkou je daleko elegantnější a čitelnější, proto používejte výhradně to.
Vytvořením struktury vlastně nevytváříte nový datový typ. Zkuste se dívat na vytvořenou strukturu podobně jako na vytvořené datové pole. Rozdíl je jen v tom, že datové pole obsahuje několik objektů stejného typu, zatímco struktura obsahuje různé datové typy. Jako není problém vytvořit pole obsahující jiné pole (vícerozměrná pole), není problém vytvořit pole obsahující strukturu.
Struktury se využívají hlavně ve velkých programech pracujících s velkým množstvím dat a také při vytváření datových typů (viz později v této kapitole typedef).
Následující příklad ilustruje možnosti použití struktury a také
použití ukazatele typu void
a aritmetiky ukazatelů.
Prostě toho ukazuje hodně :-). Možná si budete muset přečíst
komentář pod programem, než všechno správně pochopíte.
- /*------------------------------------------------*/
- /* c15/strukt.c */
- #include <stdio.h>
- #include <stddef.h>
- #define POCET 40
- /**
- * funkce zkopiruj kopiruje retezec v2 do retezce v1,
- * vcetne zarazky '\0' na konci retezce.
- * Predpoklada, ze pole v1 je nejmene tak dlouhe, jako
- * retezec v1.
- */
- {
- do {
- v1[i] = v2[i];
- }
- struct Pisen {
- } pisne[10], *ukp;
- tisknout strukturu Pisen */
- {
- struct Pisen pisen;
- zkopiruj(pisen.nazev, "Twist And Shout");
- zkopiruj(pisen.zpevak, "The Beatles");
- zkopiruj(pisen.skladatel, "Lennon/McCartney");
- pisen.delka = 2.36f;
- pisen.hodnoceni = 1.0;
- pisne[0] = pisen;
- zkopiruj(pisne[1].nazev, "Eleanor Rigby");
- zkopiruj(pisne[1].zpevak, "The Beatles");
- zkopiruj(pisne[1].skladatel, "Lennon/McCartney");
- pisne[1].delka = 2.06;
- pisne[1].hodnoceni = 1.5;
- ukp = &pisne[2]; /* do ukazatele na strukturu Pisen ukladame adresu
- tretiho prvku v poli pisne */
- zkopiruj(ukp->nazev, "From Me To You");
- zkopiruj(ukp->zpevak, "The Beatles");
- zkopiruj(ukp->skladatel, "Lennon/McCartney");
- ukp->delka = 1.58f;
- ukp->hodnoceni = 1.25;
- vytiskni(&pisne[0]);
- vytiskni(&pisne[1]);
- vytiskni(&pisne[2]);
- }
- {
- /* sstr ukazuje na zacatek struktury */
- /* sstr ukazuje za pole "nazev" ve strukture, tj. na
- * zacatek pole "zpevak" */
- }
- /*------------------------------------------------*/
void *
přetypovává na něco „smysluplnějšího“ –
na char *
.
Výstup z programu vás asi nepřekvapí:
Nazev pisne: Twist And Shout Interperet: The Beatles Skladatel(e): Lennon/McCartney Delka skladby: 2.36 Hodnoceni: 1.00 Nazev pisne: Eleanor Rigby Interperet: The Beatles Skladatel(e): Lennon/McCartney Delka skladby: 2.06 Hodnoceni: 1.50 Nazev pisne: From Me To You Interperet: The Beatles Skladatel(e): Lennon/McCartney Delka skladby: 1.58 Hodnoceni: 1.25
Použití struktury, ukazatele na strukturu a operátorů .
a ->
je, myslím, poměrně jasné.
Podívejte se na funkci vytiskni(void *);
,
která tiskne strukturu Pisen
.
Kdyby struktura Pisen
neměla jméno Pisen
(prostě byste jej na řádku 21 neuvedli), nešlo by ji použít jako
parametr funkce, vytvořit její další instanci (jako na řádku 33)
a dokonce ani vytvořit ukazatel na tuto strukturu dále v programu.
Existovaly by pouze instance definované při definici struktury (na řádku 26).
Jelikož jsem chtěl ukázat, že i tak lze (pomocí
ukazatelů) se strukturou poměrně slušně zacházet, použil jsem ve
funkci ukazatel na nedefinovaný typ (void *). Při jeho používání
jsem jej vždy nejdříve přetypoval na ukazatel potřebného typu
pomocí (char *) nebo (float *). Zatímco textové řetězce ve funkci
printf()
se předávají právě pomocí ukazatelů, čísla typu float jsem
musel předat jako hodnotu (proto ta hvězdička a závorky navíc na řádcích 78 a 81). Aby
ukazatel ukazoval do správného místa v paměti, bylo nutné jej
postupně posouvat po položkách struktury o příslušný počet bytů
(vracených operátorem sizeof
).
Programátorsky čistší prací by však bylo, kdybych funkci vytiskni()
předával jako argument ukazatel na struct Pisen
,
nebo ukazatel typu void přetypoval na ukazatel typu Pisen
.
Mohl bych i nepředávat žádný argument a používat globální ukazatel
ukp
, ale používání globálních proměnných většinou programy pouze
znepřehledňuje.
Zkuste si za domácí úkol funkci vytiskni()
takto přepsat.
Velkou nevýhodou funkce vytiskni()
je také to,
že při změně struktury Pisen
(třeba přidání prvku doprostřed struktury) přestane správně
fungovat, ale překladač nic nepozná.
Před názvem struktury je vždy nutné používat klíčové slůvko struct. Jak při deklaraci parametrů, tak při definici proměnných struktur.
Pokud předáváte strukturu jako argument, má to jednu velkou nevýhodu. Tato struktura se totiž celá zkopíruje z proměnné, kterou předáváte jako argument, kamsi do zásobníku v paměti počítače (do „argumentu funkce“). Pokud pracujete s velikými strukturami, pak takové kopírování velice zpomaluje chod programu. Mnohem efektivnější je používat ukazatele na strukturu. Pak se na zásobník volání zkopíruje jen ukazatel, který, jak víte, zabírá jen 16, 32 nebo 64 bitů.
Definování typu pomocí typedef
Pomocí typedef
se vytvářejí uživatelské datové typy. Syntaxe příkazu je takováto:
Definicí typu může být například struktura, nebo nějaký základní typ jazyka C. Identifikator je pojmenování nového datového typu. Podívejte se na následující příklad:
- /*------------------------------------------------*/
- /* c15/typedef1.c */
- #include <stdio.h>
- {
- integer c, d;
- a = 5;
- b = a + 4;
- d = (integer) a;
- c = d + 5;
- }
- /*------------------------------------------------*/
Všimněte si přetypování proměnné a
při přiřazování její hodnoty do proměnné d
na typ integer
, a také přetypování proměnných
d a c na typ int
ve funkci
printf()
. Funkce printf()
totiž
nemůže očekávat typ integer
, neboť jej norma jazyka C ani nezná.
V příkladě je definice typu integer tak jednoduchá, že by se
zřejmě dal přeložit i bez přetypování bez jakýchkoliv varování
překladače.
Takovéto „přejmenovávání“ datových typů je více méně k ničemu a přináší jenom problémy s čitelností programu, proto to raději nedělejte (a nehrajte si se sirkami).
Někdy se takové přejmenování typů ale velice hodí. Představte si, že
píšete program, který bude fungovat na různých platformách, třeba na
16-bitové a 32-bitové. Na jedné potřebujete z nějakého důvodu použít datový typ,
který má 16 bitů, na druhé 32 bitů. Jedním možným řešením je definovat si
v hlavičkovém souboru pro 16-bitovou verzi typedef short int bitcislo;
a pro 32-bitovou
verzi zase typedef int bitcislo;
. Zbytek zdrojového kódu už může pracovat s
datovým typem bitcislo
a nemusí se starat o to, kolik bitů zabírá.
(Kdyby se o to přeci jen zajímal, může použít typedef
).
Datový typ ze struktury
Teď se podívejte na vytváření nového datového typu pomocí struktury. Jak se to dělá, vidíte v příkladu. Také v něm vidíte, jak lze inicializovat struktury při jejich definici (podobně jako u polí).
Všiměte si, že na řádku 9 není jméno nové proměnné, ale jméno nového datového typu.
Přetypování datového typu Pokus
na struct pokus
(viz řádek 21) s Visual Studiem nefunguje. Vyhodí chybu
'type cast': cannot convert from 'Pokus'to 'pokus'. V normálním
„životě“
byste ale stejně měli používat buď jen struct pokus
, nebo
jen Pokus
, abyste si neznepřehledňovali kód.
a.x = 0 a.x = 6
Když už jsem definoval datový typ Pokus
,
je trochu zbytečné používat strukturu pokus
.
Používání uživatelského typu je v mnohém příjemnější, než používání
struktury jako takové (odpadá psaní slova struct
),
proto v dalším výkladu budu používat jen verzi s typedef
.
Nyní následuje poslední příklad na vytváření datového typu pomocí struktury. Dobře si jej prostudujte.
- /*------------------------------------------------*/
- /* c15/typedef3.c */
- #include <stdio.h>
- #include <stddif.h>
- /* funkce zkopiruj kopiruje retezec v2 do retezce v1 */
- {
- do {
- v1[i] = v2[i];
- }
- float delka;
- } Pisen;
- size_t pocet_pisni;
- Pisen pisen[20];
- } CD;
- {
- CD *ualbum, alba[10], album;
- /* inicializuji album */
- ualbum = &album; /* pouziti ukazatele ualbum
- je tu jen pro ilustraci */
- zkopiruj(album.nazev, "The Beatles Songs");
- album.cenaSK = 325.50;
- ualbum->cenaCZ = 286.50;
- album.pocet_pisni = 1;
- /* vkladam prvni (a posledni) pisen do alba */
- zkopiruj(album.pisen[0].nazev, "Twist And Shout");
- zkopiruj(ualbum->pisen[0].zpevak, "The Beatles");
- zkopiruj(album.pisen[0].skladatel, "Lennon/McCartney");
- ualbum->pisen[0].delka = 2.36;
- /* ukazka prace s polem alba */
- /* do pole alba vkladam prvni album */
- alba[0] = album;
- /* to same album vlozim jeste jednou,
- tentokrat prez ukazatel */
- alba[1] = *ualbum;
- /* zmenim nazev a cenu ... */
- zkopiruj(alba[1].nazev, "Salute to The Beatles");
- alba[1].cenaSK = 350.0;
- /* ... a pridam dalsi pisen */
- alba[1].pocet_pisni = 2;
- alba[1].pisen[1] = alba[1].pisen[0];
- zkopiruj(alba[1].pisen[1].nazev, "Eleanor Rigby");
- alba[1].pisen[1].delka = 2.06;
- tiskni(alba, 2);
- }
- {
- /* tisknu album "i" */
- cd[i].cenaSK);
- /* tisknu pisne z alba "i" */
- }
- }
- }
- /*------------------------------------------------*/
Výstup z programu:
The Beatles Songs 286.50Kc (325.50Sk) Nazev: Twist And Shout Interpret: The Beatles Skladatel: Lennon/McCartney Delka: 2.36 ....................... ------------------------------------------ Salute to The Beatles 220.00Kc (350.00Sk) Nazev: Twist And Shout Interpret: The Beatles Skladatel: Lennon/McCartney Delka: 2.36 ....................... Nazev: Eleanor Rigby Interpret: The Beatles Skladatel: Lennon/McCartney Delka: 2.06 ....................... ------------------------------------------
Zkuste si napsat vlastní program, kde použijete vlastní datový typ jako argument funkce, nebo jako návratovou hodnotu funkce. Něco jako:
Nebo ještě lépe, napište funkci, která bude mít jako argumenty položky ze struktury nového datového typu, a bude vracet onen datový typ vyplněný těmito argumenty.
Na závěr dodávám, že ani strukturované typy dat vytvořené pomocí
typedef
nelze sčítat, odčítat atp.
Až v jazyce C++ je možné přetěžovat operátory a tak definovat sčítání
struktur. V jazyku C si musíte vystačit s funkcemi, které mohou dělat něco
jako sčítání. Co budou dělat je čistě na vás, programátorech.
Jo jo, už vám říkám programátoři :-).