Kurz jazyka C - 3. díl

Pavel Čížek

Vítejte u další části našeho kurzu. Dnes nás čekají odvozené datové typy a operátory jazyka C.

Odvozené datové typy
Ze základních typů lze vytvářet typy odvozené - jsou to mimo jiné ukazatel, pole, struktura a sjednocení.
Ukazatel je velmi silnou a poměrně nebezpečnou zbraní jazyka C. Správné porozumění koncepci ukazatelů je důležité pro úspěšné programování v C. Navíc v systému Amigy se to jimi jen hemží. Z těchto důvodů si zde ukazatele zavedeme, ale podrobně se jimi budeme zabývat v samostatné části (až budeme vědět více o operátorech jazyka).
Ukazatel reprezentuje adresu objektu. Neexistuje speciální deklarátor, v deklaraci se užívá operátoru dereference (získání hodnoty) - znaku "*", např. zápisy

int cislo, *pc;
double realne_cislo, *ukazatel;

deklarují "cislo" jako proměnnou typu int, pc jako ukazatel na objekt typu int; podobně ukazatel je ukazatel na double. Pokud je pc ukazatel, pak (jak je vlastně zřejmé z deklarace) zápis *pc označuje hodnotu objektu, na který ukazatel ukazuje, tj. jehož adresu obsahuje. S ukazatelem je vždy spojen typ objektu, na který ukazatel ukazuje!
Pole v jazyce C jsou jednorozměrná, indexovaná vždy od nuly; vícerozměrná pole můžeme vytvářet jako pole polí... Zápis

long pole1[3], vp[2][3];

deklaruje pole pole1 tří proměnných pole1[0], pole1[1] a pole1[2] typu long a pole vp dvou tříprvkových polí - jednotlivé prvky jsou vp[0][0], vp[0][1], vp[0][2], vp[1][0], vp[1][1] a vp[1][2], opět typu long (v paměti jsou uloženy po řádcích). Jak je patrné, na pole se odkazujeme tak, že za jméno pole uvedeme index zapsaný v hranatých závorkách. Uvědomte si, že nejvyšší index pole je (díky indexování od nuly) o 1 menší než číslo uvedené v deklaraci pole. Kompilátor chápe jméno pole jako ukazatel na první položku pole. Zápis *(pole1+1) je tedy totožný se zápisem pole1[1].
Struktura je obdobou záznamu v jazyce Pascal. Deklaruje se pomocí klíčového slova struct. Obvykle se deklaruje dvoufázově. Nejprve se vytvoří šablona určující položky struktury - šabloně se žádná paměť nepřiděluje. Příkladem může být následující definice struktury:

struct Kamarad {
char jmeno[30];
unsigned telefon;
unsigned short predvolba;
};

Zde se definuje šablona (popis) struktury Kamarad - je to záznam obsahující jméno (pole 30 znaků), telefon a předvolbu. V druhé fázi se deklarují vlastní proměnné typu struct Kamarad (resp. ukazatelé na objekt tohoto typu):

struct Kamarad Honza, *dalsi;

Na jednotlivé položky struktury se odkazujeme pomocí příslušného jména uvedeného v definici šablony. Přitom užíváme operátor pro odkaz na položku struktury - záleží na tom, zda se odkazujeme na položku proměnné typu struktura (užijeme operátor .) nebo na položku proměnné typu ukazatel na strukturu (užíváme operátor ->). V tomto případě lze tedy Honzovo telefonní číslo zjistit pomocí zápisu Honza.telefon a jméno dalšího kamaráda pomocí dalsi->jmeno.
Deklaraci lze provést i jednofázově následujícím způsobem (v podstatě jména deklarovaných proměnných uvedeme hned za specifikaci šablony):

struct miry {
int hrudník, pas, boky;
} sestra, *dalsi_kandidatka;

Dvoufázový postup s uvedením jména šablony za klíčové slovo struct je však nutný v případě, kdy složkou struktury je ukazatel na stejnou strukturu, např.

struct clovek {
struct clovek *otec, matka;
/* další položky */
);

Později uvidíme mnoho dalších možností, jak lze struktury využívat operační systém Amigy jich používá desítky. Analogicky se postupuje u dalšího odvozeného typu - sjednocení.
Tento typ se deklaruje pomocí klíčového sova union, jinak je vše (co se deklarací týče) totožné s typem struktura. Hlavní rozdíl je v přidělení paměti. Složky struktury jsou v paměti uloženy za sebou, je vyhrazena paměť na uložení všech položek. U typu union jsou položky uloženy přes sebe, tj. přidělí se paměť potřebná pro nejdelší položku. Typ union tedy může obsahovat v daném okamžiku pouze jednu položku sjednocení.
Posledním odvozeným typem, o kterém si dnes povíme, bude výčtový typ (existují např. ještě bitová pole, ale o tom až jindy). Výčtový typ má jako obor hodnot podmnožinu oboru hodnot typu int definovanou explicitním výčtem. Deklarace má tvar

enum Identifikátor { výčet hodnot };

Uvedený Identifikátor (stejně jako u struktur) nadále slouží pro označení tohoto typu. Jednotlivé položky výčtu jsou oddělovány čárkami a mohou to být buď identifikátory nebo zápisy tvaru identifikátor = celočíselná konstanta. Tyto identifikátory pak označují hodnoty výčtového typu. Jednotlivým položkám jsou přiřazovány celá čísla od nuly a zvyšující se o 1. Pokud se v definici vyskytne výraz s = bude mít příslušný identifikátor hodnotu určenou příslušnou celočíselnou konstantou a další prvky výčtu se budou indexovat od této hodnoty. Tak je možno označit tutéž hodnotu více identifikátory - ty lze pak užívat jako synonyma. Uveďme si příklady deklarace výčtového typu (včetně přiřazení do příslušných proměnných):

enum Kvetiny { TULIPAN, MACESKA = 10, NARCIS, RUZE, VIOLKA = 10 };
enum Kvetiny moje, svoje = RUZE;
moje = VIOLKA;

Výčtový typ Kvetiny má obor hodnot { 0,10,11,12 } a jako konstanty typu Kvetiny lze užívat identifikátory TULIPÁN, MACESKA, NARCIS, RUZE a VIOLKA. Přitom MACESKA a VIOLKA označují tutéž hodnotu 10.
I zde je možnost definovat proměnné nepojmenovaného výčtového typu jednofázovou deklarací:

enum { nikdo = -1, muz, zena } x;
x = zena;

Toď dnešní přehlídka odvozených typů. Jimi nabízené možnosti jsou poměrně veliké, obzvláště uvážíte-li, že je lze vzájemně téměř libovolně kombinovat.

Lexikální elementy jazyka
Zápisy v jazyce C se skládají z posloupnosti lexikálních elementů, které na sebe navazují nebo jsou odděleny (nelze-li je jednoznačně rozpoznat) mezerami, tabelátory, konci řádků a komentáři. Komentář je v Céčku označen (jak již víme z 1. části) dvojicemi znaků /* a */, zpravidla je do sebe nelze vnořovat. Lexikální elementy lze rozdělit do několika tříd - identifikátory, klíčová slova, konstanty, řetězce, operátory a interpunkční znaménka.
Identifikátory jsme si popsali již minule. Klíčová slova jsou uvedena v tabulce (pro ANSI C), jsou to zpravidla označení příkazů jazyka, označení typů apod. Některé již známe, s většinou se asi seznámíme v příštím díle. Konstanty rozlišujeme celočíselné, znakové, v pohyblivé řádové čárce a konstanty výčtového typu - jejich tvar sme si popsali již minule u příslušných typů.
Dalším elementem jsou řetězce - jak jste si již určitě všimli, jedná se vlastně o posloupnost znaků ohraničenou uvozovkami. Součástí řetězce mohou být i speciální znaky jako konec řádku apod. ( , , .. viz znakové konstanty). Pro každou konstantu typu řetězec je vyhrazeno pole buněk typu char, jehož délka je o 1 větší, než počet znaků řetězce. Do poslední buňky je uložen znak (binární nula), který označuje konec řetězce. Tím je definována reprezentace řetězců v jazyce C (mohou být tedy libovolně dlouhé), která se však na Amize užívá často i v Pascalu či assembleru (neboť systémové funkce jsou z velké části napsány v Céčku).
Řetězec musí být zapsán na jednom řádku, nebo musí být v místě přerušení řetězce novým řádkem řádek ukončen znakem . V tomto případě se tento znak a konec řádku z obsahu řetězce vypouští. Důležitá vlastnost: pokud zapíšete do výrazu řetězec, pak má samozřejmě hodnotu - je to adresa prvního znaku řetězce.

Operátory
Výrazy jsou v jazyce C tvořeny z konstant a proměnných pomocí operátorů. Operátory dělíme podle počtu argumentů na unární, binární a ternární. Operátory jazyka C jsou opět shrnuty v tabulce - jsou seřazeny podle priority (operátory s nejvyšší prioritou jsou uvedeny nejvýše). Kromě počtu argumentů je vždy uveden význam daného operátoru a způsob vyhodnocování daného operátoru (-> znamená zleva doprava, <- zprava doleva). Poznamenejme, že operátory & a * jsou užity jako unární i jako binární, význam se určuje dle kontextu.

Unární operátory
Unární operátory se zpravidla zapisují před operand, výjimku tvoří operátory ++ a -- ,které lze psát před i za operand (ale s různým významem!). Tyto dva operátory slouží ke zvýšení (resp. snížení) hodnoty dané proměnné o 1 (je-li číselná). Pokud píšeme operátor před operand (++c), pak se hodnota proměnné c zvýší o 1, hodnota celého výrazu ++c je rovna nové (větší) hodnotě c. Pokud píšeme operátor za operand (např. c++), pak hodnota celého výrazu c++ je nastavena na původní hodnotu c a poté je obsah c zvýšen o 1.
Z unárních operátorů stojí ještě za zmínku operátory reference a dereference, o kterých jsme se již několikrát zmínili. Operátor dereference * předpokládá argument typu ukazatel na objekt a vrací hodnotu příslušného objektu - např. když pi je ukazatel na proměnnou typu int, tak *pi je hodnota obsažená v příslušné proměnné. Operátor reference naopak vrací adresu zadaného objektu - máme-li tedy proměnnou i typu int, tak &i je adresa této proměnné a lze provést přiřazení pi = &i (nebo i = *pi). Tyto dva operátory jsou spolu jednoznačně svázány-i je totéž jako *&i.
Mezi unární operátory patří logická negace. Jak jste si již asi všimli, nikde nebyl popsán typ "booleovská hodnota" (pravda/nepravda). V Céčku takový typ neexistuje, chceme-li vyhodnotit v logických podmínkách nějaký výraz, musí být aritmetického typu (tj. číslo) nebo typu ukazatel. Hodnota 0 se pak chápe jako nepravda (FALSE), jiná hodnota jako pravda (TRUE). V tom smyslu pracují všechny logické operátory v Céčku - lze je aplikovat na čísla či ukazatele a výsledkem je celé číslo nula nebo číslo různé od nuly.
Operátor sizeof() vrací velikost zadaného objektu v bytech, parametrem může byt proměnná nebo identifikátor příslušného typu. Operátor přetypování slouží k převodu hodnoty jistého typu na jiný námi specifikovaný (závorky obsahují popis nového typu). Některé "rozumné" převody (konverze) se v Céčku provádějí automaticky, zbylé musíme zapsat explicitně, např. (unsigned short) i, je-li i třeba typu LONG.

Binární operátory
Binární operátory se zapisují v infixové notaci tj. operátor se píše mezi operandy. Mezi binární operátory patří klasické aritmetické operace jako sčítání a násobení. Operace +, - * / lze aplikovat na výrazy celočíselné i v plovoucí řádové čárce. Dalšími binárními operátory jsou operace bitových posuvů, jejich operandy musí být celočíselné. Při posuvu vlevo jsou bity zprava nulovány. Při posuvu vpravo jsou bity zleva nulovány pro proměnné bez znaménka, výsledkem je logický posuv. U ostatních objektů se do bitů zleva kopíruje znaménkový bit, jde o aritmetický posuv. Protože je dnešní část trochu teoretičtější (jedná se snad o poslední teoretičtější díl), zpestřeme si ji alespoň jedním hezkým příkladem. Operátoru posuvu lze totiž využít ke zjištění počtu bitů int v dané implementaci.
V tomto prográmku ve funkci pocet bitu int() uděláme velice jednoduchou věc - máme proměnnou typu unsigned int a všechny bity nastavíme na 1. Pak postupně umazáváme jednu jedničku po druhé instrukcí w = w >> 1 a počítáme je (to lze samozřejmě napsat lépe, jak se za chvíli ukáže -w >>= 1).
Dalšími binárními operátory jsou operátory relační (<, >=), jejichž význam je asi jasný. Následující skupinou v tabulce jsou bitové a logické operátory - pozor, neplést mezi sebou. Jestliže např. máte proměnné x = 1 a y = 2 , pak bitový AND má za výsledek samozřejmě nulu, ale logický dává hodnotu nenulovou (TRUE)! Důležité je mít přehled o prioritách jednotlivých operátorů (máte tabulku), a pokud si nejsme jisti, raději použít závorky.
Zvláštní skupinou jsou operátory přiřazení. V Céčku existuje klasický operátor přiřazení =. Kromě toho je zde ale i v kombinaci s dalšími operátory (např. +=, *=, | |=). To umožňuje psát úspornější kód, neboť například přiřazení

a[2][10] += 7

je ekvivalentní přiřazení

a[2][10] = a[2][10] + 7

ale v prvním případě stačí hodnotit výraz a[2][10] pouze jednou. Posledním speciálnějším operátorem je čárka , - operátor zapomenutí. Odděluje dva výrazy a čte se vždy zleva doprava. Výsledkem je typ a hodnota druhého výrazu, první výraz však může být přiřazení nebo mít vedlejší účinky. Například výraz

x = 0, ++y

má hodnotu proměnné y zvýšené o 1 s vedlejším efektem vynulování x. Velmi výhodné to může být např. v tzv. for-cyklu apod. - to však uvidíte až příště. Znak čárky se užívá i v deklaracích a voláních funkcí - chceme-li tam užít operátor čárka, musí být opatřen závorkami:

funkce(první_parametr, (x = 0, y = 2), poslední_parametr);

Ternární operátor
Ternární operátor je v jazyce C jen jeden - je to tzv. podmíněný operátor. Obecně má jeho zápis tvar

výraz_1 ? výraz 2 : výraz 3

a jeho výsledkem je výraz_2, pokud je výraz_1 nenulový, tj. má logickou hodnotu TRUE, nebo výraz_3, jestliže je výraz_1 nulový, tj. má logickou hodnotu FALSE. Typy vyrazů výraz_l a výraz_2 musí být shodné a vždy se vyhodnocuje právě jeden z nich. Uveďme si klasické případy pro nalezení minima dvou čísel a absolutní hodnoty čísla:

min = ( x < y ) ? x : y;
abs = ( x < 0 ) ? -x : x;

Tím dnešní díl našeho kurzu jazyka C ukončíme. Pokud se Vám zdál příliš teoretický, slibuji, že to byl snad poslední díl kurzu takovéhoto ražení.
Během následujícího dílu (maximálně dvou) získáme znalosti o příkazech jazyka a vlastní povídání o Céčku jako programovacím jazyku bude téměř u konce. Z přednesené teorie o jazyce však nevyplývá, jak v něm psát programy (nebo i větší aplikace), nejsou zde vidět žádné zkušenosti, "finty" ani dobré programátorské praktiky. Také zatím není příliš jasno, jak využít poměrně schopný systém Amigy. Ideální (dle mého názoru) je pokračovat v dalších částech ve vývoji nějaké menší systémové aplikace, na které by bylo možno ukázat věci týkající se jazyka C při psaní větších projektů, ale i základy systémového programování na Amize (používání služeb AmigaDOSu, vytváření uživatelského rozhraní apod.). Pokud má někdo z Vás nějaký zajímavý nápad ohledně toho, jakou aplikaci bychom mohli začít vytvářet, bude výborné, pokud ho sdělíte mně a tím i ostatním (pište na adresu redakce!).
Na závěr bych chtěl ještě utěšit ty, kdož zatím žádný kompilátor nezískali. Během následujícího měsíce se pokusím získat nějaký vhodný kompilátor včetně editoru, který by mohli všichni používat a který by nebyl příliš cenově náročný na pořízení (maximálně 100 Kč). V příštím čísle se dozvíte, jak bude možno tento kompilátor získat.

Tabulka klíčových slov

auto break case
char continue const
default do double
else enum extern
float far goto
if int long
register return short
signed sizeof static
struct switch typedef
union unsigned void
volatile while

 

Tabulka operátorů jazyka C

Operátor Arita Význam Asoc.
[ ]   Odkaz na prvek pole ->
( )   Volání funkce ->
-> 2 Odkaz na prvek struktury (ukazatel) ->
. 2 Přístup na prvek struktury ->
       
| 1 Logická negace <-
- 1 Binární doplněk <-
++ 1 Inkrementace (před i za) <-
-- 1 Dekrementace (před i za) <-
+ 1 Unární plus <-
- 1 Unární minus <-
* 1 Dereference(vrací obsah buňky dané ukazatelem) <-
& 1 Reference (získání adresy objektu) <
(typ) 1 Konverze na jiný datový typ <-
sizeof() 1 Určení velikostí zadaného objektu <-
       
* / 2 Násobení a dělení ->
% 2 Zbytek po dělení ->
+ - 2 Sčítání a odčítání ->
<< >> 2 Binární posun (vlevo, vpravo) ->
<<= 2 Menší (nebo rovno) než ->
>>= 2 Větší (nebo rovno) než ->
== 2 Test na rovnost (neplést s _!) ->
!= 2 Test na nerovnost ->
       
& 2 Bitový AND ->
^ 2 Bitový XOR ->
| 2 Bitový OR ->
&& 2 Logický AND ->
|| 2 Logický OR ->
       
? : 3 Podmíněný operátor ->
= += -= *= 2 Operátor přiřazení, ->
/= %= &= ^= 2 a jeho zkrácené podoby =>
|= <<= >>= 2 (a += 4 znamená a = a + 4 atp.) ->
       
, 2 čárka (zapomenutí hodnoty výrazu) ->  

 

#include <stdio.h>
int pocet_bitu_int()
{ /* nadefinujeme si proměnnou i - bude obsahovat počet bitů */
int i=0;
/* proměnná w je typu unsigned int; na počátku ji naplníme číslem, které má v binárním zápisu samé 1, v hexa 0xF..F */
unsigned w = ~0;
/* dokud je ve w něco nenulového, provádíme následující */
while (w)
{
/* posuneme obsah w 0 1 bit doprava, tím se ztratí 1 jednička a přibude nula */
w=w >> 1;
/* započteme další smazanou 1 */
i++;
}
/* vrátíme počet smazaných 1 */
/* tj. počet bitů int */
return(i);
}

void main()
{
printf(" Počet bitů typu int: %d ", pocet_bitu_int());
}



Pozn.: články boli naskenované ako text a preto obsahujú aj zopár chýb. Taktiež neručíme za zdrojové kódy (Asm, C, Arexx, AmigaGuide, Html) a odkazy na web. Dúfame, že napriek tomu vám táto databáza dobre poslúži.

Žiadna časť nesmie byť reprodukovaná alebo inak šírená bez písomného povolenia vydavatela © ATLANTIDA Publishing