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

Unární operátory
OperátorVý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:

y = 5;
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.

float y;
int x = sizeof(float);
x = sizeof(y);

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é.

y = 5;
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.

x = f1() + f2();

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

Binární operátory
OperátorVý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ů
Relační operátory
OperátorVý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;.

Ukázka kombinací operátoru přiřazení s jiným operátorem
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 :).

printf("%s\n", (x > y) ? "x je vetsi nez y" : "x je menci ci roven y");

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;
}

/*------------------------------------------------*/
Visual Studio

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:

    printf("Mate %2" "Iu" " bitovy prekladac.\n", sizeof(int *) * 8);

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:

    printf("Mate %2Iu bitovy prekladac.\n", sizeof(int *) * 8);

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?

z > x ? x : y = z;

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á >:-).

Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 18.6.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..