[Cvičení 10] | [Obsah] | [Cvičení 12] |
V jazyce C existuje šest bitových operátorů. Přehled uvádí tabulka 1:
Operátor | Operace |
& | Bitový součin (AND) |
| | Bitový součet (OR) |
^ | Exlusivní bitový součet - výlučné NEBO (XOR) |
~ | Bitová negace |
<< | Bitový posuv vlevo |
>> | Bitový posuv vpravo |
Funkci bitových operátorů ukážeme na příkladě:
unsigned char a = 0x85; /* 133 desitkove, 10000101 dvojkove */ unsigned char b = 0x46; /* 70 desitkove, 01000110 dvojkove */ unsigned char c,d,e,f,g,h; c = a & b; d = a | b; e = a ^ b; f = ~ a; g = a << 2; h = b >> 3;Výsledky operací součinu, součtu, exlusivního součtu a negace jsou zřejmé:
&
(AND)|
(OR)^
(XOR)~
(NOT)a =
1 0 0 0 0 1 0 1 a =
1 0 0 0 0 1 0 1 a =
1 0 0 0 0 1 0 1 a =
1 0 0 0 0 1 0 1 b =
0 1 0 0 0 1 1 0 b =
0 1 0 0 0 1 1 0 b =
0 1 0 0 0 1 1 0 c =
0 0 0 0 0 1 0 0 d =
1 1 0 0 0 1 1 1 e =
1 1 0 0 0 0 1 1 f =
0 1 1 1 1 0 1 0
Operace posuvu (vlevo, resp. vpravo) mají následující syntaxi:
op << n
, resp. op >> n
. Provedou posuv operandu op o n bitů vlevo (vpravo).
Na uvolněná místa se nasouvají nuly, nenulové bity „vypadávají“. V uvedeném příkladě, hodnoty proměnných g
,
h
budou mít hodnotu
g = | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | h = | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
Pokud neznáme předem, který bit máme testovat (je dán např. hodnotou proměnné n), vyrobíme masku pomocí operace posuvu doleva čísla 1:unsigned char x; unsigned char maska; ... maska = 0x20; if (x & maska != 0) ... else ...
unsigned char x; unsigned char maska; int n; // v proměnné n je uloženo, který bit máme testovat ... maska = (1 << n); if (x & maska != 0) ... else ...
Struktura je obecně definována následujícím zápisem:
struct [jméno struktury] { typ jméno_položky; typ jméno_položky; ... } proměnná;Struktura je uvedena klíčovým slovem
struct
. Pojmenování struktury jménem je nepovinné. V bloku definic položek struktury se uvádějí jednotlivé položky struktury.
Ukážeme se definici proměnné bod
typu struktura, která má dvě položky (x-ovou a y-ovou souřadnici):
struct { float x; float y; } bod;K položkám struktury přistupujeme pomocí tečkové notace, tedy položku
x
a y
proměnné bod nastavíme takto:
bod.x = 3.5; bod.y = 4;
V tomto případě ale nemůžeme využít definici struktury na jiném místě kódu, např. jako parametr funkce. Pokud bychom strukturu pojmenovali:
struct Bod { float x; float y; };můžeme pak deklarovat proměnnou, např. A takto
struct Bod A;
. Nevýhodou je, že musíme psát stále klíčové slovo struct
.
Uživatelsky příjemnější je definovat uživatelský typ pomocí klíčového slova typedef
:
typedef struct { float x; float y; } TBod;Proměnné typu
TBod
deklarujeme jako každé jiné proměnné, již bez klíčového slova struct
:
TBod b1,b2;
Statické pole struktur deklarujeme TBod pole_bodu[10]
, k položkám pole přistupujeme pole_bodu[0].x = 3;
Je možné deklarovat také ukazatel na strukturu a paměť pro data alokovat dynamicky:
TBod *b3; b3 = (TBod*)malloc(sizeof(TBod)); ... free(b3); /* uvolnění paměti */K položkám bodu b3 přitupujeme standardně pomocí „*“ a „.“:
*b3.x = 4;
. Programátoři
v jazyce C používají častěji jiný zápis: b3 -> x = 4;
, tedy pokud je nějaká proměnná ukazatel na strukturu,
pro přístup k položkám zapisují místo hvězdičky a tečky „šipku“ (znaky minus a větší zapsané bez mezery);
oba zápisy *b3.x = 4
a b3 -> x = 4
jsou v jazyce C ekvivalentní.
Strukturu můžeme předat funkci jako parametr, předává se buď struktura sama nebo ukazatel na strukturu (což je výhodnější z hlediska paměťových nároků, pokud struktura zabírá více paměti; nezapomeňme, že v případě předání struktury se na zásobník předává kopie dat). Definici funkce, která počítá vzdálenost bodu od počátku, je v tabulce 3. Tabulka ukazuje 2 funkce, u první se předává jako parametr samotná struktura, u druhé ukazatel na strukturu.
Předání samotné struktury | Předání ukazatele na strukturu |
double vzdalenost1(TBod b) { return sqrt (b.x*b.x + b.y*b.y); } |
double vzdalenost2(TBod *b) { return sqrt (b->x*b->x + b->y*b->y); |
Tabulka 3: Předávání struktur funkcím
notepad.exe text.txt
představuje
spuštění programu notepad (poznámkový blok) s jedním parametrem - jménem souboru, který má být automaticky otevřen.
Abychom v hlavním programu v jazyce C mohli přistupovat k parametrům, musíme deklarovat hlavičku hlavního programu takto:
int main(int argc, char **argv) { ... return 0; }nebo
int main(int argc, char *argv[]) { ... return 0; }Hodnota argc nese počet parametrů na příkazovém řádku. Tento počet je včetně jména spouštěného programu! Pokud bychom programovali poznámkový blok v jazyce C a spouštěli jej
notepad.exe text.txt
, měl by parametr argc hodnotu 2. V případě spouštění fiktivního programu
progr
se dvěma parametry, progr nadpis 12
, má argc hodnotu 3.
Parametr argv je ukazatel na pole řetězů, které představují parametry příkazové řádky, včetně názvu programu. Tedy, argv[0]
je ukazatel
na řetězec s názvem spouštěného programu, který může být včetně cesty! Dále, argv[1]
je řetězec s prvním parametem, argv[2]
je řetězec s druhým parametrem atd.
Vrátíme-li se k příkladu s poznámkovým blokem a uvažujeme-li standardní instalaci systému Windows, pak argv[0]
je řetězec
C:\WINDOWS\notepad.exe
, argv[1]
má hodnotu text.txt
.
Ve druhém příkladu s programem prog obsahuje argv[0]
název programu včetně cesty, argv[1]
obsahuje řetězec
nadpis
a argv[2]
řetězec (!) 12
.
Hlavní program v úvodu kapitoly je deklarován jako funkce s návratovou hodnotou typu int
(hlavní program může vracet pouze
celé číslo). Každý program může při ukončení předat operačnímu systému (vrátit) číslo, tzv. návratový kód.
Platí nepsané pravidlo, že hodnota 0 znamená úspěšné ukončení programu, nenulová hodnota chybu.
Význam nenulových návratových hodnot není dán a záleží jen na programátorovi, jak hodnoty využije
(měl by vše popsat v dokumentaci programu).
Princip se využívá zejména v dávkových souborech (typu .bat
, skriptech v UNIXu/Linuxu), kdy po skončení programu
je možné větvit algoritmus dávky právě podle návratového kódu (test proměnné errorlevel) - viz předmět SSS.
Principiálně vypadá programování návratových kódu takto:
int main(int argc, char **argv) { kód; if (chyba) return 1; kód; return 0; }Pokud programátor nehodlá využívat parametry hlavního programu, ani vracet hodnotu, může deklarovat hlavní program jako proceduru bez parametrů:
void main(void) { ... }
Napište jednoduchou kalkulačku. Program s názvem kalk bude mít povinně 3 parametry: číslo operátor, číslo, např.kalk 3.5 + 2
. Na obrazovku pak vypíše výsledek operace. Uvažujte tyto operátory:+ - * /
. Ošetřete chybu dělení nulou a správný počet parametrů programu. Není-li počet argumentů správný, vypište chybové hlášení ve tvaru:
Spouštění: kalk číslo operátor číslo
Jméno programu kalk získejte z argumentuargv[0]
, odstraňte cestu.
(Návod: číselné hodnoty z řetězců převádějte pomocí funkcesscanf
, jméno programu nekopírujte - najděte poslední výskyt znaku '\\' a při tisku jména programu předejte ukazatel na další znak.)Polotovar:
Dev C++: kalkp.dev, kalkp.c CodeBlocks: kalkp.cbp, kalkp.c Řešení:
Dev C++: kalk.dev, kalk.c CodeBlocks: kalk.cbp, kalk.c
typedef
lze definovat vlastní typ.
Částečně jsme danou problematiku již probírali. Nyní si ji zopakujeme a rozšíříme.
Obecně má definice typu podobu:
typedef definice_typu identifikátor_typu;Jako první příklad uvedeme vytvoření de facto aliasu typu
float
:
typedef float TPrvek;Definovali jsme nový typ TPrvek, který je evivalentem typu
float
. Čtenář si možná klade otázku, proč jsme
uvedli tento jednoduchý příklad, jenž se může zdát nesmyslným. Nesmyslný příklad není, naopak, ukazuje možnost, jak psát
programy odolnější proti chybám. Představme si, že zpracováváme dynamické pole prvků typu int
. V kódu se
určitě vyskytují tyto řádky:
int main() { int *pole; ... pole = (int *)malloc(n*sizeof(int)); ... }Pokud se rozhodneme pracovat s prvky typu float, musíme přepsat typ
int
všude tam, kde se s prvky pole pracuje (zde na dvou
místech kódu - v deklaraci pole a při volání funkce malloc). Je nebezpečí, že někde v kódu zapomeneme typy zaměnit nebo zaměníme omylem typy v té části
kódu, která se prvků pole vůbec netýká (např. hlavičky procedur a funkcí).
Pokud nadefinujeme uživatelský typ a dodržujeme důsledně jeho použití v kódu všude tam, kde se pracuje s polem, stačí pak upravit pouze definici typu:
typedef int TPrvek; int main() { TPrvek *pole; ... pole = (TPrvek *)malloc(n*sizeof(TPrvek)); ... }Ukážeme si ještě dva příklady definice typů:
typedef int* TPint; // typ TPint je typ ukazatel na int, proměnnou pi typu ukazatel pak deklarujeme: TPint pi; typedef float TPoledeset[10]; // TPoledeset je typ pole o deseti prvcích typu float, vlastní pole A pak deklarujeme: TPoledeset A;Poznámka: Bývá zvykem, že identifikátory uživatelsky definovaných typů programátoři začínají písmenem T (jako typ)
typedef enum { CERVENA, MODRA, ZELENA, BILA, CERNA } TBarvy;Obsahem proměnné typu TBarva jsou tedy hodnoty CERVENA, MODRA, ZELENA, BILA nebo CERNA.
Proměnnou barva_auta
typu TBarvy
nadeklarujeme standardně:
TBarvy barva_auta;V kódu pracujeme se proměnnými výčtového typy a symbolickými hodnotami běžně jako s jinými standardními datovými typy , např.:
barva_auta = CERVENA; if (barva_auta == CERVENA) ...V jazyce C jsou hodnoty výčtového datového typu interně reprezentovány celými čísly počínaje 0, tj. v našem příkladě s barvami je hodnota CERVENA reprezentována 0, MODRA 1 atd. Při definici typu máme možnost ovlivnit vnitřní reprezentaci přiřazením hodnot:
typedef enum { CERVENA, MODRA=5, ZELENA, BILA=10, CERNA } TBarvy;V tomto případě je hodnota CERVENA vnitřně reprezentována 0, MODRA 5, ZELENA 6, BILA 10, CERNA 11. Překladač přiřazení nekontroluje, takže v jazyce C je bez problémů přeložitelná tato definice:
typedef enum { CERVENA, MODRA=5, ZELENA, BILA=3, CERNA, SEDA } TBarvy;Zde ale mají symbolické hodnoty MODRA a SEDA přiřazeny interní reprezentaci číslem 5, nejsou tedy v kódu rozlišitelné!
Poznámka: V jazycích se silnou typovou kontrolou (např. Pascal) je dovolenou používat v kódu pouze symbolická jména hodnot výčtového typu. Jazyk C je jazyk se slabou
typovou kontrolou, kdy se provádějí kontroly pouze na úrovni vnitřní reprezentace. Je tedy možné zapsat do kódu přiřazení barva_auta = 2;
Používat
tento způsob se zásadně nedoporučuje, protože snižuje přehlednost a čitelnost kódu!
[Cvičení 10] | [Obsah] | [Cvičení 12] |