4. díl - Reference v C++

C++ Pokročilé konstrukce v C++ Reference v C++

Dosud jsme si řekli o dvou "typech", kterými C++ disponuje. Byly to hodnoty (sem se řadí typy jako int, char, string apod.) a ukzatele. C++ na rozdíl od C zavádí i další skupinu typů - reference. Jedná se o takový přechod mezi ukazateli a hodnotami. Reference se na první pohled tváří jako hodnota - pracujeme s nimi naprosto stejně a jako programátoři nepoznáme rozdíl. Podobně jako ukazatele ovšem slouží jen jako alias k jinému místu v paměti.

Vytvoření a práce s referencí

Jak jsme si řekli, reference je pouze alias na nějaké místo v paměti. Stejným způsobem se bude i tvořit - tedy použijeme & (ampersand) k vytvoření reference. Nejlépe zřejmě poslouží ukázka:

int hodnota;
int& reference = hodntoa;
int* ukazatel;

Vidíme, že se reference syntaxí blíží ukazateli. To ovšem pouze při vytvoření, všechny ostatní operace se provádí stejně, jako by se jednalo o hodnotu.

int hodnota = 4;
int& reference = hodnota;             //běžné přiřazení
int mocnina = reference * reference;  //běžné aritmetické operace
int* ukazatel = &reference;           //běžné přiřazení do ukazatele

Vidíme, že kromě inicializace reference (kde se používá ampersand), se nikde žádné speciální znaky nepoužívají. Dokonce i získání reference pro ukazatel funguje správně! Vlastně vůbec nepoznáme, zda se jedná o referenci nebo o hodnotu. Pořád ale platí, že se jedná pouze o odkaz na původní hodnotu. To znamená, že ve chvíli, kdy se změní reference, změní se i původní hodnota.

int hodnota = 45;
int& reference = hodnota;
reference = 12;
cout << "Hodnota: " << hodnota << " referene: " << reference << endl;

Výsledek můžete prohlédnout:

Změna hodnoty reference

Ukazatelům, které na žádní mésto neukazují, lze přiřadit hodnotu NULL a tím říci, že ukazatel nyní nikam neukazuje. Pro reference tento přístup neplatí. Při deklaraci reference musí být přímo inicializovaná, tedy kompilátor zahlásí chybu, použijeme li pouze deklaraci:

int& reference;

Navíc nemůže reference odkazovat na jiný objekt, než jaký jsme mu přiřadili. Použijeme-li operátor rovná se, hodnota se přiřadí do původní proměnné:

int prvni_hodnota = 45;
int druha_hodnota = 13;
int& reference = prvni_hodnota;
reference = druha_hodnota;

V tomto kódu se reference inicializuje jako odkaz na prvni_hodnota. Na čtvrtém řádku se do prvni_hodnota přiřadí hodnota 13. Reference již nelze změnit tak, aby odkazovala na druha_hodnota. Protože je ovšem jazyk C++ stále nízkoúrovňový, je potřeba mít někdy referenci prázdnou, proto většina kompilátorů dovoluje do reference při její inicializaci umístit například 0. Použijete-li takovou referenci, výsledek je nedefinovaný a program pravděpodobně spadne.

Předávání parametru referencí

Velkou výhodu referencí je předávání parametrů. Pokud parametry předáváme referencí, používáme pouze odkaz na původní proměnnou. To sebou nese několik pro a proti. Výhodou je, že program se stane rychlejší. Při předání parametru hodnotou se musí celá proměnná zkopírovat na nové místo v zásobníku. Jestliže použijeme referenci, předá se pouze odkaz, který je velikostně shodný s ukazatelem. Jako příklad si vezněme strukturu osoby, kterou jsme si definovali v díle o strukturách.

struct Osoba
{
     string jmeno;
     int vek;
     string bydliste;
}

Použijeme-li předání hodnotou, musí se překopírovat oba stringy a číslo. Pro představu řekněme, že výsledná struktura bude mít 40 bajtů. Při předávání hodnotou se těchto 40 bajtů musí znovu vytvořit a původní obsah překopírovat. Při předání referencí se předá pouze odkaz, který má na 64 bitovém systému 8 bajtů. Ušetřili jsme si 32 bajtů, které nyní můžeme využít lépe. Nejlepší na celé věci je, že při parametrech předáváné referencí se volání funkce vůbec nezmění:

int secti(int& a, int& b)
{
     return a+b;
}
int main()
{
     int prvni = 15;
     int druha = 14;
     int soucet = secti(prvni,druha);
     return 0;
}

Jak je vidět, nemusíme vůbec nic připisovat. U větších objektů se zvýší rychlost, se kterou se program provádí. Při tomto zápisu také nelze volat funkci s konstantami:

int secti(int &a, int& b)
{
     return a+b;
}
int main()
{
     int soucet = secti(15,14); //nelze
     return 0;
}

Jak tuto situaci vyřešit si povíme později.

Spíše nevýhodou při předávání referencí je, že můžeme nedopatřením hodnoty uvnitř funkce změnit. Někdy je to požadovaná vlastnost - umožňuje nám to z funkce vrátit více hodnot, než pouze návratovou. Ukážeme si to na příkladu vlastní implementaci odmocniny, která bude kontrolovat, zda lze číslo odmocnit.

bool odmocnina(int cislo_k_odmocneni, int& vysledek)
{
     if(cislo_k_odmocneni < 0)
          return false;
     vysledek = sqrt(cislo_k_odmocneni);
     return true;
}
int main()
{
     int vysledek;
     if(odmocnina(5,vysledek))
        cout << "Odmocnina z 5 je " << vysledek << endl;
     if(!odmocnina(-5,vysledek))
        cout << "Odmocnina z -5 nelze spocitat" << endl;
}

V příkladu funkce vrací true nebo false na základě toho, zda se podařilo číslo odmocnit. Samotná odmocnina je uložena v proměnné výsledek. Program funguje přesně podle našich představ:

Vlastní odmocnina v C++

Pokud neplánujeme hodnotu ve funkci měnit, můžeme využít konstantní referenci. Hodnotu konstantní reference nejde změnit a navíc nám dovolí použít jako parametr konstantu. Konstantními hodnotami se budeme zabývat později.

int secti(const int &a, const int& b)
{
     // a = 1; nelze
     return a+b;
}
int main()
{
     int prvni = 15;
     int druha = 14;
     int soucet = secti(prvni,druha);
     int soucet2 = secti(5,6); //nyní již lze použít i konstanty
     return 0;
}

Lokální proměnné

Reference lze samozřejmě použít i jako návratovou hodnotu. Má to ovšem určité omezení. C++ po zavolání return smaže všechny objekty, které byly ve funkci vytvořeny na zásobníku. Jedná se o všechny lokální proměnné, které nevytváříme pomocí klíčového slova new. Pokud funkce vrátí referenci na lokální proměnnou, tato proměnná již nebude existovat a program s největší pravděpodobností spadne. Například náš příklad pro sčítání:

int& secti(const int &a, const int& b)
{
     int vysledek = a + b;
     int& odkaz = vysledek;
     return odkaz;
}
int main()
{
     int prvni = 15;
     int druha = 14;
     int& soucet = secti(prvni,druha);
     cout << "Výsledek součtu je " << soucet << endl; //zde program s největší pravděpodobností spadne
     return 0;
}

Dobře si proto rozmyslete, kdy použít hodnotu a kdy referenci. Zda použít referenci nebo ukazatel je sporné. Většina knihoven jsou určeny pro C, které referencemi nedisponuje. To znamená, že vrací ukazatele. Pokud se do funke předává hodnota, která se ve funkci nemění, je zpravidla výhodnější použít konstantní referenci.

Pro dnešní díl to bude vše, příště se podíváme na pokročilejší operace s pamětí.


 

Stáhnout

Staženo 4x (3.92 MB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

  Aktivity (2)

Č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í!


 


Miniatura
Předchozí článek
Aritmetika ukazatelů v C++
Miniatura
Všechny články v sekci
Pokročilé konstrukce v C++

 

 

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í!