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