Klíčová slova, konstanty, řetězce

V minulé kapitole jsem probral základní datové typy. Nyní vám ukáži další objekty, které vám jazyk C nabízí. Například pole, pomocí nichž se dají vytvářet (mimo jiné) textové řetězce.

Klíčová slova

ANSI norma jazyka C určuje následující slova jako klíčová. Tyto slova mají v jazyce C speciální význam a nelze je používat jako uživatelem definované identifikátory (např. jména funkcí, proměnných, konstant atd.). Jejich význam postupně proberu kapitolu po kapitole. Zatím se jejich významem nemusíte zatěžovat.

auto break case const continue char default
do double else enum extern float for
goto if int long register return short
signed sizeof static struct switch typedef union
unsigned void volatile while      

Standard C99 přináší tyto nová klíčová slova:

inline _Bool _Complex _Imaginary

Konstanty

Konstanty jsou objekty, jejichž hodnota se za chodu programu nemění. Konstanty se definují za klíčovým slovem const. Po něm následuje datový typ konstanty. Pokud ne, překladač jej chápe jako int. Dále je v definici identifikátor konstanty a za rovnítkem hodnota.

const float a = 5;

Konstanty mají stejné vlastnosti jako proměnné, až na tu maličkost, že jejich hodnota se nedá změnit. Používají se v programu namísto přímého vložení hodnoty (čísla, textu atp.).

Pokud takovou konstantu použijete v programu na několika místech, stačí změnit její hodnotu v definici a tak se změní hodnota na všech místech programu kde byla použita. Pokud byste používali místo konstant přímo hodnoty, museli byste procházet celý program a měnit hodnotu na všech místech, kde je to potřeba. To je nejenom pracné, ale také si to říká o to, že někde zapomenete …

Použití konstant také ulehčuje práci překladači s optimalizací výsledného programu. Pokud překladač ví, že se hodnota nebude měnit, může ji uložit do optimální části paměti. Některé jednoduché výpočty s konstantami může provést už za překladu.

Jako konstantu můžete označit i argument fukce. Tím dáte najevo, že hodnota, kterou funkce dostane, se nebude uvnitř bloku funkce měnit. Pokud byste se konstatní proměnnou pokusili někde v těle funkce změnit, překladač by to nedovolil. Výhodou použití konstant není jen to, že to překladači pomáhá s optimalizací výsledného programu, ale i vám jakožto programátorům to pomůže s čitelností zdrojového kódu. (Stačí se podívat na deklaraci funkce a hned víte, že se argument funkce nebude nijak měnit.)

  1. /*------------------------------------------------*/
  2. /* c06/konst.c                                    */
  3.  
  4. #include <stdio.h>
  5.  
  6. int main(void)
  7. {
  8.     const a = 5;                /* konstantu a vyhodnoti prekladac jako int 5 */
  9.     const b = 5.9;              /* konstantu b vyhodnoti prakladac jako int 5 */
  10.     const float c = 5;          /* konstanta c je urcena jako float,tj. 5.0 */
  11.     int i = 0;
  12.  
  13.     printf("a/2 = %f\n", (a / 2) * 1.0);        /* celociselne deleni ... */
  14.     printf("b/2 = %f\n", (b / 2) * 1.0);        /* celociselne deleni ... */
  15.     printf("c/2 = %f\n\n", (c / 2) * 1.0);      /* deleni s desetinnou carkou ... */
  16.  
  17.     printf("Mala nasobilka pro cislo %i\n\n", a);
  18.  
  19.     printf("%i * %i = %i\n", a, i, i * a);
  20.     i = i + 1;
  21.     printf("%i * %i = %i\n", a, i, i * a);
  22.     i = i + 1;
  23.     printf("%i * %i = %i\n", a, i, i * a);
  24.     i = i + 1;
  25.     printf("%i * %i = %i\n", a, i, i * a);
  26.     i = i + 1;
  27.     printf("%i * %i = %i\n", a, i, i * a);
  28.     i = i + 1;
  29.     printf("%i * %i = %i\n", a, i, i * a);
  30.     i = i + 1;
  31.     printf("%i * %i = %i\n", a, i, i * a);
  32.     i = i + 1;
  33.     printf("%i * %i = %i\n", a, i, i * a);
  34.     i = i + 1;
  35.     printf("%i * %i = %i\n", a, i, i * a);
  36.     i = i + 1;
  37.     printf("%i * %i = %i\n", a, i, i * a);
  38.     i = i + 1;
  39.     printf("%i * %i = %i\n", a, i, i * a);
  40.  
  41.     return 0;
  42. }
  43.  
  44. /*------------------------------------------------*/

Výstup z programu:

a/2 = 2.000000
b/2 = 2.000000
c/2 = 2.500000

Mala nasobilka pro cislo 5
5 * 0 = 0
5 * 1 = 5
5 * 2 = 10
5 * 3 = 15
5 * 4 = 20
5 * 5 = 25
5 * 6 = 30
5 * 7 = 35
5 * 8 = 40
5 * 9 = 45
5 * 10 = 50

Výsledkem programu je malá násobilka čísla 5. Pokud chcete malou násobilku např. čísla 4, stačí změnit konstantu a. Asi si říkáte, že kdyby a nebyla konstanta ale proměnná, vyšlo by to nastejno. Jenomže pak byste mohli v nějakém velkém programu zapomenout na to, že se hodnota a nemá měnit a překladač by vám změnu (na rozdíl od konstanty) umožnil. Až se jednou budete vracet k vlastnímu zdrojovému kódu třeba po půl roce, budete rádi, že máte důsledně oddělené proměnné od konstant (a také, když budete mít zdrojový kód dobře okomentován :-).

Další výhodou konstant je, že za výrazy jako je (a/2)*1.0 může už překladač dosadit výsledek, protože vše potřebné je v době překladu známo. (Pravda ale je, že v takto jednoduchém programu by překladač dokázal poznat jakou bude mít proměnná a hodnotu, i kdyby nebyla označena jako konstanta.)

Používejte konstanty všude kde je to vhodné. Slouží to jako ochrana před nechtěnou změnou konstanty, jako komentář „tato hodnota je konstantní“ a pomáhá to překladači optimalizovat rychlost a velikost výsledného programu!

Řetězce

Kromě konstantních čísel můžete mít také v programu konstantní znaky a řetězce. Definice znakové konstanty není nijak překvapivá:

const char pismeno = 'a';

V jazyku C je znakový literál ('a') překvapivě typu int. V příkladu výše se automaticky převede na typ char a uloží do proměnné pismeno, takže se o to vlastně nemusíte starat. Jazyk C++ už definuje znakový literál jako typ char.

S konstantními řetězci je to už horší. Nejdříve vám musím říct, co to vlastně textový řetězec je. Jedná se o pole znaků. Co jsou to pole vysvětlím podrobněji později. Zatím stačí, když budete vědět, že pole je spojité místo v paměti vyhrazené pro posloupnost objektů.

Textový řetězec je tedy pole znaků (ať už se jedná o konstantní pole nebo ne). Navíc má tu zvláštnost (na rozdíl od ostatních polí), že má na konci tzv. zarážku. Je to znak, který obsahuje nulu (číslo nula). Tak se pozná, kde řetězec v paměti končí. Kde začíná je uloženo v ukazateli.
Takto se jednoznačně určí, kde se textový řetězec nachází a co do něj patří. Zatímco znaky jsou v jednoduchých uvozovkách ('), textové řetězce ve dvojitých ("). Nulový znak na konci textového řetězce za vás vloží překladač sám (za znakem ne – konec konců, znak je jen jeden znak …).

char * const veta = "Toto je konstantni retezec.";

Ukazatel veta je ukazatel na typ char a ukazuje na začátek textového řetězce, v příkladě na místo v paměti, kde je uloženo písmeno T. Pokud bude s řetězcem, na který ukazatel veta ukazuje, pracovat například funkce printf(), podívá se do paměti kam veta ukazuje, přečte první písmeno, pak se podívá na další bajt (velikost typu char je 8 bitů) a přečte další písmeno atd., až narazí na bajt obsahující nulu a tím skončí.

Klíčové slovo const v předchozí delaraci označuje jako konstantu pole, na které veta ukazuje. Ale pozor! Textový literál (tj. textový řetězec který v programu zapíšete do uvozovek) nelze měnit v žádném případě! Překladač takové literály ukládá do speciální části paměti, kam nesmíte nic zapisovat (pokud to zkusíte, program selže – bude zabit operačním systémem).

Pro označení jako konstanty ukazatele dejte const před hvězdičku.

const char *veta const = "Toto je konstantni retezec.";

Toto je konstantní ukazatel na konstantí pole. Nejen že nemůžete změnit kam ukazuje ukazatel, ale nemůžete změnit ani to, na co ukazuje. Za domácí úkol zkuste přijít na to, jak definovat konstantní ukazatel na nekonstantí pole :-).

Jak vytvořit „nekonstantí“ pole (znaků), proberu až v kapitole Pole a ukazatele.

V jazyce C neexistuje žádný datový typ „řetězec“. Na řetězec (obecně jakékoliv pole objektů) se můžete odkazovat pouze pomocí ukazatelů.

  1. /*------------------------------------------------*/
  2. /* c06/konst2.c                                   */
  3.  
  4. #include <stdio.h>
  5.  
  6. int main(void)
  7. {
  8.     const char Kpismeno = 'a';  /* konstanta */
  9.     char pismeno = 'b';
  10.     const char *veta = "Konstantni veta !\n";
  11.     const char *veta2 = "a";
  12.     char *ukazatel;             /* ukazatel na typ char */
  13.  
  14.     ukazatel = &pismeno;        /* ukazatel ukazuje na promennou pismeno */
  15.  
  16.     printf("1: Tisknu pismena: %c %c\n", Kpismeno, pismeno);
  17.  
  18.     printf("2: Tisknu vetu: %s", veta);
  19.     printf("3: Tisknu vetu: %s\n", veta2);
  20.  
  21.     printf("4: Tisknu pismeno: %c\n", *ukazatel);
  22.  
  23.     printf("5: Tisknu pismeno: %c\n", *veta);
  24.     printf("6: Tisknu pismeno: %c\n", *veta2);
  25.  
  26.     return 0;
  27.     printf("7: Tisknu vetu: %s\n", ukazatel);
  28.     printf("8: Tisknu vetu: %s\n", pismeno);
  29.     /* printf("9: Tisknu: %s\n",*pismeno); je uplna blbost */
  30.     /* *veta = 'C'; chybne prirazeni */
  31. }
  32.  
  33. /*------------------------------------------------*

Výstup z programu:

1: Tisknu pismena: a b
2: Tisknu vetu: Konstantni veta !
3: Tisknu vetu: a
4: Tisknu pismeno: b
5: Tisknu pismeno: K
6: Tisknu pismeno: a

V prvním volání funkce printf() se tisknou znaky tak, jak byste očekávali. Když funkce printf() narazí na sekvenci %c najde si svůj další argument a ten vytiskne jako znak.

Ve druhém volání se tiskne věta, na kterou ukazuje konstantní ukazatel veta a ve třetím volání se tiskne věta, na kterou ukazuje ukazatel veta2. Funkce printf() ve chvíli, kdy narazí na sekvenci %s si najde odpovídající argument, který bere jako adresu do paměti. Z této paměti začne po znacích číst a znaky tisknout, dokud nenarazí na nulový znak.
Věta "a" obsahuje kromě písmena 'a' také onu zarážku (nulový bajt), je tedy 2 bajty dlouhá (narozdíl od znaku 'a' uloženého v proměnné Kpismeno).

Ve čtvrtém volání tiskne funkce hodnotu, na kterou ukazuje proměnná ukazatel. Proměnné veta a veta2 jsou také ukazatele na typ char, úplně stejně jako ukazatel. Jen mají při své deklaraci přiřazenou hodnotu (odkaz na řetětězec v uvozovkách).

Ukazatele veta a veta2 ukazují na začátky vět, tedy přesněji, obsahují adresu paměti, na které je uloženo první písmeno věty. Proto se v pátém a šestém volání printf() vytiskne první znak věty.

Za příkazem return 0; se nacházejí ještě další tři volání funkce printf(). Tyto volání jsou chybné.

Sedmé volání printf() se pokouší vytisknout větu, na kterou ukazuje ukazatel ukazatel (jak šťastně pojmenováno :-). Jenomže ukazatel neukazuje na větu, ale na znak v proměnné pismeno. Překladač nemůže kontrolovat kam proměnná ukazatel v době spuštění ukazuje (zda na řetězec nebo znak nebo jestli vůbec někam …) a tak nepozná chybu. Pokud by došlo k takovémuto volání funkce printf(), vytiskl by se nejdříve znak v proměnné pismeno (tj. znak 'b') a pak by se tiskli další a další bajty z paměti počítače, dokud by se nenarazilo někde na nulu. Takže by se vytiskla hromada nesmyslů, nebo by program zkolaboval (neoprávněný přístup do paměti). (Kdybych inicializoval proměnou ukazatel např. takto: ukazatel = veta;, bylo by sedmé volání printf() naprosto v pořádku.)

Ovšem osmé volání je jednoznačně chyba. Namísto ukazatele jsem předal proměnnou typu char. Na tuto chybu vás již překladač upozorní, ale přesto dokáže program přeložit. Již jsem řekl, že adresa v ukazateli není nic jiného než číslo v bitech, stejně tak písmena (a vůbec všechny data v počítači). V osmém volání funkce printf() vezme znak v proměnné pismeno a použije jej jako adresu do paměti, ze které se pokusí přečíst větu. V tomto případě je téměř jisté, že vám program zkolabuje kvůli neoprávněnému přístupu do paměti, nebo se vypíše hromada nesmyslů, stejně jako v sedmém volání printf().

Číselná hodnota písmena 'b' je 98. Poslední volání funkce printf() by se tedy snažilo vytisknout větu začínající na 98. bajtu v paměti (kdyby toto volání překladač umožnil).

Deváté volání je opravdu nesmysl, protože proměnná pismeno není ukazatel, nelze použít operátor * (hvězdička, odborně dereference; ukazateli se totiž říká reference …). Toto by měl překladač odmítnout přeložit.

Na posledním řádku je ještě zakomentované chybné přiřazení *veta = 'C';. To je syntakticky správně – na adresu, kam ukazuje veta, tj. do prvního písmene věty, se snažím uložit znak C (totéž lze zapsat jako veta[0] = 'C';).
Chyba je v tom, že veta ukazuje na pole označené jako konstantí. (Nechme teď stranou, že se navíc jedná o řetězcový literál, do kterého se stejně zapisovat nesmí.)

Escape sekvence

Escape sekvence jsou sekvence znaků, které vám umožňují vložit do řetězce některé zvláštní znaky. Přehled escape sekvencí vidíte v tabulce.

Escape sekvence
Escape znakVýznamPopis
\0 NULL Nula, ukončení řetězce (má být na konci každého řetězce)
\a Alert (Bell) Pípnutí. Na moderních počítačích už bohužel nefunguje.
\b Backspace návrat o jeden znak zpět
\f Formfeed nová stránka nebo obrazovka
\n Newline přesun na začátek nového řádku
\r Carriage return přesun na začátek aktuálního řádku
\t Horizontal tab přesun na následující tabelační pozici
\v Vertical tab stanovený přesun dolů
\\ Backslash obrácené lomítko
\' Single quote apostrof
\" Double quote uvozovky
\? Question mark otazník
\000 ASCII znak zadaný jako osmičková hodnota
\xHHH ASCII znak zadaný jako šestnáctková hodnota

O tabulce ASCII se zmíním později.

Ještě dodám, že pro vytištění znaku % pomocí funkce printf() je třeba zapsat znak % dvakrát za sebou. Nejedná se o escape znak, ale o vlastnost funkce printf() interpretovat % ve svém prvním argumentu zvláštním způsobem.

Příklad:

  1. /*------------------------------------------------*/
  2. /* c06/esc.c                                      */
  3.  
  4. #include <stdio.h>
  5.  
  6. int main(void)
  7. {
  8.     printf("%s %%\n", "%");
  9.     printf("%s\r%s\n", "AAAAAAA", "BB");
  10.     printf("\"\a\"\n");
  11.     printf("\x6a\n");
  12.     return 0;
  13. }
  14.  
  15. /*------------------------------------------------*/

Výstup z programu:

% %
BBAAAAA
""
j

Při třetím volání printf() by se mělo ozvat krátké pípnutí. Neozve se, protože nové počítače nemají pípátko.

Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 20.8.2017
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..