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 5: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(92 medziľahlých úprav od 4 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
== Štatistika z N čísel ==
+
== Oznamy ==
 +
* Na piatkové cvičenia treba prísť, ak ste v utorok na cvičení nezískali aspoň 5 bodov.
 +
** Na testovači máte v záložke Body, položke Piatkove_cvicenie uvedené predbežné body z utorkového cvičenia a napísané, či je alebo nie je pre vás piatkové cvičenie povinné.
 +
** Ak je pre vás cvičenie povinné a neprídete, dostanete -1 bod.
 +
** Môžete prísť aj ak je pre vás cvičenie nepovinné, ale máte nejaké otázky.
 +
* Ak by ste chceli zmeniť skupinu na utorkové cvičenia, vyplňte [https://forms.office.com/e/A1wwDiRS8P formulár]
 +
* Dnes polia, budúci pondelok hlavne ďalšie príklady a algoritmy s použitím tých častí Cčka, ktoré už poznáte
  
Od užívateľa postupne načítame N čísel a chceme o nich zistiť nejaké štatistické údaje.
+
==Hľadanie chýb v programe==
 +
* Väčšina programov nefunguje na prvý krát, hľadanie chýb patrí medzi základné činnosti programátora
 +
* Podobné chyby sa často opakujú, tréningom sa ich naučíte nájsť rýchlejšie
  
=== Maximum ===
+
Ak program ani neskompiluje
 +
* Kompilátor vypíše číslo riadku s chybou, čo vám ju môže pomôcť nájsť
 +
** Občas je však chyba trochu inde, napr. niečo chýba o riadok vyššie
 +
* Pokročilejšie prostredia, ako napr. Netbeans, vám vedia ukázať polohu chyby
 +
* Ak kompilátor vypíše veľa chýb, opravte najskôr prvú, potom skompilujte znovu, ďalšie chyby môžu byť len dôsledkom prvej
 +
* Ak neviete nájsť chybu pri kompilácii, skúste zakomentovať nejaké časti programu pomocou /* */, aby ste zúžili priestor, kde chyba môže byť
 +
* Aj varovania kompilátora môžu poukazovať na chybu v programe
 +
 
 +
Ak program dáva zlé výsledky, "cyklí sa" alebo "padá"
 +
* Môžete skúsiť program znovu prečítať, či nezbadáte chybu
 +
** Pomáha [[Prednáška 3#.C3.9Aprava_a_.C4.8Ditate.C4.BEnos.C5.A5_programov|dobrá čitateľnosť programov]]
 +
* Alebo experimentami zistiť, kde sa jeho správanie prvýkrát začne odlišovať od toho, čo očakávate
 +
* To sa dá robiť spúšťaním programu po krokoch v nástroji nazvanom debugger (nachádza sa napr. v Netbeans)
 +
* Alternatíva k debuggeru je do programu pridať pomocné výpisy, ktoré vám prezradia, ktorá časť programu sa práve vykonáva a aké sú hodnoty dôležitých premenných
 +
** Po nájdení chyby treba tieto pomocné výpisy odstrániť. Pozor, aby ste tým nespravili ďalšiu chybu
 +
* Debugger alebo výpisy vám pomôžu nájsť chybu iba vtedy, ak máte predstavu o tom, ako by program mal fungovať a hľadáte, kde sa od nej skutočné správanie líši
 +
* Pomáha vyrobiť si čo najmenší vstup, kde je zlý výsledok
 +
 
 +
== Záznam typu struct ==
 +
 
 +
V príkladoch s obvodom trojuholníka a stredom úsečky sme funkciám posielali veľa parametrov (súradnice x a y)
 +
<syntaxhighlight lang="C++">
 +
void stred(double x1, double y1, double x2, double y2, double &xm, double &ym) {
 +
    xm = (x1 + x2) / 2;
 +
    ym = (y1 + y2) / 2;
 +
}
 +
</syntaxhighlight>
 +
 
 +
Program bude krajší, ak si údaje o jednom bode spojíme do jedného záznamu
 +
<syntaxhighlight lang="C++">
 +
struct bod {
 +
    double x, y;
 +
};
 +
</syntaxhighlight>
 +
* Pomocou <tt>struct</tt> vytvoríme nový dátový typ <tt>bod</tt>, ktorý má zložky <tt>x</tt> a <tt>y</tt>
 +
* V jednom struct-e môžu byť aj položky rôznych typov, napr.
 +
<syntaxhighlight lang="C++">
 +
struct bod {
 +
  double x,y;
 +
  int id;
 +
  bool visible;
 +
};</syntaxhighlight>
 +
* Môžeme vytvárať premenné typu <tt>bod</tt>, napr. <tt>bod a, b;</tt>
 +
* K položkám bodu pristupujeme pomocou bodky, napr. <tt>a.x = 4.0; </tt>
 +
* Do funkcií body posielame radšej referenciou, aby sa zbytočne nekopírovalo veľa hodnôt
 +
 
 +
Nasledujúci program načíta súradnice troch bodov, spočíta obvod trojuholníka a stredy všetkých troch strán.
 +
<syntaxhighlight lang="C++">
 +
#include <iostream>
 +
#include <cmath>
 +
using namespace std;
 +
 
 +
struct bod {
 +
    double x, y;  // suradnice bodu v rovine
 +
};
 +
 
 +
double dlzka(bod &bod1, bod &bod2) {
 +
    // funkcia vrati dlzku usecky z bodu 1 do bodu 2
 +
    double dx = bod1.x - bod2.x;
 +
    double dy = bod1.y - bod2.y;
 +
    return sqrt(dx * dx + dy * dy);
 +
}
 +
 
 +
void stred(bod &bod1, bod &bod2, bod &stred) {
 +
    // funkcia do bodu stred spocita stred usecky z bodu 1 do bodu 2
 +
    stred.x = (bod1.x + bod2.x) / 2;
 +
    stred.y = (bod1.y + bod2.y) / 2;
 +
}
 +
 
 +
void vypisBod(bod &b) {
 +
    // funkcia vypise suradnice bodu v zatvorke a koniec riadku
 +
    cout << "(" << b.x << "," << b.y << ")" << endl;
 +
}
 +
 
 +
int main() {
 +
    // nacitame suradnice vrcholov trojuholnika
 +
    bod A, B, C;
 +
    cout << "Zadaj suradnice vrcholu A oddelene medzerou: ";
 +
    cin >> A.x >> A.y;
 +
    cout << "Zadaj suradnice vrcholu B oddelene medzerou: ";
 +
    cin >> B.x >> B.y;
 +
    cout << "Zadaj suradnice vrcholu C oddelene medzerou: ";
 +
    cin >> C.x >> C.y;
 +
    // spocitame dlzky stran
 +
    double da = dlzka(B, C);
 +
    double db = dlzka(A, C);
 +
    double dc = dlzka(A, B);
 +
    // vypiseme obvod
 +
    cout << "Obvod trojuholnika ABC: " << da + db + dc << endl;
  
Zrejme stačí čítať čísla postupne a pamätať si zatiaľ najväčšie číslo.
+
    // spocitame stredy stran
 +
    bod stredAB;
 +
    stred(A, B, stredAB);
 +
    bod stredAC;
 +
    stred(A, C, stredAC);
 +
    bod stredBC;
 +
    stred(B, C, stredBC);
  
 +
    // vypiseme stredy stran
 +
    cout << "Stred strany AB: ";
 +
    vypisBod(stredAB);
 +
    cout << "Stred strany AC: ";
 +
    vypisBod(stredAC);
 +
    cout << "Stred strany BC: ";
 +
    vypisBod(stredBC);
 +
}
 +
</syntaxhighlight>
 +
 +
Príklad behu programu:
 
<pre>
 
<pre>
 +
Zadaj suradnice vrcholu A oddelene medzerou: 0 0
 +
Zadaj suradnice vrcholu B oddelene medzerou: 0 3
 +
Zadaj suradnice vrcholu C oddelene medzerou: 4 0
 +
Obvod trojuholnika ABC: 12
 +
Stred strany AB: (0,1.5)
 +
Stred strany AC: (2,0)
 +
Stred strany BC: (2,1.5)
 +
</pre>
 +
 +
== Spracovanie väčšieho množstva dát ==
 +
 +
Naše programy doteraz spracovávali len malý počet vstupných dát načítaných od užívateľa (napr. súradnice troch bodov). Často však chceme pracovať s väčším množstvom dát
 +
* Dnes si ukážeme, ako uložiť väčšie množstvo dát do poľa
 +
* Na niektoré úlohy však pole nepotrebujeme - údaje môžeme spracovávať rovno ako ich užívateľ zadáva (takéto príklady sme už videli na cvičeniach)
 +
 +
V nasledujúcich príkladoch užívateľ zadá číslo ''N'' a potom ''N'' celých čísel
 +
* Predstavme si napríklad, že učiteľ zadá body, ktoré študenti dostali na písomke (napr. celé čísla v rozsahu 0..10)
 +
* Z týchto bodov chceme spočítať nejaké štatistiky
 +
 +
=== Priemer ===
 +
<syntaxhighlight lang="C++">
 
#include <iostream>
 
#include <iostream>
#include <cstdlib>
+
using namespace std;
 +
 
 +
int main() {
 +
    int N;
 +
    cout << "Zadaj pocet cisel: ";
 +
    cin >> N;
 +
 
 +
    int sucet = 0;
 +
    cout << "Zadavaj cisla: ";
 +
    for (int i = 0; i < N; i++) {
 +
        int x;
 +
        cin >> x;
 +
        sucet += x;
 +
    }
 +
 
 +
    double priemer = sucet / (double) N;
 +
    cout << "Priemer je " << priemer << "." << endl;
 +
}
 +
</syntaxhighlight>
 +
 
 +
* Čo by sa stalo, keby sme vo výpočte priemeru vynechali <tt>(double)</tt>?
 +
 
 +
=== Maximum ===
  
 +
<syntaxhighlight lang="C++">
 +
#include <iostream>
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     int max, x, N;
 
     int max, x, N;
  
Riadok 31: Riadok 189:
 
     cout << endl << "Maximum je " << max << endl;
 
     cout << endl << "Maximum je " << max << endl;
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 
Ako ale začať? Ako nastaviť maximum na začiatok?
 
Ako ale začať? Ako nastaviť maximum na začiatok?
* Jedna možnosť je nastaviť ho na nejakú veľmi malú hodnotu, aby sa iste neskôr zmenila. Kto nám ale zaručí, že používateľ nedá všetky čísla ešte menšie?  
+
* Jedna možnosť je nastaviť ho na nejakú veľmi malú hodnotu, aby sa iste neskôr zmenila. Ale čo ak používateľ všetky čísla ešte menšie?  
* Riešením je použiť najmenšie možné číslo - ale je príliš viazané na konkrétny rozsah, nebude fungovať po zmene typu premenných.
+
* Riešením je použiť najmenšie možné číslo. Ale to je príliš viazané na konkrétny rozsah, nebude fungovať po zmene typu premenných.
* Ďalšia možnosť je si pamätať, že ešte nemáme správne nastavené maximum a po načítaní prvého čísla ho nastaviť.
+
* Ďalšia možnosť je si pamätať, že ešte nemáme správne nastavené maximum a po načítaní prvého čísla ho nastaviť alebo spracovať prvé číslo zvlášť (mimo cyklu).
  
<pre>
+
<syntaxhighlight lang="C++">
 
#include <iostream>
 
#include <iostream>
#include <cstdlib>
 
 
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     int max, x, N;
 
     int max, x, N;
  
Riadok 51: Riadok 207:
  
 
     cout << "Zadavajte cisla: ";
 
     cout << "Zadavajte cisla: ";
     cin >> x;
+
     cin >> x;  // načítanie prvého čísla
 +
    max = x;
  
    max = x;
+
     for (int i = 1; i < N; i++) { // cyklus cez N-1 ďalších čísel
     for (int i = 1; i < N; i++) {
 
 
         cin >> x;
 
         cin >> x;
 
         if (x > max) {
 
         if (x > max) {
Riadok 63: Riadok 219:
 
     cout << endl << "Maximum je " << max << endl;
 
     cout << endl << "Maximum je " << max << endl;
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 
'''Cvičenie:'''  
 
'''Cvičenie:'''  
* Čo treba v programe zmeniť, ak chceme hľadať minimum namiesto maxima?  
+
* Ako by sme program rozšírili tak, aby vedel vypísať aj koľké číslo v poradí bolo najväčšie?
* Ako by sme spočítali priemer čísel?
+
* Čo treba v programe zmeniť, ak chceme hľadať minimum namiesto maxima?
 
 
=== Výskyty čísel 0..9 ===
 
 
 
Na vstupe sú iba čísla od 0 do 9 a chceme vedieť, koľko jednotlivých čísel je. Mohli by sme to riešiť takto:
 
 
 
* Pre každú možnú hodnotu si vytvoríme jednu premennú (dokopy ich bude teda 10 - napríklad p0, p1 .. p9) na začiatku nastavenú na 0.
 
* V prípade, že prečítané číslo bolo 0 zväčšíme hodnotu p0, ak bolo 1 zväčšíme p1 ...
 
 
 
Bol by to dlhý a komplikovaný program, v ktorom sa ľahko spraví chyba. Už vôbec by nebolo praktické to robiť takto ak by hodnoty boli od 0 po milión...
 
* To, že štvrtá premenná je p4, vieme iba my ako programátori a počítač o tom nevie - nemá žiaden súvis medzi jednotlivými premennými.
 
  
Teraz ukážeme riešenie tohto príkladu pomocou poľa.
+
== Prvé použitie poľa: podpriemer / nadpriemer ==
* <tt>int p[10]</tt> vytvorí pole dĺžky 10, t.j. tabuľku s prvkami <tt>p[0], p[1],..., p[9]</tt>
 
* s každým <tt>p[i]</tt> môžeme pracovať ako s premennou typu int
 
 
 
<pre>
 
#include <iostream>
 
using namespace std;
 
 
 
int main(void) {
 
    int p[10];  // pole dlzky 10
 
    int N;
 
    cout << "Zadajte pocet cisel: ";
 
    cin >> N;
 
 
 
    for (int i = 0; i < 10; i++) p[i] = 0; // inicializácia pola p[0]=0; p[1]=0; ... p[9]=0;
 
 
 
    cout << "Zadavajte " << N << "cisel z intervalu 0-9: ";
 
    for (int i = 0; i < N; i++) {
 
        int x;
 
        cin >> x;
 
        if (x >= 0 && x < 10) p[x]++; // test, či je číslo z požadovaného rozsahu
 
    }
 
 
 
    cout << endl;
 
    for (int i = 0; i < 10; i++) {
 
        cout << "Pocet vyskytov " << i << " je " << p[i] << endl; // výpis
 
    }
 
}</pre>
 
 
 
=== Podpriemer / nadpriemer ===
 
  
 
Chceme spočítať priemer a o každom vstupnom čísle vypísať, či je nadpriemerné alebo podpriemerné.
 
Chceme spočítať priemer a o každom vstupnom čísle vypísať, či je nadpriemerné alebo podpriemerné.
 
* Priemer vieme až keď načítame všetky čísla, musíme si ich teda zapamätať
 
* Priemer vieme až keď načítame všetky čísla, musíme si ich teda zapamätať
* Keby sme vedeli dopredu, koľko ich bude vedeli by sme to urobiť podobne ako v predchádzajúcom príklade.
+
* Na to používame tabuľky, ktoré sa volajú polia.
 +
* Na začiatok pre jednoduchosť predpokladajme, že vopred vieme, že počet údajov N je napr. 20 (N teda nenačítavame)
 +
* Príkaz <tt>int a[20];</tt> vytvorí pole s 20 premennými typu int, ku ktorým pristupujeme <tt>a[0]</tt>, <tt>a[1]</tt>, ..., <tt>a[19]</tt>
 +
** pozor, <tt>a[20]</tt> neexistuje
  
<pre>
+
<syntaxhighlight lang="C++">
 
#include <iostream>
 
#include <iostream>
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     const int N = 20; // premennu N uz nebude mozne menit, ma konstantnu hodnotu 20
 
     const int N = 20; // premennu N uz nebude mozne menit, ma konstantnu hodnotu 20
     int p[N];
+
     int a[N];
 
     double sucet = 0;
 
     double sucet = 0;
  
 
     cout << "Zadavajte " << N << " cisel: ";
 
     cout << "Zadavajte " << N << " cisel: ";
 
     for (int i = 0; i < N; i++) {
 
     for (int i = 0; i < N; i++) {
         cin >> p[i];
+
         cin >> a[i];
         sucet += p[i];
+
         sucet += a[i];
 
     }
 
     }
  
 
     double priemer = sucet / N;
 
     double priemer = sucet / N;
 
     cout << "Priemer je " << priemer << "." << endl;
 
     cout << "Priemer je " << priemer << "." << endl;
     for (int i = 0; i < N; i++)
+
     for (int i = 0; i < N; i++) {
         if (p[i] > priemer) cout << p[i] << ": vacsie ako priemer." << endl;
+
         if (a[i] > priemer) {
         else if (p[i] < priemer) cout << p[i] << ": mensie ako priemer." << endl;
+
            cout << a[i] << ": vacsie ako priemer." << endl;
         else cout << p[i] << ": priemer." << endl;
+
         } else if (a[i] < priemer) {
 +
            cout << a[i] << ": mensie ako priemer." << endl;
 +
         } else {
 +
            cout << a[i] << ": priemer." << endl;
 +
        }
 +
    }
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 +
Na zamyslenie:
 
* Pozor, chyby pri zaokrúhľovaní môžu spôsobiť, že niekedy priemerné číslo bude považované za nad/podpriemerné
 
* Pozor, chyby pri zaokrúhľovaní môžu spôsobiť, že niekedy priemerné číslo bude považované za nad/podpriemerné
 
** Vedeli by ste program prerobiť tak, aby používal iba premenné typu int a nerobil žiadnu chybu v zaokrúhľovaní?
 
** Vedeli by ste program prerobiť tak, aby používal iba premenné typu int a nerobil žiadnu chybu v zaokrúhľovaní?
 
** Môže aj po takejto zmene niekedy dať zlú odpoveď?
 
** Môže aj po takejto zmene niekedy dať zlú odpoveď?
 
 
  
 
== Polia ==
 
== Polia ==
  
 
Rozsah poľa je konštantný výraz väčší ako 0. Prvky sa indexujú od 0 po počet - 1
 
Rozsah poľa je konštantný výraz väčší ako 0. Prvky sa indexujú od 0 po počet - 1
<pre>
+
<syntaxhighlight lang="C++">
 
int a[10];
 
int a[10];
 
const int N=20;
 
const int N=20;
double b[20];
+
double b[N];
</pre>
+
</syntaxhighlight>
  
 
Ak nepoznáme vopred počet prvkov, ktoré chceme dať do poľa, môžeme odhadnúť, že ich nebude viac ako NMax, ktoré definujeme ako konštantu v programe.
 
Ak nepoznáme vopred počet prvkov, ktoré chceme dať do poľa, môžeme odhadnúť, že ich nebude viac ako NMax, ktoré definujeme ako konštantu v programe.
 
* Vytvoríme pole veľkosti NMax, použijeme z neho len prých N hodnôt
 
* Vytvoríme pole veľkosti NMax, použijeme z neho len prých N hodnôt
<pre>
+
<syntaxhighlight lang="C++">
 +
#include <iostream>
 +
using namespace std;
 +
 
 +
int main() {
 
     const int NMax = 1000;
 
     const int NMax = 1000;
 
     int p[NMax];
 
     int p[NMax];
Riadok 163: Riadok 291:
 
     if (N > NMax) {
 
     if (N > NMax) {
 
         cout << "Prilis velke N" << endl;
 
         cout << "Prilis velke N" << endl;
         return 1;
+
         return 1; // ukončíme funkciu main a tým aj program
 
     }
 
     }
 
   cout << "Zadavajte " << N << " cisel: ";
 
   cout << "Zadavajte " << N << " cisel: ";
 
   ...
 
   ...
</pre>
+
</syntaxhighlight>
  
  
Nie je možné priamo zadefinovať ako veľkosť poľa premennú N, ktorú si prečítame od používateľa alebo inak spočítame za behu
+
Čo ak ako veľkosť poľa použijeme premennú N, ktorú si prečítame od používateľa alebo inak spočítame za behu?
<pre>
+
<syntaxhighlight lang="C++">
 
     int N;
 
     int N;
 
     cout << "Zadaj pocet cisel: ";
 
     cout << "Zadaj pocet cisel: ";
 
     cin >> N;
 
     cin >> N;
 
     int p[N];
 
     int p[N];
</pre>
+
</syntaxhighlight>
* V niektorých verziách C resp. C++ to funguje
+
* V starších verziách C resp. C++ to nefunguje, aj keď niektoré novšie kompilátory to zvládajú
* Pole veľkosti N, ktorá nie je konštanta, sa naučíme vytvárať trochu inak v druhej polovici semestra
+
* Na prednáškach tento spôsob nebudeme používať
 +
* Pole veľkosti N, ktorá nie je konštanta, sa naučíme vytvárať inak v druhej polovici semestra
  
 
=== Vytvorenie a inicializácia poľa ===
 
=== Vytvorenie a inicializácia poľa ===
Riadok 184: Riadok 313:
 
V definícii môžeme pole inicializovať zoznamom prvkov.  
 
V definícii môžeme pole inicializovať zoznamom prvkov.  
  
<pre>
+
<syntaxhighlight lang="C++">
int A[4]={3, 6, 8, 10}; //spravne
+
// spravne: inicializacia zoznamom
int B[4]; //spravne
+
int A[4] = {3, 6, 8, 10};  
B[4]={3, 6, 8, 10};  //nespravne, pole to nedefinujeme
+
// spravne: definicia bez inicializacie
B[0]=3; B[1]=6; B[2]=8; B[3]=10;  // spravne - menime prvky existujuceho pola
+
int B[4];  
</pre>
+
// nespravne, pole tu nedefinujeme:
 
+
// B[4] = {3, 6, 8, 10};   
Pri inicializácii sa dá dodať aj menej hodnôt ako má pole. Pri čiastočnej inicializácii nastaví prekladač ostatné prvky na nulu.
+
// spravne - menime prvky existujuceho pola:
 
+
B[0] = 3; B[1] = 6; B[2] = 8; B[3] = 10;   
<pre>
+
</syntaxhighlight>
double C[5]={5.0, 13.9}; // inicializuje C[0]=5.0, C[1]=13.9 a C[2]..C[4]=0
 
double C[5]={0}; // jednoduchá inicializácia všetkých prvkov na 0
 
</pre>
 
 
 
Ak pri inicializácii poľa necháme hranaté zátvorky prázdne, prekladač si sám spočíta prvky poľa. Nie je to však odporúčaný postup.
 
 
 
<pre>
 
int A[]={1, 5, 3, 8}; // zistí rozsah poľa 0..3
 
</pre>
 
  
 
=== Indexovanie hodnotou mimo intervalu ===
 
=== Indexovanie hodnotou mimo intervalu ===
  
 
Pozor, kompilátor nekontroluje indexy prvkov
 
Pozor, kompilátor nekontroluje indexy prvkov
<pre>
+
<syntaxhighlight lang="C++">
 
   int a[10];
 
   int a[10];
 
   a[10] = 1234;
 
   a[10] = 1234;
</pre>
+
</syntaxhighlight>
* Skompilujete, ale hodnota 1234 sa zapíše do pamäte na zlé miesto,
+
* Skompiluje, ale hodnota 1234 sa zapíše do pamäti na zlé miesto
 
* Môže to mať nepredvídateľné následky: prepísanie obsahu iných premenných (chybný výpočet alebo „nevysvetliteľné“ správanie sa programu) alebo dokonca prepísanie časti kódu vášho programu
 
* Môže to mať nepredvídateľné následky: prepísanie obsahu iných premenných (chybný výpočet alebo „nevysvetliteľné“ správanie sa programu) alebo dokonca prepísanie časti kódu vášho programu
  
Riadok 217: Riadok 337:
  
 
V prípade, že chceme vytvoriť pole, ktoré je kópiou už existujúceho poľa, ponúka sa možnosť príkazu priradenia <tt>b=a;</tt>.
 
V prípade, že chceme vytvoriť pole, ktoré je kópiou už existujúceho poľa, ponúka sa možnosť príkazu priradenia <tt>b=a;</tt>.
Takýto príkaz však neskompilujete – nedá sa takto priraďovať, treba kopírovať prvok po prvku.
+
Takýto príkaz však neskompiluje – nedá sa takto priraďovať, treba kopírovať prvok po prvku.
  
<pre>
+
<syntaxhighlight lang="C++">
  for (i=0; i<10; i++) b[i]=a[i];
+
for (int i=0; i < 10; i++) {
</pre>
+
  b[i] = a[i];
 +
}
 +
</syntaxhighlight>
  
Podobne sa nedá porovnávať polia pomocou podmienky <tt>if (a==b) cout << "Ok";</tt>.
+
Polia sa tiež nedajú porovnávať pomocou operátora <tt>==</tt>. Podmienku <tt>if (a==b) { cout << "rovnake"; }</tt>.
Takúto podmienku síce skompilujete, ale nikdy to nebude pravda – neporovná sa obsah poľa, ale niečo úplne iné (adresy polí v pamäti).
+
síce skompilujete, ale nikdy to nebude pravda – neporovná sa obsah poľa, ale niečo úplne iné (adresy polí v pamäti).
Treba to riešiť opäť prvok po prvku.
+
Treba opäť porovnávať prvok po prvku.
  
<pre>
+
<syntaxhighlight lang="C++">
  bool rovne = true;
+
bool rovne = true;
  for (i = 0; i < 10; i++) { rovne = rovne && a[i] == b[i]; }
+
for (int i = 0; i < 10; i++) {  
  if (rovne) cout << "Ok\n";  
+
    rovne = rovne && a[i] == b[i];  
</pre>
+
}
 +
if (rovne) {
 +
    cout << "Rovnaju sa" << endl;  
 +
}
 +
</syntaxhighlight>
  
== Príklady na prácu s poľom ==
+
Ten istý kúsok programu môžeme napísať napr. aj takto:
  
* Načítajte pole čísel a vypíšte ho v opačnom poradí.
+
<syntaxhighlight lang="C++">
* Skúste poradie povymienať priamo v poli a nie iba pri výpise.
+
bool rovne = true;
 +
for (int i = 0; i < 10; i++) {
 +
    if(a[i] != b[i]) {
 +
        rovne = false;
 +
        break;
 +
    }
 +
}
 +
if (rovne) {
 +
    cout << "Rovnaju sa" << endl;
 +
}
 +
</syntaxhighlight>
  
* Načítajte pole čísel a vypíšte ho v náhodnom poradí.
+
== Výskyty čísel 0...9 ==
* Ako by ste pole náhodne povymieňali priamo v pamäti?
 
  
== Kreslíme padajúce kruhy ==
+
Na vstupe je číslo ''N'' a ''N'' celých čísel od 0 do 9 a chceme vedieť, koľkokrát sa jednotlivé čísla na vstupe vyskytli.
  
Chceme vykresliť [[:Image:PROG-P5-kruhy.svg|animáciu padajúcich kruhov]]
+
Prvý prístup:
* Vytvoríme si polia pre x-ovú a y-ovú súradnicu kruhu.
+
* vstupné čísla uložíme do poľa
* Potrebujeme aj pole, do ktorého si budeme dávať celočíselné identifikátory nakreslených kruhov, aby sme ich neskôr mohli zmazať.
+
* pre každú hodnotu ''i'' od 0 po 9 prejdeme pole a spočítame počet výskytov ''i''
  
<pre>
+
Druhý prístup:
#include "../SVGdraw.h"
+
* samotné vstupné čísla neukladáme do poľa, spracovávame ich po jednom
#include <cstdlib>
+
* vytvoríme si pole počítadiel dĺžky 10, v ktorom p[i] bude počet výskytov čísla ''i''
#include <ctime>
 
  
int main(void) {
+
<syntaxhighlight lang="C++">
    const int count = 10; /* počet kruhov */
+
#include <iostream>
    int size = 300; /* veľkosť obrázku */
+
using namespace std;
    int diameter = 15; /* polomer kruhu */
 
    int step = 4; /* o kolko padne dolu v jednom kroku */
 
    int repeat = 100; /* pocet iteracii */
 
    double wait = 0.2; /* cakaj po kazdej iteracii */
 
  
     /* inicializácia generátora pseudonáhodných čísel */
+
int main() {
     srand(time(NULL));
+
     int p[10];  // pole dlzky 10
 +
    int N;
 +
     cout << "Zadajte pocet cisel: ";
 +
    cin >> N;
  
     SVGdraw drawing(size, size, "kruhy.svg");
+
     for (int i = 0; i < 10; i++) {
     drawing.setFillColor("lightblue");
+
      p[i] = 0; // inicializácia poľa p[0]=0; p[1]=0; ... p[9]=0;
 +
     }
  
     int x[count]; /* x-ova poloha kruzku */
+
     cout << "Zadavajte " << N << "cisel z intervalu 0-9: ";
    int y[count]; /* y-ove poloha kruzku */
+
    for (int i = 0; i < N; i++) {
     int id[count]; /* id objektu na obrazovke */
+
        int x;
 +
        cin >> x;
 +
        if (x >= 0 && x < 10) { // test, či je číslo z požadovaného rozsahu
 +
            p[x]++;             // zvýšime počítadlo pre hodnotu x
 +
        }
 +
     }
  
     /* kazdemu kruzku vygeneruj nahodnu polohu */
+
     cout << endl;
     for (int i = 0; i < count; i++) {
+
     for (int i = 0; i < 10; i++) {
         x[i] = rand() % (size - diameter);
+
         cout << "Pocet vyskytov " << i << " je " << p[i] << endl; // výpis
        y[i] = rand() % (size - diameter);
 
 
     }
 
     }
 +
}</syntaxhighlight>
 +
 +
== Odbočka: grafická knižnica SVGdraw ==
 +
 +
[[Image:P5-svgdraw.png|frame|Výsledný obrázok]]
 +
* Aplikácie s grafickým rozhraním budeme programovať až budúci semester
 +
* V tomto semestri, ale budeme kresliť obrázky v [https://en.wikipedia.org/wiki/Scalable_Vector_Graphics SVG formáte] pomocou jednoduchej knižnice [[SVGdraw]]
 +
* Knižnicu si stiahnite a nainštalujte [[Zimný semester, softvér#Pr.C3.A1ca_v_Kate_s_grafickou_kni.C5.BEnicou_SVGdraw|podľa návodu]]
 +
* Na začiatku programu zapnite knižnicu pomocou <tt>#include "SVGdraw.h"</tt>
 +
* Tu je malý ukážkový program, ktorý vykreslí zelený štvorec a v ňom červený kruh:
 +
<syntaxhighlight lang="C++">
 +
#include "SVGdraw.h"
 +
 +
int main() {
 +
  /* Vytvor obrázok s šírkou 150 a výškou 100 a
 +
  * ulož ho do súboru stvorec.svg*/
 +
  SVGdraw drawing(150, 100, "stvorec.svg");
 +
 +
  /* Nastav farbu vyfarbovania na zelenú. */
 +
  drawing.setFillColor("green");
 +
  /* Vykresli štvorec s ľavým horným
 +
  * rohom v bode (20,10)
 +
  * a s dĺžkou strany 80,
 +
  * t.j. pravým dolným rohom 100,90 */
 +
  drawing.drawRectangle(20,10,80,80);
 +
 
 +
  /* Nastav farbu vyfarbovania na červenú */
 +
  drawing.setFillColor("red");
 +
  /* Vykresli kruh so stredom
 +
  * v bode (60,50) a polomerom 40. */
 +
  drawing.drawEllipse(60,50,40,40);
 +
 +
  /* Ukonči vypisovanie obrázka. */
 +
  drawing.finish();
 +
}
 +
</syntaxhighlight>
  
    /* opakuj repeat iteracii */
+
== Kreslíme kruhy ==
    for (int r = 0; r < repeat; r++) {
 
        /* chod cez vsetky kruhy */
 
        for (int i = 0; i < count; i++) {
 
            /* ak nie sme v prvej iteracii, treba zmazat kruh */
 
            if (r > 0) {
 
                drawing.hideItem(id[i]);
 
            }
 
            /* zvys y-ovu suradnicu o step */
 
            y[i] += step;
 
            /* ak sme prilis nizko, zacni na vrchu na nahodnom x */
 
            if (y[i] >= size - diameter) {
 
                y[i] = 0;
 
                x[i] = rand() % (size - diameter);
 
            }
 
            /* vykresli kruzok na novom mieste */
 
            id[i] = drawing.drawEllipse(x[i], y[i], diameter, diameter);
 
        }
 
        /* na konci iteracie chvilu pockaj */
 
        drawing.wait(wait);
 
    }
 
}</pre>
 
  
* Takéto polia nie sú ideálne, lebo údaje o jednom kruhu v troch rôznych poliach a bolo by logickejšie ich mať pokope.  
+
[[Image:P5-kruhy.png|thumb|150px]]
* Situácia by bola ešte horšia, ak by každý kruh mal napr. aj náhodnú farbu s troma zložkami R,G,B. To by sme potrebovali šesť polí.
+
Nasledujúci program náhodne vygeneruje súradnice niekoľkých kruhov a vykreslí ich pomocou knižnice SVGdraw.
* Na spojenie údajov k jednému kruhu použijeme dátovú štruktúru struct
+
* Údaje o jednom kruhu si uloží do záznamu <tt>struct kruh</tt>, v programe používame pole takýchto kruhov.
 +
* Navyše o každom kruhu zistí, či sa pretína s iným kruhom, a ak áno, pri vykresľovaní ho orámuje červenou farbou.
  
=== Padajúce kruhy so struct ===
+
<syntaxhighlight lang="C++">
[[:Image:PROG-P5-kruhy.svg|Výsledok]]
+
#include "SVGdraw.h"
<pre>
 
#include "../SVGdraw.h"
 
 
#include <cstdlib>
 
#include <cstdlib>
 
#include <ctime>
 
#include <ctime>
 +
#include <cmath>
  
 
struct kruh {
 
struct kruh {
     int x, y; /* suradnice */
+
     int x, y; /* suradnice stredu */
     int id;   /* id na mazanie */
+
     int polomer; /* polomer kruhu */
     int r, g, b; /* zlozky farby: red, green, blue */
+
     bool pretinaSa; /* pretína sa s iným? */
 
};
 
};
  
void generujKruh(kruh &k, int max) {
+
void generujKruh(kruh &k, int velkost, int polomer) {
     k.x = rand() % max;
+
    /* inicializuj kruh s nahodnou polohou
    k.y = rand() % max;
+
    * a danym polomerom */
     k.r = rand() % 256;
+
     k.x = rand() % (velkost - 2 * polomer)  
    k.g = rand() % 256;
+
          + polomer;
    k.b = rand() % 256;
+
     k.y = rand() % (velkost - 2 * polomer)  
     k.id = -1;
+
          + polomer;
 +
     k.polomer = polomer;
 
}
 
}
  
int main(void) {
+
bool pretinajuSa(kruh &k1, kruh &k2) {
     const int count = 10; /* počet kruhov */
+
     /* zisti, ci sa dva kruhy pretinaju */
     int size = 300; /* veľkosť obrázku */
+
     int dx = k1.x - k2.x;
     int diameter = 15; /* polomer kruhu */
+
     int dy = k1.y - k2.y;
     int step = 4; /* o kolko padne dolu v jednom kroku */
+
     double d2 = sqrt(dx * dx + dy * dy);
    int repeat = 100; /* pocet iteracii */
+
     return d2 <= k1.polomer + k2.polomer;
     double wait = 0.2; /* cakaj po kazdej iteracii */
+
}
  
     int max = size - diameter; /* maximalna mozna suradnica */
+
int main() {
 +
    const int pocet = 10; /* počet kruhov */
 +
    const int velkost = 300; /* veľkosť obrázku */
 +
     const int polomer = 15; /* polomer kruhu */
  
     /* inicializácia generátora pseudonáhodných čísel */
+
     /* inicializácia generátora  
 +
    * pseudonáhodných čísel */
 
     srand(time(NULL));
 
     srand(time(NULL));
  
     SVGdraw drawing(size, size, "kruhy2.svg");
+
    /* inicializácia obrázku */
 +
     SVGdraw drawing(velkost, velkost, "kruhy.svg");
  
 
     /* pole kruhov */
 
     /* pole kruhov */
     kruh kruhy[count];
+
     kruh kruhy[pocet];
  
    /* kazdemu kruzku vygeneruj nahodnu polohu */
+
     for (int i = 0; i < pocet; i++) {
     for (int i = 0; i < count; i++) {
+
        /* kazdemu kruhu vygeneruj nahodnu polohu */
         generujKruh(kruhy[i], max);
+
         generujKruh(kruhy[i], velkost, polomer);
 
     }
 
     }
  
     /* opakuj repeat iteracii */
+
     /* zisti, ktoré kruhy pretínajú iné kruhy */
     for (int r = 0; r < repeat; r++) {
+
     for (int i = 0; i < pocet; i++) {
         /* chod cez vsetky kruhy */
+
         kruhy[i].pretinaSa = false;
         for (int i = 0; i < count; i++) {
+
         for (int j = 0; j < pocet; j++) {
             /* ak nie sme v prvej iteracii, treba zmazat kruh */
+
             if (i != j && pretinajuSa(kruhy[i],
            if (r > 0) {
+
                                      kruhy[j])) {
                 drawing.hideItem(kruhy[i].id);
+
                 kruhy[i].pretinaSa = true;
 
             }
 
             }
            /* zvys y-ovu suradnicu o step */
 
            kruhy[i].y += step;
 
            /* ak sme prilis nizko, zacni na vrchu na nahodnom x */
 
            if (kruhy[i].y >= max) {
 
                generujKruh(kruhy[i], max);
 
                kruhy[i].y = 0;
 
            }
 
            /* vykresli kruzok na novom mieste */
 
            drawing.setFillColor(kruhy[i].r, kruhy[i].g, kruhy[i].b);
 
            kruhy[i].id = drawing.drawEllipse(kruhy[i].x, kruhy[i].y,
 
                    diameter, diameter);
 
 
         }
 
         }
        /* na konci iteracie chvilu pockaj */
 
        drawing.wait(wait);
 
 
     }
 
     }
}
 
</pre>
 
 
== Eratostenovo sito ==
 
 
Chceme vypísať všetky prvočísla medzi 2 a N. Mohli by sme ísť cez všetky čísla a pre každé testovať, koľko má deliteľov (deliteľov sme už hľadali predtým), ale vieme to spraviť aj rýchlejšie. Použijeme algoritmus zvaný Eratostenovo sito.
 
 
* Vytvoríme pole ''A'' pravdivostných hodnôt, kde ''A[i]'' nám hovorí, či je ''i'' ešte potenciálne prvočíslo.
 
* Na začiatku budú všetky hodnoty true, lebo sme ešte žiadne číslo nevylúčili.
 
* Začneme číslom 2 - toto je iste prvočíslo (tak ho vypíšeme). O jeho násobkoch však vieme, že iste nemôžu byť prvočísla - nastavíme preto pre každý násobok j=2*k pravdivostnú hodnotu A[j] na false.
 
* Potom prechádzame v poli, kým nenájdeme najbližšiu ďalšiu hodnotu true. Toto číslo je prvočíslo (vypíšeme ho) a vyškrtáme jeho násobky.
 
  
<pre>
+
     /* vykresluj kruhy */
#include <iostream>
+
     drawing.setFillColor("gray");
using namespace std;
+
     for (int i = 0; i < pocet; i++) {
 
+
         if (kruhy[i].pretinaSa) {
int main(void) {
+
             drawing.setLineColor("red");
     const int N = 25;
+
        } else {
     bool A[N + 1];
+
             drawing.setLineColor("black");
 
 
    for (int i = 2; i <= N; i++) {
 
        A[i] = true;
 
    }
 
     for (int i = 2; i <= N; i++) {
 
         if (A[i]) {
 
             cout << i << " ";
 
             for (int j = 2 * i; j <= N; j = j + i) {
 
                A[j] = false;
 
            }
 
 
         }
 
         }
 +
        drawing.drawEllipse(kruhy[i].x,
 +
                            kruhy[i].y,
 +
                            kruhy[i].polomer,
 +
                            kruhy[i].polomer);
 
     }
 
     }
     cout << endl;
+
 
 +
     /* ukoncenie vykreslovania */
 +
    drawing.finish();
 
}
 
}
</pre>
+
</syntaxhighlight>
 
 
Výstup programu
 
<pre>
 
2 3 5 7 11 13 17 19 23
 
</pre>
 
  
Priebeh programu:
+
== Polia: zhrnutie ==
<pre>
 
0  1  2  3  4  5  6  7  8  9 10 11 12 ...
 
?  ?  T  T  T  T  T  T  T  T  T  T  T ... na zaciatku
 
?  ?  T  T  F  T  F  T  F  T  F  T  F ... po vyskrtani i=2
 
?  ?  T  T  F  T  F  T  F  F  F  T  F ... po vyskrtani i=3
 
?  ?  T  T  F  T  F  T  F  F  F  T  F ... dalej sa uz skrtaju len vacsie cisla
 
</pre>
 
  
'''Cvičenie:''' Napíšte funkciu, ktorá uloží prvočísla medzi 2 a N do poľa (ak by sme ich chceli použiť na ďalšie výpočty).
+
* Pole je tabuľka hodnôt. V poli dĺžky ''n'' máme hodnoty ''p[0], p[1], ..., p[n-1]''
 +
* Kopírovanie a porovnávanie polí si musíme naprogramovať
 +
* C resp. C++ nekontrolujú, či index nie je mimo rozsahu poľa
  
== Zhrnutie ==
+
== Ďalšie príklady na prácu s poľom ==
  
* V prípade využívania viacerých premenných, ktoré sú vlastne jednotlivé prvky nejakej postupnosti (načítavané čísla, ..) môžeme využívať polia
+
* Načítajte pole čísel a vypíšte ho v opačnom poradí.
* Jednotlivé spolu súvisiace informácie (vlastnosti) o nejakom prvku spojíme do štruktúry pomocou struct
+
* Skúste poradie povymieňať priamo v poli a nie iba pri výpise.
 
 
== Reklama ==
 
  
* V piatok od 13:10 v H6 školské kolo programátorskej súťaže ACM
+
* Načítajte pole čísel, náhodne ich premiešajte a zase vypíšte.

Aktuálna revízia z 23:17, 8. október 2024

Oznamy

  • Na piatkové cvičenia treba prísť, ak ste v utorok na cvičení nezískali aspoň 5 bodov.
    • Na testovači máte v záložke Body, položke Piatkove_cvicenie uvedené predbežné body z utorkového cvičenia a napísané, či je alebo nie je pre vás piatkové cvičenie povinné.
    • Ak je pre vás cvičenie povinné a neprídete, dostanete -1 bod.
    • Môžete prísť aj ak je pre vás cvičenie nepovinné, ale máte nejaké otázky.
  • Ak by ste chceli zmeniť skupinu na utorkové cvičenia, vyplňte formulár
  • Dnes polia, budúci pondelok hlavne ďalšie príklady a algoritmy s použitím tých častí Cčka, ktoré už poznáte

Hľadanie chýb v programe

  • Väčšina programov nefunguje na prvý krát, hľadanie chýb patrí medzi základné činnosti programátora
  • Podobné chyby sa často opakujú, tréningom sa ich naučíte nájsť rýchlejšie

Ak program ani neskompiluje

  • Kompilátor vypíše číslo riadku s chybou, čo vám ju môže pomôcť nájsť
    • Občas je však chyba trochu inde, napr. niečo chýba o riadok vyššie
  • Pokročilejšie prostredia, ako napr. Netbeans, vám vedia ukázať polohu chyby
  • Ak kompilátor vypíše veľa chýb, opravte najskôr prvú, potom skompilujte znovu, ďalšie chyby môžu byť len dôsledkom prvej
  • Ak neviete nájsť chybu pri kompilácii, skúste zakomentovať nejaké časti programu pomocou /* */, aby ste zúžili priestor, kde chyba môže byť
  • Aj varovania kompilátora môžu poukazovať na chybu v programe

Ak program dáva zlé výsledky, "cyklí sa" alebo "padá"

  • Môžete skúsiť program znovu prečítať, či nezbadáte chybu
  • Alebo experimentami zistiť, kde sa jeho správanie prvýkrát začne odlišovať od toho, čo očakávate
  • To sa dá robiť spúšťaním programu po krokoch v nástroji nazvanom debugger (nachádza sa napr. v Netbeans)
  • Alternatíva k debuggeru je do programu pridať pomocné výpisy, ktoré vám prezradia, ktorá časť programu sa práve vykonáva a aké sú hodnoty dôležitých premenných
    • Po nájdení chyby treba tieto pomocné výpisy odstrániť. Pozor, aby ste tým nespravili ďalšiu chybu
  • Debugger alebo výpisy vám pomôžu nájsť chybu iba vtedy, ak máte predstavu o tom, ako by program mal fungovať a hľadáte, kde sa od nej skutočné správanie líši
  • Pomáha vyrobiť si čo najmenší vstup, kde je zlý výsledok

Záznam typu struct

V príkladoch s obvodom trojuholníka a stredom úsečky sme funkciám posielali veľa parametrov (súradnice x a y)

void stred(double x1, double y1, double x2, double y2, double &xm, double &ym) {
    xm = (x1 + x2) / 2;
    ym = (y1 + y2) / 2;
}

Program bude krajší, ak si údaje o jednom bode spojíme do jedného záznamu

struct bod {
    double x, y;
};
  • Pomocou struct vytvoríme nový dátový typ bod, ktorý má zložky x a y
  • V jednom struct-e môžu byť aj položky rôznych typov, napr.
struct bod { 
   double x,y; 
   int id; 
   bool visible; 
};
  • Môžeme vytvárať premenné typu bod, napr. bod a, b;
  • K položkám bodu pristupujeme pomocou bodky, napr. a.x = 4.0;
  • Do funkcií body posielame radšej referenciou, aby sa zbytočne nekopírovalo veľa hodnôt

Nasledujúci program načíta súradnice troch bodov, spočíta obvod trojuholníka a stredy všetkých troch strán.

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

struct bod {
    double x, y;  // suradnice bodu v rovine
};

double dlzka(bod &bod1, bod &bod2) {
    // funkcia vrati dlzku usecky z bodu 1 do bodu 2
    double dx = bod1.x - bod2.x;
    double dy = bod1.y - bod2.y;
    return sqrt(dx * dx + dy * dy);
}

void stred(bod &bod1, bod &bod2, bod &stred) {
    // funkcia do bodu stred spocita stred usecky z bodu 1 do bodu 2
    stred.x = (bod1.x + bod2.x) / 2;
    stred.y = (bod1.y + bod2.y) / 2;
}

void vypisBod(bod &b) {
    // funkcia vypise suradnice bodu v zatvorke a koniec riadku
    cout << "(" << b.x << "," << b.y << ")" << endl;
}

int main() {
    // nacitame suradnice vrcholov trojuholnika
    bod A, B, C;
    cout << "Zadaj suradnice vrcholu A oddelene medzerou: ";
    cin >> A.x >> A.y;
    cout << "Zadaj suradnice vrcholu B oddelene medzerou: ";
    cin >> B.x >> B.y;
    cout << "Zadaj suradnice vrcholu C oddelene medzerou: ";
    cin >> C.x >> C.y;
    // spocitame dlzky stran
    double da = dlzka(B, C);
    double db = dlzka(A, C);
    double dc = dlzka(A, B);
    // vypiseme obvod
    cout << "Obvod trojuholnika ABC: " << da + db + dc << endl;

    // spocitame stredy stran
    bod stredAB;
    stred(A, B, stredAB);
    bod stredAC;
    stred(A, C, stredAC);
    bod stredBC;
    stred(B, C, stredBC);

    // vypiseme stredy stran
    cout << "Stred strany AB: ";
    vypisBod(stredAB);
    cout << "Stred strany AC: ";
    vypisBod(stredAC);
    cout << "Stred strany BC: ";
    vypisBod(stredBC);
}

Príklad behu programu:

Zadaj suradnice vrcholu A oddelene medzerou: 0 0
Zadaj suradnice vrcholu B oddelene medzerou: 0 3
Zadaj suradnice vrcholu C oddelene medzerou: 4 0
Obvod trojuholnika ABC: 12
Stred strany AB: (0,1.5)
Stred strany AC: (2,0)
Stred strany BC: (2,1.5)

Spracovanie väčšieho množstva dát

Naše programy doteraz spracovávali len malý počet vstupných dát načítaných od užívateľa (napr. súradnice troch bodov). Často však chceme pracovať s väčším množstvom dát

  • Dnes si ukážeme, ako uložiť väčšie množstvo dát do poľa
  • Na niektoré úlohy však pole nepotrebujeme - údaje môžeme spracovávať rovno ako ich užívateľ zadáva (takéto príklady sme už videli na cvičeniach)

V nasledujúcich príkladoch užívateľ zadá číslo N a potom N celých čísel

  • Predstavme si napríklad, že učiteľ zadá body, ktoré študenti dostali na písomke (napr. celé čísla v rozsahu 0..10)
  • Z týchto bodov chceme spočítať nejaké štatistiky

Priemer

#include <iostream>
using namespace std;

int main() {
    int N;
    cout << "Zadaj pocet cisel: ";
    cin >> N;

    int sucet = 0;
    cout << "Zadavaj cisla: ";
    for (int i = 0; i < N; i++) {
        int x;
        cin >> x;
        sucet += x;
    }

    double priemer = sucet / (double) N;
    cout << "Priemer je " << priemer << "." << endl;
}
  • Čo by sa stalo, keby sme vo výpočte priemeru vynechali (double)?

Maximum

#include <iostream>
using namespace std;

int main() {
    int max, x, N;

    cout << "Zadaj pocet cisel: ";
    cin >> N;

    cout << "Zadavajte cisla: ";

    max = ?
    for (int i = 0; i < N; i++) {
        cin >> x;
        if (x > max) {
            max = x;
        }
    }

    cout << endl << "Maximum je " << max << endl;
}

Ako ale začať? Ako nastaviť maximum na začiatok?

  • Jedna možnosť je nastaviť ho na nejakú veľmi malú hodnotu, aby sa iste neskôr zmenila. Ale čo ak používateľ dá všetky čísla ešte menšie?
  • Riešením je použiť najmenšie možné číslo. Ale to je príliš viazané na konkrétny rozsah, nebude fungovať po zmene typu premenných.
  • Ďalšia možnosť je si pamätať, že ešte nemáme správne nastavené maximum a po načítaní prvého čísla ho nastaviť alebo spracovať prvé číslo zvlášť (mimo cyklu).
#include <iostream>
using namespace std;

int main() {
    int max, x, N;

    cout << "Zadaj pocet cisel: ";
    cin >> N;

    cout << "Zadavajte cisla: ";
    cin >> x;   // načítanie prvého čísla
    max = x;

    for (int i = 1; i < N; i++) {  // cyklus cez N-1 ďalších čísel 
        cin >> x;
        if (x > max) {
            max = x;
        }
    }

    cout << endl << "Maximum je " << max << endl;
}

Cvičenie:

  • Ako by sme program rozšírili tak, aby vedel vypísať aj koľké číslo v poradí bolo najväčšie?
  • Čo treba v programe zmeniť, ak chceme hľadať minimum namiesto maxima?

Prvé použitie poľa: podpriemer / nadpriemer

Chceme spočítať priemer a o každom vstupnom čísle vypísať, či je nadpriemerné alebo podpriemerné.

  • Priemer vieme až keď načítame všetky čísla, musíme si ich teda zapamätať
  • Na to používame tabuľky, ktoré sa volajú polia.
  • Na začiatok pre jednoduchosť predpokladajme, že vopred vieme, že počet údajov N je napr. 20 (N teda nenačítavame)
  • Príkaz int a[20]; vytvorí pole s 20 premennými typu int, ku ktorým pristupujeme a[0], a[1], ..., a[19]
    • pozor, a[20] neexistuje
#include <iostream>
using namespace std;

int main() {
    const int N = 20; // premennu N uz nebude mozne menit, ma konstantnu hodnotu 20
    int a[N];
    double sucet = 0;

    cout << "Zadavajte " << N << " cisel: ";
    for (int i = 0; i < N; i++) {
        cin >> a[i];
        sucet += a[i];
    }

    double priemer = sucet / N;
    cout << "Priemer je " << priemer << "." << endl;
    for (int i = 0; i < N; i++) {
        if (a[i] > priemer) { 
             cout << a[i] << ": vacsie ako priemer." << endl;
        } else if (a[i] < priemer) {
             cout << a[i] << ": mensie ako priemer." << endl;
        } else {
             cout << a[i] << ": priemer." << endl;
        }
    }
}

Na zamyslenie:

  • Pozor, chyby pri zaokrúhľovaní môžu spôsobiť, že niekedy priemerné číslo bude považované za nad/podpriemerné
    • Vedeli by ste program prerobiť tak, aby používal iba premenné typu int a nerobil žiadnu chybu v zaokrúhľovaní?
    • Môže aj po takejto zmene niekedy dať zlú odpoveď?

Polia

Rozsah poľa je konštantný výraz väčší ako 0. Prvky sa indexujú od 0 po počet - 1

int a[10];
const int N=20;
double b[N];

Ak nepoznáme vopred počet prvkov, ktoré chceme dať do poľa, môžeme odhadnúť, že ich nebude viac ako NMax, ktoré definujeme ako konštantu v programe.

  • Vytvoríme pole veľkosti NMax, použijeme z neho len prých N hodnôt
#include <iostream>
using namespace std;

int main() {
    const int NMax = 1000;
    int p[NMax];
    int N;
    cout << "Zadaj pocet cisel: ";
    cin >> N;
    if (N > NMax) {
        cout << "Prilis velke N" << endl;
        return 1;  // ukončíme funkciu main a tým aj program
    }
   cout << "Zadavajte " << N << " cisel: ";
   ...


Čo ak ako veľkosť poľa použijeme premennú N, ktorú si prečítame od používateľa alebo inak spočítame za behu?

    int N;
    cout << "Zadaj pocet cisel: ";
    cin >> N;
    int p[N];
  • V starších verziách C resp. C++ to nefunguje, aj keď niektoré novšie kompilátory to zvládajú
  • Na prednáškach tento spôsob nebudeme používať
  • Pole veľkosti N, ktorá nie je konštanta, sa naučíme vytvárať inak v druhej polovici semestra

Vytvorenie a inicializácia poľa

V definícii môžeme pole inicializovať zoznamom prvkov.

// spravne: inicializacia zoznamom
int A[4] = {3, 6, 8, 10}; 
// spravne: definicia bez inicializacie
int B[4]; 
// nespravne, pole tu nedefinujeme:
// B[4] = {3, 6, 8, 10};  
// spravne - menime prvky existujuceho pola:
B[0] = 3; B[1] = 6; B[2] = 8; B[3] = 10;

Indexovanie hodnotou mimo intervalu

Pozor, kompilátor nekontroluje indexy prvkov

  int a[10];
  a[10] = 1234;
  • Skompiluje, ale hodnota 1234 sa zapíše do pamäti na zlé miesto
  • Môže to mať nepredvídateľné následky: prepísanie obsahu iných premenných (chybný výpočet alebo „nevysvetliteľné“ správanie sa programu) alebo dokonca prepísanie časti kódu vášho programu

Kopírovanie a testovanie rovnosti

V prípade, že chceme vytvoriť pole, ktoré je kópiou už existujúceho poľa, ponúka sa možnosť príkazu priradenia b=a;. Takýto príkaz však neskompiluje – nedá sa takto priraďovať, treba kopírovať prvok po prvku.

for (int i=0; i < 10; i++) {
   b[i] = a[i];
}

Polia sa tiež nedajú porovnávať pomocou operátora ==. Podmienku if (a==b) { cout << "rovnake"; }. síce skompilujete, ale nikdy to nebude pravda – neporovná sa obsah poľa, ale niečo úplne iné (adresy polí v pamäti). Treba opäť porovnávať prvok po prvku.

bool rovne = true;
for (int i = 0; i < 10; i++) { 
    rovne = rovne && a[i] == b[i]; 
}
if (rovne) {
    cout << "Rovnaju sa" << endl; 
}

Ten istý kúsok programu môžeme napísať napr. aj takto:

bool rovne = true;
for (int i = 0; i < 10; i++) { 
    if(a[i] != b[i]) {
        rovne = false;
        break;
    }
}
if (rovne) {
    cout << "Rovnaju sa" << endl; 
}

Výskyty čísel 0...9

Na vstupe je číslo N a N celých čísel od 0 do 9 a chceme vedieť, koľkokrát sa jednotlivé čísla na vstupe vyskytli.

Prvý prístup:

  • vstupné čísla uložíme do poľa
  • pre každú hodnotu i od 0 po 9 prejdeme pole a spočítame počet výskytov i

Druhý prístup:

  • samotné vstupné čísla neukladáme do poľa, spracovávame ich po jednom
  • vytvoríme si pole počítadiel dĺžky 10, v ktorom p[i] bude počet výskytov čísla i
#include <iostream>
using namespace std;

int main() {
    int p[10];  // pole dlzky 10
    int N;
    cout << "Zadajte pocet cisel: ";
    cin >> N;

    for (int i = 0; i < 10; i++) {
       p[i] = 0; // inicializácia poľa p[0]=0; p[1]=0; ... p[9]=0;
    }

    cout << "Zadavajte " << N << "cisel z intervalu 0-9: ";
    for (int i = 0; i < N; i++) {
        int x;
        cin >> x;
        if (x >= 0 && x < 10) { // test, či je číslo z požadovaného rozsahu
            p[x]++;             // zvýšime počítadlo pre hodnotu x
        }
    }

    cout << endl;
    for (int i = 0; i < 10; i++) {
        cout << "Pocet vyskytov " << i << " je " << p[i] << endl; // výpis
    }
}

Odbočka: grafická knižnica SVGdraw

Výsledný obrázok
  • Aplikácie s grafickým rozhraním budeme programovať až budúci semester
  • V tomto semestri, ale budeme kresliť obrázky v SVG formáte pomocou jednoduchej knižnice SVGdraw
  • Knižnicu si stiahnite a nainštalujte podľa návodu
  • Na začiatku programu zapnite knižnicu pomocou #include "SVGdraw.h"
  • Tu je malý ukážkový program, ktorý vykreslí zelený štvorec a v ňom červený kruh:
#include "SVGdraw.h"

int main() {
  /* Vytvor obrázok s šírkou 150 a výškou 100 a 
   * ulož ho do súboru stvorec.svg*/
  SVGdraw drawing(150, 100, "stvorec.svg");

  /* Nastav farbu vyfarbovania na zelenú. */
  drawing.setFillColor("green");
  /* Vykresli štvorec s ľavým horným 
   * rohom v bode (20,10)
   * a s dĺžkou strany 80, 
   * t.j. pravým dolným rohom 100,90 */
  drawing.drawRectangle(20,10,80,80);
  
  /* Nastav farbu vyfarbovania na červenú */
  drawing.setFillColor("red");
  /* Vykresli kruh so stredom 
   * v bode (60,50) a polomerom 40. */
  drawing.drawEllipse(60,50,40,40);

  /* Ukonči vypisovanie obrázka. */
  drawing.finish();
}

Kreslíme kruhy

P5-kruhy.png

Nasledujúci program náhodne vygeneruje súradnice niekoľkých kruhov a vykreslí ich pomocou knižnice SVGdraw.

  • Údaje o jednom kruhu si uloží do záznamu struct kruh, v programe používame pole takýchto kruhov.
  • Navyše o každom kruhu zistí, či sa pretína s iným kruhom, a ak áno, pri vykresľovaní ho orámuje červenou farbou.
#include "SVGdraw.h"
#include <cstdlib>
#include <ctime>
#include <cmath>

struct kruh {
    int x, y; /* suradnice stredu */
    int polomer; /* polomer kruhu */
    bool pretinaSa; /* pretína sa s iným? */
};

void generujKruh(kruh &k, int velkost, int polomer) {
    /* inicializuj kruh s nahodnou polohou 
     * a danym polomerom */
    k.x = rand() % (velkost - 2 * polomer) 
          + polomer;
    k.y = rand() % (velkost - 2 * polomer) 
          + polomer;
    k.polomer = polomer;
}

bool pretinajuSa(kruh &k1, kruh &k2) {
    /* zisti, ci sa dva kruhy pretinaju */
    int dx = k1.x - k2.x;
    int dy = k1.y - k2.y;
    double d2 = sqrt(dx * dx + dy * dy);
    return d2 <= k1.polomer + k2.polomer;
}

int main() {
    const int pocet = 10; /* počet kruhov */
    const int velkost = 300; /* veľkosť obrázku */
    const int polomer = 15; /* polomer kruhu */

    /* inicializácia generátora 
     * pseudonáhodných čísel */
    srand(time(NULL));

    /* inicializácia obrázku */
    SVGdraw drawing(velkost, velkost, "kruhy.svg");

    /* pole kruhov */
    kruh kruhy[pocet];

    for (int i = 0; i < pocet; i++) {
        /* kazdemu kruhu vygeneruj nahodnu polohu */
        generujKruh(kruhy[i], velkost, polomer);
    }

    /* zisti, ktoré kruhy pretínajú iné kruhy */
    for (int i = 0; i < pocet; i++) {
        kruhy[i].pretinaSa = false;
        for (int j = 0; j < pocet; j++) {
            if (i != j && pretinajuSa(kruhy[i],
                                      kruhy[j])) {
                kruhy[i].pretinaSa = true;
            }
        }
    }

    /* vykresluj kruhy */
    drawing.setFillColor("gray");
    for (int i = 0; i < pocet; i++) {
        if (kruhy[i].pretinaSa) {
            drawing.setLineColor("red");
        } else {
            drawing.setLineColor("black");
        }
        drawing.drawEllipse(kruhy[i].x, 
                            kruhy[i].y,
                            kruhy[i].polomer,
                            kruhy[i].polomer);
    }

    /* ukoncenie vykreslovania */
    drawing.finish();
}

Polia: zhrnutie

  • Pole je tabuľka hodnôt. V poli dĺžky n máme hodnoty p[0], p[1], ..., p[n-1]
  • Kopírovanie a porovnávanie polí si musíme naprogramovať
  • C resp. C++ nekontrolujú, či index nie je mimo rozsahu poľa

Ďalšie príklady na prácu s poľom

  • Načítajte pole čísel a vypíšte ho v opačnom poradí.
  • Skúste poradie povymieňať priamo v poli a nie iba pri výpise.
  • Načítajte pole čísel, náhodne ich premiešajte a zase vypíšte.