[Obsah]  

Doplňující látka z jazyka C


Témata


Bitové operátory

Bitové operace patří k operacím nižší úrovně, manipulují totiž s přímo s jednotlivými bity operandů. Využití najdou zejména při programování řídicích aplikací pro jednočipové mikropočítače, kdy je práce s jednotlivými bity nezbytná. V běžných aplikacích je třeba pracovat s bitovými operacemi „opatrně“; často je třeba znát vnitřní reprezentaci dat a chování programu tak nemusí být stejné na všech platformách (překladačích, procesorech, operačních systémech).

V jazyce C existuje šest bitových operátorů. Přehled uvádí tabulka 1:

OperátorOperace
&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

Tabulka 1: Bitové operátory

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 =10000101  a =10000101  a =10000101  a =10000101
b =01000110  b =01000110  b =01000110   
c =00000100  d =11000111  e =11000011  f =01111010 

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 =00010100  h =00001000
Operaci logického součinu můžeme využít při zjišťování, zda jsou určité bity ve slově nastaveny pomocí tzv. masky. Chceme zjistit, zda např. bit 5. řádu proměnné x je nastaven. Hodnota 25=32 = 0x20 (20 je zapsána v šestnáctkové soustavě) je maska, která ma nastaven na hodnotu 1 právě bit 5. řádu. Zjištění spočívá v testu na nenulovou hodnotu bitového součinu masky a proměnné x:

unsigned char x;
unsigned char maska;
...
maska = 0x20;
if (x & maska != 0) ...
else ...
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;
int n; // v proměnné n je uloženo, který bit máme testovat
...
maska = (1 << n);
if (x & maska != 0) ...
else ...

Ukazatel na funkci

Speciální typ ukazatele, který je možné v jazyce C deklarovat, je ukazatel na funkci. Do něj je možné přiradit identifikátor funkce (interně je to adresa funkce), kterou chceme pomocí něho volat. Ukazatel na funkci se dá využít např. při výběru pololožek z nabídky, kdy do ukazatele uložíme funkci odpovídající vybrané položce.

Ukazatel na funkci deklarujeme obecně takto: návr. hodnota (*identifikátor)(seznam form. parametrů)

Příklad:

float(*moje_fce)(int a, int b);
O řádek výše je deklarována proměnná moje_fce, což je ukazatel na funkci, která vrací hodnotu typu float a má dva parametry typu int. Závorky (*moje_fce) jsou nezbytné, jinak bychom nedeklarovali ukazatel na fukcni, ale hlavičku funkce vracející ukazatel na float.

Použití ilustruje následující příklad:

float obsah(int a, int b)
{
  return a*b;
}

int main(void)
{
  float(*moje_fce)(int a, int b);
  moje_fce = obsah;

  float o = moje_fce(3,4);,
}
Do ukazatele moje_fce je možné uložit pouze adresu funkce, která vrací hodnotu typu float a má dva parametry typu int.
Využití ukazatele na funkci demonstrujeme ještě na jednom příkladě. V poli typu int kódujeme informace o vlastnostech pěti osob (konkrétně jde o kódování chromozomu v genetickém algoritmu). Máme pole typu int o deseti prvcích, první pětice obsahuje čísla od 0 do 4, druhá pětice také čísla od 0 do 4. První pětice přiřazuje jménům oblíbená jídla, druhá pětice přiřazuje jménům oblíbené atrakce na pouti. Index prvku určuje osobu (jméno). Přiřazení čísel jménům, jídlům a atrakcím definuje následující tabulka 2.

Jména Jídla  Atrakce
indexyjméno hodnota jídlo hodnotaatrakce
0 a 5Ron 0žvýkačka 0horská dráha
1 a 6Joe 1zmrzlina 1zvonková dráha
2 a 7Sam 2párek v rohlíku 2strašidelný zámek
3 a 8Len 3hranolky 3kolotoč
4 a 9Don 4cukrová vata 4lochneska

Tabulka 2: Kódování chromozomu

Příklad chromozomu je na obrázku 1.

Obrázek 1: Příklad zakódování chromozomu

Úkolem je napsat program, který vytiskne text s přiřazením jídel a atrakcí jménům. Nadefinujeme tři funkce char *vrat_jmeno(int h), char *vrat_jidlo(int h), char *vrat_atrakci(int h),které mají jako parametr celé číslo (kód); první vrací odpovídající jméno, druhá vrací odpovídající jídlo a třetí atrakci.
Výpis textu provedeme v jednom cyklu; nadeklarujeme ukazatel na převodní funkci. Na počátku do něj přiřadíme funkci vrat_jidlo; jakmile se dostaneme k indexu 5, tj. začneme vypisovat druhou část chromozomu, přiřadíme do ukazatele funkci vrat_atrakci.

Příklad: Dev C++: jmena.dev, jmena.cpp


Funkce s proměnným počtem parametrů

Jazyk C umožňuje uživateli definovat funkce s proměnným počtem parametrů (čtenář zná např. funkci printf, která má proměnný počet parametrů).

Problematika funkcí s proměnným počtem parametrů je vysvětlena v uvedené prezentaci: prom_param.ppt

Příklad z prezentace: Dev C++: varpar.dev, varpar.c


Struktury a uniony

Struktury

Pole je homogenní datový typ, tedy všechny položky pole jsou stejné. V případě struktur jde o heterogenní datový typ. Struktura tedy obsahuje několik položek různých datových typů, které ovšem spolu logicky souvisejí. Struktura umožňuje „společné“ pojmenování všech sdružených údajů, s nimiž se potom pohodlněji pracuje.

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é a zpravidla se ani nepoužívá (pouze v případě, že struktura odkazuje sama na sebe). 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;

Pomocí typedef můžeme definovat uživatelský typ, např. TBod:

typedef struct
{
  float x;
  float y;
} TBod;
Proměnné typu TBod deklarujeme jako každé jiné proměnné:
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

Poznámka: Pokud položka struktury obsahuje ukazatel na sebe, musíme uvést jméno struktury před definicí. Následující definici využijeme při implementaci spojového seznamu, viz dále:
typedef struct TPolozka
{
  int hodnota;
  TPolozka *dalsi;
} TPolozka;
Jednoduchý program využívající strukturu pro uložení bodu si můžete stáhnout: Dev C++: body.dev, body.c

Příklad struktury - formát BMP

Grafický formát pro uložení obrázků bmp (Bitmap) obsahuje úvodní hlavičku o délce 14 bytů. Její struktura je zobrazena v tabulce 4:

PoložkaDélka položkyPopis
typ2 byty2 znaky „BM“
velikost4 bytycelková velikost souboru
rezervováno12 bytyrezervováno pro pozdější použití, musí být na 0
rezervováno22 bytyrezervováno pro pozdější použití, musí být na 0
posun4 bytyposun obrazových dat od začátku této hlavičky (struktury)

Tabulka 4: Hlavička formátu BMP

Předpokládejme, že pracujeme na platformě Intel (uložení dat little-endian, tj. slabiky nižšího řádu jsou uloženy na nižších adresách), dále že sizeof(unsigned short)=2 a sizeof(unsigned int)=4. Strukturu, která odpovídá hlavičce souboru, nadeklarujeme následovně:

typedef struct
{
  unsigned char B;
  unsigned char M;
  unsigned int velikost;
  unsigned short res1;
  unsigned short res2;
  unsigned int posun;
} THeadBMP;
Nadeklarujeme proměnnou pro hlavičku: THeadBMP hlavicka;
Hlavičku načteme ze soubor vst, který byl otevřen pomocí fopen v binárním módu: fread((void*)&hlavicka,sizeof(THeadBMP),1,vst).
Upozornění: Některé překladače v rámci optimalizace přístupu do paměti neukládaí položky struktur těsně za sebou, ale zarovnávají je na adresy dělitelné 4. V takovém případě by mezi položkami M a velikost byla v paměti mezera a velikost struktury by byla větší než velikost hlavičky. Museli bychom tedy v překladači tuto optimalizaci (zarovnání položek na adresy dělitelné 4) vypnout.

Uniony, bitová pole

Prezentace o sjednocení (uniony) a bitových polích si můžete stáhnout: uniony.ppt.