Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

3. diel - Aritmetika ukazovateľov v jazyku C

V minulej lekcii, Dynamická alokácia pamäte v jazyku C , sme sa naučili dynamickú alokáciu pamäte. Dnešná C tutoriál 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 100 int ov. Vieme, že pointer Optimálna 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é int y bezprostredne za sebou. Adresu piateho prvku teda vypočítame tak, že vezmeme adresu Pointer Optimálna (1. prvku) a pripočítame k nej štvornásobok veľkosti int u. Tým získame adresu 5. prvku, ktorú uložíme do pointera p_paty.

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éčko jeho adresu nezvýši o 1, ale o veľkosť typu prvku, na ktorý pointer ukazuje. V poli sa teda posúvame dopredu alebo dozadu (ak odčítame) on prvkov.

Kód pre prácu s piatym prvkom nášho dynamického poľa 100 int ov by vyzeral nasledovne:

int *p_i, *p_paty;
// Alokace 100 krát velikosti intu
p_i = (int *) malloc(sizeof(int) * 100);
if (p_i == NULL)
{
    printf("Nedostatek paměti.\n");
    exit(1);
}

// Výpočet adresy pátého prvku
p_paty = p_i + 4;

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

// Uvolnění paměti
free(p_i);
p_i = NULL;

Hoci to tak doteraz mohlo vyzerať, tak Pointer nie sú len celé čísla s adresou, ale Céčko 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 hodnoty 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:

printf("Prvek, na který ukazuje p_paty je v poli na indexu %d.", p_paty - p_i);

výsledok:

c_aritmetika_pointeru
Prvek, na který ukazuje p_paty je v poli na indexu 4.

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 <, >, ==, <=, >=, and !=. 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 (p_paty > p_i)
{
    printf("p_paty je v paměti až za p_i");
}

výsledok:

c_aritmetika_pointeru
p_paty je v paměti až za p_i

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 (Hoci sme od malloc() 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 = p_i; p_pozice < p_i + 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:

int i;
for (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éčko len pripočíta k adrese bajty. Pri použití indexov musí Céčko vynásobiť veľkosť typu int 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éčko, 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 p_i. V našom prípade teda 4 bajty (veľkosť typu int). 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 ukončujú znakom '\0'.

Možno by nás ale mohlo zaujímať, čo urobí operácie sizeof(p_i) (všimnite si chýbajúce hviezdičky). V tomto prípade získame veľkosť ukazovatele samotného, nie hodnoty, na ktorú ukazuje. 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 uloženie adresy do pamäti potrebujeme vždy rovnako veľký typ. Napríklad pre 32-bitovú architektúru bude veľkosť ukazovatele 4bajty, 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.

Možno namietate, že priemer by sme mohli vypočítať aj úplne bez ukladania známok. Keby 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 <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    int pocet, *p_i, *p_pozice;
    printf("Zadej počet známek: ");
    scanf("%d", &pocet);
    // Alokace bloku s daným počtem intů
    p_i = (int *) malloc(sizeof(int) * pocet);
    if (p_i == NULL)
    {
        printf("Nedostatek paměti.\n");
        exit(1);
    }
    // Postupné načtení známek do pole
    for (p_pozice = p_i; p_pozice < p_i + pocet; p_pozice++)
    {
        printf("Zadej známku: ");
        scanf("%d", p_pozice);
    }
    // Výpočet průměru ze známek
    int soucet = 0;
    for (p_pozice = p_i; p_pozice < p_i + pocet; p_pozice++)
    {
        soucet += *p_pozice;
    }
    double prumer = (double)soucet / pocet;
    printf("Průměr tvých známek je: %lf.", prumer);
    // Uvolnění paměti
    free(p_i);
    p_i = NULL;
    return (EXIT_SUCCESS);
}

výsledok:

c_prumer
Zadej počet známek: 5
Zadej známku: 1
Zadej známku: 2
Zadej známku: 3
Zadej známku: 3
Zadej známku: 5
Průměr tvých známek je: 2.800000.

Zdrojový kód by mal byť zrozumiteľný, pretože je podobný ako vyššie uvedené príklady. Za povšimnutie stojí, že pri načítaní známok pomocou scanf() do p_pozice neuvádzame znak & ani *, pretože pointer je sám adresou, ktorú scanf() v parametri očakáva. Ďalšia zaujímavosť je pretypovanie jednej premennej na typ double pri výpočte priemeru. Ak totiž delíme v céčko 2 celé čísla, výsledkom je vždy celé číslo. Ak chceme deliť desatinné, musí byť aspoň jedno číslo reálne.

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

Dobre, po dnešnej lekcii 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ť? To sa dozviete ďalej v kurze :) Nabudúce budeme pokračovať lekcií Dynamické textové reťazce a štruktúry v jazyku C .


 

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é 103x (32.41 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C

 

Predchádzajúci článok
Dynamická alokácia pamäte v jazyku C
Všetky články v sekcii
Dynamická práca s pamäťou v jazyku C
Preskočiť článok
(neodporúčame)
Dynamické textové reťazce a štruktúry v jazyku C
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
2 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity