| ← funkce II | C/C++ | vytváření typů II → |
Zatím jsme pracovali jen s typy, které byly definovány jazykem C (např. typ int, float atp.). V této kapitole se naučíme používat nástroje, pomocí nichž můžeme vytvářet složitější 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 :-).
Struktura nám umožní spojit několik datových typů do jednoho. Nejdříve si ukážeme, jak ji definovat.
struct [jmeno] {
typ jmeno_polozky;
typ jmeno_polozky;
...
} [promenne];
Podívejte se na příklad struktury polozka. S definicí struktury rovnou vytvoříme dvě proměnné (struktury) typu 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. Poté 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 by jste 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é.
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 pracujeme s ukazatelem na strukturu.
polozka p, *ukazatel; ukazatel = &p; /* prirazeni 20 do casti struktury polozka pojmenovane rok_narozeni */ p.rok_narozeni = 20; /* stejne prirazeni, jen pres ukazatel */ (*ukazatel).rok_narozeni = 20; /* stejne prirazeni pres ukazatel, jen jiny (hezci) zapis */ ukazatel->rok_narozeni = 20;
Použité závorky jsou nutné, aby bylo jasné že operátor dereference * (hvězdička) pracuje s proměnnou ukazatel, a né s proměnnou rok_narozeni (což mimochodem není ukazatel, takže by to byla blbost). Přiřazení se šipkou je daleko elegantnější, proto používejte výhradně to.
Vytvořením struktury však 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: /* strukt.c */ 3: #include <stdio.h> 5: #define POCET 40 6: /* funkce zkopiruj kopiruje retezec v2 do retezce v1 */ 7: void zkopiruj(char *v1, char *v2) 8: { 9: int i = 0; 10: do { 11: v1[i] = v2[i]; 12: } while (v2[i++]); 13: } 15: struct Pisen { 16: char nazev[POCET]; 17: char zpevak[POCET]; 18: char skladatel[POCET]; 19: float delka, hodnoceni; 20: } pisne[10], *ukp; 22: void vytiskni(void *str); /* deklarujeme funkci, 23: ktera bude tisknout strukturu Pisen */ 25: int main(void) 26: { 27: struct Pisen pisen; 29: zkopiruj(pisen.nazev, "Twist And Shout"); 30: zkopiruj(pisen.zpevak, "The Beatles"); 31: zkopiruj(pisen.skladatel, "Lennon/McCartney"); 32: pisen.delka = 2.36; 33: pisen.hodnoceni = 1.0; 35: pisne[0] = pisen; 37: zkopiruj(pisne[1].nazev, "Eleanor Rigby"); 38: zkopiruj(pisne[1].zpevak, "The Beatles"); 39: zkopiruj(pisne[1].skladatel, "Lennon/McCartney"); 40: pisne[1].delka = 2.06; 41: pisne[1].hodnoceni = 1.5; 43: ukp = &pisne[2]; /* do ukazatele na strukturu Pisen ukladame adresu 44: tretiho prvku v poli pisne */ 45: zkopiruj(ukp->nazev, "From Me To You"); 46: zkopiruj(ukp->zpevak, "The Beatles"); 47: zkopiruj(ukp->skladatel, "Lennon/McCartney"); 48: ukp->delka = 1.58; 49: ukp->hodnoceni = 1.25; 51: vytiskni(&pisne[0]); 52: vytiskni(&pisne[1]); 53: vytiskni(&pisne[2]); 55: return 0; 56: } 58: void vytiskni(void *str) 59: { 60: /* str ukazuje na zacatek struktury */ 61: printf("Nazev pisne:\t%s\n", (char *) str); 63: str += POCET * sizeof(char); /* delka pole "nazev" ve strukture */ 64: /* str ukazuje za pole "nazev" ve strukture, tj. na 65: * zacatek pole "zpevak" */ 66: printf("Interperet:\t%s\n", (char *) str); 68: str += POCET * sizeof(char); 69: printf("Skladatel(e):\t%s\n", (char *) str); 71: str += POCET * sizeof(char); 72: printf("Delka skladby:\t%.2f\n", *((float *) str)); 74: str += 1 * sizeof(float); 75: printf("Hodnoceni:\t%.2f\n\n", *((float *) str)); 76: } 78: /*------------------------------------------------*/
Výstup z programu vás jistě 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íváme se tedy na funkci vytiskni(void *);, která tiskne strukturu Pisen.
Kdyby struktura Pisen neměla jméno Pisen (prostě by jsme jej na řádku 15 neuvedli), nešlo by jí použít jako parametr funkce, vytvořit její další instanci (jako na řádku 27) a dokonce ani vytvořit ukazatel na tuto strukturu dále v programu. Existovali by pouze instance definované při definici struktury (na řádku 20).
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). 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, kdybychom funkci vytiskni() předávali jako argument ukazatel na Pisen, nebo ukazatel typu void přetypovali na ukazatel typu Pisen. Mohli bychom i nepředávat žádný argument a používat globální ukazatel ukp. Zkuste si funkci vytiskni() takto přepsat.
Velkou nevýhodou funkce vytiskni() je také to, že při změně struktury Pisen přestane správně fungovat a 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 arg);
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á operace velice zpomaluje chod programu. Mnohem efektivnější je používat ukazatele na strukturu.
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ívejme se na následující příklad:
1: /*------------------------------------------------*/ 2: /* typedef1.c */ 3: #include <stdio.h> 5: typedef int integer; 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); 15: d = (integer) a; 16: c = d + 5; 17: printf("d = %i, b = %i\n", (int) d, (int) c); 18: return 0; 19: } 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 ineteger, neboť jej norma jazyka C ani nezná. V našem 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).
Teď se podíváme 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 proměnné při jejich definici (podobně jako u polí).
1: /*------------------------------------------------*/ 2: /* typedef2.c */ 3: #include <stdio.h> 5: typedef struct pokus { 6: int x; 7: int y; 8: char text[20]; 9: } Pokus; 11: int main(void) 12: { 13: struct pokus a = { 6, 6, "sest" }; 14: Pokus b = { 4, 5, "Ahoj" }; 16: a.x = 0; 17: b.x = 6; 18: return 0; 19: } 21: /*------------------------------------------------*/
Když už jsme definovali 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é, proto v dalším výkladu budu používat jen uživatelské typy. Nyní následuje poslední příklad na vytváření datového typu pomocí struktury. Dobře si jej prostudujte.
1: /*------------------------------------------------*/ 2: /* typedef3.c */ 3: #include <stdio.h> 5: /* funkce zkopiruj kopiruje retezec v2 do retezce v1 */ 6: void zkopiruj(char *v1, char *v2) 7: { 8: int i = 0; 9: do { 10: v1[i] = v2[i]; 11: } while (v2[i++]); 12: } 14: typedef struct { 15: char nazev[40]; 16: char zpevak[40]; 17: char skladatel[40]; 18: float delka; 19: } Pisen; 21: typedef struct { 22: char nazev[40]; 23: int pocet_pisni; 24: float cenaSK, cenaCZ; 25: Pisen pisen[20]; 26: } CD; 28: void tiskni(CD * cd, int pocet); 30: int main(void) 31: { 32: CD *ualbum, alba[10], album; 33: /* inicializuji album */ 34: ualbum = &album; /* pouziti ukazatele album 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; 45: /* ukazka prace z 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; 60: tiskni(alba, 2); 61: return 0; 62: } 64: void tiskni(CD * cd, int pocet) 65: { 66: int i, j; 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: } 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.
| ← funkce II | C/C++ | vytváření typů II → |
