Programovanie (1) v C/C++
1-INF-127, ZS 2024/25

Úvod · Pravidlá · Prednášky · Softvér · Testovač
· Kontaktujte nás pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Prosíme študentov, aby si pravidelne čítali e-maily na @uniba.sk adrese alebo aby si tieto emaily preposielali na adresu, ktorú pravidelne čítajú.


Prednáška 13: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
Riadok 20: Riadok 20:
 
</pre>
 
</pre>
  
 +
== Dvojrozmerné polia ==
  
== Smerníková aritmetika ==
+
* Doteraz sme stále pracovali s jednorozmerným poľom, čo však ak potrebujeme dvojrozmerné pole, maticu?
 
+
** Napríklad na matice z algebry, alebo na dvojrozmerné tabuľky napr. body študentov z domácich úloh
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
+
* Podobne môžeme potrebovať aj polia väčších rozmerov, pracuje sa s nimi analogicky ako s dvojrozmenrnými
<syntaxhighlight lang="C++">
 
T *p;
 
T *p1;
 
T *p2;
 
</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:
+
=== Dvojrozmerné polia s konštantnou veľkosťou ===
<syntaxhighlight lang="C++">
 
#include <iostream>
 
using namespace std;
 
  
const int maxN = 1000;
+
* Ak je veľkosť dvojrozmerného poľa vopred známa konštanta, môžeme ho vytvoriť veľmi jednoducho
 
+
** napr. <tt>int a[2][5]</tt> vytvorí tabuľku s dvomi riadkami a piatimi stĺpcami
int main(void) {
+
** <tt>a[i][j]</tt> je potom prvok na riadku <tt>i</tt> a stĺpci <tt>j</tt>  
    int a[maxN];
+
** Rozmery poľa musíme uviesť aj ak pole posielame do funkcie, napr. <tt>void vypis(int a[2][5])</tt>
    int N;
+
* Tento spôsob však nebudeme ďalej používať, lebo väčšinou chceme rozmery prispôsobiť potrebám daného vstupu
    cout << "Zadaj pocet cisel: ";
 
    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>
 
 
 
== Deklarácie so smerníkmi a poľami ==
 
 
 
Na dnešnej prednáške budeme kombinovať smerníky a polia &ndash; budeme tak písať výrazy obsahujúce súčasne operátor <tt>[ ]</tt> pre prístup k danému prvku poľa a dereferenčný operátor <tt>*</tt> pre smerníky. Na správnu interpretáciu takýchto výrazov je potrebné vedieť nasledujúce:
 
* ''Operátor <tt>[ ]</tt> má vyššiu prioritu ako <tt>*</tt>''. Napríklad zápis <tt>*a[10]</tt> teda treba chápať ako <tt>*(a[10])</tt>. To znamená, že vezmeme pole <tt>a</tt>, pozrieme sa na jeho desiaty prvok a následne na tento desiaty prvok aplikujeme derefernciu. Prvky poľa <tt>a</tt> sú teda (v prípade, že robíme niečo zmysluplné) smerníky. Naopak <tt>(*p)[10]</tt> znamená nasledovné: vezmeme <tt>p</tt>, aplikujeme dereferenciu (<tt>p</tt> je teda smerník), ktorej výsledkom je pole a pozrieme sa na desiaty prvok tohto poľa.
 
* ''Operátor <tt>[ ]</tt> je zľava asociatívny a operátor <tt>*</tt> je sprava asociatívny''. To znamená, že napríklad <tt>a[2][3]</tt> je to isté ako <tt>(a[2])[3]</tt> a <tt>**p</tt> je to isté ako <tt>*(*p)</tt>.
 
 
 
Napríklad deklaráciu
 
<syntaxhighlight lang="C++">
 
int *a[10]; // t. j. *(a[10])
 
</syntaxhighlight>
 
teraz treba chápať takto: ak vezmeme <tt>a</tt>, pozrieme sa na niektorý z desiatich prvkov tohto poľa a nakoniec aplikujeme dereferenciu, dostaneme hodnotu typu <tt>int</tt>. Zadeklarovali sme teda desaťprvkové pole smerníkov na <tt>int</tt>. Rovnako možno intepretovať aj nasledujúce deklarácie:
 
<syntaxhighlight lang="C++">
 
int **a;        // a je smernik na smernik na int; kazde pole smernikov na int tak mozno priradit do a (ale nie opacne)
 
int (*a)[10];    // a je smernik na pole desiatich celych cisel
 
int *(*(a[10])); // a je desatprvkove pole smernikov na smerniky na int
 
int **a[10];    // to iste, ako na predchadzajucom riadku
 
</syntaxhighlight>
 
 
 
== Dvojrozmerné polia ==
 
 
 
Doposiaľ sme pracovali iba s jednorozmernými poľami. Často je však potrebné pracovať s viacrozmernými údajmi &ndash; tými sú napríklad tabuľky, matice, atď. Na tejto prednáške sa zameriame na dvojrozmerný prípad, potreba ktorého vyvstáva najčastejšie.
 
 
 
Najjednoduchší &ndash; hoci väčšinou nepraktický &ndash; spôsob práce s takýmito údajmi poskytuje priamo C++ v podobe takzvaných ''viacrozmerných polí''. Prácu s ''dvojrozmernými poľami'' si teraz v stručnosti ukážeme. Ide o jednoduché rozšírenie jednorozmerných polí &ndash; akurát namiesto <tt>i</tt>-teho prvku <tt>a[i]</tt> pristupujeme k prvkom <tt>a[i][j]</tt> v <tt>i</tt>-tom riadku a <tt>j</tt>-tom stĺpci dvojrozmerného poľa.
 
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 90: Riadok 38:
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
     /* Vytvorime pole s dvoma riadkami a piatimi stlpcami a rovno ho aj inicializujeme: */  
+
     /* Vytvorime pole s dvoma riadkami a piatimi stlpcami  
 +
    * a rovno ho aj inicializujeme: */  
 
     int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};  
 
     int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};  
  
Riadok 101: Riadok 50:
 
         cout << endl;
 
         cout << endl;
 
     }
 
     }
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>   
 
</syntaxhighlight>   
  
Dvojrozmerné pole o <tt>m</tt> riadkoch a <tt>n</tt> stĺpcoch sa v pamäti reprezentuje ako jediný súvislý úsek, v ktorom idú za sebou jednotlivé riadky reprezentované ako jednorozmerné polia. To je znázornené na nasledujúcom obrázku.
 
 
[[Súbor:Pamat8.png]]
 
 
Z tejto reprezentácie ale vyplýva, že typ dvojrozmerného poľa je inherentne previazaný s dĺžkou jednotlivých riadkov (t. j. s počtom stĺpcov). To sa ukazuje byť nepraktické napríklad pri predávaní dvojrozmerných polí ako parametrov pre funkcie, v hlavičkách ktorých je nutné tento počet stĺpcov zadať:
 
 
<syntaxhighlight lang="C++">
 
...
 
 
void f1(int a[2][5]) {
 
    ...
 
}
 
 
void f2(int a[][5]) {
 
    ...
 
}
 
 
void f3(int (*a)[5]) {
 
    ...
 
}
 
 
void f4(int a[][]) {  // uz samotny tento zapis vyusti v chybu
 
    ...
 
}
 
 
int main(void) {
 
    int a[2][5];
 
   
 
    ...
 
 
 
    f1(a); // OK
 
    f2(a); // OK
 
    f3(a); // OK
 
    f4(a); // nefunguje
 
 
    ...
 
}
 
</syntaxhighlight>
 
 
Budeme preto hľadať pohodlnejší spôsob práce s dvojrozmernými údajmi.
 
  
== Polia smerníkov ==
+
== Dvojrozmerné pole pomocou poľa smerníkov ==
  
Omnoho pohodlnejšou alternatívou k dvojrozmerným poliam ''polia smerníkov''. Dvojrozmerné dáta tu uchovávame prostredníctvom poľa, <tt>i</tt>-ty prvok ktorého je smerníkom ukazujúcim na prvý &ndash; t. j. vlastne nultý &ndash; prvok <tt>i</tt>-teho riadku. Pre každý riadok potom môžeme naalokovať samostatné pole pre jeho prvky. Má to okrem iného tú výhodu, že jednotlivé riadky nemusia byť rovnako dlhé. Zatiaľ si však len ukážme spôsob, ako vytvoriť pole smerníkov reprezentujúce obdĺžnikovú tabuľku typu <tt>m</tt> krát <tt>n</tt>; to bude v pamäti reprezentované podobne, ako na nasledujúcom obrázku.
+
* Omnoho flexibilnejšou alternatívou sú polia smerníkov.  
 +
* Každý riadok tabuľky bude jedno dynamicky alokované pole a smerník na jeho začiatok si uložíme do poľa smerníkov
 +
* Tabuľka s <tt>m</tt> riadkami a <tt>n</tt> slpcami bude v pamäti uložená nejako takto:
  
 
[[Súbor:Pamat9.png]]
 
[[Súbor:Pamat9.png]]
  
Nasledujúci program vytvorí pole smerníkov, ktoré reprezentuje obdĺžnikovú tabuľku o <tt>m</tt> krát <tt>n</tt> celých číslach, načíta do nej prvky zo vstupu a nakoniec na výstup vypíše aritmetické priemery hodnôt v jednotlivých jej stĺpcoch.
+
Nasledujúci program načíta rozmery dvojrotmernej tabuľky, potom jej prvky a spočíta priemer čísel v každom stĺpci.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 159: Riadok 68:
 
using namespace std;
 
using namespace std;
  
const int max_m = 200;
+
int main() {
 
+
     int m, n;
int main(void) {
 
     int m,n;
 
    int *a[max_m];
 
 
     cout << "Zadaj pocet riadkov: ";
 
     cout << "Zadaj pocet riadkov: ";
 
     cin >> m;
 
     cin >> m;
Riadok 169: Riadok 75:
 
     cin >> n;
 
     cin >> n;
 
      
 
      
     /* Alokuj jednotlive riadky: */
+
     /* Alokuj pole smernikov na riadky: */
    for (int i = 0; i <= m-1; i++) {
 
        a[i] = new int[n];          // a[i] je smernik na i-ty riadok
 
    }
 
   
 
    /* Nacitanie prvkov tabulky: */
 
    cout << "Zadaj cisla tabulky:" << endl;
 
    for (int i = 0; i <= m-1; i++) {
 
        for (int j = 0; j <= n-1; j++) {
 
            cin >> a[i][j];          // nacitaj j-ty prvok i-teho riadku
 
        }
 
    }
 
   
 
    /* Spocitaj a vypis priemery hodnot v jednotlivych stlpcoch: */
 
    for (int j = 0; j <= n-1; j++) {
 
        int sum = 0;
 
        for (int i = 0; i <= m-1; i++) {
 
            sum += a[i][j];
 
        }
 
        cout << "Priemer hodnot v stlpci " << j + 1 << " je " << (sum * 1.0)/m << endl;
 
    }
 
 
 
    /* Uvolnenie pamate: */
 
    for (int i = 0; i <= m-1; i++) {
 
        delete[] a[i];
 
    }
 
 
 
    return 0;
 
}
 
</syntaxhighlight>
 
 
 
== Dynamicky alokované polia smerníkov ==
 
 
 
Aj samotné polia smerníkov je možné alokovať dynamicky, čo umožňuje počas behu nastaviť nielen veľkosť jednotlivých riadkov, ale aj ich počet. Príklad s priemermi jednotlivých stĺpcov tak vieme prepísať napríklad nasledovne:
 
 
 
<syntaxhighlight lang="C++">
 
#include <iostream>
 
using namespace std;
 
 
 
int main(void) {
 
    int m,n;
 
 
     int **a;
 
     int **a;
    cout << "Zadaj pocet riadkov: ";
 
    cin >> m;
 
    cout << "Zadaj pocet stlpcov: ";
 
    cin >> n;
 
   
 
    /* Alokuj pole smernikov na riadky: */
 
 
     a = new int *[m];
 
     a = new int *[m];
 
      
 
      
 
     /* Alokuj jednotlive riadky: */
 
     /* Alokuj jednotlive riadky: */
 
     for (int i = 0; i <= m-1; i++) {
 
     for (int i = 0; i <= m-1; i++) {
         a[i] = new int[n];          // a[i] je smernik na i-ty riadok
+
         // a[i] je smernik na i-ty riadok  
 +
        a[i] = new int[n];         
 
     }
 
     }
 
      
 
      
 
     /* Nacitanie prvkov tabulky: */
 
     /* Nacitanie prvkov tabulky: */
 
     cout << "Zadaj cisla tabulky:" << endl;
 
     cout << "Zadaj cisla tabulky:" << endl;
     for (int i = 0; i <= m-1; i++) {
+
     for (int i = 0; i < m; i++) {
         for (int j = 0; j <= n-1; j++) {
+
         for (int j = 0; j < n; j++) {
             cin >> a[i][j];          // nacitaj j-ty prvok i-teho riadku
+
            // nacitaj j-ty prvok i-teho riadku
 +
             cin >> a[i][j];           
 
         }
 
         }
 
     }
 
     }
 
      
 
      
     /* Spocitaj a vypis priemery hodnot v jednotlivych stlpcoch: */
+
     /* Spocitaj a vypis priemery jednotlivych stlpcov: */
     for (int j = 0; j <= n-1; j++) {
+
     for (int j = 0; j < n; j++) {
 
         int sum = 0;
 
         int sum = 0;
         for (int i = 0; i <= m-1; i++) {
+
         for (int i = 0; i < m; i++) {
 
             sum += a[i][j];
 
             sum += a[i][j];
 
         }
 
         }
         cout << "Priemer hodnot v stlpci " << j + 1 << " je " << (sum * 1.0)/m << endl;
+
         cout << "Priemer hodnot v stlpci " << j  
 +
            << " je " << (sum * 1.0)/m << endl;
 
     }  
 
     }  
 
      
 
      
Riadok 246: Riadok 109:
 
     }
 
     }
 
     delete[] a;
 
     delete[] a;
   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Príklad: výšková mapa ===
+
Cvičenie: ako by ste spočítali priemery stĺpcov vstupnej tabuľky s využitím iba jednorozmerného poľa? (Celú tabuľku teda nechceme ukladať.)
 +
 
 +
== Príklad: výšková mapa ==
  
 
Pokračujme ukážkou o niečo väčšieho programu využívajúceho dynamicky alokované polia smerníkov. Ten bude v obdĺžnikovej tabuľke celých čísel uchovávať výškovú mapu nejakého územia, v ktorom nadmorská výška nadobúda hodnoty medzi <tt>0</tt> a <tt>2000</tt> metrami nad morom.  
 
Pokračujme ukážkou o niečo väčšieho programu využívajúceho dynamicky alokované polia smerníkov. Ten bude v obdĺžnikovej tabuľke celých čísel uchovávať výškovú mapu nejakého územia, v ktorom nadmorská výška nadobúda hodnoty medzi <tt>0</tt> a <tt>2000</tt> metrami nad morom.  
Riadok 477: Riadok 340:
 
         cout << argv[i] << endl;
 
         cout << argv[i] << endl;
 
     }
 
     }
 +
    return 0;
 +
}
 +
</syntaxhighlight>
 +
 +
 +
== Deklarácie so smerníkmi a poľami ==
 +
 +
Na dnešnej prednáške budeme kombinovať smerníky a polia, vytvoríme napríklad pole smerníkov. Môže sa vám hodiť nasledujúce:
 +
* ''Operátor <tt>[ ]</tt> má vyššiu prioritu ako <tt>*</tt>''. Napríklad zápis <tt>*a[10]</tt> teda treba chápať ako <tt>*(a[10])</tt>. To znamená, že vezmeme pole <tt>a</tt>, pozrieme sa na jeho desiaty prvok a následne na tento desiaty prvok aplikujeme dereferenciu. Prvky poľa <tt>a</tt> sú teda (v prípade, že robíme niečo zmysluplné) smerníky. Naopak <tt>(*p)[10]</tt> znamená nasledovné: vezmeme <tt>p</tt>, aplikujeme dereferenciu (<tt>p</tt> je teda smerník), ktorej výsledkom je pole a pozrieme sa na desiaty prvok tohto poľa.
 +
* ''Operátor <tt>[ ]</tt> je zľava asociatívny a operátor <tt>*</tt> je sprava asociatívny''. To znamená, že napríklad <tt>a[2][3]</tt> je to isté ako <tt>(a[2])[3]</tt> a <tt>**p</tt> je to isté ako <tt>*(*p)</tt>.
 +
 +
Napríklad deklaráciu
 +
<syntaxhighlight lang="C++">
 +
int *a[10]; // t. j. *(a[10])
 +
</syntaxhighlight>
 +
teraz treba chápať takto: ak vezmeme <tt>a</tt>, pozrieme sa na niektorý z desiatich prvkov tohto poľa a nakoniec aplikujeme dereferenciu, dostaneme hodnotu typu <tt>int</tt>. Zadeklarovali sme teda desaťprvkové pole smerníkov na <tt>int</tt>. Rovnako možno intepretovať aj nasledujúce deklarácie:
 +
<syntaxhighlight lang="C++">
 +
int **a;        // a je smernik na smernik na int; kazde pole smernikov na int tak mozno priradit do a (ale nie opacne)
 +
int (*a)[10];    // a je smernik na pole desiatich celych cisel
 +
int *(*(a[10])); // a je desatprvkove pole smernikov na smerniky na int
 +
int **a[10];    // to iste, ako na predchadzajucom riadku
 +
</syntaxhighlight>
 +
 +
== Smerníková aritmetika ==
 +
 +
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
 +
<syntaxhighlight lang="C++">
 +
T *p;
 +
T *p1;
 +
T *p2;
 +
</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:
 +
<syntaxhighlight lang="C++">
 +
#include <iostream>
 +
using namespace std;
 +
 +
const int maxN = 1000;
 +
 +
int main(void) {
 +
    int a[maxN];
 +
    int N;
 +
    cout << "Zadaj pocet cisel: ";
 +
    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;
 
     return 0;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>

Verzia zo dňa a času 10:05, 30. október 2021

Opakovanie smerníkov

Smerníky na jednoduché premenné:

int n = 7;         // premenná typu int 
int *p = NULL;     // smerník na premennú typu int 
p = &n;            // p ukazuje na n 
*p = 8;            // v premennej n je teraz 8 
n = (*p)+1;        // v premennej n je teraz 9

Smerníky a polia, alokovanie poľa:

int a[3];
int *b = a; // a,b sú teraz takmer rovnocenné premenné 
*b = 3;
b[1] = 4;
a[2] = 5;   // v poli sú teraz čísla 3,4,5 
b = new int[a[1]];  // b teraz ukazuje na nové pole dĺžky 4 
delete[] b;         // uvoľníme pamäť alokovanú pre nové pole

Dvojrozmerné polia

  • Doteraz sme stále pracovali s jednorozmerným poľom, čo však ak potrebujeme dvojrozmerné pole, maticu?
    • Napríklad na matice z algebry, alebo na dvojrozmerné tabuľky napr. body študentov z domácich úloh
  • Podobne môžeme potrebovať aj polia väčších rozmerov, pracuje sa s nimi analogicky ako s dvojrozmenrnými

Dvojrozmerné polia s konštantnou veľkosťou

  • Ak je veľkosť dvojrozmerného poľa vopred známa konštanta, môžeme ho vytvoriť veľmi jednoducho
    • napr. int a[2][5] vytvorí tabuľku s dvomi riadkami a piatimi stĺpcami
    • a[i][j] je potom prvok na riadku i a stĺpci j
    • Rozmery poľa musíme uviesť aj ak pole posielame do funkcie, napr. void vypis(int a[2][5])
  • Tento spôsob však nebudeme ďalej používať, lebo väčšinou chceme rozmery prispôsobiť potrebám daného vstupu
#include <iostream>
using namespace std;

int main() {
    /* Vytvorime pole s dvoma riadkami a piatimi stlpcami 
     * a rovno ho aj inicializujeme: */ 
    int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}}; 

    /* Vypiseme pole ako tabulku: */
    for (int i = 0; i <= 1; i++) {
        for (int j = 0; j <= 4; j++) {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
}


Dvojrozmerné pole pomocou poľa smerníkov

  • Omnoho flexibilnejšou alternatívou sú polia smerníkov.
  • Každý riadok tabuľky bude jedno dynamicky alokované pole a smerník na jeho začiatok si uložíme do poľa smerníkov
  • Tabuľka s m riadkami a n slpcami bude v pamäti uložená nejako takto:

Pamat9.png

Nasledujúci program načíta rozmery dvojrotmernej tabuľky, potom jej prvky a spočíta priemer čísel v každom stĺpci.

#include <iostream>
using namespace std;

int main() {
    int m, n;
    cout << "Zadaj pocet riadkov: ";
    cin >> m;
    cout << "Zadaj pocet stlpcov: ";
    cin >> n;
    
    /* Alokuj pole smernikov na riadky: */
    int **a;
    a = new int *[m];
    
    /* Alokuj jednotlive riadky: */
    for (int i = 0; i <= m-1; i++) {
        // a[i] je smernik na i-ty riadok 
        a[i] = new int[n];           
    }
    
    /* Nacitanie prvkov tabulky: */
    cout << "Zadaj cisla tabulky:" << endl;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            // nacitaj j-ty prvok i-teho riadku 
            cin >> a[i][j];          
        }
    }
    
    /* Spocitaj a vypis priemery jednotlivych stlpcov: */
    for (int j = 0; j < n; j++) {
        int sum = 0;
        for (int i = 0; i < m; i++) {
            sum += a[i][j];
        }
        cout << "Priemer hodnot v stlpci " << j 
             << " je " << (sum * 1.0)/m << endl;
    } 
    
    /* Uvolnenie pamate: */
    for (int i = 0; i <= m-1; i++) {
        delete[] a[i];
    }
    delete[] a;
}

Cvičenie: ako by ste spočítali priemery stĺpcov vstupnej tabuľky s využitím iba jednorozmerného poľa? (Celú tabuľku teda nechceme ukladať.)

Príklad: výšková mapa

Pokračujme ukážkou o niečo väčšieho programu využívajúceho dynamicky alokované polia smerníkov. Ten bude v obdĺžnikovej tabuľke celých čísel uchovávať výškovú mapu nejakého územia, v ktorom nadmorská výška nadobúda hodnoty medzi 0 a 2000 metrami nad morom.

Program na vstupe najprv dostane dvojicu prirodzených čísel m a n. Výškovou mapou potom bude obdĺžnik pozostávajúci z m krát n štvorčekov, kde každý zo štvorčekov bude mať danú nejakú nadmorskú výšku od 0 po 2000 metrov nad morom (nadmorská výška 0 znamená more a kladná nadmorská výška znamená pevninu). Následne program postupne prečíta zo vstupu nadmorské výšky všetkých štvorčekov.

Takto zadanú mapu program vykreslí pomocou knižnice SVGdraw, pričom každý štvorček dostane určitú farbu podľa svojej nadmorskej výšky. Následne zavolá funkciu najvyssiVrch, ktorá nájde najvyšší bod (resp. jeden z najvyšších bodov) vykresľovaného územia a v mape ho zvýrazní rámikom.

Príklad vstupu a výstupu:

Mapa.png
22 11
 0 0 0 0 0 0 0 0 0 0 0
 0 20 40 60 80 100 120 140 120 0 0
 0 40 80 120 160 200 240 280 190 100 0
 0 60 120 180 240 300 360 420 260 100 0
 0 80 160 240 320 400 480 560 260 100 0
 0 100 200 300 400 500 600 700 330 100 0
 0 120 240 360 480 600 720 840 400 100 0
 0 140 280 420 560 700 840 980 470 100 0
 0 160 320 480 640 800 960 700 200 0 0
 0 180 360 540 720 900 700 500 0 0 0
 0 200 400 600 800 1000 1200 1400 680 100 0
 0 220 440 660 880 1100 1320 1540 750 100 0
 0 240 480 720 960 1200 1440 1680 820 100 0
 0 260 520 780 1040 1300 1560 1820 1200 400 0
 0 280 560 840 1120 1400 1680 1960 1500 600 0
 0 240 480 720 960 1200 1440 1680 1000 400 0
 0 200 400 600 800 1000 1200 1400 680 100 0
 0 160 320 480 640 800 960 1120 540 100 0
 0 120 240 360 480 600 720 840 400 100 0
 0 80 160 240 320 400 480 560 260 100 0
 0 40 80 120 160 200 240 280 120 0 0
 0 0 0 0 0 0 0 0 0 0 0

Samotný program:

#include <iostream>
#include "SVGdraw.h"
using namespace std;

/* velkost stvorceka mapy v pixeloch */
const int stvorcek = 15;

int **vytvorMapu(int m, int n) {
    /* Vytvori a vrati na vystupe mapu (obdlznikovu tabulku) s m riadkami a n stlpcami. */
    int **a;
    a = new int *[m];
    for (int i = 0; i <= m-1; i++) {
        a[i] = new int[n];
    }
    return a;
}

void zmazMapu(int m, int n, int **a) {
    /* Uvolni z pamate mapu s m riadkami a n stlpcami.
       Parameter n je nadbytocny, ale mohol by sa zist, keby napriklad 
       bolo treba z pamate uvolnovat aj jednotlive prvky matice a. */
    for (int i = 0; i <= m-1; i++) {
        delete[] a[i];
    }
    delete[] a;
}

void nacitajMapu(int m, int n, int **a) {
    /* Nacita hodnoty (nadmorske vysky) do uz vytvorenej mapy velkosti m krat n. */
    for (int i = 0; i <= m-1; i++) {
        for (int j = 0; j <= n-1; j++) {
            cin >> a[i][j];
        }
    }
}

void farba(SVGdraw &drawing, int r, int g, int b) {
    /* Nastavi farbu ciary aj vyplne na dane hodnoty. */
    drawing.setLineColor(r, g, b);
    drawing.setFillColor(r, g, b);
}

void vykresliMapu(int m, int n, int **a, SVGdraw &drawing) {
    /* Ofarbi jednotlive stvorceky mapy podla ich nadmorskej vysky:
     * modra -- more (nadmorska vyska 0)
     * zelena -- niziny (nadmorska vyska 1,...,200)
     * hneda -- "pohoria" (nadmorska vyska 200,...,2000) */
    for (int i = 0; i <= m-1; i++) {
        for (int j = 0; j <= n-1; j++) {
            /* nastavenie farby podla hodnoty */
            if (a[i][j] == 0) {
                farba(drawing, 0, 0, 255);
            } else if (a[i][j] <= 200) {
                double x = a[i][j] / 200.0;
                farba(drawing, x * 255, 127 + x * 127, 0);
            } else {
                double x = (a[i][j] - 200) / 1800.0;
                farba(drawing, 255 - x * 150, 255 - x * 200, 0);
            }
            /* Vykreslenie stvorceka; POZOR: vymena suradnic */
            drawing.drawRectangle(j * stvorcek, i * stvorcek, stvorcek, stvorcek);
        }
    }
}

void najvyssiVrch(int m, int n, int **a, int &riadok, int &stlpec) {
    /* Najde v mape a o rozmeroch m krat n stvorcek s najvyssou nadmorskou vyskou
       a jeho suradnice ulozi do premennych riadok resp. stlpec. */
    riadok = 0;
    stlpec = 0;
    for (int i = 0; i <= m-1; i++) {
        for (int j = 0; j <= n-1; j++) {
            if (a[i][j] > a[riadok][stlpec]) {
                riadok = i;
                stlpec = j;
            }
        }
    }
}

int main(void) {
    /* nacitaj rozmery matice */
    int m, n;
    cin >> m >> n;

    /* vytvor a nacitaj maticu */
    int **a = vytvorMapu(m, n);

    nacitajMapu(m, n, a);

    /* zobraz maticu */
    SVGdraw drawing(n * stvorcek, m * stvorcek, "mapa.svg"); // POZOR: vymena suradnic
    vykresliMapu(m, n, a, drawing);

    /* najdi najvyssi vrch a zvyrazni ho stvorcekom */
    int riadok, stlpec;
    najvyssiVrch(m, n, a, riadok, stlpec);

    drawing.setLineColor("black");
    drawing.setLineWidth(3);
    drawing.setNoFill();
    drawing.drawRectangle(stlpec * stvorcek, riadok * stvorcek, stvorcek, stvorcek); // POZOR: vymena suradnic

    /* ukonci vykreslovanie */
    drawing.finish();

    /* uvolni pamat matice */
    zmazMapu(m, n, a);
    
    return 0;
}

Polia reťazcov

Každý reťazec je pole znakov, ktoré možno interpretovať aj ako smerník na char. Pole reťazcov teda možno implementovať ako pole smerníkov na char. Keďže sa vo väčšine aplikácií môžu vyskytovať reťazce rôznych dĺžok, ukazuje sa tu byť užitočná vlastnosť polí smerníkov spomínaná vyššie – jednotlivé ich riadky môžu mať rôzne dĺžky.

Nasledujúci jednoduchý program je ukážkou použitia takto implementovaných polí reťazcov. Zo vstupu postupne načítava riadky, až kým je zadaný prázdny riadok. Tie postupne ukladá do poľa. Na záver sa všetky tieto reťazce vypíšu na výstup, oddelené medzerami.

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

const int maxN = 1000;
const int maxRiadok = 1000;

int main(void) {
    char *a[maxN];
    char riadok[maxRiadok];
    int N = 0;
    while (N <= maxN-1) {                 
        cin.getline(riadok, maxRiadok);      // nacitame jeden riadok zo vstupu
        if (strcmp(riadok, "") == 0) {       // v pripade prazdneho riadku ukoncime nacitavanie
            break;
        }
        /* 
           Alokujeme pamat pre N-ty retazec pola a.
           (Musi byt o 1 vacsia, nez jeho dlzka -- dovodom je znak \0 na konci).
        */
        a[N] = new char[strlen(riadok) + 1]; 
        strcpy(a[N], riadok);
        N++;
    }
    // Vypiseme jednotlive riadky oddelene medzerami:
    for (int i = 0; i <= N-1; i++) {
        cout << a[i] << " ";
    }
    // Uvolnime pamat:
    for (int i = 0; i <= N-1; i++) {
        delete[] a[i];
    }

    return 0;
}

Cvičenie: prerobte tento program tak, aby namiesto poľa a fixnej veľkosti maxN používal dynamické pole.

Zadávanie argumentov programu z príkazového riadku

Polia reťazcov umožňujú okrem iného aj napísať program, ktorý dostane a spracuje jeden alebo viacero argumentov z príkazového riadku a na základe nich prípadne „upraví svoje správanie”. Príkladom programu využívajúcim túto funkcionalitu je aj samotný kompilátor g++. Jeho najjednoduchšie volanie

g++ program.cpp

obsahuje okrem názvu programu g++ aj argument program.cpp – ten dáva kompilátoru informáciu o tom, ktorý zdrojový súbor má kompilovať.

Na písanie programov umožňujúcich spracovanie takýchto argumentov je potrebné využiť „jemne pokročilejšiu” verziu funkcie main s hlavičkou

int main(int argc, char **argv)

– tú automaticky generujú viaceré pokročilejšie textové editory alebo IDE (napríklad NetBeans). Význam parametrov argc a argv je nasledovný:

  • argv je pole reťazcov (resp. pole smerníkov na char) a argc je počet reťazcov v tomto poli.
  • Reťazec argv[0] je vždy názov programu.
  • Reťazce argv[1],...,argv[argc-1] sú jednotlivé argumenty.

Nasledujúci jednoduchý program postupne vypíše všetky argumenty, ktoré dostal z príkazového riadku.

#include <iostream>
using namespace std;

int main(int argc, char **argv) {
    for (int i = 0; i <= argc-1; i++) {
        cout << argv[i] << endl;
    }
    return 0;
}


Deklarácie so smerníkmi a poľami

Na dnešnej prednáške budeme kombinovať smerníky a polia, vytvoríme napríklad pole smerníkov. Môže sa vám hodiť nasledujúce:

  • Operátor [ ] má vyššiu prioritu ako *. Napríklad zápis *a[10] teda treba chápať ako *(a[10]). To znamená, že vezmeme pole a, pozrieme sa na jeho desiaty prvok a následne na tento desiaty prvok aplikujeme dereferenciu. Prvky poľa a sú teda (v prípade, že robíme niečo zmysluplné) smerníky. Naopak (*p)[10] znamená nasledovné: vezmeme p, aplikujeme dereferenciu (p je teda smerník), ktorej výsledkom je pole a pozrieme sa na desiaty prvok tohto poľa.
  • Operátor [ ] je zľava asociatívny a operátor * je sprava asociatívny. To znamená, že napríklad a[2][3] je to isté ako (a[2])[3] a **p je to isté ako *(*p).

Napríklad deklaráciu

int *a[10]; // t. j. *(a[10])

teraz treba chápať takto: ak vezmeme a, pozrieme sa na niektorý z desiatich prvkov tohto poľa a nakoniec aplikujeme dereferenciu, dostaneme hodnotu typu int. Zadeklarovali sme teda desaťprvkové pole smerníkov na int. Rovnako možno intepretovať aj nasledujúce deklarácie:

int **a;         // a je smernik na smernik na int; kazde pole smernikov na int tak mozno priradit do a (ale nie opacne)
int (*a)[10];    // a je smernik na pole desiatich celych cisel
int *(*(a[10])); // a je desatprvkove pole smernikov na smerniky na int
int **a[10];     // to iste, ako na predchadzajucom riadku

Smerníková aritmetika

Na smerníkoch možno vykonávať určité operácie, súhrn ktorých býva honosne nazývaný smerníkovou aritmetikou. Nech p, p1, p2 sú smerníky definované ako

T *p;
T *p1;
T *p2;

kde T označuje nejaký typ. Nech n je typu int. Potom:

  • p + n označuje smerník na n-tý pamäťový úsek (postačujúci práve na uchovanie hodnoty typu T) za adresou p.
    • Napríklad p+n je to isté ako &p[n] a *(p+n) je to isté ako p[n].
    • p++ je skratkou pre p = p+1, ...
  • Operátor [ ] je teda nadbytočný – p[n] je len skratkou pre často používaný výraz *(p+n).
  • p - n označuje smerník na n-tý pamäťový úsek (postačujúci práve na uchovanie hodnoty typu T) pred adresou p.
  • p1 - p2 je celé číslo k také, že p1 == p2 + k. Zmysluplný výsledok možno očakávať len vtedy, keď p1 a p2 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 ==, <, >, <=, >=, !=. Výsledok je zmysluplný opäť len vtedy, keď p1 a p2 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:

#include <iostream>
using namespace std;

const int maxN = 1000;

int main(void) {
    int a[maxN];
    int N;
    cout << "Zadaj pocet cisel: ";
    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;
}