Kurz jazyka C - 2. dílPavel Čížek
Vítám Vás u dnešní, v pořadí již druhé části našeho kurzu. Čeká nás mnoho práce,
takže se do ní rovnou pustíme. V dnešní části se totiž začneme seznamovat se
všemi možnými datovými objekty jazyka C.
V úvodu si nejprve něco povíme o identifikaci, tj. o tom, jak lze označovat
konstanty, proměnné, funkce a jejich parametry atd. Platí pro ně stejná
pravidla. Identifikátor je posloupnost písmen nebo číslic, začínající písmenem.
Přípustné jsou pouze znaky ASCII kódu (tj. 26 malých a velkých písmen anglické
abecedy) a pro zpřehlednění i podtržítko "_" (i jím může identifikátor začínat).
Správné identifikátory tedy jsou např. "test", "moje_2 funkce", ale chybné jsou
již "malá funkce" nebo "2 pokus". Případné omezení délky identifikátorů a počet
rozlišovaných znaků je dán konkrétní implementací, ale vždy se lze spolehnout na
to, že identifikátory jsou rozlišovány nejméně podle prvních 8 znaků (norma ANSI
doporučuje 31). Dalším podmínkou je, že identifikátor nesmí být totožný s
některým z tzv. klíčových slov. Jedná se vyhrazená slova, která mají v Céčku
speciální význam (většinou se jedná o příkazy - např. jsme již potkali klíčová
slova "while", "if", "return" nebo "int"). Jako doporučení lze zavést konvence
pro odlišení identifikátorů konstant, proměnných atd. V rámci tohoto kurzu
budeme např. pro identifikátory konstant používat pouze velká písmena pro
odlišení od proměnných a funkcí.
Počítače ve své podstatě dnes slouží téměř výhradně ke zpracování dat. Každý
program (pokud vůbec něco dělá) vytváří či zpracovává data. Proto je u vyšších
programovacích jazyků důležité, jak lze k datům přistupovat. Paměťové buňky již
od minula nazýváme proměnnými. Dohoda o identifikaci buňky (jak si ji
pojmenujeme) a o způsobu interpretace obsahu se nazývá deklarace. Obecně má tři
části: "popis typu" (= interpretace obsahu), "deklarované objekty" (=
pojmenování jednotlivých objektů) a případně i "počáteční hodnoty" (= počáteční
obsah paměťové buňky). "Popis typu" se skládá z určení paměťové třídy (určuje
požadovaný nebo doporučený způsob uložení proměnné) a z určení typu proměnné
(lze použít již předdefinované typy, odvozené typy nebo lze definovat svoje
vlastní; navíc je možné typ dále modifikovat). O tom všem si nyní podrobněji
povíme. Na místě typu však může stát též speciální klíčové slovo "void", které
je třeba chápat jako libovolný nebo neznámý typ (nebo také žádný!). Např.
deklarace "void Nic(void)" označuje funkci, která nemá žádné parametry a nevrací
žádnou hodnotu.
Paměťové třídy
Při tvorbě programu je třeba mít možnost popsat způsob používání proměnných.
Jedná se především o rozlišení lokálních a globálních objektů. V Céčku jsou
proměnné deklarované uvnitř bloku (je ohraničen složenými závorkami "{" a "}") v
tomto bloku lokální. Tělo každé funkce je také blok a tedy proměnné deklarované
v těle funkce jsou v této funkci lokální a vně této funkce jsou nepřístupné.
Naopak proměnné deklarované mimo funkce jsou globální a přístupné ze všech
funkcí (pokud nejsou zastíněny lokální deklarací - pokud máte globální proměnnou
"test" a v těle nějaké funkce deklarujete lokální proměnnou "test", pak v této
funkci nebude globální proměnná přístupná, "test" bude znamenat onu lokální).
Pokud je v nějakém modulu deklarována globální proměnná (např. "int citac;") a
chceme ji používat i v jiném modulu, je nutno v rámci tohoto modulu kompilátoru
oznámit, že chceme používat již někde existující externí objekt - k tomu slouží
klíčové slovo "extern". Chceme-li užít např. proměnnou "citac" v jiném modulu,
musíme před jejím použitím oznámit její existenci kompilátoru pomocí "extern int
citac;". Pozor! Deklarace "int citac" zajistí vytvoření globální proměnné (tj.
přiřadí ji paměť apod.) zatímco "extern int citac" udává pouze odkaz na již
existující objekt, žádné místo v paměti se již neobsazuje. Poznamenejme, že
globální proměnné jsou externí pro všechny funkce, ale v modulu, kde jsou
deklarovány, lze využít implicitního dovozu globálních proměnných do funkcí.
Často je globální proměnná výhodným řešením, neboť na rozdíl od lokálních
proměnných existuje po celou dobu běhu programu. Je však snadno přístupná v řadě
funkcí, což může mít za následek nechtěné změny a těžko odhalitelné chyby.
Globální proměnnou lze proto deklarovat jako "static" (např. "static chat znak")
- tím oznamujeme překladači, že se jedná o globální proměnnou viditelnou a
použitelnou pouze v daném modulu (resp. funkci). Podívejme se na příklad. Mějme
funkci "pocet()", která bude počítat, kolikrát byla zavolána. Vždy po jejím
volání vytiskneme onen počet.
Varianta s globální proměnnou viz první příklad.
Každý by asi čekal, že když dvakrát zavoláme funkci "pocet()", program vytiskne
"1 2". Nicméně díky chybičce (překlepu) v prvním volání funkce "printf" vytiskne
"1 3". To je právě nevýhoda globální proměnné, která slouží k jedinému účelu
jedné funkci, ale je všude přístupná. Podívejme se, jak to dopadne při použití
statické proměnné.
Varianta se statickou proměnnou viz program číslo 2.
Jak je vidět, nyní k žádným výše zmiňovaným problémům docházet nemůže. A jak je
to s inicializací globálních proměnných (pozor - liší se od lokálních, viz
dále)? Buď je počáteční hodnota předepsána již v deklaraci; pak je na počátku
programu tato hodnota uložena do proměnné a více se k ní již program nevrací -
např. "static int citac = 0" nastaví na počátku proměnnou "citac" na nulu. Pokud
předepsána počáteční hodnota není, pak jsou globální proměnné nastaveny na nulu.
Není-li proměnná deklarována mimo jakoukoliv funkci, není globální, pak musí
nutně být lokální. Lokální proměnné lze předznačit pro paměťovou třídu "auto",
"register" nebo "static". Chování proměnné třídy "static" jsme si již osvětlili
existuje stále, nevzniká a neinicializuje se při každém volání funkce. Všechny
lokální proměnné, které nejsou nijak označeny, jsou považovány za tzv.
automatické proměnné, proměnné třídy "auto". Tyto proměnné vznikají automaticky
v okamžiku potřeby (= při vstupu do bloku) na zásobníku a při opuštění funkce
zanikají. Mimo tento blok neexistují (šetří to paměť). Lokální proměnnou lze při
vstupu (při každém!) nastavit na nějakou hodnotu - to lze zařídit v deklaraci.
Pokud počáteční hodnota není udána, pak je po vytvoření obsah proměnné
libovolný, přesněji řečeno, není definován. V klasickém Céčku je možno
inicializovat pouze lokální proměnné jednoduchých typů (čísla, znak), v ANSI C
lze inicializovat i podstatně složitější objekty. Pokud je nějaká lokální
proměnná hodně využívána, mohlo by být výhodné umístit ji místo do paměti přímo
do registru procesoru. Doporučit to můžete kompilátoru direktivou "register".
Řada kompilátorů ji však ignoruje; dobré kompilátory pro procesory Motorola však
nejpoužívanější proměnné umisťují do registrů automaticky.
Základní datové typy
V jazyce C jsou tyto základní typy: char, int, float, double. Později se
seznámíme i s typy odvozenými. Existují různé varianty těchto typů, které jsou
dány použitím různých modifikátorů - short, LONG, unsigned, signed. Poslední
modifikátor je pouze v ANSI C. Základní rozdíl mezi prvními a druhými dvěma je v
reprezentaci čísel. Typy char a int představují čísla v pevné řádové čárce (tj.
celé číslo se znaménkem), typy float a double reprezentují čísla v pohyblivé
řádové čárce (tj. reálná čísla = exponent + mantisa). Protože nemusí být obory
těchto typů v každé implementaci totožné, jsou v hlavičkovém souboru "limit.s"
shrnuty základní charakteristiky celočíselných typů, v souboru "float.h" jsou
pak charakteristiky typů float a double. V následující tabulce jsou přehledně
uvedeny všechny základní a odvozené typy spolu s jejich rozsahem na procesorech
Motorola (rozsah je uveden číselně i pomocí zmíněných konstant z hlavičkových
souborů). Navíc jsou v tabulce poznámky - v Céčku je možné speciální typ (třeba
dlouhý na psaní, ale i základní jako např. "int") nějak pojmenovat svým vlastním
názvem. Na Amize jsou v souboru "exec/types.h" zavedena pro přehlednost různá
další označení základních typů; ta jsou uvedena u dalších poznámek k těmto
typům. Poznamenejme ještě, že u typů odvozených od "int", např. "unsigned int",
lze klíčové slovo "int" vynechávat a psát jen "unsigned" apod. Je toho pro
stručnost využito i v tabulce.
Povězme si o těchto typech něco více. Hned v úvodu musím upozornit, že na Amize
je typ "int" reprezentován 4 byty (= 32 bitů) a má tedy stejný rozsah jako
"long". Pro označování hodnot typu "int" (a samozřejmě typů odvozených) lze
využívat jednak běžný dekadický zápis (např. 97), nelze v něm však psát vedoucí
nevýznamné nuly. Zápisy uvozené znakem "0" jsou chápány jako zápisy v jiných
číselných soustavách. Je to buď oktalová soustava, pokud za "0" následuje znak
"0" - "7", nebo hexadecimální (šestnáctková) soustava, začíná-li zápis znaky
"0x", resp. "0X". Číslo 97 lze tedy zapsat takto: 97 = 0141 = 0x61. Jak jste si
již všimli minule, pokud chceme celá čísla číst/tisknout pomocí scanf/printf,
uváděli jsme na příslušné místo kombinaci "%d". Pokud se místo ní objeví "%o",
resp. "%x", pak se čísla budou načítat/tisknout v oktalové, resp. hexadecimální
soustavě. Jistě jste už také pochopili, že typy normální jsou se znaménkem, typy
předznačené "unsigned" jsou bez znaménka.
Znakové konstanty (typ char) se zapisují jako tištitelný znak mezi apostrofy
(např. "A", "*"). Chceme-li zapsat znak apostrof, resp. znak obrácené lomítko,
musí mu předcházet obrácené lomítko -"", resp. "\". Této konvence se využívá
i pro některé řídící ASCII znaky, např. "
" = nový řádek, "f" = nová stránka
(form feed), "" = návrat o 1 znak (backspace). Navíc libovolný znak ASCII kódu
může být vyjádřen svou oktalovou hodnotou uvedenou za lomítkem, např. "12" je
totéž co"
".
A zde jsou pro zajímavost některé na dodefinované typy z modulu "exec/types.h".
V závorce vždy uvedu odpovídající klasickou definici. LONG (= long), ULONG (=
unsigned long int), LONGBITS (= unsigned long int, má naznačit, že manipulujeme
s jednotlivými bity), WORD (= short), UWORD (= unsigned short), WORDBITS (=
unsigned short, opět značí manipulaci s bity), SHORT (= short), USHORT (=
unsigned short), COUNT (= short, tj. počet), UCOUNT (= unsigned short), BOOL (=
short, slouží pro logické hodnoty, dodefinovány jsou i TRUE a FALSE), BYTE (=
signed char), UBYTE (= unsigned char), BYTEBITS (= unsigned char, opět přístup
po bitech), TEXT (= unsigned char).
Typy float a double slouží pro reprezentaci čísel v pohyblivé řádové čárce.
Zmíněný "long double" nemusí být povinně implementován; odpovídá rozsahu a
formátu čísel, v jakém pracují koprocesory (FPU). Konstanty v pohyblivé řádové
čárce se zapisují obvyklým způsobem, např.
1., .78, -0.456, l .5e10, 5E-7 atd.
Nyní můžeme na základě těchto znalostí vytvořit jednoduchý prográmek, který
načte číslo z klávesnice, převede ho do oktalové a hexadecimální soustavy a
vytiskne jej. Uvidíte, jak je to jednoduché.
Ukážeme si ještě jeden prográmek, který by vás měl upozornit na jistá nebezpečí
v jazyce C. Céčko si dělá nárok na generování rychlého a krátkého kódu. Proto
neprovádí kontroly ničeho (pouze zásobníku). Na tom je založen následující
program, který má zjistit rozsah typu "short". Princip spočívá v tom, že když do
proměnné budeme stále přičítat 1, pak někdy dojde k tomu, že číslo v proměnné
"x" bude mimo rozsah daného typu - budete pro něj potřebovat více než 16 bitů (=
rozsah short). Protože "x" je typu short, zůstane v proměnné pouze oněch dolních
16 bitů, což má za následek, že v "x" se najednou objeví (pokud tam bylo
největší možné číslo a přičteme 1) číslo nejmenší, dojde k tzv. přetečení. A
toho právě využijeme.
Snad jste se moc nevyděsili. Takových zajímavých situací může nastávat celá
řada. A vše jen díky tomu, že nejsou prováděny žádné kontroly problémových a
kritických situací. Je tedy třeba dávat pozor a takovýchto situací se vyvarovat.
V příštím díle se podíváme na složitější typy (jako jsou ukazatele, struktury,
pole apod.) a začneme asi s příkazy jazyka. Pak už budeme vybaveni alespoň tím
základním pro pronikání do operačního systému Amigy. Nashledanou.
#include <stdio.h>
int citac = 0; /* globální počitadlo */
int pocet() /* funkce pocet - vrací počet volání */
{
/* zvětšíme pocet = proměnná "citac" a vrátíme její novou hodnotu */,
return( ++citac );
}
main()
{
/* oznámíme existenci globální proměnné, v rámci jednoho modulu to ale není
nutné */
extern int citac;
/* zavoláme vždy funkci "pocet" a tiskneme "citac" */
pocet(); printf("%d
", citac++);
pocet(); printf("%d
", citac);
}
#include <stdio.h>
int pocet() /* funkce pocet - vrací počet volání */
{
static int citac = 0;
/* deklarujeme uvnitř funkce statickou proměnnou */
/* bude tedy existovat po dobu běhu celého programu, ale používat ji lze jen v
této funkci */
/* zvětšíme počet = proměnná "citac" */
/* a vytiskneme hlášení ..: */
printf("Pocet volání funkce: %d
", ++citac);
/* a vrátíme její novou hodnotu */
return( citac );
}
main()
{
/* zavoláme vždy funkci "pocet", a to je vše */
pocet();
pocet();
}
main()
{
int x; /* deklarace celočíselné proměnné x */
/* vypíšeme výzvu a načteme číslo z klávesnice do x */
printf("Zadejte číslo v desítkové soustavě:
");
scanf("%d", &x);
/* vypíšeme zadanou hodnotu v dekadickém zápisu */
printf("
Zadaná hodnota: %d
", x);
/* vypíšeme zadanou hodnotu v oktálovém zápisu */
printf("Číslo,v oktalové soustavě: %o
", x);
/* vypíšeme zadanou hodnotu v hexadecimálním zápisu */
printf("Číslo v hex. soustavě: %X
", x);
}
main()
{
/* deklarace celočíselných proměnných max, min a x */
/* x má na počátku hodnotu = 0 */
short max, min, x = 0;
/* provede se přirazení max = x, poté se zvýší proměnná x o 1 (příkaz x++); */
/* pak se testuje, zda hodnota výrazu (max = x++) */
/* (tj. hodnota max, což je hodnota x před přičtením 1) */
/* je menší niž x (obsahuje novou hodnotu); */
/* to platí (a, do té doby provádíme cyklus while = dokud),*/
/* pokud nedojde k přetečení pak se cyklus zastaví */
while ( (max = x++) < x);
/* v max je potom nejvyšší možná hodnota, v x je nejmenší,*/
/* a proto ji uložíme do min */
min = x;
/* vypíšeme maximum a minimum rozsahu short */
printf("
Rozsah typu short: %d ... %d
", min, max);
}
Tabulka typů:
Typ |
Dolní mez |
Horní mez |
(signed) char |
-128 (SCHAR_MIN) |
127 (SCHAR_MAX) |
unsigned char |
0 |
255 (UCHAR_MAX) |
short int |
-32768 (SHORT_MIN) |
32767 (SHORT_MAX) |
int |
-2147483648 (INT_MIN) |
2147483647 (INT_MAX) |
long int |
-2147483648 (LONG_MIN) |
2147483647 (LONG_MAX) |
unsigned short |
0 |
65535 (USHRT_MAX) |
unsigned int |
0 |
4294967295 (UINT_MAX) |
unsigned long |
0 |
4294967295 (ULONG_MAX) |
Nejmenší kladné Největší kladné
|
Nejmenší kladné |
Největší kladné |
float |
1*10^-37 (FLT_MIN) |
1*10^37 (FLT_MAX) |
|
FLT_DIG = 6 = počet platných cifer mantisy |
|
double |
1*10^-307 (DBL_MIN) |
1*10^37 (FLT_MAX) |
|
DBL_DIG = 15 |
|
long double |
1*10^-4932 |
1*10^4932 |
|
FLT_DIG = 33 |
|
Vytlačiť článok
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
none
|