6. díl - Konstantní hodnoty

C++ Pokročilé konstrukce Konstantní hodnoty

V dřívějších dílech jsme si pro konstanty nadefinovali makra. Přímo v článku o makrech jsme si řekli, že se od maker opouští a používají se jiné způsoby. Nyní se na ně podíváme.

Konstantní hodnoty

Konstantní hodnota je taková hodnota, kterou nelze změnit. Nadefinujeme ji pouze jednou, přímo u deklarace jí přiřadíme hodnotu, ale pokud bychom chtěli někde dále hodnotu změnit, tak nám to kompilátor nedovolí. Proměnou označíme jako konstantní klíčovým slovem const, které se umístí před typ proměnné. Vytvoříme si základní program a otestujeme, co se bude dít.

#include <stdio.h>

int main(int argc, char** argv)
{
    const int konstantni_promenna = 25;

    printf("Konstanta = %d\n",konstantni_promenna);

    return 0;
}

Jak bylo řečeno, konstantě musí být rovnou přiřazena její hodnota. Program přepíšeme do následující podoby:

#include <stdio.h>

int main(int argc, char** argv)
{
    const int konstantni_promenna;
    konstantni_promenna = 25;

    printf("Konstanta = %d\n",konstantni_promenna);

    return 0;
}

Kompilace neproběhne a kompilátor zahlásí následující chybovou hláškou:

main.c: In function 'main':
main.c:26:25: error: assignment of read-only variable 'konstantni_promenna'
     konstantni_promenna = 25;

Chyba je naprosto zřejmá. Nemůžeme přiřadit hodnotu do proměnné, která je pouze pro čtení. To je pro konstantní proměnnou přirozené, Nechceme, aby se hodnota dále v programu měnila. Bohužel použití konstant není vždy tak přímočaré, jak by se mohlo zdát. Kdyby ano, bylo by možné článek ukončit zde. Podíváme se tedy na další aspekty, které se ohledně konstantních hodnot týkají.

Parametry funkcí

V článku o kompilaci jazyka C jsem mluvil o tom, že se parametry funkcí předávají hodnotou. To znamená, že má funkce svou vlastní proměnnou, která neovlivní hodnotu původní proměnné. Co to vlastně znamená? Použijeme-li jako parametr cokoliv, víme, že funkce nezmění hodnotu, kterou jsme jí poslali jako parametr. Přesně to požadujeme od konstantních hodnot. Funkce samozřejmě může mít konstantní parametr, ten ale v tuhle chvíli udává, že funkce sama hodnotu nemůže změnit. Pro volající program je tato informace naprosto nepodstatná. Následující program se bez problémů zkompiluje a spustí.

#include <stdio.h>

int inkrementace(int a)
{
    return ++a;
}

int konstantni_inkrementace(const int a)
{
    return (a+1); //++a nemůžeme použít, protože by se změnila hodnota "a"
}

int main(int argc, char** argv)
{
    const int konstantni_promenna = 5;
    int obvykla_promenna = 4;

    inkrementace(konstantni_promenna);
    konstantni_inkrementace(konstantni_promenna);
    inkrementace(obvykla_promenna);
    konstantni_inkrementace(obvykla_promenna);
    inkrementace(5);
    konstantni_inkrementace(5);

    return 0;
}

Konstantní ukazatele

U ukazatelů začne být situace ještě zajímavější. Pro začátek je potřeba rozlišit dvě rozdílné situace: konstantní ukazatel a ukazatel na konstantní hodnotu. Konstantní ukazatel ukazuje vždy na stejné místo v paměti. Kompilátor nám zabrání konstantnímu ukazateli přiřadit jinou adresu. Naopak ukazateli na konstantní hodnotu lze přiřadit jiné místo v paměti, ale toto místo nelze měnit. Ukazatel lze dereferencovat za účelem získání hodnoty, ale nemůžeme do něj hodnotu přiřadit. Opět si ukážeme pár příkladů.

int obycejna_hodnota = 5;
const int konstantni_hodnota = 5;

//běžný ukazatel
int* obycejny_ukazatel = &obycejna_hodnota;
// obycejny_ukazatel = &konstantni_hodnota; //lze, kompilátor ale hlásí varování
// *obycejny_ukazatel = 14; //nedefinované chování

//ukazatel na konstantní hodnotu
const int* ukazatel_na_konstantni_hodnotu = &obycejna_hodnota;
//*ukazatel_na_konstantni_hodnotu = 4; //nelze
obycejna_hodnota = *ukazatel_na_konstantni_hodnotu; //čtení je povoleno
ukazatel_na_konstantni_hodnotu = &konstantni_hodnota; //lze priradit jinou hodnotu

//konstantni ukazatel
int* const konstantni_ukazatel_1 = &obycejna_hodnota;
*konstantni_ukazatel_1 = 6; //hodnota obyčejné proměnné je nyní 6
// int* const konstantni_ukazatel_2 = &konstantni_hodnota; //lze, ale kompilátor hlásí chybu
// *konstantni_ukazatel_2 = 4; //stejně jako výše, jedná se o nedefinované chování
// konstantni_ukazatel_2 = &obycejna_hodnota; //nelze - konstantní ukazatel

//konstantní ukazatel na konstantní hodnotu
const int* const konstantni_ukazatel_na_konstantni_hodnotu_1 = &obycejna_hodnota;
const int* const konstantni_ukazatel_na_konstantni_hodnotu_2 = &konstantni_hodnota;
//lze pouze číst

Z kódu by mělo být zcela patrné, co lze a nelze s konstantními ukazateli dělat. Čteme vždy zprava, tedy pro "const int* const p" přečteme "p je (const) konstantní (*) ukazatel na (int) celé číslo, které je (const) konstantní". Ještě se vrátím k řádkům, které jsou zakomentované, ale současně je u nich napsané, že je lze provést. Stejně jako je tomu u maker, i u konstantních hodnot se může kompilátor v rámci optimalizace rozhodnout nahradit všechny výskyty proměnné její hodnotou. Program poté poběží rychleji, protože si nebudou muset do registrů nahrávat další proměnné. Pokud na takovou hodnotu vytvoříme ukazatel, znemožníme tím (v lepším případě) kompilátoru optimalizaci provést. V horším případě se konstanta nahradí nebo se přesune na speciální místo v paměti. Jestliže se program pokusí na toto místo zapsat novou hodnotu, může to vést až k pádu programu. Jedná se o jeden z mnoha případů, kdy je-li něco povoleno, tak to automaticky neznamená, že by se to mělo používat.

V článku o ukazatelích jsme si řekli o zřetězení ukazatelů, tedy ukazatel na ukazatel a další podobné konstrukce. Překvapivě i v takových situacích lze využít konstanty, a to dokonce na každou úroveň. Vezmeme si deklaraci "const int * const * * const p" a budeme číst zprava. P je (const) konstantní (*) ukazatel na (*) ukazatel na (const) konstantní (*) ukazatel na (const int) konstantní celočíselný typ". Takové šílené obraty nejsou v C běžné, nicméně je lze použít. Sami ovšem vidíte, že o přehlednosti se vůbec mluvit nedá.

Konverze

Z předchozích příkladů jste si mohli všimnou implicitní konverze, která probíhá během převodu na konstantní hodnotu. Sedí-li po odstranění klíčového slova const typ, poté se typ automaticky přetypuje na typ konstantní. Opět uvedu pár příkladů, které by měli problém vysvětlit:

int a = 5;
const int b = a;    //konverze z int na const int
const int* c = &a;  //konverze z int* na const int*
const int* d = &b;  //zde konverze neprobíhá

Inline funkce

Vedle maker pro definování jsme si také řekli o funkčních makrech. To jsou makra, po kterých následují závorky. Syntaxí se velmi podobají funkcím, ale ve skutečnosti preproccessor nahradí volání makra jeho tělem. To sebou nese několik problémů (vzpomeňme si třeba na expanzi inkrementace). Protože se navíc makro vyexpanduje a původní volání se přepíše, nejsou makra tak komplexní jako funkce. Co kdybychom chtěli provést dvě operace? Můžeme do makra napsat středník, ale co když takové makro použijeme například v cyklu?

//makro
#define INCREMENTACE(a,b) a++;b++

//kód
int a, b;
for( a = 0, b = 0; a < 10 ; INCREMENTACE(a, b)) { /* ..... */}

//vyexpandováno
int a, b;
for( a = 0, b = 0; a < 10; a++; b++) { /* ..... */}

To určitě není to, co jsme požadovali.

Naopak pro funkce jsme si řekli opačný problém. Volání funkce sebou nese snížení výkonu, protože se musí do zásobníku uložit parametry, aktuální místo a ještě se musí provést skok. To je spousta operací pouze k tomu, abychom zavolali jednu funkci s pár operacemi. Východiskem jsou inline funkce. Klíčovým slovem inline kompilátoru řekneme, že chceme tělo funkce vložit na místo jejího volání. Kompilátor se obvykle ještě rozhodně, zda by se skutečně jednalo o zvýšení výkonu a pokud ano, tak volání funkce nahradí jejím tělem. Příklad výše by šel přepsat následovně:

inline void incrementace(int* a, int* b)
{
    (*a)++;
    (*b)++;
}

int a, b;
for (a = 0, b = 0; a < 10; incrementace(&a, &b))

Pozn. autora: Na Cygwinu jsem se opět setkal s problémy a příklad výše nejde zkompilovat. Na Linuxu a ve Visual Studio lze příklad zkompilovat bez problémů.

Všimněme si, že funkce vypadá naprosto stejně jako běžné funkce, pouze má na začátku klíčové slovo inline. Jediná změna, kterou jsme museli udělat je předání ukazatelů namísto hodnoty (jinak by se změna nepromítla do zbytku programu).

Nyní byste měli plně rozumět problému konstantních hodnot. Také byste měli umět v existujícím programu odstranit makra a přepsat je do inline funkcí a konstant. V příštím díle se přesuneme o něco níže - podíváme se na binární operace.


 

  Aktivity (3)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!


 



 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!