Předchozí [Operátory v C]

Tak se zase hlásím! A začnu hned opakováním a pár příklady, ať je jistota, že vše pasuje a sedí 😉.

Dělení

Prohlédněte si následující kód. Dokážete tipnout, jaký bude výstup?

#include <stdio.h>
int main(void) {
    float x;
    x = 5 / 2;
    printf("Výsledek: %.2f\n", x);
    return 0;
}

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Řešení (klikni na šipečku)
 Výsledek: 2.00 
Dělíte-li celé číslo celým číslem, bude výsledek vždy celé číslo. A je jedno, že ho ukládáte do proměnné, která by mohla pojmout desetinné číslo.

Pozn.: Format specifier výše udává, že do textu bude vloženo desetinné číslo (f) a že bude omezeno na 2 místa za desetinnou čárkou (.2).

Zkusíte kód přepsat tak, aby byl výsledek správný?

Řešení 2 - upravený kód
#include <stdio.h>
int main(void) {
  float x;
  x = 5.0 / 2;
  printf("Výsledek: %.2f\n", x);
  return 0;
} 
(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Stačí, aby jeden z operandů byl desetinným číslem, aby byl i výsledek desetinným číslem! Ideálně si oba prográmky zkuste zkompilovat a spustit. Pro připomenutí (pokud se váš soubor jmenuje ex1.c):
clang ex1.c 
Případně, pokud chcete, aby vám compiler zobrazil i varování (nejenom chyby), a výsledný, spustitelný program měl jiný název než a.out (např. ex1), zkompilujte následovně:
clang -Wall -Wextra -pedantic -o ex1 ex1.c 


ASCII tabulka

ASCII tabulku jsem krátce zmínila ve druhém díle (První program, datové typy a printf). Tabulku tvoří tisknutelné a netisknutelné znaky a jejich ekvivalent v číselné podobě (desítkové, šestnáctkové a osmičkové soustavě) a jejich HTML znak.

ASCII tabulka, převzato z https://www.asciitable.com/
ASCII tabulka, převzato z https://www.asciitable.com/

Červeně jsem označila znaky, které nejsou tisknutelné. Schválně si zkuste přeložit a spustit následující kód, případně můj znak nahraďte jiným a koukněte, co se stane (tip: zapněte si zvuky u PC).

#include <stdio.h>
int main(void) {
  char a = 7;
  printf("Netisknutelný znak %c\n", a);
  return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Věnujte chvíli pozornost tzv. format specifier, který udává, jak má program zpracovat výstup proměnné udané na místě dalších parametrů (prvním parametrem je náš textový řetězec, který může obsahovat takováto rezervovaná místa, která jsou následně ve výstupu nahrazena tím, co následuje za textovým řetězcem). V kódu výše udává %c, že bude (měl by) následovat tzv. character, char, tedy jednotlivý znak.

Co tedy bude výstupem?
Znak s číslem 7 je podle ASCII tabulky *bell*, neboli zvukový signál. Na konzoli se nám tedy zobrazí pouze text Netisknutelný znak , ale měl by být slyšet zvukový signál. Pokud byste v řetězci udali, že má být výstupem číslo, např. pomocí format specifieru %d, pak by nenásledoval žádný zvukový signál, ale na konzoli byste měli vidět text Netisknutelný znak 7. Vyzkoušejte si to! (Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)


Další ukázkou práce s ASCII tabulkou a znaky by mohl být následující program. Zkuste si nejdřív v hlavě (a za pomocí tabulky) tipnout, co by tak mohlo být výstupem!

#include <stdio.h>

int main(void)
{
  int a1 = 0x41;	// 0x znamena, ze se nejedna o decimal, ale o hexadecimal
  int a2 = 0150;	// 0 na zacatku znaci oktal
  int a3 = 111;
  int a4 = a2 + 2;
  int a5 = '!';

  printf("%c%c%c%c%c\n", a1, a2, a3, a4, a5);

  return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výstup na konzoli:
Ahoj! 


Všimněte si, že znaky jsou také jenom čísla. Můžeme tedy oboje zaměňovat a ukládat do char i int (pozor jenom na rozsah, pokud byste zaměňovali i jinde; u znaků problém zatím nehrozí), a také počty jsou možné.

A pro úplnost, pokud se nad tím někdo podivoval - jednoduché uvozovky slouží pro zápis znaků, dvojité pro textové řetězce.

Operátory

Operátory můžeme dělit podle dvou kritérií:

  • kolik operandů potřebují pro danou operaci
    • unární (např. inkrement a dekrement)
    • binární (např. klasické sčítání, tedy +)
    • ternární (zatím neznáme; jedná se o ? : operátor)
  • na jaké pozici se nachází
    • infix (tak, jak to známe, např. a + b)
    • prefix (–a)
    • postfix (a++)

Zatím jsme poznali aritmetické, logické, inkrement a dekrement a srovnání. Existují i bitové nebo třeba operátor, kterým se získá adresa proměnné.

Následuje opět pár příkladů. Zkuste si nejdřív v hlavě promyslet, co by mohlo být výsledkem.

Aritmetické operátory

#include <stdio.h>

int main(void)
{
  int a = 10;
  int b = 4;

  printf("a = %d, b = %d\n\n", a, b);

  printf("%d\n", a / b);
  printf("%d\n", a % b);
  printf("%d\n", a += b);
  printf("%d\n", a /= b);

  printf("\na = %d, b = %d\n", a, b);

  return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výstup na konzoli:
a = 5, b = 2

2
1
7
3

a = 3, b = 2 
Pro objasnění, proč a kdy se co děje: Hned na začátku tu máme dělení dvou celých čísel, jejichž výsledkem je opět celé číslo. To znamená, že cokoliv za desetinnou čárkou se prostě zahodí a tudíž 5 / 2 je 2, protože z 2.5 se 0.5 prostě odsekne, neproběhne žádné zaokrouhlení.

Procento je tzv. modulo operátor, jehož výsledkem je zbytek celočíselného dělení. 5 / 2 je 2, zbytek 1.

Následují zkrácené verze početních operací. Např. a += b je to samé jako a = a + b. Tudíž výstupem bude nejdříve 7 (protože a je nyní 5 + 2), potom 3 (protože 7 / 2 je 3, cokoli za desetinnou čárkou je zahozeno). První dva printf nezměnili hodnotu skrytou za proměnnou a nebo b!

Poslední řádek slouží už jenom pro úplnost a vytiskne obsah proměnných a, b.


Logické operátory a srovnání

Zkusíte si tipnout, co bude výsledkem?

#include <stdio.h>

int main(void) {
  int a = 5;
  int b = 2;
  int c = (b != 0) && (a / b);
  printf("%d\n", c);
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výsledek:
1 
Výsledkem není výsledek dělení. Výsledkem je v tomto případě pravdivostní hodnota (0 v případě nepravdy, jakékoli jiné číslo v případě pravdy). Nejdřív se zkontroluje, zda b není rovno nule (pokud ano, došlo by k tzv. short circuit evaluation, vyhodnocování by bylo přerušeno, protože nepravda a cokoliv dalšího je vždy nepravda) a potom se sice vyhodnotí výsledek dělení, avšak v tomto kontextu bude hrát roli jen a pouze jako pravdivostní hodnota (tudíž bude výsledek druhého výrazu pravda, protože výsledkem dělení je hodnota jiná než 0). Konečný výsledek je 1, neboli pravda.


Snažte se vždy vyvarovat srovnávání desetinných čísel. Počítač ne vždy počítá s přesností, na jakou jsme zvyklí. I proto máme možnost využít float či double (double má 2x takovou přesnost). Pokud se dostanete do situace, že budete srovnávat desetinná čísla, použijte k tomu nějaké epsilon, hodnota v rámci které je srovnání ještě OK (např. +/- 0.00000001, při srovnání dvou čísel a, b by to vypadalo následovně: a > b - epsilon && a < b + epsilon; pokud by obě podmínky byly splněny, hledělo by se na a a b jako na dvě stejná čísla).

Ale pryč od teorie. Schválně si to vyzkoušejte.

#include <stdio.h>

int main(void) {
  float a = 0.9f;
  printf("%d\n", a == 0.9);
  printf("%d\n", a == 0.9f);

  return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výsledek:
0
1 
V prvním případě je srovnáván float s double proměnnou, která má větší přesnost. Zkuste si obě čísla nechat vypsat na konzoli s přesností 8 a více míst a uvidíte rozdíl (připomínám, že toho jde dosáhnout format specifierem %.8f).


#include <stdio.h>

int main(void) {
  int a = 0;
  int b = 1;
  int c = a || (a || b) && (a > b);
  printf("%d\n", c);
  return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výsledek:
0 


Inkrement a dekrement

Jako i předtím - představím kód a vy si zkuste v hlavě projít, co by asi tak mohlo být výsledkem.

#include <stdio.h>

int main(void) {
    int a = 1;
    int b = 1;
    printf("a = %d, b = %d\n", a, b);
    printf("%d\n", a++ + --b);
    printf("a = %d, b = %d\n", a, b);
    return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výsledek:
a = 1, b = 1
1
a = 2, b = 0 


#include <stdio.h>

int main(void) {
    int a = 1;
    int b = 1;
    int c = 1;
    printf("a = %d, b = %d, c = %d\n", a, ++b, c++);
    printf("c = %d\n", c);
    return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výsledek:
a = 1, b = 2, c = 1
c = 2 


Overflow a underflow

Již jsem zmínila nahoře, že je třeba dbát na možný rozsah jednotlivých datových typů. Téma již jsem načla i v předposlední kapitole, kód je k nalezení na GitHubu. Zkoušeli jste, co se stane, pokud povolený rozsah přesáhnete?

#include <stdio.h>

int main(void) {
    unsigned char a = 0;
    a -= 1;
    printf("%d\n", a);

    char b = 127;
    printf("%d\n", b + 1);

    char c = 567;
    printf("%d\n", c);

    return 0;
} 

(Kód je jako vždy k nalezení i na GitHubu a to pod následujícím odkazem)

Výsledek:
255
128
55 
Číslo se překlopí na druhý konec intervalu a tam pokračuje. V případě prvního máme tedy povolený interval od 0 do 255 (unsigned značí, že číslo bude bez znaménka, tedy nebude možné znázornit v něm negativní hodnoty). Pokud od 0 odečteme 1, nedostaneme -1, ale 255! To samé platí u ostatních s rozdílem povoleného intervalu. V případě posledního se překlopí tolikrát, kolikrát to bude možné, než dosáhne "567".

Pokud se dostanete přes povolený rozsah, jedná se o overflow. Pokud pod něho, pak se mluví o underflow.


Snad je to takhle vše pochopitelné (a kdyby ne, klidně se mi ozvětě na Discordu) 🙃.

Kam dál?

Smyčky a podmínky