IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

3. diel - Aritmetika ukazovateľov v C ++

V minulom tutoriále sme sa naučili dynamickú alokáciu pamäte v jazyku C ++. Dnešný diel je venovaný ďalšiu prácu s ukazovateľmi. Naučíme sa s nimi vykonávať základné aritmetické operácie, pracovať s nimi pomocou indexov a vytvoríme jednoduchý program pre výpočet priemeru známok.

Aritmetika ukazovateľov

Pretože ukazovatele sú vlastne adresy do pamäti, možno vás napadlo, či s nimi pôjde nejakým spôsobom počítať. Práve touto problematikou sa zaoberá tzv. Pointerová aritmetika.

Pričítanie / odčítanie celého čísla k ukazovateľu

Majme aplikáciu z minulej lekcie, ktorá v pamäti vytvorí blok pre 10 intů. Vieme, že pointer pole ukazuje na prvý int tohto bloku (čiže dynamického poľa, ak chcete). Ako sa však dostaneme napr. Na 5. int?

Vieme, že v pamäti leží jednotlivé inty hneď za sebou. Adresu piateho prvku teda vypočítame tak, že vezmeme adresu Pointer pole (1. prvku) a pripočítame k nej štvornásobok veľkosti intu. Tým získame adresu 5. prvku, ktorú uložíme do pointera paty_prvek.

Jazyk C ++ nám celú záležitosť veľmi uľahčuje a to pomocou pričítanie / odčítanie celých čísel k pointera. Akonáhle k Pointer pripočítame napr. Jedničku, C ++ jeho adresu nezvýši o 1, ale o veľkosť prvku, na ktorý pointer ukazuje. V poli sa teda posúvame dopredu alebo dozadu (ak odčítame) on prvkov.

// Alokace 100 intů
int *pole = new int[100];
if( pole == NULL )
{
    cout << "Nedostatek pameti." << endl;
    return 1;
}

// Výpočet adresy pátého prvku
int *paty_prvek = pole + 4;

// Uložení hodnoty na pátý prvek
*paty_prvek = 56;

// Uvolnění paměti
delete[] pole;
pole = NULL;

Hoci to tak doteraz mohlo vyzerať, tak Pointer nie sú len celé čísla s adresou, ale C ++ s nimi pracuje iným spôsobom. +4 v skutočnosti spôsobilo pripočítanie čísla 16 k adrese (pretože 4 inty majú 16 bajtov).

Odčítanie ukazovateľov

Ak máme 2 ukazovatele, ktoré ukazujú na rovnaký blok pamäti, môžeme ich hodnotu odčítať. Keď bude každý ukazovateľ ukazovať na dáta, ktoré spolu vôbec nesúvisí, získame nezmyselnú hodnotu. Pokiaľ bude ale napr. Jeden ukazovateľ ukazovať na začiatok dynamického poľa intů, ako sme vytvárali minule, a druhý bude ukazovať napr. Na piaty prvok tohto poľa, získame odpočítaním ukazovateľov číslo 4. Skúsme si to a niekam pred uvoľnenie pamäti pripíšte do vyššie uvedeného programu nasledujúci riadok:

cout << "Prvek, na ktery ukazuje paty_prvek je v poli na indexu " << paty_prvek - pole << endl;

výsledok:

Odčítanie ukazovateľov v C ++ - Pokročilé konštrukcia C ++

Všimnite si, že odčítame prvý prvok od piateho. To preto, že piaty je v pamäti ďalej.

Porovnávanie ukazovateľov

Ak ukazujú 2 ukazovatele opäť na rovnaký pamäťový blok, ale napríklad na iné miesta v ňom, môžeme ich porovnať pomocou štandardných operátorov <> == <=> = a! =. Zistíme tým či ukazuje prvý ukazovateľ na prvok pred prvkom, na ktorý ukazuje druhý ukazovateľ, či ukazujú obaja na rovnaký prvok alebo naopak prvý ukazuje na prvok, ktorý je v pamäti ďalej.

if( paty_prvek > pole )
    cout << "paty_prvek je v pameti az za pole" << endl;

výsledok:

Porovnávanie ukazovateľov v C ++ - Pokročilé konštrukcia C ++

Pointer a polia

S pamäťovým blokom 100 intů, ktorý sme vyššie deklarovali, už dokážeme pracovať pomocou Pointerova aritmetiky. Nemal by pre nás byť príliš veľký problém naplniť poľa číslami, napr. Samými nulami (pretože sme od operátora new dostali nejakú pamäť, nemôžeme si byť nikdy istí, čo je v nej uložené).

Kód pre naplnenie poľa nulami by vyzeral asi takto:

int *p_pozice;
for (p_pozice = pole; p_pozice < pole + 100; p_pozice++)
{
    *p_pozice = 0;
}

Vytvoríme si pomocný pointer, ktorý v cykle posúvame o 1 prvok dopredu, kým sa nedostaneme na koniec bloku. Pomocou tohto Pointer cestujeme blokom a ukladáme do prvkov nuly.

S blokom však môžeme pracovať úplne rovnako ako s poľom, pretože pole v jazyku C ++ nie je tiež nič iné, než blok súvislé pamäte. Úplne rovnako môžeme všetky inty nastaviť na 0 aj týmto spôsobom:

for (int i = 0; i < 100; i++)
{
    p_i[i] = 0;
}

K prvkom v bloku teda môžeme pristupovať ako by to bolo pole, pomocou hranatých zátvoriek a indexov. Prvý spôsob pomocou Pointerova aritmetiky je rýchlejší, pretože C ++ len pripočíta k adrese bajty. Pri použití indexov musí C ++ vynásobiť veľkosť intu indexom a toto číslo pripočítať k adrese začiatku poľa, čo trvá o chlp dlhšie. Rozdiely sú väčšinou pre bežnú prácu zanedbateľné, ale keď už programujeme v C ++, budeme sa to snažiť robiť efektívne.

Sizeof ()

Ak by vás napadlo, čo vráti nasledujúci kód:

sizeof(*p_i);

Bude to veľkosť jedného prvku v bloku, na ktorý ukazuje Ak chcete nájst. V našom prípade teda 4 bajty (veľkosť intu). Počet prvkov v bloku (v našom prípade 100) bohužiaľ už nikdy nezistíme a musíme si ho po založení pamätať alebo uložiť. To je tiež dôvod, prečo sa textové reťazce v C ukončujú znakom '\ 0'.

Možno by nás ale mohlo zaujímať, čo urobí operácie sizeof (Optimálna) (všimnite si chýbajúce hviezdičky). V tomto prípade získame všeobecnú veľkosť ukazovatele. Veľkosť ukazovatele bude rovnaká pre všetky typy, teda sizeof (char ) sa rovná *sizeof (int *). To je tým, že z princípu ukazovateľ iba ukazuje na miesto pamäti. Pre adresáciu pamäte potrebujeme vždy rovnako veľkú hodnotu. Napríklad pre 32-bitovú architektúru bude veľkosť ukazovatele 4 bajty, pre 64-bitovú architektúru 8 bajtov.

Príklad: výpočet priemeru z čísel

Pretože sme pomerne dlho teoretizovali, ukážme si na záver reálny príklad toho, čo sme sa naučili. Nižšie uvedený program sa užívateľa opýta koľko chce zadať známok, následne pre nich vytvorí v pamäti poľa a známky do neho postupne uloží. Na konci vypíše priemer z týchto známok.

Pozn .: Možno namietate, že priemer by sme mohli vypočítať aj úplne bez ukladania známok. keď by nás však zaujímal napr. medián alebo sme sa známkami chceli nejako ďalej pracovať, čo sa v programoch stáva v podstate neustále, potrebujeme mať dáta niekde uložené.

#include <iostream>
using namespace std;

int main( ) {
    cout << "Zadej pocet znamek: ";
    int pocet;
    cin >> pocet;
    // Alokace bloku s daným počtem intů
    int* data = new int[pocet];
    if( data == NULL )
    {
        cout << "Nedostatek pameti" << endl;
        return 1;
    }
    // Postupné načtení známek do pole
    for(int* pozice = data; pozice < data + pocet; pozice++ )
    {
        cout << "Zadejte znamku: ";
        cin >> *pozice;
    }
    // Výpočet průměru ze známek
    int soucet = 0;
    for(int* pozice = data; pozice < data + pocet; pozice++ )
        soucet += *pozice;
    double prumer = (double)soucet / pocet;
    cout << "Prumer tvych znamek je " << prumer << endl;
    // Uvolnění paměti
    delete[] data;
    data = NULL;
    cin.get(); cin.get();
    return 0;
}

výsledok:

Výpočet priemeru v C ++ - Pokročilé konštrukcia C ++

Zdrojový kód by mal byť zrozumiteľný, pretože je podobný ako vyššie uvedené príklady. Zaujímavosť je pretypovanie jednej premennej na typ double pri výpočte priemeru. Ak totiž delíme v C ++ 2 celé čísla, výsledkom je vždy celé číslo. Ak chceme deliť desatinné, musí byť aspoň jedno číslo typu double.

Program je v prílohe k stiahnutiu so zdrojovým kódom.

Dobre, po dnešnom dieli teda dokážeme za behu programu vytvoriť ľubovoľne veľké pole. Stále však musíme špecifikovať jeho veľkosť. Ako možno teda vytvoriť zoznam tovaru na sklade, ktorý nebude veľkostne vôbec obmedzený, a do ktorého budeme môcť položky stále pridávať? Najjednoduchším spôsobom je strážiť si veľkosť poľa. Pri pridanie ďalšieho prvku, ktoré sa už do poľa nevojde, vytvoríme nové (väčšie) pole, pôvodné prvky doň prekopíruje a pôvodné polia zmažeme. Pre užívateľov (teda pre programátora používajúce také pole), sa potom zdá, že sa pole dynamicky zväčšuje. Na veľmi podobnom princípe funguje objekt string.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 66x (3.57 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C++

 

Predchádzajúci článok
Dynamická správa pamäte v C ++
Všetky články v sekcii
Pokročilé konštrukcia C ++
Preskočiť článok
(neodporúčame)
Referencie v C ++
Článok pre vás napísal Patrik Valkovič
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity