Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 1 - Úvod do ukazatelů v C++

Vítejte u první lekce pokročilého kurzu o programování v jazyce C++. V této sekci se naučíme pracovat s dynamicky alokovanou pamětí v jazyce C++ a dostaneme se také k práci se soubory. Asi vás nepřekvapí, že předpokladem ke zdolání seriálu je znalost základů C++.

Adresy v paměti

Když jsme se poprvé zmiňovali o proměnných, říkali jsme si, že proměnná je "místo v paměti", kam si můžeme uložit nějakou hodnotu. Také víme, že proměnné mají různé datové typy (např. int) a ty zabírají v paměti různě místa (např. int zabírá 32 bitů, tedy 32 nul a jedniček).

Paměť počítače si můžeme představit jako dlouhou (téměř nekonečnou :) ) řadu nul a jedniček. Některé části paměti jsou obsazené jinými aplikacemi a některé jsou operačním systémem chápány jako volné místo. Aby se dalo s pamětí rozumně pracovat, je adresována, jako jsou např. domy v ulici. Adresy se většinou zapisují v šestnáctkové soustavě, ale stále se jedná o obyčejná čísla. Adresy jdou chronologicky za sebou a na každé adrese se nachází 1 bajt (tedy 8 bitů, protože adresování po drobných bitech by bylo nepraktické).

Jakmile v C++ deklarujeme nějakou proměnnou ve zdrojovém kódu a aplikaci spustíme, C++ si řekne operačnímu systému o tolik paměti, kolik je pro tuto proměnnou třeba. Od systému získá adresu do paměti, na kterou může hodnotu proměnné uložit (zjednodušeně řečeno).

Získání adresy proměnné

Jazyk C++ nás od adres zatím plně odsťiňoval, paměť rezervoval za nás a s proměnnými jsme pracovali jednoduše pomocí jejich jmen. Vytvořme si nyní jednoduchý program, který založí proměnnou typu int a do ni uloží hodnotu 56. Adresu této proměnné si získáme pomocí tzv. referenčního operátoru & (ampersand) a vypíšeme ji do konzole.

int main()
{
    int a;
    a = 56;
    cout << "Proměnná a s hodnotou " << a << " je v paměti uložená na adrese " << &a << endl;
    cin.get();
    return 0;
}

Výsledek:

Adresa proměnné v paměti - Pokročilé konstrukce C++

Vidíte, že na mém počítači si systém vybral adresu 0x23aadc. Vy tam budete mít jiné číslo. Situace v paměti počítače bude vypadat takto:

Uložení proměnné v paměti - Pokročilé konstrukce C++

(Datový typ int má 32 bitů, proto tedy zabírá 4 osmice bitů a 4 adresy. Udáváme vždy adresu začátku hodnoty.)

Ukazatelé (pointery)

Získat číslo adresy je sice hezké, ale pokud bychom s pamětí takto pracovali, bylo by to poněkud nepraktické. Z toho důvodu jazyk C++ podporuje tzv. ukazatele (anglicky pointery). Ukazatel je proměnná, jejíž hodnotou je adresa někam do paměti. C++ ukazatel však nebere jako pouhé číslo, ale ví, že ho má používat jako adresu. Když do ukazatele tedy něco uložíme nebo z něj naopak něco vypisujeme, nevypisuje se adresa (hodnota ukazatele), ale použije se hodnota, na kterou ukazatel ukazuje.

Vraťme se opět k našemu programu. Tentokrát si kromě proměnné a definujeme i ukazatel na proměnnou a. Ten bude také typu int, avšak před jeho názvem bude tzv. dereferenční operátor * (hvězdička).

int main()
{
    int a = 56;
    int *p_a = &a; // Uloží do p_a adresu proměnné a
    *p_a = 15; // Uloží hodnotu 15 na adresu v p_a
    cout << "Ukazatel p_a ma hodnotu " << p_a << " a ukazuje na hodnotu " << *p_a << endl;
    cout << "Hodnota ulozena v a je " << a << endl;
    cin.get();
    return 0;
}

Aplikace si vytvoří proměnnou typu int a dále ukazatel na int. Ukazatelé také mají vždy svůj datový typ podle toho, na hodnotu jakého typu ukazují. Do proměnné a se uloží hodnota 56.

Do ukazatele p_a (zatím bez hvězdičky) se uloží adresa proměnné a, kterou získáme pomocí referenčního operátoru &. Nyní budeme chtít tam, kam ukazuje pointer p_a, uložit číslo 15. Použijeme dereferenční operátor (*) a tím neuložíme hodnotu do ukazatele, ale tam, kam ukazatel ukazuje.

Následně vypíšeme hodnotu ukazatele (což je nějaká adresa v paměti, obvykle vysoké číslo, zde ho vypisujeme v šestnáctkové soustavě) a dále vypíšeme hodnotu, na kterou ukazatel ukazuje. Kdykoli pracujeme s hodnotou ukazatele (ne adresou), používáme operátor *.

Výsledek:

Editace proměnné ukazatelem - Pokročilé konstrukce C++

Předávání referencí

Umíme tedy na proměnnou vytvořit ukazatel. K čemu je to ale dobré? Do proměnné jsme přeci uměli ukládat i předtím. Jednou z výhod pointerů je tzv. předávání referencí. Vytvořme si funkci, které přijdou v parametru 2 čísla a my budeme chtít, aby jejich hodnoty prohodila (této funkci se anglicky říká swap). Naivně bychom mohli napsat následující kód:

// Tento kód nefunguje
void prohod(int a, int b)
{
    int pomocna = a;
    a = b;
    b = pomocna;
}

int main()
{
    int cislo1 = 15;
    int cislo2 = 8;
    prohod(cislo1, cislo2);
    cout << "V cislo1 je číslo " << cislo1 << " a v cislo2 je číslo " << cislo2 << endl;
    cin.get();
    return 0;
}

Výsledek:

Špatná implementace funkce swap - Pokročilé konstrukce C++

Proč že aplikace nefunguje? Při volání funkce prohod ve funkci main() se vezmou hodnoty proměnných cislo1 a cislo2 a ty se zkopírují do proměnných a a b v definici funkce. Funkce dále změní tyto proměnné a a b, avšak původní proměnné cislo1 a cislo2 zůstanou beze změny. Tomuto způsobu, kdy se hodnota proměnné do parametru funkce zkopíruje, říkáme předávání hodnotou.

Pozn.: K prohození 2 čísel potřebujeme pomocnou proměnnou. Kdybychom ve funkci prohod() napsali jen a = b; b = a;, byla by v obou proměnných hodnota b, protože hodnota a se mezitím změnila.

Libovolnou proměnnou můžeme předat referencí a to tak, že funkci upravíme aby přijímala v parametrech pointery a při volání funkce použijeme referenční operátor &:

void prohod( int *a, int *b )
{
    int pomocna = *a;
    *a = *b;
    *b = pomocna;
}

int main()
{
    int cislo1 = 15;
    int cislo2 = 8;
    prohod( &cislo1, &cislo2 );
    cout << "V cislo1 je cislo " << cislo1 << " a v cislo2 je cislo " << cislo2 << endl;
    cin.get();
    return 0;
}

Výsledek:

Předávání referencí v jazyce C++ - Pokročilé konstrukce C++

Jelikož funkci nyní předáváme adresu, je schopna změnit původní proměnnou.

Někteří programátoři v jazyce C++ používají často parametry funkcí k vracení hodnoty. To ovšem není příliš přehledné a pokud nás netlačí výpočetní čas a je to jen trochu možné, měla by funkce vracet jen jednu hodnotu pomocí příkazu return.

Předávání pole

Pole a pointery mají v C++ mnoho společného. Proto když předáme pole do parametru nějaké funkce a pole v ní změníme, změny se v původním poli projeví. Pole je na rozdíl od ostatních typů vždy předáváno referencí aniž bychom se o to museli snažit.

void napln_pole(int pole[], int delka)
{
    int i;
    for (i = 0; i < delka; i++)
    {
        pole[i] = i + 1;
    }
}

int main() {
    int cisla[10];
    napln_pole(cisla, 10);
    cout << cisla[5] << endl; // Vypíše číslo 6
    cin.get();
    return 0;
}

Jak jsme si řekli dříve, pole je vlastně spojité místo v paměti. Ale takové místo musíme umět nějak adresovat. Adresujeme ho právě pomocí ukazatele. Samotné jméno proměnné u pole není nic jiného než ukazatel. To znamená, že nám bez problémů projde následující operace přiřazení.

int pole[10];
int* p_pole = pole;

NULL

Všem pointerům libovolného typu můžeme přiřadit konstantu NULL. Ta udává, že je pointer prázdný a že zrovna na nic neukazuje. Na většině platforem se NULL rovná hodnotě 0 a tak se v některých kódech můžete setkat s přiřazením 0 místo NULL. To se obecně nedoporučuje kvůli kompatibilitě mezi různými platformami. Tuto hodnotu budeme v budoucnu hojně používat.

Co si zapamatovat: Pointer je proměnná, ve které je uložena adresa do paměti. Můžeme pracovat buď s touto adresou nebo s hodnotou na této adrese a to pomocí operátoru *. Adresu libovolné proměnné získáme pomocí operátoru &.

Pointery jsme si poměrně slušně uvedli.

V příští lekci, Dynamická správa paměti v C++, se podíváme na dynamické alokování paměti, které je pravým účelem pointerů.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 74x (10.94 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

Všechny články v sekci
Pokročilé konstrukce C++
Přeskočit článek
(nedoporučujeme)
Dynamická správa paměti v C++
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
67 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity