Obsah
V této části si ukážeme tvorbu a použití takových strukturovaných datových typů, jaké nám přináší život. Také si ukážeme definici výčtových typů, která umožní hodnoty nejen pojmenovat, ale provádět při překladu i jejich typovou kontrolu.
Potřebné znalosti | |
K zvládnutí obsahu této kapitoly je nutné mít znalosti z kapitoly 2 až 10 - umět psát i složitějsí programy, chápat problematiku souborů (textových i binárních), umět do nich zapisovat a číst z nich data. |
Vyšší programovací jazyk má k dispozici takové základní datové typy, které pokryjí většinu potřeb. S jejich pomocí můžeme vytvářet rozsáhlejší homogenní datové struktury, jako například pole, či heterogenní, jako strukturu či unii (struct a union). Je však nutné musí mít k dispozici mechanismus, kterým si vytvoří datový typ podle svých potřeb. Tento mechanismus se nazývá typedef a má následující syntaxi:
Po klíčovém slově typedef následuje definice typu definice typu. Poté je novému typu určen identifikátor identifikátor.
A hned si ukažme několik příkladů. Nejprve si vytvořme nové jméno smallint, což bude jméno pro signed char a bude použito pro deklaraci i.
Další příklad je založen na kopírování řetězců. Pomocí konstrukce typedef vytvoříme nový datový typ string. Tím si zajistíme, že hvězdičky při deklaraci argumentů zmizí.
Musíme si však uvědomit dvě základní věci:
|
Příklad: | |
typedef int vyska; typedef vyska delka; typedef delka hloubka; hloubka d; |
Proměnná d je stále typu int.
Nejprve si shrňme definice, které bychom už měli zvládnout.
Přesto se může stát, že si u některých definic přestáváme být jisti. Při deklaracích typ (*jméno)[]; a typ (*jméno)(); jsou závorky nezbytné, ale v případě typ *(jméno[]); jsou závorky naopak nadbytečné - zvyšují pouze čitelnost.
Zásady pro správnou interpretaci definic:
|
Tento postup si nejlépe vysvětlíme na příkladu.
Příklad: | |
char *( *( *var) ()) [10]; |
A podle popsaných zásad postupujeme následovně:
Další příklad, zde již popíšeme pouze výsledek.
Příklad: | |
double ( *var (double (*)[3])) [3] |
Tato funkce vrací ukazatel na pole tří hodnot typu double. Její argument, stejně jako návratová hodnota, je ukazatel na pole tří prvků typu double.
Argument předchozí funkce je konstrukce, která se nazývá abstraktní deklarace. Obecně se jedná o deklaraci bez identifikátoru. Deklarace obsahuje jeden či více ukazatelů, polí nebo modifikací funkcí. Pro zjednodušení a zpřehlednění abstraktních deklarací se používá konstrukce typedef.
Na závěr této části si uveďme několik příkladů abstraktních definic.
Příklad: | |||||||||||||||
|
V jazyku C můžeme definovat seznam pojmenovaných celočíselných konstant, nazvaných výčet (enumerace). Tyto konstanty lze pak použít všude, kde lze zadat celé číslo. Pro definici výčtového typu použijeme tento obecný formát:
Kde:
Příklad: | |
enum {cervena, zelena, zluta} barva; |
Výčtový typ se zde skládá z konstant cervena, zelena a zluta. Byla vytvořena jedna proměnná se jménem barva. Protože překladač přiřazuje konstantám výčtu celočíselnou hodnotu počínaje od nuly je cervena rovna 0, zelena rovna 1 a zluta rovna 2. Toto standardní přiřazení hodnot překladačem však můžeme změnit zadáním explicitní hodnoty konstanty.
Příklad: | |
enum {cervena, zelena=9, zluta} barva; |
Je cervena zase rovna 0, zelena je rovna 9 a zluta je rovna 10.
Výčet je v podstatě celočíselný typ a výčtová proměnná může obsahovat libovolnou celočíselnou hodnotu, nejen tu, která je definována výčtem. Kvůli zachování přehlednosti bychom však měli používat výčtové proměnné jen pro uchování hodnot definovaných jejich výčtovým typem.
Hlavním důvodem použití výčtových typů je napomoci vytváření samo vysvětlujícího a přehlednějšího zápisu programu.
Následující krátký program vytváří výčtový typ sestávající se z některých názvů částí počítače. Přiřadí comp hodnotu CPU (což je vlastně 1) a zobrazí ji.
Příklad 11.3. | |
/****************************** * enum.c * 19.03.2002 ******************************/ #include <stdio.h> enum {klavesnice, CPU, monitor, tiskarna} comp; int main(void) { comp = CPU; printf("%d", comp); return 0; } |
Všimněte si, že hodnoty výčtových typů nelze posílat na výstup ve tvaru, v jakém jsme je definovali. Můžeme je zobrazit pouze jako odpovídající celá čísla. Obdobně je můžeme číst ze vstupu. Výčtové konstanty se tedy ve své textové podobě nacházejí pouze ve zdrojovém tvaru programu. Přeložený program pracuje již jen s číselnými hodnotami výčtových konstant. |
Struktura je sdružený datový typ, který je složen ze dvou, nebo více proměnných nazvaných prvky struktury. Na rozdíl od pole, ve kterém jsou všechny prvky stejného typu, může mít každý prvek struktury svůj vlastní typ, který se může lišit od ostatních typů.
Struktury se v jazyku C definují podle následujícího obecného formátu:
struct jméno-typu { typ prvek1[, prvek1, ....]; typ prvek2[, prvek2, ....]; typ prvek3[, prvek3, ....]; . . . typ prvekN[, prvekN, ....]; } seznam-proměnných; |
Kde:
Pro přístup k prvkům struktury používáme selektor struktury (záznamu) . "tečka". Tu umístíme mezi identifikátory proměnné typu struktura a identifikátor položky, s níž chceme pracovat. V případě, kdy máme ukazatel na strukturu, použijeme místo hvězdičky a nezbytných závorek raději operátor ->.
Informace ve struktuře mají většinou nějaký logický vztah. Strukturu bychom mohli například používat pro uchování adresy osoby.
Příklad: | |
struct { char jmeno[20]; char prijmeni[30]; char ulice[30]; int cislo_popisne; char mesto[30]; unsigned char psc; } adresa; |
Přiřazení hodnot může vypadat následovně:
adresa.jmeno="Petr"; adresa.prijmeno="Novak"; adresa.cislo_popisne=158;
Následující příklad nám předvede některé ze způsobů práce s prvky struktury.
Příklad 11.4. | |
/****************************** * struct_1.c * 19.03.2002 ******************************/ #include <stdio.h> struct { int i; double d; char str[80]; } s; int main(void) { printf("Zadejte cele cislo: "); scanf("%d", &s.i); printf("Zadejte desetinne cislo: "); scanf("%lf", &s.d); printf("Zadejte retezec: "); scanf("%s", &s.str); printf("Bylo zadano: \n"); printf("%d %lf %s", s.i, s.d, s.str); return 0; } |
Další příklad nám ukáže, že jména prvků struktury nebudou v konfliktu s jinými proměnnými používající stejná jména. Proto se taky na obrazovku vypíše 10 100 101. Proměnná i a strukturovaný prvek i nemají mezi sebou žádný vztah.
Příklad 11.5. | |
/****************************** * struct_2.c * 19.03.2002 ******************************/ #include <stdio.h> struct { int i; int j; } s; int main(void) { int i; i = 10; s.i = 100; s.j = 101; printf("%d %d %d", i, s.i, s.j); return 0; } |
Syntaxe typu FILE
typedef struct { short level; unsigned flags; char fd; unsigned char hold; short bsize; unsigned char *buffer, *curp; unsigned istemp; short token; } FILE;
Typ FILE je jeden z typů, které již používáme dlouho dobu. Jeho definice je v hlavičkovém souboru stdio.h
Problém nastane v okamžiku, kdy potřebujeme definovat dvě struktury, které spolu navzájem souvisí. Přesněji řečeno, jedna obsahuje prvek typu té druhé. A naopak. Pravdou sice je, že se nejedná o častou situaci, nicméně se můžeme podívat na použití neúplné deklarace.
Příklad: | |
struct A; /* neuplna */ struct B {struct A *pa}; struct A {struct B *pb}; /* dokonceni */ |
Vidíme, že u neúplné deklarace určíme identifikátoru A třídu struct. V těle struktury B se ovšem může vyskytovat pouze ukazatel na takto neúplně deklarovanou strukturu A. Její velikost totiž ještě není známa. Velikost nutná pro uložení ukazatele ovšem známa je. Proto je taková konstrukce možná.
Syntaxe:
Již na první pohled je unie velmi podobná strukturám. S jedním podstatným rozdílem, který není zřejmý ze syntaxe, ale je dán sémantikou. Z položek unie lze používat v jednom okamžiku pouze jednu. Ostatní mají nedefinovanou hodnotu. Každý z prvků unie totiž začíná na jejím začátku. Můžeme si představit, že paměťově delší prvky překrývají ty kratší. Této skutečnosti můžeme někdy využít. Nevíme-li, jakého typu bude návratový argument, definujeme unii, mající položky všech požadovaných typů. Dalším argumentem předáme informaci o skutečném typu hodnoty. Pak podle ní provedeme přístup k správnému členu unie. Méně čitelné řešení předá vždy argument nejdelšího typu a poté jej podle potřeby přetypuje.
Jazyk C má zvláštní druh struktury nazvaný bitové pole. Bitové pole se skládá z jednoho nebo více bitů. Pomocí bitového pole lze přistupovat prostřednictvím jména k jednomu nebo více bitům v bytu nebo ve slově. Pro definici bitového pole použijeme tento obecný formát:
Položka typ je buď int nebo unsigned. Zadáte-li bitové pole se znaménkem, pak je nejvyšší bit považován za znaménkový bit. Počet bitů v poli udává položka délka. Všimněte si, že je jméno bitového pole odděleno od od délky pole v bitech dvojtečkou.
Bitová pole se hodí, když potřebujeme natěsnat informace do co nejmenšího místa. Zde je například struktura, která používá bitové pole pro uložení inventárních informací.
Příklad: | |
struct { unsigned department: 3 /* max. 7 oddeleni */ unsigned instock: 1 /* 1 je na sklade, 0 neni na sklade */ unsigned ordered: 1 /* 1 je objednana, 0 neni objednana */ unsigned time: 3 /* doba vedeni objednavky v mesicich */ } inv[MAX_ITEM]; |
V tomto případě lze použít jeden byte k uložení informací o inventární položce, které by bez bitových polí zabraly normálně 4 byty. S bitovým pole se pracuje stejně jako s jinými prvky struktury.
Například následující příkaz přiřadí do bitového pole department v 10. položce pole inv hodnotu 3.
Příklad: | |
inv[9].department = 3; |
Následující příkaz zjišťuje, zda je 5. položka na skladě:
Příklad: | |
if(!inv[4].instock) printf("Neni na sklade"); else printf("Je na sklade"); |