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.

struct [jmeno] {
     typ jmeno_polozky;
     typ jmeno_polozky;
     ...
} [promenne];

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.

struct polozka {
       unsigned int rok_narozeni;
       int pohlavi;
       char jmeno[20];
       char prijmeni[20];
} Martin, Pavla;

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.

struct polozka p, *ukazatel;
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.

  1. /*------------------------------------------------*/
  2. /* c15/strukt.c                                   */
  3. #include <stdio.h>
  4. #include <stddef.h>
  5.  
  6. #define POCET 40
  7. /**
  8.  *  funkce zkopiruj kopiruje retezec v2 do retezce v1,
  9.  *  vcetne zarazky '\0' na konci retezce.
  10.  *  Predpoklada, ze pole v1 je nejmene tak dlouhe, jako
  11.  *  retezec v1.
  12.  */
  13. void zkopiruj(char *v1, char *v2)
  14. {
  15.     size_t i = 0;
  16.     do {
  17.         v1[i] = v2[i];
  18.     } while (v2[i++]);
  19. }
  20.  
  21. struct Pisen {
  22.     char nazev[POCET];
  23.     char zpevak[POCET];
  24.     char skladatel[POCET];
  25.     float delka, hodnoceni;
  26. } pisne[10], *ukp;
  27.  
  28. void vytiskni(void *str);  /* deklarujeme funkci, ktera bude
  29.                               tisknout strukturu Pisen */
  30.  
  31. int main(void)
  32. {
  33.     struct Pisen pisen;
  34.  
  35.     zkopiruj(pisen.nazev, "Twist And Shout");
  36.     zkopiruj(pisen.zpevak, "The Beatles");
  37.     zkopiruj(pisen.skladatel, "Lennon/McCartney");
  38.     pisen.delka = 2.36f;
  39.     pisen.hodnoceni = 1.0;
  40.  
  41.     pisne[0] = pisen;
  42.  
  43.     zkopiruj(pisne[1].nazev, "Eleanor Rigby");
  44.     zkopiruj(pisne[1].zpevak, "The Beatles");
  45.     zkopiruj(pisne[1].skladatel, "Lennon/McCartney");
  46.     pisne[1].delka = 2.06;
  47.     pisne[1].hodnoceni = 1.5;
  48.  
  49.     ukp = &pisne[2];            /* do ukazatele na strukturu Pisen ukladame adresu
  50.                                    tretiho prvku v poli pisne */
  51.     zkopiruj(ukp->nazev, "From Me To You");
  52.     zkopiruj(ukp->zpevak, "The Beatles");
  53.     zkopiruj(ukp->skladatel, "Lennon/McCartney");
  54.     ukp->delka = 1.58f;
  55.     ukp->hodnoceni = 1.25;
  56.  
  57.     vytiskni(&pisne[0]);
  58.     vytiskni(&pisne[1]);
  59.     vytiskni(&pisne[2]);
  60.  
  61.     return 0;
  62. }
  63.  
  64. void vytiskni(void *str)
  65. {
  66.     char *sstr = (char *) str;
  67.     /* sstr ukazuje na zacatek struktury */
  68.     printf("Nazev pisne:\t%s\n", sstr);
  69.  
  70.     sstr += POCET * sizeof(char);        /* delka pole "nazev" ve strukture */
  71.     /* sstr ukazuje za pole "nazev" ve strukture, tj. na
  72.      * zacatek pole "zpevak" */
  73.     printf("Interperet:\t%s\n", sstr);
  74.  
  75.     sstr += POCET * sizeof(char);
  76.     printf("Skladatel(e):\t%s\n", sstr);
  77.  
  78.     sstr += POCET * sizeof(char);
  79.     printf("Delka skladby:\t%.2f\n", *((float *) sstr));
  80.  
  81.     sstr += 1 * sizeof(float);
  82.     printf("Hodnoceni:\t%.2f\n\n", *((float *) sstr));
  83. }
  84.  
  85. /*------------------------------------------------*/
Ukazatel str se z typu 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.

void vytiskni(struct Pisen pisen);

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:

typedef definice_typu identifikator;

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:

  1. /*------------------------------------------------*/
  2. /* c15/typedef1.c                                 */
  3. #include <stdio.h>
  4.  
  5. typedef int integer;
  6.  
  7. int main(void)
  8. {
  9.     int a, b;
  10.     integer c, d;
  11.     a = 5;
  12.     b = a + 4;
  13.     printf("a = %i, b = %i\n", a, b);
  14.  
  15.     d = (integer) a;
  16.     c = d + 5;
  17.     printf("d = %i, b = %i\n", (int) d, (int) c);
  18.     return 0;
  19. }
  20.  
  21. /*------------------------------------------------*/

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í).

  1. /*------------------------------------------------*/
  2. /* c15/typedef2.c                                 */
  3. #include <stdio.h>
  4.  
  5. typedef struct pokus {
  6.     int x;
  7.     int y;
  8.     char text[20];
  9. } Pokus;
  10.  
  11. int main(void)
  12. {
  13.     struct pokus a = { 6, 6, "sest" };
  14.     Pokus b = { 4, 5, "Ahoj" };
  15.  
  16.     a.x = 0;
  17.     b.x = 6;
  18.  
  19.     printf("a.x = %i\n",a.x);
  20.     /* nefunguje ve Visual Studiu */
  21.     a = (struct pokus) b;
  22.     printf("a.x = %i\n", a.x);
  23.     return 0;
  24. }
  25.  
  26. /*------------------------------------------------*/

Všiměte si, že na řádku 9 není jméno nové proměnné, ale jméno nového datového typu.

Visual Studio

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.

  1. /*------------------------------------------------*/
  2. /* c15/typedef3.c                                 */
  3. #include <stdio.h>
  4. #include <stddif.h>
  5.  
  6. /* funkce zkopiruj kopiruje retezec v2 do retezce v1 */
  7. void zkopiruj(char *v1, char *v2)
  8. {
  9.     size_t i = 0;
  10.     do {
  11.         v1[i] = v2[i];
  12.     } while (v2[i++]);
  13. }
  14.  
  15.     char nazev[40];
  16.     char zpevak[40];
  17.     char skladatel[40];
  18.     float delka;
  19. } Pisen;
  20.  
  21.     char nazev[40];
  22.     size_t pocet_pisni;
  23.     float cenaSK, cenaCZ;
  24.     Pisen pisen[20];
  25. } CD;
  26.  
  27. void tiskni(CD * cd, size_t pocet);
  28.  
  29. int main(void)
  30. {
  31.     CD *ualbum, alba[10], album;
  32.     /* inicializuji album */
  33.     ualbum = &album; /* pouziti ukazatele ualbum
  34.                             je tu jen pro ilustraci */
  35.     zkopiruj(album.nazev, "The Beatles Songs");
  36.     album.cenaSK = 325.50;
  37.     ualbum->cenaCZ = 286.50;
  38.     album.pocet_pisni = 1;
  39.     /* vkladam prvni (a posledni) pisen do alba */
  40.     zkopiruj(album.pisen[0].nazev, "Twist And Shout");
  41.     zkopiruj(ualbum->pisen[0].zpevak, "The Beatles");
  42.     zkopiruj(album.pisen[0].skladatel, "Lennon/McCartney");
  43.     ualbum->pisen[0].delka = 2.36;
  44.  
  45.     /* ukazka prace s polem alba */
  46.     /* do pole alba vkladam prvni album */
  47.     alba[0] = album;
  48.     /* to same album vlozim jeste jednou,
  49.        tentokrat prez ukazatel */
  50.     alba[1] = *ualbum;
  51.     /* zmenim nazev a cenu ... */
  52.     zkopiruj(alba[1].nazev, "Salute to The Beatles");
  53.     alba[1].cenaSK = 350.0;
  54.     /* ... a pridam dalsi pisen */
  55.     alba[1].pocet_pisni = 2;
  56.     alba[1].pisen[1] = alba[1].pisen[0];
  57.     zkopiruj(alba[1].pisen[1].nazev, "Eleanor Rigby");
  58.     alba[1].pisen[1].delka = 2.06;
  59.  
  60.     tiskni(alba, 2);
  61.     return 0;
  62. }
  63.  
  64. void tiskni(CD * cd, size_t pocet)
  65. {
  66.     size_t i, j;
  67.  
  68.     for (i = 0; i < pocet; i++) {
  69.         /* tisknu album "i" */
  70.         printf("%s\t%5.2fKc (%5.2fSk)\n", cd[i].nazev, cd[i].cenaCZ,
  71.                cd[i].cenaSK);
  72.         for (j = 0; j < cd[i].pocet_pisni; j++) {
  73.             /* tisknu pisne z alba "i" */
  74.             printf("\t\tNazev:\t%s\n", cd[i].pisen[j].nazev);
  75.             printf("\t\tInterpret:\t%s\n", cd[i].pisen[j].zpevak);
  76.             printf("\t\tSkladatel:\t%s\n", cd[i].pisen[j].skladatel);
  77.             printf("\t\tDelka:\t%.2f\n", cd[i].pisen[j].delka);
  78.             printf("\t\t.......................\n");
  79.         }
  80.         printf("------------------------------------------\n");
  81.     }
  82. }
  83.  
  84. /*------------------------------------------------*/

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:

typedef struct {
 ...
 } struktura;
 ...
 struktura nejaka_funkce(struktura a) {
 ...
 }
 ...
 struktura x,y;
 ...
 x = nejaka_funkce(y);

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

Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 20.6.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..