Programovanie (2) v Jave
1-INF-166, letný semester 2023/24

Prednášky · Pravidlá · Softvér · Testovač
· Vyučujúcich predmetu možno kontaktovať mailom na adresách uvedených na hlavnej stránke. Hromadná mailová adresa zo zimného semestra v letnom semestri nefunguje.
· JavaFX: cesta k adresáru lib je v počítačových učebniach /usr/share/openjfx/lib.


Prednáška 12: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
d (C nemá referencie.)
 
(32 medziľahlých úprav od 3 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
== Zjednodušený model pamäte ==
+
== Oznamy ==
  
Pamäť počítača si možno ''veľmi zjednodušene'' predstaviť ako konečnú postupnosť pamäťových miest, kde každé pamäťové miesto má kapacitu práve jeden byte. To možno znázorniť nasledujúcim obrázkom.
+
* DÚ2 zverejnená, odovzdávajte do utorka 14.11. 22:00.
 +
* Dnes nová téma: smerníky a práca s pamäťou.
 +
* Zajtra cvičenia, rozcvička z dnešného učiva, iba dva príklady.
 +
** Odporúčame získať na cvičeniach aspoň 2 body. Ak ich nezískate, budú pre vás povinné cvičenia v piatok budúci týždeň.
 +
* Tento týždeň je v stredu sviatok, štvrtok a piatok voľno.
 +
** Odporúčame využiť voľnejší týždeň na prácu na domácej úlohe.
 +
* Budúci utorok 7.11. na cvičeniach bude krátky test podobne ako na prednáške 4.10.
  
[[Súbor:Pamat1.png]]
+
== Ukazovateľ, smerník, pointer==
  
Každé pamäťové miesto pridelené svoju ''adresu''. Presný formát adries závisí od konkrétnej architektúry (môže napríklad ísť o 32-bitové alebo 64-bitové čísla), z pohľadu programátora však zvyčajne nie je dôležitý. 
+
* Pamäť v počítači je rozdelená na dieliky, napr. bajty
 +
* Každá premenná zaberá niekoľko takýchto dielikov
 +
* Každý dielik má adresu (poradové číslo)
 +
* Ukazovateľ (resp. smerník alebo pointer) je premenná, ktorej hodnota je adresa iného dieliku pamäte
  
Premenným základných typov (ako napríklad <tt>int</tt>, <tt>char</tt>, <tt>double</tt>,...) sú počas vykonávania programu pridelené ''súvislé úseky pamäťových miest''. Napríklad na architektúre s 32-bitovým &ndash; čiže 4-bytovým &ndash; typom <tt>int</tt> sa premenným tohto typu priraďujú úseky štyroch po sebe idúcich pamäťových miest. ''Adresou premennej'' potom rozumieme adresu ''prvého'' z týchto pamäťových miest. Táto situácia je znázornená na obrázku nižšie.
+
* Na obrázku je na adrese <tt>24</tt> uložená premenná <tt>x</tt> typu <tt>int</tt>, ktorej hodnota je 17.
 +
* Na adrese <tt>40</tt> je uložený smerník <tt>p</tt>, ktorého hodnota je <tt>24</tt>.
 +
** Hovoríme, že smerník <tt>p</tt> ukazuje na premennú <tt>x</tt>, zjednodušene to budeme kresliť ako šípku, viď spodok obrázku.
 +
** Väčšinou nás nezaujíma, aké sú presné hodnoty adries, chceme si ale vedieť nakresliť podobný obrázok so šípkami.
  
[[Súbor:Pamat2.png]]
+
[[Súbor:memory-c.png|700px]]
  
Poliam sa taktiež prideľujú ''súvislé'' úseky pamäte. Pre pole dĺžky <tt>N</tt> pozostávajúce z prvkov typu <tt>T</tt> sa pritom vyhradí <tt>N</tt> po sebe idúcich pamäťových úsekov, kde každý z nich postačuje práve na uchovanie jednej hodnoty typu <tt>T</tt>. Napríklad poľu <tt>a</tt> dĺžky <tt>N</tt> prvkov typu <tt>int</tt> sa na architektúre s 32-bitovým (4-bytovým) typom <tt>int</tt> pridelí súvislý úsek <tt>4*N</tt> pamäťových miest tak, ako na nasledujúcom obrázku.
 
  
[[Súbor:Pamat3.png]]
+
Takúto situáciu (len s inými adresami) vyrobíme príkazmi
 +
<pre>
 +
int x = 17;  // vytvorenie a inicializácia premennej x
 +
int *p;      // vytvorenie premennej p, ktorá bude smerník na int
 +
p = &x;      // &n vráti adresu premennej x, tú uložíme do premennej p
 +
</pre>
  
== Smerníky ==
+
=== Operátor <tt>*</tt> (dereferencia, dáta na adrese) ===
  
''Smerník'' (niekde tiež ''ukazovateľ'', angl. ''pointer'') je premenná, ktorej hodnotou je pamäťová adresa. Napríklad na nasledujúcom obrázku je na adrese <tt>[adresa 2]</tt> uložená premenná typu <tt>int</tt>, ktorej hodnota je 12345. Na adrese <tt>[adresa 1]</tt> je uložený smerník, ktorého hodnota je <tt>[adresa 2]</tt> &ndash; hovoríme, že smerník ukazuje na pamäťovú adresu <tt>[adresa 2]</tt>.
+
* Ak <tt>p</tt> je smerník, pomocou <tt>*p</tt> môžeme pristúpiť k údajom na adrese reprezentovanej smerníkom <tt>p</tt>.  
 +
* Tieto údaje potom možno aj meniť
 +
* Ak máme <tt>int x = 17; int *p = &x;</tt>, tak ďalej v programe <tt>*p</tt> aj <tt>x</tt> sú mená pre ten istý dielik pamäte.
  
[[Súbor:Pamat5.png]]
 
 
=== Definovanie smerníka ===
 
 
V C++ sa smerník ukazujúci na &bdquo;pamäťový objekt&rdquo; typu <tt>T</tt> definuje takto:
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
T *p;
+
int x = 17;
 +
int *p = &x;        // p ukazuje na adresu premennej x
 +
cout << *p << endl; // vypíše hodnotu z adresy p, t.j. 17
 +
*p = 9;            // hodnota na adrese p sa zmení na 9
 +
cout << x << endl;  // vypíše hodnotu premennej x, t.j. 9
 +
(*p)++;            // hodnota na adrese p sa zmení na 10
 +
cout << x << endl;  // vypíše hodnotu premennej x, t.j. 10
 +
x = 42;
 +
cout << *p << endl;  // vypíše hodnotu na adrese p, t.j. 42
 
</syntaxhighlight>
 
</syntaxhighlight>
Napríklad teda môžeme písať:
 
<syntaxhighlight lang="C++">
 
int *p1;    // smernik p1 na int
 
char *p2;  // smernik p2 na char
 
double *p3; // smernik p3 na double
 
  
// ...
+
=== Smerník <tt>NULL</tt> ===
</syntaxhighlight>
 
  
Smerníky ukazujúce na &bdquo;pamäťové objekty&rdquo; rôznych typov sú takisto rôznych typov. ''Vo všeobecnosti nemožno realizovať priradenia medzi smerníkmi rôznych typov'' (nedôjde k automatickému pretypovaniu).
+
Dôležitým špeciálnym prípadom smerníka je konštanta <tt>NULL</tt> reprezentujúca smerník, ktorý nikam neukazuje.
<syntaxhighlight lang="C++">
+
* Je definovaná vo viacerých štandardných knižniciach, ako napríklad <tt>cstdlib</tt> alebo <tt>iostream</tt>.
int *p1;
+
* Možno ju priradiť do smerníka ľubovoľného typu.
int *p2;
 
double *p3;
 
  
p1 = p2;  // korektne priradenie
+
Pozor, ak do premennej typu smerník nič nepriradíme, má nedefinovanú hodnotu, ukazuje na náhodné miesto v pamäti, alebo niekde mimo.
p3 = p1;  // chyba, lebo p3 a p1 su smerniky roznych typov
 
</syntaxhighlight>
 
  
Bez ohľadu na typ smerníka je jeho hodnotou vždy len ''pamäťová adresa'', a teda sa ich práve popísané správanie môže zdať na prvý pohľad zvláštnym. Smerníky rôznych typov sa však, ako onedlho uvidíme, v určitých situáciách môžu &bdquo;správať&rdquo; rozdielne.
+
== Smerník ako parameter funkcie ==
  
=== Operátor <tt>&</tt> (adresa) ===
+
Namiesto odovzdávania parametrov referenciou ich môžeme odovzdať pomocou smerníka. Tu je napríklad smerníková verzia funkcie swap, ktorá vymieňa hodnoty dvoch premenných.
  
Operátor <tt>&</tt> poskytuje azda najjednoduchší spôsob, ako získať zmysluplný smerník. Adresu premennej <tt>x</tt> nejakého základného typu (ako napríklad <tt>int</tt>, <tt>char</tt>,...) získame tak, že napíšeme <tt>&x</tt> &ndash; túto adresu potom možno priradiť do smerníka.
+
<syntaxhighlight lang="C++">
 +
#include <iostream>
 +
using namespace std;
  
Nasledujúci program vytvorí celočíselnú premennú <tt>n</tt>, ktorej adresu priradí do smerníka <tt>p</tt>.
+
void swap(int *px, int *py) {  // parametre sú smerníky
 +
    // hodnotu z adresy px uložíme do tmp:
 +
    int tmp = *px;
 +
    // na adresu px uložíme hodnotu z adresy py:
 +
    *px = *py;
 +
    // na adresu py uložíme tmp:
 +
    *py = tmp;
 +
}
  
<syntaxhighlight lang="C++">
+
int main() {
int main(void) {
+
     int x, y;
     int n = 12345;
+
    cout << "Zadaj x,y: ";
     int *p;
+
     cin >> x >> y;
     p = &n;
+
     // ako parametre pošleme adresy premenných x,y
 
+
    swap(&x, &y);                                
    return 0;
+
    cout << "x = " << x
 +
        << ", y = " << y << endl;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Stav pamäte tesne pred skončením vykonávania tohto programu je znázornený na nasledujúcom obrázku.
+
Knižničné funkcie v C často používajú odovzdávanie parametrov cez smerníky.
  
[[Súbor:Pamat4.png]]
+
== Smerníky a polia ==
 
 
Rovnakým spôsobom možno operátor <tt>&</tt> aplikovať aj na prvky poľa (t.j. napríklad <tt>&a[2]</tt> je adresa druhého &ndash; resp. tretieho &ndash; prvku poľa <tt>a</tt>).
 
  
Operátor <tt>&</tt> ''nemožno'' aplikovať na konštanty (ako napríklad <tt>3.14</tt> alebo <tt>'c'</tt>), ani na výrazy (ako napríklad <tt>n+2</tt>).
+
Smerníky a polia v jazyku C spolu veľmi úzko súvisia.
 +
* Pole je vlastne smerník na nultý prvok.
 +
* Môžeme ho nakopírovať do premennej typu <tt>T *</tt>.
 +
* Na premenné typu <tt>T *</tt> môžeme použiť operátor <tt>[]</tt>.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
    int n = 0;
+
int a[4] = {10, 20, 30, 40};
    int a[5] = {1, 2, 3, 4, 5};
+
int *p;
    int *p;
+
p = a;       // p ukazuje na nulty prvok pola a
 
+
cout << a[1]; // vypise 20
    p = &n;     // korektne priradenie
+
cout << p[1]; // vypise 20
    p = &a[2]; // korektne priradenie
 
    p = &(n+1); // chyba (vyraz nema adresu)
 
    p = &42;    // chyba (konstanta 42 nema adresu)
 
    p = &a;     // chyba (a nie je typu int)
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Operátor <tt>*</tt> (dereferencia) ===
+
* Polia v C pozostávajú z políčok rovnakej veľkosti uložených v pamäti jedno za druhým, veľkosť políčka je daná typom prvku
 
+
* Výraz <tt>p[i]</tt> zoberie adresu uloženú v <tt>p</tt>, zvýši ju o <tt>veľkosť_políčka * i</tt> a pozrie sa na príslušnú adresu
Kľúčovým operátorom na smerníkoch je tzv. ''dereferencia'', ktorá sa realizuje operátorom <tt>*</tt>. Ak <tt>p</tt> je smerník, možno pomocou zápisu <tt>*p</tt> pristúpiť k údajom na adrese reprezentovanej smerníkom <tt>p</tt>. Tieto údaje potom možno aj meniť, t. j. <tt>*p</tt> môže vystupovať napríklad aj na ľavej strane priradenia. To demonštrujeme na drobnom rozšírení predchádzajúceho príkladu.
+
* <tt>p[0]</tt> je teda to isté ako <tt>*p</tt>
 
+
* Pozor, C umožní <tt>p[i]</tt> použiť aj keď <tt>p</tt> neukazuje na pole, vtedy pristupuje do pamäte s neznámym obsahom, program môže skončiť s chybou alebo sa správať "záhadne"
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
#include <iostream>
+
int x = 10;
using namespace std;
+
int *p;
 
+
p = &x;
int main(void) {
+
cout << p[1]; // ??? pristupuje do pamäte za premennou x
    int n = 12345;
+
              // môže to mať nepríjemné dôsledky
    int *p;      
 
 
 
    p = &n;             // smernik p odteraz ukazuje na adresu premennej n
 
    cout << *p << endl;  // vypise hodnotu uchovavanu na adrese p == &n, t. j. 12345
 
    *p = 9;              // hodnota uchovavana na adrese p == &n sa zmeni na 9; preto uz aj n == 9
 
    cout << n << endl;  // vypise hodnotu premennej n, t. j. 9
 
    (*p)++;             // hodnota uchovavana na adrese p == &n sa zmeni na 10; preto uz aj n == 10
 
    cout << n << endl;  // vypise hodnotu premennej n, t. j. 10
 
    n = 42;
 
    cout << *p << endl;  // vypise hodnotu uchovavanu na adrese p == &n, t. j. 42
 
 
 
    return 0;
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Operátor dereferencie vysvetľuje aj spôsob, ktorým sa smerníky deklarujú. Riadok
+
Pozor, polia sú konštantné smerníky, nemožno ich zmeniť
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
int *p;
+
int a[4] = {10, 20, 30, 40};
 +
int b[3] = {1, 2, 3};
 +
int *p = b;  // ok
 +
a = b;      // nedá sa
 +
a = p;       // nedá sa
 
</syntaxhighlight>
 
</syntaxhighlight>
deklarujúci smerník na <tt>int</tt> totiž treba chápať takto: ''ak vezmeme smerník <tt>p</tt> a aplikujeme na neho operátor <tt>*</tt>, získame hodnotu typu <tt>int</tt>''. Takáto interpretácia sa ukáže byť veľmi užitočnou pri komplikovanejších deklaráciách so smerníkmi.
 
 
=== Smerník <tt>NULL</tt> ===
 
 
Dôležitým špeciálnym prípadom smerníka je konštanta <tt>NULL</tt> reprezentujúca ''smerník, ktorý nikam neukazuje''.
 
* Je definovaná vo viacerých štandardných knižniciach, ako napríklad <tt>cstdlib</tt> alebo <tt>iostream</tt>.
 
* Možno ju priradiť do smerníka ľubovoľného typu.
 
 
== Smerník ako parameter funkcie ==
 
  
V &bdquo;čistom C&rdquo; okrem iného nie je možné predávať parametre funkcií referenciou. Rovnaký efekt však možno docieliť predávaním hodnotou tak, že sa ako hodnota pošle smerník. To demonštrujeme pomocou nasledujúcej &bdquo;smerníkovej&rdquo; verzie funkcie realizujúcej výmenu hodnôt dvoch premenných.
+
Vo funkciách pracujúcich s poliami môžeme namiesto parametra <tt>int a[]</tt> písať aj <tt>int *a</tt> a kód ani použitie funkcie sa nemení.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 129: Riadok 129:
 
using namespace std;
 
using namespace std;
  
void swap(int *px, int *py) {                     // parametre su smerniky (adresy v pamati)
+
void vypisPole(int a[], int n) {
     int tmp = *px;                                 // hodnotu na adrese px ulozime do tmp
+
     for (int i = 0; i < n; i++) {
     *px = *py;                                    // hodnotu na adrese px zmenime na hodnotu na adrese py
+
        cout << a[i] << " ";
     *py = tmp;                                     // hodnotu na adrese py zmenime na tmp
+
     }
 +
     cout << endl;
 
}
 
}
  
int main(void) {
+
void vypisPole2(int *a, int n) {
     int x,y;
+
     for (int i = 0; i < n; i++) {
    cout << "Zadaj x,y: ";
+
        cout << a[i] << " ";
     cin >> x >> y;
+
     }
    swap(&x, &y);                                  // ako parametre posleme adresy premennych x,y
+
     cout << endl;
     cout << "x = " << x << ", y = " << y << endl;
+
}
  
     return 0;
+
int main() {
 +
     const int N  = 4;
 +
    int a[N] = {10, 20, 30, 40};
 +
    int *b = a;
 +
   
 +
    //styrikrat vypiseme to iste
 +
    vypisPole(a, N);
 +
    vypisPole(b, N);
 +
    vypisPole2(a, N);
 +
    vypisPole2(b, N);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Poznať uvedenú alternatívu k predávaniu parametrov referenciou môže byť užitočné aj pri práci v C++, keďže ju často využívajú rôzne knižničné funkcie.
 
  
 
== Dynamická alokácia a dealokácia pamäte ==
 
== Dynamická alokácia a dealokácia pamäte ==
  
 
Doteraz sme videli:  
 
Doteraz sme videli:  
* ''Globálne premenné'', ktoré majú vopred známu veľkosť a vyhradenú pamäť.
+
* Globálne premenné, ktoré majú vopred známu veľkosť a vyhradenú pamäť.
* ''Lokálne premenné'', ktoré majú vopred známu veľkosť, ale pamäť sa im prideľuje až pri volaní funkcie na tzv. ''zásobníku volaní funkcií'' (angl. ''call stack'').
+
* Lokálne premenné, ktoré majú vopred známu veľkosť, ale pamäť sa im prideľuje až pri volaní funkcie na zásobníku volaní funkcií (call stack).
  
 
Program si ale počas behu môže podľa potreby vyhradiť aj ďalšiu pamäť:
 
Program si ale počas behu môže podľa potreby vyhradiť aj ďalšiu pamäť:
 
* Používa sa na to operátor <tt>new</tt>.
 
* Používa sa na to operátor <tt>new</tt>.
* Pamäť sa vyhradí v oblasti zvanej ''halda'' (angl. ''heap'').
+
* Pamäť sa vyhradí v oblasti zvanej halda (heap).
* Nepotrebnú pamäť vyhradenú takýmto spôsobom je dobrým zvykom uvoľniť pomocou operátora ''delete''.
+
* Keď už pamäť nepotrebujeme, uvoľníme ju príkazom <tt>delete</tt>.
 +
* Uvoľnená pamäť môže byť znovu použitá pri ďalších volaniach <tt>new</tt>.
 +
 
 +
===Alokácia pamäte na jednu premennú===
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
#include <iostream>
+
int *p;  
using namespace std;
 
  
int main(void) {
+
// new vyhradí úsek pamäte pre jednu hodnotu typu int
    int *p;      
+
// adresa tohto úseku sa uloží do smerníka p
 +
p = new int;  
  
    p = new int;  // new int vyhradi usek pamate postacujuci na uchovanie prave jednej hodnoty typu int
+
// do alokovanej pamäte sa uloží hodnota 50 
                  // adresa tohto novovytvoreneho useku pamate sa ulozi do smernika p 
+
*p = 50;
    *p = 50;       // do alokovanej pamate sa ulozi hodnota 50
+
cout << *p << endl;  // výpis 50
    cout << *p << endl;   
 
    delete p;      // uvolnenie alokovanej pamate
 
  
    return 0;
+
delete p;     // uvoľnenie alokovanej pamäte
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Smerníky a polia ==
+
=== Alokácia pamäte pre pole ===
  
Smerníky a polia spolu veľmi úzko súvisia. Operátor <tt>[ ]</tt> je totiž ''v prvom rade'' definovaný na smerníkoch. Nech <tt>p</tt> je smerník definovaný ako
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
T *p;
+
int *p;  
</syntaxhighlight>
 
kde <tt>T</tt> označuje nejaký typ. V takom prípade:
 
* Zápis <tt>p[0]</tt> vyjadruje to isté ako <tt>*p</tt> &ndash; ide o hodnotu typu <tt>T</tt> uloženú na adrese reprezentovanej smerníkom <tt>p</tt>.
 
* Zápis <tt>p[i]</tt> vyjadruje hodnotu typu <tt>T</tt> uloženú na <tt>i</tt>-tom pamäťovom úseku (o veľkosti postačujúcej práve na uloženie hodnoty typu <tt>T</tt>) za úsekom s adresou reprezentovanou smerníkom <tt>p</tt>. Hoci tento zápis funguje vždy, je dôležité používať ho iba vtedy, keď vieme, čo na danej adrese je.
 
  
Táto situácia je znázornená na nasledujúcom obrázku.
+
// new vyhradí úsek pamäte pre poli 5 hodnôt typu int
 +
p = new int[5];
  
[[Súbor:Pamat7.png]]
+
// premenná p sa dá použiť ako pole dĺžky 5
 +
for(int i=0; i<5; i++) {
 +
  p[i] = i;
 +
  
Pole <tt>a</tt> prvkov typu <tt>T</tt> definované ako
+
delete[] p;     // uvoľnenie alokovanej pamäte
<syntaxhighlight lang="C++">
 
T a[N];
 
 
</syntaxhighlight>
 
</syntaxhighlight>
je potom ''konštantný smerník'' na prvok typu <tt>T</tt> (prvý &ndash; t. j. vlastne nultý &ndash; prvok poľa). Konštantným je preto, lebo adresa prvého prvku takto definovaného poľa je počas behu programu fixná a nemôže sa meniť.
 
  
[[Súbor:Pamat6.png]]
 
  
Každé pole je teda zároveň aj smerníkom na svoj prvý prvok. Každé pole typu <tt>T</tt> tak možno priradiť do smerníka na prvok typu <tt>T</tt> (avšak nie naopak, lebo polia sú ''konštantné'' smerníky a súčasťou typu poľa je aj informácia o jeho dĺžke). Zápis <tt>int a[]</tt> pre pole ľubovoľnej veľkosti predávané ako parameter funkcie je navyše len rozdielnym zápisom pre <tt>int *a</tt>. Korektný je tak napríklad aj nasledujúci program.
+
* Pozor, ak alokujeme pole, pamäť uvoľnujeme cez <tt>delete[]</tt>, nie <tt>delete</tt>
 +
* Ak zamieňate <tt>delete[]</tt> a <tt>delete</tt>, správanie programu môže byť nedefinované
 +
<pre>
 +
int *p;
  
<syntaxhighlight lang="C++">
+
p = new int;
#include <iostream>
+
// ...
using namespace std;
+
delete p;
  
const int maxN = 1000;
+
p = new int[5];
 
+
// ...
void vypisPole(int a[], int pokial) {
+
delete[] p;
    for (int i = 0; i <= pokial; i++) {
+
</pre>
        cout << a[i] << " ";
 
    }
 
    cout << endl;
 
}
 
 
 
void vypisPoleOdzadu(int *a, int odkial) {
 
    for (int i = odkial; i >= 0; i--) {
 
        cout << a[i] << " ";
 
    }
 
    cout << endl;
 
}
 
  
int main(void) {
+
Dynamickú alokáciu polí možno využiť napríklad na vytvorenie poľa, ktorého veľkosť zadá používateľ.
    int N;
 
    int a[maxN];
 
    int *b;
 
    cout << "Zadaj pocet cisel: ";
 
    cin >> N;
 
    for (int i = 0; i <= N-1; i++) {
 
        cout << "Zadaj cislo " << i+1 << ": ";
 
        cin >> a[i];
 
    }
 
   
 
    vypisPole(a,N-1);
 
    b = a;
 
    vypisPole(b,N-1);
 
   
 
    vypisPoleOdzadu(a,N-1);
 
   
 
    return 0;
 
}
 
</syntaxhighlight>
 
 
 
=== Dynamické alokovanie poľa ===
 
 
 
Operátorom <tt>new</tt> možno alokovať aj pole zadanej dĺžky <tt>N</tt> (t. j. súvislý úsek pamäte postačujúci na uchovanie práve <tt>N</tt> hodnôt daného typu). Napríklad príkaz
 
<syntaxhighlight lang="C++">
 
int *p = new int[10];
 
</syntaxhighlight>
 
vyhradí pamäť postačujúcu práve na uchovanie desiatich celých čísel &ndash; čiže vlastne pole veľkosti 10. Z pozorovaní učinených vyššie vyplýva, že smerník <tt>p</tt> je potom možné používať úplne rovnakým spôsobom, ako polia:
 
* Hodnota <tt>p[0]</tt> je tá istá ako <tt>*p</tt>.
 
* Hodnota <tt>p[i]</tt> pre <tt>i = 0,...,9</tt> reprezentuje hodnotu <tt>i</tt>-teho prvku poľa.
 
 
 
Vytvorené a už nepotrebné pole je dobrým zvykom uvoľniť operátorom <tt>delete[]</tt> (hranaté zátvorky na konci sú naozaj podstatné; program používajúci na pole iba operátor <tt>delete</tt> síce skompiluje, ale jeho správanie je nedefinované).
 
 
 
Takúto dynamickú alokáciu polí možno využiť napríklad na vytvorenie poľa o používateľom zadanej veľkosti tak, ako v nasledujúcom ukážkovom programe.
 
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 265: Riadok 227:
 
     int *a = new int[N];
 
     int *a = new int[N];
 
      
 
      
 +
    cout << "Zadavaj " << N << " cisel:" << end;
 
     for (int i = 0; i <= N-1; i++) {
 
     for (int i = 0; i <= N-1; i++) {
        cout << "Zadaj cislo " << i+1 << ": ";
 
 
         cin >> a[i];
 
         cin >> a[i];
 
     }
 
     }
 +
    cout << "Tu su cisla odzadu:" << endl;
 
     for (int i = N-1; i >= 0; i--) {
 
     for (int i = N-1; i >= 0; i--) {
 
         cout << a[i] << " ";
 
         cout << a[i] << " ";
 
     }
 
     }
    delete[] a;
 
 
     cout << endl;
 
     cout << endl;
  
     return 0;  
+
     delete[] a;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
<span style="font-size:85%">''Poznámka'': Niektoré kompilátory (ako napríklad <tt>gcc</tt>) umožňujú vytvoriť pole o používateľom zadanej veľkosti aj jednoduchším spôsobom, keďže akceptujú deklarácie typu <tt>int a[N];</tt>, kde <tt>N</tt> môže byť aj premenná, ktorej hodnota už bola načítaná príkazom <tt>cin >> N;</tt>. Nemusí to ale fungovať vždy &ndash; odporúčané je používať radšej dynamickú alokáciu poľa tak, ako v príklade vyššie.</span>
+
Poznámka:  
 +
* V našich programoch sme vytvárali polia, ktorých veľkosť bola konštanta <tt>const int maxN = 100; int a[maxN];</tt>
 +
* Niektoré kompilátory dovolia vytvoriť aj pole, ktorého veľkosť sa zistí počas behu programu <tt>int N; cin >> N; int a[N];</tt>
 +
** Nefunguje to však vždy, navyše môže byť problém s veľkými poliami, lebo veľkosť zásobníka volaní môže byť obmedzená
 +
* Pri alokovaní poľa pomocou <tt>new</tt> vždy môžeme použiť veľkosť, ktorá sa zistila až počas behu <tt>int N; cin >> N; int *a = new int[N];</tt>
 +
** Alokovanie má aj ďalšie výhody, takto vytvorené pole sa napríklad dá vrátiť ako výsledok funkcie
  
 
== Aplikácia smerníkov: dynamické polia ==
 
== Aplikácia smerníkov: dynamické polia ==
  
Asi najvýraznejším nedostatkom polí je ich fixná veľkosť. Počas behu programu totiž môže vzniknúť potreba pridávať ďalšie a ďalšie prvky, ktoré sa postupne do poľa nemusia vojsť. Riešenie tejto situácie nadhodnotením veľkosti poľa nie je ideálne, keďže sa tak plytvá pamäťou.
+
V praxi často narazíme na nasledujúci problém: chceme zo vstupu načítať do poľa nejaké údaje, ale vopred nevieme, koľko ich bude a teda aké veľké pole potrebujeme vytvoriť.
 +
* Doteraz sme to riešili konštantou <tt>MaxN</tt> ohraničujúcou maximálnu povolenú veľkosť vstupu, ale to má problémy:
 +
** Ak je vstup väčší ako <tt>MaxN</tt>, nevieme ho spracovať, aj ak by inak kapacity počítača postačovali
 +
** Ak je vstup oveľa menší ako <tt>MaxN</tt>, zbytočne zaberáme pamäť veľkým poľom, ktorého veľká časť je nevyužitá
 +
* Pre jednoduchosť budeme uvažovať na vstupe postupnosť nezáporných čísel ukončenú -1, napr. <tt>7 3 0 4 3 -1</tt>
 +
** Do poľa chceme uložiť všetko okrem poslednej -1
 +
** Používateľ nám vopred nezadá počet prvkov
  
Ako riešenie tohto problému teraz naprogramujeme tzv. ''dynamické pole'', ktoré mení svoju veľkosť s tým, ako sa doň pridávajú prvky. Základná idea pritom bude nasledovná:
+
Riešením je postupne alokovať väčšie a väčšie polia podľa potreby
* Zakaždým, keď sa pole plne naplní, alokujeme preň nový a väčší pamäťový úsek, kam celé pole presunieme. Starý pamäťový úsek z pamäte uvoľníme.
+
* Začneme s malým poľom (napr. veľkosti 2)
* Keďže kopírovanie poľa do novoalokovaného pamäťového úseku bude mierne neefektívne (bude potrebné prejsť cez celé pole), budeme sa snažiť vyvarovať toho, aby sme ho museli realizovať zakaždým, keď do poľa pridáme nejaký prvok. Na druhej strane však nechceme alokovať zbytočne veľké úseky pamäte. Rozumným kompromisom sa javí byť zdvojnásobenie veľkosti alokovaného úseku zakaždým, keď sa pole naplní.
+
* Vždy keď sa pole zaplní, alokujeme nové pole dvojnásobnej veľkosti, prvky do neho skopírujeme a staré pole odalokujeme
* V štandardných C++ knižniciach je definovaná dátová štruktúra [http://www.cplusplus.com/reference/vector/vector/ vector], ktorá sa správa podobne. My teraz vo svojej podstate implementujeme zjednodušenú verziu tejto štruktúry.
+
* Presúvanie prvkov dlho trvá, preto pole vždy zdvojnásobíme, aby sme nemuseli presúvať často
 +
* Spolu pri načítaní ''n'' prvkov robíme najviac ''2n'' presunov jednotlivých prvkov
  
Pre jednoduchosť napíšeme iba verziu dynamického poľa pre celé čísla (typ <tt>int</tt>). Analogicky by sme však mohli postupovať aj pre iné typy.
+
Takáto verzia poľa, ktorá rastie podľa potreby, sa nazýva ''dynamické pole''
 +
* V štandardných C++ knižniciach je definovaná dátová štruktúra [http://www.cplusplus.com/reference/vector/vector/ vector], ktorá sa správa podobne.
 +
* My teraz implementujeme zjednodušenú verziu tejto štruktúry.
 +
* Pre jednoduchosť napíšeme iba verziu dynamického poľa pre typ <tt>int</tt>. Analogicky by sme postupovali pre iné typy.
  
 
Dynamické pole celých čísel budeme reprezentovať ako štruktúru typu <tt>dynArray</tt>, ktorá bude pozostávať z nasledujúcich troch zložiek:
 
Dynamické pole celých čísel budeme reprezentovať ako štruktúru typu <tt>dynArray</tt>, ktorá bude pozostávať z nasledujúcich troch zložiek:
* Zo smerníku <tt>p</tt> ukazujúceho na prvý prvok poľa (čiže vlastne pole samotné).
+
* Zo smerníku <tt>p</tt> ukazujúceho na nultý prvok poľa (čiže vlastne pole samotné).
* Z celočíselnej premennej <tt>size</tt>, v ktorej bude uchovávaná veľkosť alokovanej pamäte pre pole <tt>p</tt>.
+
* Z celočíselnej premennej <tt>length</tt>, v ktorej bude počet prvkov, ktoré sú aktuálne v poli.
* Z celočíselnej premennej <tt>length</tt>, v ktorej bude uchovávaný počet prvkov doposiaľ pridaných do poľa.
+
* Z celočíselnej premennej <tt>size</tt>, v ktorej bude veľkosť alokovanej pamäte pre pole <tt>p</tt>.
 +
Štruktúra <tt>dynArray</tt> teda v sebe združuje pole aj jeho dĺžku, stačí posielať jeden parameter.
  
Napíšeme potom niekoľko funkcií, pomocou ktorých budeme s dynamickými poľami manipulovať.  
+
Napíšeme niekoľko funkcií, pomocou ktorých budeme s dynamickými poľami manipulovať.  
* Funkcia <tt>void init(dynArray &a)</tt> inicializuje dynamické pole <tt>a</tt>, pričom mu alokuje nejaký rozumne malý objem pamäte (ten v našej implementácii bude postačovať na uchovanie práve dvoch prvkov typu <tt>int</tt>).
+
* Funkcia <tt>void init(dynArray &a)</tt> inicializuje dynamické pole <tt>a</tt>, v ktorom je nula prvkov. Funkcia ale alokuje nejaký malý objem pamäte (u nás dva prvky).
* Funkcia <tt>void add(dynArray &a, int x)</tt> pridá na koniec dynamického poľa <tt>a</tt> prvok s hodnotou <tt>x</tt>. V prípade potreby ešte predtým realokuje pamäť.
+
* Funkcia <tt>void add(dynArray &a, int x)</tt> pridá do dynamického poľa <tt>a</tt> prvok s hodnotou <tt>x</tt>, čím počet prvkov vzrastie o jedna. V prípade potreby ešte predtým realokuje pamäť.
 
* Funkcia <tt>int get(dynArray a, int index)</tt> vráti prvok dynamického poľa <tt>a</tt> na pozícii <tt>index</tt>. V prípade, že <tt>index</tt> nereprezentuje korektnú pozíciu prvku poľa (teda je menší ako <tt>0</tt> alebo väčší, než <tt>a.length - 1</tt>), ukončí vykonávanie programu pomocou <tt>assert</tt>.
 
* Funkcia <tt>int get(dynArray a, int index)</tt> vráti prvok dynamického poľa <tt>a</tt> na pozícii <tt>index</tt>. V prípade, že <tt>index</tt> nereprezentuje korektnú pozíciu prvku poľa (teda je menší ako <tt>0</tt> alebo väčší, než <tt>a.length - 1</tt>), ukončí vykonávanie programu pomocou <tt>assert</tt>.
 
* Funkcia <tt>void set(dynArray &a, int index, int x)</tt> nastaví prvok dynamického poľa na pozícii <tt>index</tt> na hodnotou <tt>x</tt>. Ak <tt>index</tt> nereprezentuje korektnú pozíciu prvku poľa, ukončí vykonávanie programu pomocou <tt>assert</tt>.
 
* Funkcia <tt>void set(dynArray &a, int index, int x)</tt> nastaví prvok dynamického poľa na pozícii <tt>index</tt> na hodnotou <tt>x</tt>. Ak <tt>index</tt> nereprezentuje korektnú pozíciu prvku poľa, ukončí vykonávanie programu pomocou <tt>assert</tt>.
* Funkcia <tt>int length(dynArray a)</tt> vráti počet prvkov doposiaľ uložených do dynamického poľa <tt>a</tt>.
+
* Funkcia <tt>int length(dynArray a)</tt> vráti počet prvkov aktuálne uložených v dynamickom poli <tt>a</tt>.
 
* Funkcia <tt>void destroy(dynArray &a)</tt> zlikviduje dynamické pole <tt>a</tt> (uvoľní pamäť).
 
* Funkcia <tt>void destroy(dynArray &a)</tt> zlikviduje dynamické pole <tt>a</tt> (uvoľní pamäť).
  
Bez ohľadu na implementáciu samotného dynamického poľa už teda vieme napísať kostru programu, ktorý ho využíva:
+
Bez ohľadu na implementáciu samotného dynamického poľa už teda vieme napísať kostru programu, ktorý ho využíva, napríklad na výpis vstupu odzadu.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 316: Riadok 294:
 
};
 
};
  
void init(dynArray &a) {
+
// definície funkcií init, add, get, set, length, destroy
// ...
 
}
 
 
 
void add(dynArray &a, int x) {
 
// ...
 
}
 
 
 
int get(dynArray &a, int index) {
 
// ...
 
}
 
 
 
void set(dynArray &a, int index, int x) {
 
// ...
 
}
 
 
 
int length(dynArray &a) {
 
// ...
 
}
 
 
 
void destroy(dynArray &a) {
 
// ...
 
}
 
  
int main(void) {
+
int main() {
 
     dynArray a;
 
     dynArray a;
     init(a);
+
     init(a); // inicializuje a
 
      
 
      
     int k;
+
     int x;
     cin >> k;
+
     cin >> x;
     while (k >= 0) {                 
+
    // pridavame prvky, kym su nezaporne
         add(a,k);                   // pridava prvky do pola, kym su nezaporne
+
     while (x >= 0) {                 
         cin >> k;     
+
         add(a, x);    
 +
         cin >> x;     
 
     }
 
     }
 +
    // vypise prvky pola od konca
 
     for (int i = length(a) - 1; i >= 0; i--) {
 
     for (int i = length(a) - 1; i >= 0; i--) {
         cout << get(a,i) << " ";    // vypise prvky pola od konca
+
         cout << get(a, i) << " ";     
 
     }
 
     }
 
     cout << endl;
 
     cout << endl;
    set(a,0,42);
 
    cout << get(a,0) << endl;
 
  
     destroy(a);
+
     // ukazka pouzitia get a set
 +
    set(a, 0, 42); 
 +
    cout << get(a, 0) << endl;
  
     return 0;  
+
     destroy(a); // uvolni pamat
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Môžeme teraz prejsť k samotnej implementácii dynamického poľa:
+
===Implementácia dynamického poľa===
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 371: Riadok 329:
  
 
/* Dynamicke pole celych cisel */
 
/* Dynamicke pole celych cisel */
 
 
struct dynArray {
 
struct dynArray {
     int *p;                   // smernik na prvy prvok pola
+
     int *p;         // smernik na prvy prvok pola
     int size;                 // velkost alokovaneho pola
+
     int size;       // velkost alokovaneho pola
     int length;               // pocet prvkov pridanych do pola
+
     int length;     // pocet prvkov pridanych do pola
 
};
 
};
  
 
void init(dynArray &a) {
 
void init(dynArray &a) {
/* Inicializuje dynamicke pole, pricom na zaciatok pren alokuje pole velkosti 2 */
+
    /* Inicializuje dynamicke pole velkosti 2 */
 
     a.size = 2;
 
     a.size = 2;
 
     a.length = 0;
 
     a.length = 0;
Riadok 386: Riadok 343:
  
 
void add(dynArray &a, int x) {
 
void add(dynArray &a, int x) {
/* Prida na koniec dynamickeho pola prvok x a v pripade potreby realokuje pole */
+
    /* Prida na koniec dynamickeho pola prvok x
     if (a.length == a.size) {               // ak uz sa x do pola nevojde
+
    * a v pripade potreby realokuje pole */
 +
 
 +
    // ak uz sa x do pola nevojde
 +
     if (a.length == a.size) {
 +
        // zdvojnasobime velkost
 
         a.size *= 2;
 
         a.size *= 2;
         int *newp = new int[a.size];        // alokuje pole dvojnasobnej velkosti
+
        // alokujeme vacsie pole
         for (int i = 0; i <= a.length - 1; i++) {  
+
         int *newp = new int[a.size];
             newp[i] = a.p[i];               // prekopiruje stare pole do noveho
+
         // prekopirujeme stare pole do noveho
 +
         for (int i = 0; i < a.length; i++) {  
 +
             newp[i] = a.p[i];          
 
         }
 
         }
         delete[] a.p;                       // zmaze stare pole
+
        // uvolnime stare pole
         a.p = newp;                         // a.p odteraz ukazuje na nove pole
+
         delete[] a.p;                  
 +
         a.p = newp; // a.p teraz ukazuje na nove pole
 
     }
 
     }
     a.p[a.length] = x;                       // ulozi x na koniec pola
+
    // teraz je pole urcite dost velke
     a.length++;                             // zvysi pocet prvkov ulozenych v poli
+
    // pridame x a zvysime pocet prvkov
 +
     a.p[a.length] = x;
 +
     a.length++;
 
}
 
}
  
 
int get(dynArray &a, int index) {
 
int get(dynArray &a, int index) {
/* Vrati prvok dynamickeho pola a na pozicii index (ak ide o korektnu poziciu)*/
+
    /* Vrati prvok dynamickeho pola a na pozicii index  
 +
    * (ak ide o korektnu poziciu)*/
 
     assert(index >= 0 && index <= a.length - 1);
 
     assert(index >= 0 && index <= a.length - 1);
 
     return a.p[index];
 
     return a.p[index];
Riadok 407: Riadok 374:
  
 
void set(dynArray &a, int index, int x) {
 
void set(dynArray &a, int index, int x) {
/* Nastavi prvok dynamickeho pola a na pozicii index na hodnotu x (ak ide o korektnu poziciu)*/
+
    /* Ulozi na poziciu index dynamickeho pola a hodnotu x  
 +
    * (ak ide o korektnu poziciu)*/
 
     assert(index >= 0 && index <= a.length - 1);
 
     assert(index >= 0 && index <= a.length - 1);
 
     a.p[index] = x;
 
     a.p[index] = x;
Riadok 413: Riadok 381:
  
 
int length(dynArray &a) {
 
int length(dynArray &a) {
/* Vrati pocet prvkov ulozenych v dynamickom poli a */
+
    /* Vrati pocet prvkov v dynamickom poli a */
 
     return a.length;
 
     return a.length;
 
}
 
}
  
 
void destroy(dynArray &a) {
 
void destroy(dynArray &a) {
/* Zlikviduje dynamicke pole a (uvolni alokovanu pamat) */
+
    /* Uvolni alokovanu pamat pre pole a */
 
     delete[] a.p;
 
     delete[] a.p;
 
}
 
}
  
int main(void) {
+
int main() {
 
     dynArray a;
 
     dynArray a;
     init(a);
+
     init(a); // inicializuje a
+
   
     int k;
+
     int x;
     cin >> k;
+
     cin >> x;
     while (k >= 0) {                 
+
    // pridavame prvky, kym su nezaporne
         add(a,k);                   // pridava prvky do pola, kym su nezaporne
+
     while (x >= 0) {                 
         cin >> k;     
+
         add(a, x);    
 +
         cin >> x;     
 
     }
 
     }
 +
    // vypise prvky pola od konca
 
     for (int i = length(a) - 1; i >= 0; i--) {
 
     for (int i = length(a) - 1; i >= 0; i--) {
         cout << get(a,i) << " ";    // vypise prvky pola od konca
+
         cout << get(a, i) << " ";     
 
     }
 
     }
 
     cout << endl;
 
     cout << endl;
    set(a,0,42);
 
    cout << get(a,0) << endl;
 
 
    destroy(a);
 
  
     return 0;  
+
     // ukazka pouzitia get a set
 +
    set(a, 0, 42);  
 +
    cout << get(a, 0) << endl; 
 +
 
 +
    destroy(a);  // uvolni pamat
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Smerníková aritmetika ==
+
==Ďalšie detaily používania smerníkov==
  
Na smerníkoch možno vykonávať určité operácie, súhrn ktorých býva honosne nazývaný ''smerníkovou aritmetikou''. Nech <tt>p</tt>, <tt>p1</tt>, <tt>p2</tt> sú smerníky definované ako
+
=== Typy smerníkov ===
 +
 
 +
Smerník ukazujúci na premennú typu <tt>T</tt> sa definuje ako <tt>T *p</tt>, napríklad:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
T *p;
+
int    *p1;   // smerník p1 na int
T *p1;
+
char  *p2;   // smerník p2 na char
T *p2;
+
double *p3;   // smerník p3 na double
 +
 
 +
// atď
 
</syntaxhighlight>
 
</syntaxhighlight>
kde <tt>T</tt> označuje nejaký typ. Nech <tt>n</tt> je typu <tt>int</tt>. Potom:
 
* <tt>p + n</tt> označuje smerník na <tt>n</tt>-tý pamäťový úsek (postačujúci práve na uchovanie hodnoty typu <tt>T</tt>) za adresou <tt>p</tt>.
 
** Napríklad <tt>p+n</tt> je to isté ako <tt>&p[n]</tt> a <tt>*(p+n)</tt> je to isté ako <tt>p[n]</tt>.
 
** <tt>p++</tt> je skratkou pre <tt>p = p+1</tt>, ...
 
* Operátor <tt>[ ]</tt> je teda nadbytočný &ndash; <tt>p[n]</tt> je len ''skratkou'' pre často používaný výraz <tt>*(p+n)</tt>.
 
* <tt>p - n</tt> označuje smerník na <tt>n</tt>-tý pamäťový úsek (postačujúci práve na uchovanie hodnoty typu <tt>T</tt>) pred adresou <tt>p</tt>.
 
* <tt>p1 - p2</tt> je celé číslo <tt>k</tt> také, že <tt>p1 == p2 + k</tt>. Zmysluplný výsledok možno očakávať len vtedy, keď <tt>p1</tt> a <tt>p2</tt> sú adresami prvkov v tom istom poli (v jedinom súvislom kuse pamäte).
 
* Smerníky tiež možno prirodzene porovnávať relačnými operátormi <tt>==, <, >, <=, >=, !=</tt>. Výsledok je zmysluplný opäť len vtedy, keď <tt>p1</tt> a <tt>p2</tt> sú adresami prvkov v tom istom poli.
 
  
Program, ktorý najprv načíta pole a následne prvky tohto poľa vypíše od konca, tak možno napísať napríklad aj takto:
+
Smerníky ukazujúce na premenné rôznych typov sú takisto rôznych typov. Bez pretypovania sa nedá medzi nimi priraďovať.
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
#include <iostream>
+
int *p1;
using namespace std;
+
int *p2;
 +
double *p3;
 +
 
 +
p1 = p2;  // korektné priradenie
 +
p3 = p1;  // chyba
 +
</syntaxhighlight>
 +
 
 +
 
 +
=== Operátor <tt>&</tt> (adresa) ===
 +
 
 +
* Videli sme, že adresu premennej vieme zistiť operátorom &
 +
* Tú potom môžeme priradiť do premennej typu smerník
 +
 
 +
<syntaxhighlight lang="C++">
 +
int x = 17;
 +
int *p;
 +
p = &x;
 +
</syntaxhighlight>
 +
 
 +
Premenná ''p'' teraz ukazuje na miesto, kde je uložená celočíselná premenná ''x''.
 +
 
 +
* Operátor <tt>&</tt> možno aplikovať aj na prvky poľa (alebo položky struct-u)
 +
* Operátor <tt>&</tt> nemožno aplikovať na konštanty ani na výrazy
  
const int maxN = 1000;
+
<syntaxhighlight lang="C++">
 +
int x = 0;
 +
int a[5] = {1, 2, 3, 4, 5};
 +
int *p;
  
int main(void) {
+
p = &x;      // korektné priradenie
    int a[maxN];
+
p = &a[2];   // korektné priradenie
    int N;
+
p = &(x + 1); // chyba (výraz nemá adresu)
    cout << "Zadaj pocet cisel: ";
+
p = &42;     // chyba (konštanta 42 nemá adresu)
    cin >> N;
 
    for (int i = 0; i <= N-1; i++) {
 
        cout << "Zadaj cislo " << i + 1 << ": ";
 
        cin >> *(a + i);   
 
    }
 
    for (int i = N-1; i >= 0; i--) {
 
        cout << *(a + i) << " ";
 
    }
 
    cout << endl;
 
    return 0;
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
  
 
== Ladenie programov so smerníkmi ==
 
== Ladenie programov so smerníkmi ==
  
* Smerníky môžu byť nepríjemným zdrojom chýb, keďže kompilátor nekontroluje, či sú používané správne.
+
* Smerníky (a polia) môžu byť nepríjemným zdrojom chýb, keďže kompilátor nekontroluje, či sú používané správne.
 
* Napríklad možno čítať aj zapisovať mimo alokovanej pamäte.  
 
* Napríklad možno čítať aj zapisovať mimo alokovanej pamäte.  
 
* S odchytávaním takýchto chýb môžu pomôcť automatizované nástroje, ako napríklad [http://valgrind.org/ Valgrind] (pre Linux) alebo [http://drmemory.org/ Dr. Memory] (pre Windows aj Linux).
 
* S odchytávaním takýchto chýb môžu pomôcť automatizované nástroje, ako napríklad [http://valgrind.org/ Valgrind] (pre Linux) alebo [http://drmemory.org/ Dr. Memory] (pre Windows aj Linux).
* [[#Valgrind|Návod na prácu s programom valgrind]]
+
* [[Valgrind|Návod na prácu s programom valgrind]]
 +
 
 +
==Zhrnutie==
 +
* Smerník, ukazovateľ, pointer je premenná, v ktorej je uložená adresa nejakého pamäťového miesta
 +
* Typ smerníku určuje, na aký typ premennej by mal ukazovať, napr. <tt>int *p</tt>
 +
* Do smerníku môžeme priradiť <tt>NULL</tt>, adresu nejakej premennej (<tt>&i</tt>), novoalokovanú pamäť pomocou <tt>new</tt>, iný smerník toho istého typu
 +
* Ku políčku, na ktoré ukazuje smerník ''p'', pristupujeme pomocou (<tt>*p</tt>)
 +
* Pole je vlastne smerník na svoj prvý (nultý) prvok
 +
* Pole určitej dĺžky (ktorá je známa až počas behu) alokujeme pomocou <tt>new typ[pocet]</tt>
 +
* Pamäť alokovanú cez <tt>new</tt> by sme mali odalokovať pomocou <tt>delete</tt> alebo <tt>delete[]</tt> (podľa toho, či to bolo pole)
 +
* Pri práci so smerníkmi ľahko spravíme chybu, pomôcť nám môže napríklad valgrind

Aktuálna revízia z 23:36, 29. október 2023

Oznamy

  • DÚ2 zverejnená, odovzdávajte do utorka 14.11. 22:00.
  • Dnes nová téma: smerníky a práca s pamäťou.
  • Zajtra cvičenia, rozcvička z dnešného učiva, iba dva príklady.
    • Odporúčame získať na cvičeniach aspoň 2 body. Ak ich nezískate, budú pre vás povinné cvičenia v piatok budúci týždeň.
  • Tento týždeň je v stredu sviatok, štvrtok a piatok voľno.
    • Odporúčame využiť voľnejší týždeň na prácu na domácej úlohe.
  • Budúci utorok 7.11. na cvičeniach bude krátky test podobne ako na prednáške 4.10.

Ukazovateľ, smerník, pointer

  • Pamäť v počítači je rozdelená na dieliky, napr. bajty
  • Každá premenná zaberá niekoľko takýchto dielikov
  • Každý dielik má adresu (poradové číslo)
  • Ukazovateľ (resp. smerník alebo pointer) je premenná, ktorej hodnota je adresa iného dieliku pamäte
  • Na obrázku je na adrese 24 uložená premenná x typu int, ktorej hodnota je 17.
  • Na adrese 40 je uložený smerník p, ktorého hodnota je 24.
    • Hovoríme, že smerník p ukazuje na premennú x, zjednodušene to budeme kresliť ako šípku, viď spodok obrázku.
    • Väčšinou nás nezaujíma, aké sú presné hodnoty adries, chceme si ale vedieť nakresliť podobný obrázok so šípkami.

Memory-c.png


Takúto situáciu (len s inými adresami) vyrobíme príkazmi

int x = 17;   // vytvorenie a inicializácia premennej x
int *p;       // vytvorenie premennej p, ktorá bude smerník na int
p = &x;       // &n vráti adresu premennej x, tú uložíme do premennej p

Operátor * (dereferencia, dáta na adrese)

  • Ak p je smerník, pomocou *p môžeme pristúpiť k údajom na adrese reprezentovanej smerníkom p.
  • Tieto údaje potom možno aj meniť
  • Ak máme int x = 17; int *p = &x;, tak ďalej v programe *p aj x sú mená pre ten istý dielik pamäte.
int x = 17;
int *p = &x;        // p ukazuje na adresu premennej x
cout << *p << endl; // vypíše hodnotu z adresy p, t.j. 17
*p = 9;             // hodnota na adrese p sa zmení na 9
cout << x << endl;  // vypíše hodnotu premennej x, t.j. 9
(*p)++;             // hodnota na adrese p sa zmení na 10
cout << x << endl;  // vypíše hodnotu premennej x, t.j. 10
x = 42;
cout << *p << endl;  // vypíše hodnotu na adrese p, t.j. 42

Smerník NULL

Dôležitým špeciálnym prípadom smerníka je konštanta NULL reprezentujúca smerník, ktorý nikam neukazuje.

  • Je definovaná vo viacerých štandardných knižniciach, ako napríklad cstdlib alebo iostream.
  • Možno ju priradiť do smerníka ľubovoľného typu.

Pozor, ak do premennej typu smerník nič nepriradíme, má nedefinovanú hodnotu, ukazuje na náhodné miesto v pamäti, alebo niekde mimo.

Smerník ako parameter funkcie

Namiesto odovzdávania parametrov referenciou ich môžeme odovzdať pomocou smerníka. Tu je napríklad smerníková verzia funkcie swap, ktorá vymieňa hodnoty dvoch premenných.

#include <iostream>
using namespace std;

void swap(int *px, int *py) {  // parametre sú smerníky
    // hodnotu z adresy px uložíme do tmp:
    int tmp = *px;
    // na adresu px uložíme hodnotu z adresy py:
    *px = *py;
    // na adresu py uložíme tmp:
    *py = tmp;
}

int main() {
    int x, y;
    cout << "Zadaj x,y: ";
    cin >> x >> y;
    // ako parametre pošleme adresy premenných x,y
    swap(&x, &y);                                  
    cout << "x = " << x 
         << ", y = " << y << endl;
}

Knižničné funkcie v C často používajú odovzdávanie parametrov cez smerníky.

Smerníky a polia

Smerníky a polia v jazyku C spolu veľmi úzko súvisia.

  • Pole je vlastne smerník na nultý prvok.
  • Môžeme ho nakopírovať do premennej typu T *.
  • Na premenné typu T * môžeme použiť operátor [].
int a[4] = {10, 20, 30, 40};
int *p;
p = a;        // p ukazuje na nulty prvok pola a
cout << a[1]; // vypise 20
cout << p[1]; // vypise 20
  • Polia v C pozostávajú z políčok rovnakej veľkosti uložených v pamäti jedno za druhým, veľkosť políčka je daná typom prvku
  • Výraz p[i] zoberie adresu uloženú v p, zvýši ju o veľkosť_políčka * i a pozrie sa na príslušnú adresu
  • p[0] je teda to isté ako *p
  • Pozor, C umožní p[i] použiť aj keď p neukazuje na pole, vtedy pristupuje do pamäte s neznámym obsahom, program môže skončiť s chybou alebo sa správať "záhadne"
int x = 10;
int *p;
p = &x;
cout << p[1]; // ??? pristupuje do pamäte za premennou x
              // môže to mať nepríjemné dôsledky

Pozor, polia sú konštantné smerníky, nemožno ich zmeniť

int a[4] = {10, 20, 30, 40};
int b[3] = {1, 2, 3};
int *p = b;  // ok
a = b;       // nedá sa
a = p;       // nedá sa

Vo funkciách pracujúcich s poliami môžeme namiesto parametra int a[] písať aj int *a a kód ani použitie funkcie sa nemení.

#include <iostream>
using namespace std;

void vypisPole(int a[], int n) {
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

void vypisPole2(int *a, int n) {
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main() {
    const int N  = 4;
    int a[N] = {10, 20, 30, 40};
    int *b = a;
    
    //styrikrat vypiseme to iste
    vypisPole(a, N); 
    vypisPole(b, N);
    vypisPole2(a, N);
    vypisPole2(b, N);
}

Dynamická alokácia a dealokácia pamäte

Doteraz sme videli:

  • Globálne premenné, ktoré majú vopred známu veľkosť a vyhradenú pamäť.
  • Lokálne premenné, ktoré majú vopred známu veľkosť, ale pamäť sa im prideľuje až pri volaní funkcie na zásobníku volaní funkcií (call stack).

Program si ale počas behu môže podľa potreby vyhradiť aj ďalšiu pamäť:

  • Používa sa na to operátor new.
  • Pamäť sa vyhradí v oblasti zvanej halda (heap).
  • Keď už pamäť nepotrebujeme, uvoľníme ju príkazom delete.
  • Uvoľnená pamäť môže byť znovu použitá pri ďalších volaniach new.

Alokácia pamäte na jednu premennú

int *p;   

// new vyhradí úsek pamäte pre jednu hodnotu typu int
// adresa tohto úseku sa uloží do smerníka p
p = new int;   

// do alokovanej pamäte sa uloží hodnota 50  
*p = 50;
cout << *p << endl;  // výpis 50

delete p;      // uvoľnenie alokovanej pamäte

Alokácia pamäte pre pole

int *p;   

// new vyhradí úsek pamäte pre poli 5 hodnôt typu int
p = new int[5];

// premenná p sa dá použiť ako pole dĺžky 5
for(int i=0; i<5; i++) {
   p[i] = i;
}   

delete[] p;     // uvoľnenie alokovanej pamäte


  • Pozor, ak alokujeme pole, pamäť uvoľnujeme cez delete[], nie delete
  • Ak zamieňate delete[] a delete, správanie programu môže byť nedefinované
int *p;

p = new int;
// ...
delete p;

p = new int[5];
// ...
delete[] p;

Dynamickú alokáciu polí možno využiť napríklad na vytvorenie poľa, ktorého veľkosť zadá používateľ.

#include <iostream>
using namespace std;

int main(void) {
    cout << "Zadaj pocet cisel: ";
    int N;
    cin >> N;
    int *a = new int[N];
    
    cout << "Zadavaj " << N << " cisel:" << end;
    for (int i = 0; i <= N-1; i++) {
        cin >> a[i];
    }
    cout << "Tu su cisla odzadu:" << endl;
    for (int i = N-1; i >= 0; i--) {
        cout << a[i] << " ";
    }
    cout << endl;

    delete[] a;
}

Poznámka:

  • V našich programoch sme vytvárali polia, ktorých veľkosť bola konštanta const int maxN = 100; int a[maxN];
  • Niektoré kompilátory dovolia vytvoriť aj pole, ktorého veľkosť sa zistí počas behu programu int N; cin >> N; int a[N];
    • Nefunguje to však vždy, navyše môže byť problém s veľkými poliami, lebo veľkosť zásobníka volaní môže byť obmedzená
  • Pri alokovaní poľa pomocou new vždy môžeme použiť veľkosť, ktorá sa zistila až počas behu int N; cin >> N; int *a = new int[N];
    • Alokovanie má aj ďalšie výhody, takto vytvorené pole sa napríklad dá vrátiť ako výsledok funkcie

Aplikácia smerníkov: dynamické polia

V praxi často narazíme na nasledujúci problém: chceme zo vstupu načítať do poľa nejaké údaje, ale vopred nevieme, koľko ich bude a teda aké veľké pole potrebujeme vytvoriť.

  • Doteraz sme to riešili konštantou MaxN ohraničujúcou maximálnu povolenú veľkosť vstupu, ale to má problémy:
    • Ak je vstup väčší ako MaxN, nevieme ho spracovať, aj ak by inak kapacity počítača postačovali
    • Ak je vstup oveľa menší ako MaxN, zbytočne zaberáme pamäť veľkým poľom, ktorého veľká časť je nevyužitá
  • Pre jednoduchosť budeme uvažovať na vstupe postupnosť nezáporných čísel ukončenú -1, napr. 7 3 0 4 3 -1
    • Do poľa chceme uložiť všetko okrem poslednej -1
    • Používateľ nám vopred nezadá počet prvkov

Riešením je postupne alokovať väčšie a väčšie polia podľa potreby

  • Začneme s malým poľom (napr. veľkosti 2)
  • Vždy keď sa pole zaplní, alokujeme nové pole dvojnásobnej veľkosti, prvky do neho skopírujeme a staré pole odalokujeme
  • Presúvanie prvkov dlho trvá, preto pole vždy zdvojnásobíme, aby sme nemuseli presúvať často
  • Spolu pri načítaní n prvkov robíme najviac 2n presunov jednotlivých prvkov

Takáto verzia poľa, ktorá rastie podľa potreby, sa nazýva dynamické pole

  • V štandardných C++ knižniciach je definovaná dátová štruktúra vector, ktorá sa správa podobne.
  • My teraz implementujeme zjednodušenú verziu tejto štruktúry.
  • Pre jednoduchosť napíšeme iba verziu dynamického poľa pre typ int. Analogicky by sme postupovali pre iné typy.

Dynamické pole celých čísel budeme reprezentovať ako štruktúru typu dynArray, ktorá bude pozostávať z nasledujúcich troch zložiek:

  • Zo smerníku p ukazujúceho na nultý prvok poľa (čiže vlastne pole samotné).
  • Z celočíselnej premennej length, v ktorej bude počet prvkov, ktoré sú aktuálne v poli.
  • Z celočíselnej premennej size, v ktorej bude veľkosť alokovanej pamäte pre pole p.

Štruktúra dynArray teda v sebe združuje pole aj jeho dĺžku, stačí posielať jeden parameter.

Napíšeme niekoľko funkcií, pomocou ktorých budeme s dynamickými poľami manipulovať.

  • Funkcia void init(dynArray &a) inicializuje dynamické pole a, v ktorom je nula prvkov. Funkcia ale alokuje nejaký malý objem pamäte (u nás dva prvky).
  • Funkcia void add(dynArray &a, int x) pridá do dynamického poľa a prvok s hodnotou x, čím počet prvkov vzrastie o jedna. V prípade potreby ešte predtým realokuje pamäť.
  • Funkcia int get(dynArray a, int index) vráti prvok dynamického poľa a na pozícii index. V prípade, že index nereprezentuje korektnú pozíciu prvku poľa (teda je menší ako 0 alebo väčší, než a.length - 1), ukončí vykonávanie programu pomocou assert.
  • Funkcia void set(dynArray &a, int index, int x) nastaví prvok dynamického poľa na pozícii index na hodnotou x. Ak index nereprezentuje korektnú pozíciu prvku poľa, ukončí vykonávanie programu pomocou assert.
  • Funkcia int length(dynArray a) vráti počet prvkov aktuálne uložených v dynamickom poli a.
  • Funkcia void destroy(dynArray &a) zlikviduje dynamické pole a (uvoľní pamäť).

Bez ohľadu na implementáciu samotného dynamického poľa už teda vieme napísať kostru programu, ktorý ho využíva, napríklad na výpis vstupu odzadu.

#include <iostream>
#include <cassert>
using namespace std;

struct dynArray {
// ...
};

// definície funkcií init, add, get, set, length, destroy

int main() {
    dynArray a;
    init(a);  // inicializuje a
    
    int x;
    cin >> x;
    // pridavame prvky, kym su nezaporne
    while (x >= 0) {                 
        add(a, x);     
        cin >> x;    
    }
    // vypise prvky pola od konca
    for (int i = length(a) - 1; i >= 0; i--) {
        cout << get(a, i) << " ";     
    }
    cout << endl;

    // ukazka pouzitia get a set
    set(a, 0, 42);   
    cout << get(a, 0) << endl;  

    destroy(a);  // uvolni pamat
}

Implementácia dynamického poľa

#include <iostream>
#include <cassert>
using namespace std;

/* Dynamicke pole celych cisel */
struct dynArray {
    int *p;         // smernik na prvy prvok pola
    int size;       // velkost alokovaneho pola
    int length;     // pocet prvkov pridanych do pola
};

void init(dynArray &a) {
    /* Inicializuje dynamicke pole velkosti 2 */
    a.size = 2;
    a.length = 0;
    a.p = new int[a.size];
}

void add(dynArray &a, int x) {
    /* Prida na koniec dynamickeho pola prvok x
     * a v pripade potreby realokuje pole */

    // ak uz sa x do pola nevojde
    if (a.length == a.size) {
        // zdvojnasobime velkost
        a.size *= 2;
        // alokujeme vacsie pole
        int *newp = new int[a.size];
        // prekopirujeme stare pole do noveho
        for (int i = 0; i < a.length; i++) { 
            newp[i] = a.p[i];           
        }
        // uvolnime stare pole
        delete[] a.p;                   
        a.p = newp;  // a.p teraz ukazuje na nove pole
    }
    // teraz je pole urcite dost velke
    // pridame x a zvysime pocet prvkov
    a.p[a.length] = x;
    a.length++;
}

int get(dynArray &a, int index) {
    /* Vrati prvok dynamickeho pola a na pozicii index 
     * (ak ide o korektnu poziciu)*/
    assert(index >= 0 && index <= a.length - 1);
    return a.p[index];
} 

void set(dynArray &a, int index, int x) {
    /* Ulozi na poziciu index dynamickeho pola a hodnotu x 
     * (ak ide o korektnu poziciu)*/
    assert(index >= 0 && index <= a.length - 1);
    a.p[index] = x;
}

int length(dynArray &a) {
    /* Vrati pocet prvkov v dynamickom poli a */
    return a.length;
}

void destroy(dynArray &a) {
    /* Uvolni alokovanu pamat pre pole a */
    delete[] a.p;
}

int main() {
    dynArray a;
    init(a);  // inicializuje a
    
    int x;
    cin >> x;
    // pridavame prvky, kym su nezaporne
    while (x >= 0) {                 
        add(a, x);     
        cin >> x;    
    }
    // vypise prvky pola od konca
    for (int i = length(a) - 1; i >= 0; i--) {
        cout << get(a, i) << " ";     
    }
    cout << endl;

    // ukazka pouzitia get a set
    set(a, 0, 42);   
    cout << get(a, 0) << endl;  

    destroy(a);  // uvolni pamat
}

Ďalšie detaily používania smerníkov

Typy smerníkov

Smerník ukazujúci na premennú typu T sa definuje ako T *p, napríklad:

int    *p1;   // smerník p1 na int
char   *p2;   // smerník p2 na char
double *p3;   // smerník p3 na double

// atď

Smerníky ukazujúce na premenné rôznych typov sú takisto rôznych typov. Bez pretypovania sa nedá medzi nimi priraďovať.

int *p1;
int *p2;
double *p3;

p1 = p2;   // korektné priradenie
p3 = p1;   // chyba


Operátor & (adresa)

  • Videli sme, že adresu premennej vieme zistiť operátorom &
  • Tú potom môžeme priradiť do premennej typu smerník
int x = 17;
int *p;
p = &x;

Premenná p teraz ukazuje na miesto, kde je uložená celočíselná premenná x.

  • Operátor & možno aplikovať aj na prvky poľa (alebo položky struct-u)
  • Operátor & nemožno aplikovať na konštanty ani na výrazy
int x = 0;
int a[5] = {1, 2, 3, 4, 5};
int *p;

p = &x;       // korektné priradenie
p = &a[2];    // korektné priradenie
p = &(x + 1); // chyba (výraz nemá adresu)
p = &42;      // chyba (konštanta 42 nemá adresu)


Ladenie programov so smerníkmi

  • Smerníky (a polia) môžu byť nepríjemným zdrojom chýb, keďže kompilátor nekontroluje, či sú používané správne.
  • Napríklad možno čítať aj zapisovať mimo alokovanej pamäte.
  • S odchytávaním takýchto chýb môžu pomôcť automatizované nástroje, ako napríklad Valgrind (pre Linux) alebo Dr. Memory (pre Windows aj Linux).
  • Návod na prácu s programom valgrind

Zhrnutie

  • Smerník, ukazovateľ, pointer je premenná, v ktorej je uložená adresa nejakého pamäťového miesta
  • Typ smerníku určuje, na aký typ premennej by mal ukazovať, napr. int *p
  • Do smerníku môžeme priradiť NULL, adresu nejakej premennej (&i), novoalokovanú pamäť pomocou new, iný smerník toho istého typu
  • Ku políčku, na ktoré ukazuje smerník p, pristupujeme pomocou (*p)
  • Pole je vlastne smerník na svoj prvý (nultý) prvok
  • Pole určitej dĺžky (ktorá je známa až počas behu) alokujeme pomocou new typ[pocet]
  • Pamäť alokovanú cez new by sme mali odalokovať pomocou delete alebo delete[] (podľa toho, či to bolo pole)
  • Pri práci so smerníkmi ľahko spravíme chybu, pomôcť nám môže napríklad valgrind