5. díl - Pokročilé operace s pamětí v jazyce C++

C++ Pokročilé konstrukce v C++ Pokročilé operace s pamětí v jazyce C++

Z minulých lekcí již umíme dynamicky alokovat paměť a pracovat s ní alespoň an základní úrovni. V dnešním dílu se blíže podíváme na některé funkce, které nám standardní knihovna nabízí a také se ještě jednou podíváme na ukazatele.

Řetězení ukazatelů

Dosud jsme pracovali pouze s jednoduchými ukazateli. Jednoduše jsme alokovali blok paměti a brali ho jako pole. Co ale dělat, když budeme chtít mít např. pole polí? Bez dynamické alokace by byl postup jednoduchý:

int vicerozmerne_pole[25][25];

Pomocí dynamické alokace to ale tak snadné nebude. Nejdříve se podívejme na řetězení ukazatelů. Víme, že ukazatel ukazuje na místo v paměti. Co na takovém místě může být uloženo? Prakticky cokoliv - a to včetně dalšího ukazatele. Podívejme se na menší ukázku.

int cislo = 5;
int* ukazatel = &cislo; // známe z předchozích článků
int** ukazatel_na_ukazatel = &ukazatel; // toto je nové
**ukazatel_na_ukazatel = 4; // změníme původní hodnotu

Bude program fungovat? Samozřejmě. Interně si můžeme ukazatel představit jako pouhé číslo. Toto číslo říká, na kterou adresu (místo) v paměti se má program obracet. Hvězdičkou před názvem proměnné řekneme, že nechceme použít hodnotu na tomto místě, ale až v přesměrovaném místě. Tím jsme opět dostali nějakou hodnotu. A když přidáme ještě jednu hvězdičku, jsme opět přesměrováni. Toto přesměrování (tedy získání hodnoty, na kterou ukazatel ukazuje) se nazývá dereferencování ukazatele.

Pojďme si tedy vytvořit vícerozměrné pole, které bude celé uloženo na haldě. Nejprve dynamicky alokujeme pole ukazatelů. Na tyto ukazatele následně alokujeme jednotlivé prvky pole. Nakonec pole naplníme a vypíšeme si hodnotu na určených souřadnicích.

int sirka = 15;
int vyska = 12;
int main( )
{
        // vytvoreni pole
        int** pole = new int*[vyska];
        for( int i = 0; i < vyska; i++ )
                pole[i] = new int[sirka];
        // naplneni pole
        for(int i = 0; i < vyska; i++ )
                for(int j = 0; j < sirka; j++ )
                        pole[i][j] = i * 10 + j;
        // vypsani hodnoty
        cout << "Hodnota na souradnicich [8,2] je " << pole[8][2] << endl;
        // smazani pole
        for( int i = 0; i < vyska; i++ )
                delete[] pole[i];
        delete[] pole;
        cin.get();
        return 0;
}
Pole polí v C++

Jednorozměrné pole simulující vícerozměrné

Místo vytvoření opravdového vícerozměrného pole bychom mohli alternativně vytvořit pole jednorozměrné a "souřadnice" v něm přepočítávat tak, aby se jednotlivé řádky ukládaly za sebe, do jednoho řádku, které pole tvoří. Předchozí postup byl složitý kvůli vytváření a mazání pole. Je-li vytvářené pole čtvercové, lze souřadnici snadno dopočítat. To demonstruje následující kód:

// vytvoreni pole
int* jednorozmerne_pole = new int[vyska*sirka];
// plneni pole
for( int i = 0; i < vyska; i++ )
        for( int j = 0; j < sirka; j++ )
                jednorozmerne_pole[i * sirka + j] = i * 10 + j;
// vypsani hodnoty
cout << "Hodnota na souradnicich [8,2] je " << jednorozmerne_pole[8 * SIRKA + 2] << endl;
// smazani pole
delete[] jednorozmerne_pole;

Výhoda je jednodušší vytváření a mazání pole. Nevýhodou je neustále dopočítávání skutečného indexu. Čím více by mělo pole dimenzí, tím složitější by výpočet byl.

Funkce pro práci s pamětí

Protože již víme, že textové řetězce jsou pouhé pole znaků, poskytuje knihovna cstring kromě funkcí pro práci s řetězci také funkce pro práci s pamětí. Proč jsou tyto funkce umístěny do knihovny cstring, která reprezentuje podle názvu řetězce? V C je řetězec pouze pole znaků (typ string toto pole pouze obaluje) a pro manipulaci s řetězci se používají právě funkce pro práci s pamětí.

memset()

Funkce memset() nastaví všechny bajty na specifickou hodnotu. Jako první parametr přijímá začátek pole a dále hodnotu, kterou má nastavit. Jako poslední parametr přijímá počet bajtů, které má zpracovat. Lze s ní například vynulovat pole.

int* pole = new int[10];
memset(pole, 0, sizeof(int) * 10);

memcpy()

Jak je z názvu patrné, memcpy() kopíruje paměť z jednoho pole do druhého. První parametr je pole, do kterého chceme kopírovat, druhým parametrem je pole, ze kterého kopírujeme a třetí parametr je počet bajtů ke kopírování. Můžeme tak překopírovat jedno pole do druhého.

int* original = new int[10];
// naplnění pole
int* kopie = new int[10];
memcpy(kopie, original, sizeof(int) * 10);

memmove()

Zde název funkce tak úplně neodpovídá tomu, co funkce ve skutečnosti dělá. memmove() paměť ve skutečnosti nepřesouvá, ale kopíruje. Rozdíl je v tom, že memmove() počítá s tím, že se bloky mohou překrývat. Nejlépe asi poslouží ukázka. Pořadí parametrů je stejné jako u memcpy().

char text[256] = "svete!";
cout << "Puvodni text: " << text << endl;

memmove( text + 5, text, sizeof( char ) * 7 );
cout << "Po presunuti textu: " << text << endl;

memcpy( text, "Ahoj ", sizeof( char ) * 5 );
cout << "Po kopirovani textu: " << text << endl;

Výsledek:

Memmove v C++

Ukazatele a struktury

Již víme, že ke členům (atributům) struktury přistupujeme pomocí tečky. Máme-li strukturu člověka, ke jménu se dostaneme takto:

CLOVEK pavel;
pavek.vek = 42;

Jiná situace je ovšem pro ukazatele. Nejdříve musíme ukazatel dereferencovat (získat hodnotu z ukazatele) a teprve poté můžeme přistupovat k jednotlivým atributům struktury. C++ navíc zavádí pro operátory dereferencování (hvězdička) a přístupu (tečka) rozdílné priority. Operátor přístupu má vyšší prioritu a vyhodnotí se dříve (bude probráno v díle o prioritách operátorů). To vede k tomu, že musíme dereferencování ukazatele uzávorkovat, aby bylo vyhodnoceno dříve, jak je ukázáno v dalším příkladu:

clovek* pavel = new clovek;
(*pavel).vek = 42;

Rozhodně se nejedná o elegantní zápis. Proto C++ zavádí další operátor, který tuto konstrukci nahrazuje. Nejdříve dereferencuje ukazatel a následně se přesune na atribut struktury. Tímto operátorem je prostá šipka složená z pomlčky a znaky větší než (->).

clovek* pavel = new clovek;
pavel->vek = 42;
pavel->jmeno = "Pavel";
char treti_pismeno_ve_jmene = pavel->jmeno[2];

Ukazatel na funkci

Dosud jsme se setkali pouze s ukazatelem na proměnnou v programu. V C++ můžeme zajít ještě dál. Jak víme, funkce je pouze série příkazů. Tyto příkazy musí být uloženy někde ve zkompilovaném souboru. Také jsme si říkali, že ukazatel pouze ukazuje na nějaké místo v paměti. Může tedy existovat i ukazatel na funkci (lépe řečeno ukazatel na první instrukci funkce). Přirozeně na takový ukazatel nemůžeme aplikovat aritmetiku ukazatelů, ale můžeme funkci pomocí tohoto ukazatele volat. Ukážeme si krátký příklad.

int secti(int a, int b)
{
    return a+b;
}

int main()
{

    int(*ukazatel_na_funkci)(int,int);
    ukazatel_na_funkci = secti;

    int vysledek = ukazatel_na_funkci(5,4);
    cout << "Vysledek po volani ukazatele na funkci: " << vysledek << endl;
    cin.get();
    return 0;
}

Výsledek:

Ukazatel na funkci v C++

Nyní již byly probrány všechny aspekty dynamické alokace v C++. Příště se podíváme na několik konstrukcí, které C++ zdědilo od C, poté již můžeme přejít na objektově orientované programování.


 

Stáhnout

Staženo 1x (14.57 kB)
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
Reference v C++
Miniatura
Všechny články v sekci
Pokročilé konstrukce v C++
Miniatura
Následující článek
Makra

 

 

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