S objektem je spojena jeho adresa (adresa přiděleného prostoru v paměti) a jeho hodnota (bitový vzorek nacházející se v přiděleném prostoru a interpretovaný ve shodě s typem objektu). Proto rozeznává jazyk C dva druhy výrazů. Adresový výraz (v angličtině lvalue, protože je obvykle na levé straně přiřazovacího výrazu) odkazuje na objekt, hodnotový výraz udává hodnotu objektu. Typickým případem obou výrazů je identifikátor, který je podle kontextu chápán jako adresa objektu (na levé straně přiřazovacího výrazu) nebo jako jeho hodnota (na pravé straně přiřazovacího výrazu). Dále např. objekt typu ukazatel má hodnotu, která je adresou jiného objektu. Je-li E hodnotový výraz typu ukazatel, pak *E je adresový výraz odkazující na objekt, na který ukazuje E. Operátorem výběru složky struktury získáme rovněž adresový výraz.
Výrazy jsou tvořeny z operandů pomocí operátorů. Je stanovena priorita a rovněž asociativa operátorů (zleva či zprava). Jinak není pořadí vyhodnocování výrazů definováno a předpokládá se, že kompilátor může toto pořadí měnit tak, aby dosáhl efektivního kódu. Tak např. není definováno pořadí vyhodnocování podvýrazů a dokonce ve výrazech s komutativními a asociativními operátory nemusí kompilátor brát ohled na uzávorkování. Jediná metoda, jak vnutit pořadí vyhodnocení je použití mezivýsledků.
Ošetření chyb při výpočtu (přeplnění apod.) není definováno. Celočíselné přeplnění je zpravidla ignorováno. Dělení nulou a chyby při aritmetice v pohyblivé řádové čárce má důsledky závislé na implementaci.
Primární výraz je základním stavebním kamenem všech dalších výrazů. V jazyku C k primárním výrazům patří:
První z nich je tvořen primárním výrazem, za kterým následuje tečka a identifikátor, druhá varianta využívá šipku (->) místo tečky. Tak např.
(a) (b), [3], s.1
Typ identifikátoru v roli primárního výrazu je udán jeho deklarací; jde-li o identifikátor pole objektů, jeho hodnotou je ukazatel na první objekt a typ výrazu je ukazatel na objekt. Navíc identifikátor pole není adresovým výrazem. Obdobná přeměna na ukazatel platí rovněž pro identifikátor funkce.
O konstantách a řetězcích jsme hovořili v předešlém článku; protože řetězec je pole znaků, je ve shodě s předešlým pravidlem modifikován na ukazatel na první znak řetězce.
Uzávorkování výrazu nemění jeho typ a hodnotu, tedy např. ((((i)))) znamená totéž co i.
Indexový výraz se zpravidla používá ve spojení s poli. Jde-li např. o výraz a[i], kde a je identifikátor pole, i proměnná typu int, lze chápat hodnotu výrazu jako i-tý prvek pole a. Podle definice jsou zápisy
a[i] *((a) + (i)),
b[i][j] *(*((b) + (i)) + (i)).
Pole jsou v jazyku C ukládána po řádcích, tedy tak, že poslední index se mění nejrychleji. Rozeberme si nyní poslední uvedený příklad pro případ deklarace
int b[3][5].
Identifikátor b se převede na ukazatel na první ze tří pětičlenných polí typu int. Výraz b[i] se chápe jako *((b)+(i)), b se opět bere jako ukazatel na pole o pěti objektech typu int, i se převede na stejný typ, tedy vynásobí se délkou ukazovaného objektu (tj. délkou pole o pěti objektech typu int), výsledek se sečte, operátor indirekce dá výsledek pole (pěti objektů int), a to se převede na ukazatel na první z těchto pěti objektů. Dále se j převede na stejný typ, tj. na int, vynásobí se délkou typu int a výsleek se sečte. Poslední nevyřízený operátor indirekce (úplně vlevo) dá očekávaný výsledek j-tý objekt z i-tého řádku pole b.
Primární výraz, tvořící první část volání funkce, musí být typu funkce vracející objekt; hodnotou volání funkce je pak vrácený objekt. Tak např. ve výrazu
f(1,2,3)
(*pf)(1,2,3)
extern int f();
Při předávání skutečných parametrů se provádějí konverze typu float na typ double, char a short na int. Jména polí a funkcí se převedou na ukazatele. Jiné konverze se automaticky neprovádějí. Jsou-li třeba, musí je programátor vyznačit přetypováním. Typickým příkladem je použití celočíselného argumentu pro funkci s parametrem typu double:
sqrt((double)2).
Pořadí vyhodnocení ergumentů není jazykem definováno.
Ve složkových výrazech jde o výběr složky struktury nebo unie. Primární výraz před tečkou musí být struktura nebo unie a identifikátor za tečkou musí označovat některou její složku. Hodnota složky je hodnotou výrazu. Jde přitom o adresový výraz, byl-li primární výraz před tečkou adresovým výrazem. Šipka (->) vyžaduje, aby před ní byl použit ukazatel na strukturu nebo unii. Výsledkem je adresový výraz, ukazující na složku určenou identifikátorem. Je tedy
ps->x
(*ps).x
V následujících odstavcích budeme probírat jednotlivé skupiny operátorů. Učiníme tak v pořadí jejich přednosti při vyhodnocování, tzn. že unární operátory se vyhodnotí před multiplikativními operátory atd.
Unární operátory mění hodnotu jednoho výrazu. S výjimkou operátorů inkrementace a dekrementace se zapisují před výrazem a je-li jich použito více současně, vyhodnocují se zprava doleva:
* indirekce (získání odkazovaného objektu), & adresa (získání adresy objektu), - aritmetické minus, ! logická negace (změní nulu na jedničku , nenulu na nulu), ~ bitový komplement (změní bitové nuly na jedničky a naopak), ++ zvýšení hodnoty před, resp. po vyhodnocení následujícího, resp. předcházejícího operandu (inkrementace), -- snížení hodnoty před, resp. po vyhodnocení, následujícího, resp. předcházejícího operandu, (dekrementace), (type) explicitní převod na typ v závorkách, sizeof délka objektu, typu. |
Operátor indirekce očekává ukazatel na objekt a vrací tento objekt; je-li pi ukazatel na int, je *pi odkazovaný objekt typu int. Naopak ukazatel na objekt je výsledkem operátoru adresy aplikovaného na hodnotový výraz (např. na identifikátor). Můžeme tedy psát např.
pi=&i;
Operátor logické negace je překvapivě použitelný na aritmetické typy a ukazatele, dává výsledek typu int. Opět připomínáme, že nulová hodnota se implementuje jako false, nenulová jako true. Ukazatel má nulovou hodnotu, neodkazuje-li na žádný objekt.
Bitový komplement lze použít pouze na celočíselné typy. Operátory inkrementace a dekrementace pracují s adresovým výrazem. Výsledek závisí na tom, zda jsou použity v prefixové nebo postfixové notaci. V prefixové notaci je výsledkem zvýšená nebo snížená hodnota objektu určeného adresovým výrazem, např. ++i je ekvivalentní i=i+1, analogicky --i je ekvivalentní i=i-1.
V postfixové notaci je výsledkem hodnota objektu určeného adresovým výrazem a hodnota objektu je následně zvýšena, resp. snížena.
Zvýšení nebo snížení závisí na typu objektu. Pro cesločíselné typy se hodnota změní o 1, pro typ ukazatel na objekt v poli dojde k přesunutí na ukazatele na následující, resp. předcházející objekt pole. Obě použití operátorů inkrementace najdeme ve funkci strlen, která vrátí počet znaků argumentu - řetězce:
int strlen(s) char *s; { int i; i = 0; while(*s++) i++; return(i); }
Přetypování byl již věnován odst. 5.1.6 a bude ještě diskutováno v čl. 5.4.
Operátor sizeof dává jako výsledek počet slabik reprezentace objektu nebo typu. Nikde jinde se v definici jazyka o slabice nemluví a předpokládá se, že slabika je přesně prostor pro char. Typ výsledku je unsigned a chápe se jako konstanta (odvodí se z deklarací objektů operandu nebo použitého typu).
Kontrolním testem implementace může být např. program
main(){ if(sizeof(char) != 1) printf("chybna implementace\n"); }
Tak např. úryvek programu:
char s[20]; unsigned j; ... j = sizeof(s);
Jsou to operátory násobení *, dělení / a modulo % a jsou vyhodnocovány zleva doprava. Operátor násobení je asociativní a komutativní (což platí i pro operátor sčítání), takže kompilátor může změnit libovolně pořadí vyhodnocování i v případě uzávorkování. Tak např. a * (b * c) může být vyhodnoceno jako (a * b) *c.
Na hodnotu výsledku to má vliv jen ve zcela výjimečných případech a pak je třeba využít mezivýsledkové proměnné, např.
x = b *c;
Operátor dělení zaokrouhlí výsledek dělení dvou celočíselných kladných operandů směrem k nule; je-li však jeden z operandů záporný, je způsob zaokrouhlování závislý na implementaci. Zpravidla má zbytek stejné znaménko jako dělenec. Tak např. 7/4 je 1 a -7/4 je -1.
Analogické vlastnosti má operátor modulo. Tedy 7%4 dá výsledek 3 a -7%4 dá výsledek -3.
Pro celočíslená a, b vždy platí, že (a / b) * b + a % b = a, pro b různé od 0.
Operátor | První operand | Druhý operand | Výsledek |
+ | aritmetický ukazatel | aritmetický int | aritmetický ukazatel |
- | aritmetický ukazatel | aritmetický ukazatel | aritmetický int |
Při sčítání ukazatele na objekt v poli a celého čísla se celé číslo převede na posunutí adresy tak, že se vynásobí velikostí objektu. Je-li tedy p ukazatel na objekt v poli, pak výraz p + 1 ukazuje na následující objekt, p - 1 na objekt předcházející. To odpovídá použití operátorů inkrementace p++ a dekrementace p--.
Odečtením dvou ukazatelů dostaneme vzdálenost obou objektů, tj. počet objektů, která je v poli dělí (vzdálenost sousedních objektů je rovna 1). To se využívá v následující variantě funkce strlen:
int strlen(s) char *s; { char *p; p = s; while(*p++); return(p-s); }
Operátory posuvů jsou dva, posuv vlevo a vpravo a jejich operandy musí být celočíselné. Značí se dvojicemi znaků << a >>. Levostranný operand se posouvá o počet bitů udaný pravostranným operandem. Výsledek posuvu není definován, je-li pravostranný operand záporný nebo větší než počet bitů reprezentace levostranného objektu.
Při posuvu vlevo jsou bity zprava nulovány. Při posuvu vpravo jsou bity zleva nulovány pouze pro objekty bez znaménka (unsigned) a výsledkem je logický posuv. U jiných objektů se do bitů zleva kopíruje znaménkový bit a jde o aritmetický posuv. Operátor posuvů vpravo můžeme s úspěchem využít v celočíselnéfunkci nbint, vracející počet bitů reprezentace typu int:
int nbint() { int i; unsigned w; i = 0; w = ~0; while(w){ w = w >> 1; i++; } return(i); }
Konstantní výraz ~0 je převeden na typ int a bitový komplement naplní všechny bity reprezentace w jedničkami. Proměnná w se tak dlouho posouvá vpravo o jeden bit, až se zleva vynulují všechny bity.
Provnávat lze aritmetické typy a ukazatele na objekty v jednom poli. Povolené relace jsou menší <, větší >, menší nebo rovno <= a větší nebo rovno >=. Výsledkem typu int je 1, je-li relace splněna, nebo 0, když splněna není.
Příkladem může být úryvek programu hledající menší ze dvou hodnot x, y:
... if(x < y) min = x; else min = y; ...
Pro ukazatele se relace menší interpretuje jako relace předchozí. Operátory rovnosti == a nerovnosti != mají shodné vlastnosti s relačními operátory, ale mají nižší prioritu.
Máme-li např. naprogramovat funkci, která určí počet výskytů znaku v řetězci, můžeme to učinit takto:
int match(s, c) char *s,c; { int i; while(*s){ if(*s+ + = = c) i++; } return(i); }
Na rovnost či nerovnost můžeme testovat i ukazatele. Lze dokonce testovat, zda ukazatel má nebo nemá nulovou hodnotu, tj. zda neodkazuje nebo odkazuje na nějaký objekt. Doporučuje se však využít symbolickou konstantu NULL (aby se vyloučila) závislost na implementaci ukazatelů).
Jde o operátory bitových operací logického součinu &, nonekvivalence ^ a logického součtu |. Operandy musí být celočíselné a jejich priorita klesá od součinu k součtu.
Ukažme si jejich použití na příkladě funkce swap, která vrátí argument se zaměněným pořadím slabik:
unsigned swap(w) unsigned w; { return( (w&0377) << 8 | w>>8); }
Logické operátory součinu && a součtu || se vyhodnocují vždy zleva doprava. Výsledkem typu int je v případě součinu hodnota 1 (true), je-li alespoň jeden z ooperandů nenulový (true). V obou případech se druhý operand nevyhodnocuje, je-li znám výsledek již po vyhodnocení prvního operandu. Pěkným příkladem je funkce hexa, která zjistí, zda zadaný znak je hexadecimální číslicí:
int hexa(c) char c; { if(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f') return(1); else return(0); }
Logický součin má přednost před logickým součtem. Je třeba rozlišovat bitové logické a logické operátory. podívejme se, jaké hodnoty bude postupně mít proměnná z:
... int x,y,z; x = 1; x = 2; z = x & y; z = x && y; z = x | y; z = x || y; ...
Podmíněný operátor je jediným ternárním operátorem jazyka C. Má tvar
E1 ? E2 : E3
... min = x < y ? x : y; ...
... abs = x < 0 ? -x : x. ...
Přiřazovací operátory jsou =, +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=. Na levé straně přiřazovacího operátoru musí být adresový výraz a hodnota výrazu na pravé straně nahradí objekt jím odkazovaný. Součástí přiřazení je konverze aritmetických typů, jinak musí být výrazy na obou stranách stejného typu. Jde-li např. o struktury, resp. unie, musí mít stejný typ (tj. šablonu), jde-li o ukazatele, musí ukazovat na objekt stejného typu.
První uvedený operátor = je prosté přiřazení. Všechny další jsou kombinované binární operátory s přiřazením. Výraz
E1 op = E2
Pro operátory + = a - = může být levý operand ukazatelem a pravý operand typu int. Pak platí totéž, co pro aditivní operátory (odst. 5.3.5). Operátory přiřazení jsou asociativní zprava doleva, tedy
x = y = z = 0;
x = (y = (z = 0));
Přiřazení je výrazem, výsledkem je přiřazovací hodnota (v našem příkladě nula).
Operátor čárky (zapomenutí) odděluje dva výrazy a vyhodnocuje 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řazovací a mít vedlejší efekty. Tak např. výsledkem výrazu
x = 0, ++y
Znak čárky se však používá jako oddělovač v seznamu argumentů ve volání funkce a v inicializačním seznamu (odst. 5.6.2 a 5.4.5). Má-li být operátor čárky použit i na těchto místech, musí být výraz opatřen závorkami. Tak např. volání funkce
f(x,(y = 0, y + 2), z)