1. díl - Úvod do ukazatelů v C++

C++ Pokročilé konstrukce v C++ Úvod do ukazatelů v C++

Vítejte u prvního dílu pokročilé sekce seriálů 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

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

(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 desítkové 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

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

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í hodnodnou.

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++

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 &.

Ačkoli jsme si pointery poměrně slušně uvedli, jejich pravým účelem je zejména dynamické alokování paměti, na které se podíváme hned v příštím dílu.


 

Stáhnout

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

 

  Aktivity (4)

Č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?
Celkem (1 hlasů) :
55555


 


Miniatura
Všechny články v sekci
Pokročilé konstrukce v C++
Miniatura
Následující článek
Dynamická správa paměti v C++

 

 

Komentáře

Avatar
Ženda
Člen
Avatar
Ženda:

Jak tam píšeš o předávání polí tak jsem se na chvilku zarazil u:
int pole[10];
int* p_pole = cisla;

Nemělo by to být:
int pole[10];
int* p_pole = pole; ?
Jinak supr článek ;)

 
Odpovědět 23. srpna 12:46
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovědět 23. srpna 13:18
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
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.

Zobrazeno 2 zpráv z 2.