Operátory a výrazy
V této kapitole popíši operátory, které můžete v jazyku C používat. Některé speciální operátory (jako např. přetypování) už znáte, a naopak použití jiných vysvětlím až v některé z příštích kapitol. Po výčtu všech operátorů bude na konci jeden velký příklad. U většiny operátorů však nechám na vás, abyste si jejich chování vyzkoušeli ve vlastních příkladech.
Operátory lze rozdělit několika způsoby. Mimo jiné na unární, binární, ternární, neboli na operátory s jedním, dvěma nebo třemi operandy. Operandy jsou data (většinou čísla), se kterými operátory (např. plus) pracují. Operátory se také rozdělují na aritmetické, bitové, relační a logické. Čtěte dále.
Unární operátory
Operátor | Význam |
---|---|
+,- | unární plus a mínus, |
& | reference (získání adresy objektu) |
* | dereference (získání hodnoty objektu dle adresy) |
! | logická negace |
~ | bitová negace |
++, -- | inkrementace a dekrementace hodnoty |
(typ) | přetypování |
sizeof() | operátor pro získání délky objektu nebo typu! |
Unární plus a mínus určuje znaménko čísla. Například ve výrazu
5 + (-4)
je plus binární operátor sčítání
(má dva operandy) a mínus je unární operátor (vztahuje se jen ke
čtyřce).
S operátory reference &
a
*
dereference jste se již setkali
při práci s ukazateli.
V jazyce C neexistuje datový typ boolean. Pokud potřebujete
někde získat nebo uchovat hodnotu pravda/nepravda (TRUE/FALSE),
můžete k tomu využít např. typ int. V jazyce C je totiž vše
nenulové považováno za TRUE a ostatní (včetně. např. prázdného
řetězce, tj řetězce, jehož první znak je nulový znak) za FALSE.
Nula je tedy FALSE a jiné číslo (nejčastěji se používá jednička) TRUE.
Další info viz boolovské datové typy.
Výsledkem logické negace !
je TRUE nebo
FALSE, což jazyk C vyhodnocuje jako 1 nebo 0. Například
!5
je 0
.
Bitová negace ~
je však něco úplně jiného.
Bitová negace obrací jednotlivé bity ve výrazu. Takže například
~0x05
je 0xFA
(~00000101
je 11111010
).
!""
je 0
(FALSE), protože negujete adresu řetězce ""
,
která je nenulová. !*""
je už 1
(TRUE), protože negujete hodnotu
prázdného řetězce. Což není překvapivé, když si uvědomíte, že prázdný řetězec vlastně obsahuje
pouze nulový znak '\0', což je, v bitech zapsáno, nula.
Operátory inkrementace ++
a dekrementace
--
mohou být zapsány jak před
výrazem, tak za ním. Zvyšují, resp. snižují hodnotu čísla/ukazatele o jedničku (nezapomeňte ale na aritmetiku ukazatelů).
Pokud leží ++
před proměnnou, nejdříve se
zvýší její hodnota a pak se proměnná použije ve výrazu (pokud v
nějakém je), leží-li ++
za proměnnou,
nejdříve se proměnná použije ve výrazu a pak se teprve zvýší její hodnota.
Obdobně je to u dekrementace --
.
Například:
x = ++y; // y se inkrementuje na 6 a poté se dosadí 6 do x
x = y++; // do x se dosadí y (6) a pak se y inkrementuje (na 7)
Výsledkem je, že x je 6 a y je 7.
Operátor sizeof()
vrací velikost datového typu nebo objektu v bytech.
sizeof(y) == sizeof(float)
Priorita operátorů
Priorita operátorů stanovuje, která část výrazu se vyhodnotí dříve, pokud
to není závorkami určeno jinak.
Klasickým příkladem je vyhodnocení výrazu
x = 1+1*0;
. Výsledkem bude samozřejmě
číslo 1, protože operátor násobení má větší prioritu než operátor sčítání.
Priorita operátorů je v jazyku C velice komplikovaná záležitost a proto se omezím na stručnou radu: používejte závorky!
Ovšem pozor! Pořadí vyhodnocení inkrementace (dekrementace) ve výrazu není normou jazyka C nijak dáno. Tudíž není zaručeno, že každý překladač pořadí (rozuměj prioritu) výpočtu vyhodnotí stejně. Vlastně i jeden překladač se na různých místech může v zájmu optimalizace programu rozhodnout k různému pořadí vyhodnocování výrazu (obdobně to platí i pro funkce, viz níže).
Následující konstrukci (a jí podobné) byste se měli vyhýbat jako čert kříži. Odhalit, proč se program nechová jak má kvůli jinému vyhodnocování různými překladači, bývá náročné.
x = y+++++y;
Po této operaci bude y rovno sedmi
(dvakrát se inkrementuje).
Ale co x? Může s to vyhodnotit jako 5+6,
nebo jako 5+7? V tomto případě by vám ani závorky nepomohli. Překladač si totiž
řekne: první y se má zvýšit až po použití ve
výrazu, tj první y bude 5.
A teď si může říct 2 věci:
y už jsem použil, můžu ho inkrementovat.
Nebo si to neřekne a přejde na vyhodnocování druhého y,
které inkrementuje (na 6), obě čísla sečte (5+6)
a až teď teprve se rozhodne, že provede postfixovou inkrementaci
y a zvýší jej na 7.
Taktéž u volání funkcí není jasné, která se ve výrazu zavolá dříve. Podívejte se na následující příklad.
Pokud funkce f1()
a f2()
vypisují na obrazovku nějaký text (kromě toho, že vracejí nějaké číslo, které se pak sečte),
nemůžete si být nikdy jistí, který text se vypíše jako první. A jako vždy, to že si to vyzkoušíte
s vaším překladačem neznamená, že všechny ostatní překladače to udělají stejně.
Funkce, která něco vypisuje a zároveň něco počítá, dělá 2 věci, což je obvykle špatně. Správně navržené funkce dělají vždy jen jednu věc. Nejen kvůli výše popsanému problému, ale taky kvůli jednoduššímu použití a případným změnám v programu.
Binární a relační operátory
Operátor | Význam |
---|---|
= | přiřazení |
+,-,*,/ | plus,mínus,krát,děleno |
% | zbytek po celočíselném dělení (modulo) |
<<, >> | bitový posun vlevo, vpravo |
& | bitové AND |
| | bitové OR |
^ | bitové XOR |
&& | logické AND |
|| | logické OR |
. | tečka, přímý přístup ke členu struktury |
-> | nepřímý přístup ke členu struktury |
, | čárka, oddělení výrazů |
Operátor | Význam |
---|---|
< | menší než |
> | větší než |
<= | menší nebo rovno |
>= | větší nebo rovno |
== | rovnost |
!= | nerovnost |
Při bitovém posunu vlevo (vpravo) se poslední levý (pravý) bit
ztrácí a zprava (zleva) je dosazena nula. Bitové operace je možné
provádět jen s celočíselnými hodnotami. Při posunu čísel se
znaménkem se však znaménko zachovává (znaménkový bit se nikam neposune).
(Na tom vidíte, jak je důležité určovat, zda je datový typ se znaménkem (signed)
nebo bez znaménka (unsigned).)
Vlevo od operátoru je objekt, ve kterém
se bity posouvají, vpravo od operátoru je číslo určující, kolikrát se bity
posunou.
U logického AND a OR, výrazů >= atp. je výsledkem TRUE nebo
FALSE, tedy 1 nebo 0.
Operátor přiřazení lze kombinovat s některými výpočty:
+=, -=, *-, /=, <<=, >>=, &=, |=, ^=
.
Jde jen o zkrácený zápis, tj. např. a += b;
je zkrácený zápis
pro a = a + b;
.
Výraz | Ekvivalent |
---|---|
x -= 5; | x = x - 5; |
x *= x; | x = x * x; |
x >>= 2; | x = x >> 2; |
K operátorům ->, ==, >, < atp. se ještě dostanu v některých z dalších kapitol.
Podmíněný operátor
Podmíněný operátor ? :
je ternárním operátorem (má 3 operandy).
Prvním operandem je výraz, který se vyhodnotí jako
logický výraz (TRUE nebo FALSE). Pokud se vyhodnotí jako TRUE,
výsledkem bude druhý operand (výraz mezi ?
a
:
), jinak třetí operand (výraz za :
).
Toto je vlastně první příklad, kde se vyhodnocuje nějaká podmínka (x > y
)
– na základě které se program rozhodne, co vytiskne.
Příklad
/* c10/operator.c */
#include <stdio.h>
#include <stddef.h>
#include <limits.h> /* ziskame konstanty UINT_MAX a INT_MIN */
#ifdef _MSC_VER
#define ZU "Iu"
#else
#define ZU "zu"
#endif
int main(void)
{
int x, y, z;
int pole[] = { 5, -10, 15, -20, 25, -30 };
size_t delka_pole;
x = y = z = 10;
x = y++;
z++;
printf("%3d %3d %3d\n", x, y, z);
printf("%3d %3d %3d\n", ++x, y++, ~z);
printf("%3d %3d %3d\n", x, y, z);
printf("Mate %2" ZU " bitovy prekladac.\n", sizeof(int *) * 8);
printf("Pole pole[] zabira %2" ZU " bytu.\n\n", sizeof(pole));
printf("UINT_MAX = %u = %u\n", UINT_MAX, ~0); /* UINT_MAX = max velikost cisla v typu unsigned int */
printf("INT_MIN = %i = %i \n", INT_MIN, 1 << ((sizeof(int)*8)-1)); /* posunu jednicku az do "nejlevejsiho bitu" */
printf("-51 >> 1 = %i\n\n", -51 >> 1);
delka_pole = sizeof(pole) / sizeof(pole[0]);
printf("Zadejte index pole (od 0 do %2" ZU "): ", delka_pole - 1);
scanf("%i", &x);
printf("Zadal jsi %i\n", x);
/* musi se zkontrolovat platnost zadaneho indexu pole !! */
x = (x < 0) ? 0 : x;
x = (x >= delka_pole) ? delka_pole - 1 : x;
delka_pole = ((size_t)x >= delka_pole) ? delka_pole - 1 : (size_t)x;
printf("pole[%" ZU "] = %i\n", delka_pole, pole[delka_pole]);
return 0;
}
/*------------------------------------------------*/
V příkladu je použitý podmíněný překlad (řádky 8 - 12).
Řádek 9 se použije pro Visual Studio, řádek 11 pro všechny ostatní.
ZU
se dále v kódu nahradí definovanou hodnotou, tj. například řádek
28 bude před překladem nahrazen ve Visual Studiu tímto řádkem:
A jak dobře víte, pokud zapíšete zasebou textové literály (to co je v dvojitých uvozovkách), překladač je automaticky spojí do jednoho:
A to už je snad srozumitelné :) Podmíněný překlad proberu podrobně v další kapitole.
Výstup z programu:
10 11 11 11 11 -12 11 12 11 Mate 32 bitovy prekladac. Pole pole[] zabira 24 bytu. UINT_MAX = 4294967295 = 4294967295 INT_MIN = -2147483648 = -2147483648 -51 >> 1 = -26 Zadejte index pole (od 0 do 5): -11 Zadal jsi -11 pole[0] = 5
Protože mám 32 bitový překladač, ukazatel (int *) zabírá 32 bitů, tj 4 bajty. Pole pole zabírá celkem 24 bajtů. To může být i u 64-bitového překladače, protože standard přesně neříká, jak velký má datový typ int být.
Pokud jste zmateni z výpočtu INT_MIN, vzpomeňte si, co jsem psal o dvojkové soustavě a doplňkovém kódu.
Všimněte si, jakým způsobem jsem získal meze pro index
pole[]
.
Pole je vždy indexováno od 0. Počet prvků však může
být různý. Kdybyste v kontrole horní meze napsali natvrdo
x = x>5 ? 5 : x;
,
museli byste při změně počtu prvků v poli změnit i tento výraz.
Ve velkém zdrojovém kódu byste to mohli
snadno přehlédnout (nehledě na to, že je to pracné hledání).
Takhle se nemusíte o nic starat.
Operátor ? :
lze použít i trochu jinak. Jestlipak správně pochopíte
význam následujícího výrazu?
Jestli si nejste jisti, tak si to vyzkoušejte v programu. Jestli si ste jisti, tak si to raději stejně vyzkoušejte :-). První co vás napadne jako odpověď nemusí být hned správně :-)1).
Použití NULL
Čistě jenom pro zopakování ukážu jak lze využít hodnotu NULL, společně s podmíněným operátorem.
/* c10/null1.c */
#include <stdio.h>
int main(void)
{
float *uk = NULL;
float p[] = { 10.2, 5.1, 0.6 };
printf("%s\n", uk == NULL ? "Ukazatel neni inicializovan" :
"Ukazatel lze pouzit");
printf("%f\n", uk == NULL ? 0.0 : uk[0]);
uk = p;
printf("%s\n", uk == NULL ? "Ukazatel neni inicializovan" :
"Ukazatel lze pouzit");
printf("%f\n", uk == NULL ? 0.0 : uk[0]);
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
Ukazatel neni inicializovan 0.000000 Ukazatel lze pouzit 10.200000
Jo jo, můžete místo NULL použít nulu, nebo lépěji (float *)0
.
Ale NULL je prostě nulový ukazatel a byl stvořen pro tyto účely, aby byl
program čitelnější. S NULL si taky nemusíte dělat starosti s přetypováváním.
Překladač ví, co NULL znamená a jak se k němu chovat. V jazyce C++ je NULL definováno jako konstanta 0
,
v jazyce C jako (void *)0
, nebo taky jen 0
.
1) Je možné, že tuto konstrukci vám překladač ani nedovolí přeložit. Můžete si pomoct se závorkami, ale to už pak nebude taková sranda dešifrovat, co to vlastně dělá >:-).