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