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í
(Vytvorená stránka „<!-- == Organizačné poznámky == * DÚ3 zverejnená, odovzdávajte do budúceho pondelka 22:00. * Na stránke pridané riešenia príkladov z minulých cvičení. * A...“)
 
(64 medziľahlých úprav od 4 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
<!--
+
== Oznamy ==
== Organizačné poznámky ==
+
* 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
  
* DÚ3 zverejnená, odovzdávajte do budúceho pondelka 22:00.
+
==Hľadanie chýb v programe==
* Na stránke pridané riešenia príkladov z minulých cvičení.
+
* Väčšina programov nefunguje na prvý krát, hľadanie chýb patrí medzi základné činnosti programátora
* Ak máte problémy s DÚ alebo s príkladmi z cvičení, príďte piatok na doplnkové cvičenie. Nenechávajte si prácu na DÚ na poslednú chvíľu.
+
* Podobné chyby sa často opakujú, tréningom sa ich naučíte nájsť rýchlejšie
* Ak máte ešte stále problémy s Netbeans pod Windows, dnes na cvičení, prípadne zajtra na prednáške si môžete na USB kľúčik nakopírovať inštaláciu Netbeans, ktorá sa veľmi ľahko spojazdní na vašom počítači (treba 1.4GB miesta)
 
* Nezabudnite si prípadné zmeny v zápise dať tento týždeň potvrdiť na študijnom.
 
-->
 
  
== Štatistika z N čísel ==
+
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
  
Od užívateľa postupne načítame N čísel a chceme o nich zistiť nejaké štatistické údaje.
+
Ak program dáva zlé výsledky, "cyklí sa" alebo "padá"
* Predstavme si napríklad, že učiteľ zadá body, ktoré študenti dostali na písomke.
+
* 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
  
=== Maximum ===
+
== 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;
 +
 
 +
    // spocitame stredy stran
 +
    bod stredAB;
 +
    stred(A, B, stredAB);
 +
    bod stredAC;
 +
    stred(A, C, stredAC);
 +
    bod stredBC;
 +
    stred(B, C, stredBC);
  
Zrejme stačí čítať čísla postupne a pamätať si zatiaľ najväčšie číslo.
+
    // 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 42: 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 62: 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 74: Riadok 219:
 
     cout << endl << "Maximum je " << max << endl;
 
     cout << endl << "Maximum je " << max << endl;
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 
'''Cvičenie:'''  
 
'''Cvičenie:'''  
 
* Ako by sme program rozšírili tak, aby vedel vypísať aj koľké číslo v poradí bolo najväčšie?
 
* 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?  
+
* Čo treba v programe zmeniť, ak chceme hľadať minimum namiesto maxima?
* Ako by sme spočítali priemer čísel?
 
 
 
=== Výskyty čísel 0...9 ===
 
 
 
Na vstupe sú iba čísla od 0 do 9 a chceme vedieť, koľkokrát sa jednotlivé čísla na vstupe vyskytli. Mohli by sme to riešiť takto:
 
 
 
* Pre každú hodnotu 0...9 si vytvoríme jednu premennú (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.
 
* <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 <tt>int</tt>
 
 
 
<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 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
 
    }
 
}</pre>
 
  
=== Podpriemer / nadpriemer ===
+
== Prvé použitie poľa: 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, že čísel bude napr. 20, môžeme vytvoriť pole pevnej veľkosti 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];
 
     }
 
     }
  
Riadok 148: Riadok 252:
 
     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) {  
+
         if (a[i] > priemer) {  
             cout << p[i] << ": vacsie ako priemer." << endl;
+
             cout << a[i] << ": vacsie ako priemer." << endl;
         } else if (p[i] < priemer) {
+
         } else if (a[i] < priemer) {
             cout << p[i] << ": mensie ako priemer." << endl;
+
             cout << a[i] << ": mensie ako priemer." << endl;
 
         } else {
 
         } else {
             cout << p[i] << ": priemer." << endl;
+
             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í?
Riadok 166: Riadok 271:
  
 
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[N];
 
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>
 
#include <iostream>
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     const int NMax = 1000;
 
     const int NMax = 1000;
 
     int p[NMax];
 
     int p[NMax];
Riadok 190: Riadok 295:
 
   cout << "Zadavajte " << N << " cisel: ";
 
   cout << "Zadavajte " << N << " cisel: ";
 
   ...
 
   ...
</pre>
+
</syntaxhighlight>
  
  
 
Č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?
 
Č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 starších verziách C resp. C++ to nefunguje, aj keď niektoré novšie kompilátory to zvládajú
 
* 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ť
 
* Na prednáškach tento spôsob nebudeme používať
Riadok 208: 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 tu 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>
 
* Skompiluje, ale hodnota 1234 sa zapíše do pamäti 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 243: Riadok 339:
 
Takýto príkaz však neskompiluje – 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>
  
Polia sa tiež nedajú porovnávať pomocou operátora <tt>==</tt>. Podmienku <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>.
 
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 opäť porovnávať 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++) {  
+
for (int i = 0; i < 10; i++) {  
      rovne = rovne && a[i] == b[i];  
+
    rovne = rovne && a[i] == b[i];  
  }
+
}
  if (rovne) cout << "Rovnaju sa" << endl;  
+
if (rovne) {
</pre>
+
    cout << "Rovnaju sa" << endl;  
 +
}
 +
</syntaxhighlight>
  
 
Ten istý kúsok programu môžeme napísať napr. aj takto:
 
Ten istý kúsok programu môžeme napísať napr. aj takto:
  
<pre>
+
<syntaxhighlight lang="C++">
  bool rovne = true;
+
bool rovne = true;
  for (i = 0; i < 10; i++) {  
+
for (int i = 0; i < 10; i++) {  
      if(a[i] != b[i]) {
+
    if(a[i] != b[i]) {
          rovne = false;
+
        rovne = false;
          break;
+
        break;
      }
+
    }
  }
+
}
  if (rovne) cout << "Rovnaju sa" << endl;  
+
if (rovne) {
</pre>
+
    cout << "Rovnaju sa" << endl;  
 +
}
 +
</syntaxhighlight>
  
== Odbočka: grafická knižnica SVGdraw ==
+
== Výskyty čísel 0...9 ==
 
* 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 [[Netbeans#Pr.C3.A1ca_v_Netbeans_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:
 
<pre>
 
  
</pre>
+
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''
  
== Kreslíme padajúce kruhy ==
+
<syntaxhighlight lang="C++">
 +
#include <iostream>
 +
using namespace std;
  
Chceme vykresliť [[#:Image:PROG-P5-kruhy.svg|animáciu padajúcich kruhov]]
+
int main() {
* Viď [[#SVGdraw#Animácie|príkazy na animáciu v knižnici SVGdraw]]
+
    int p[10];  // pole dlzky 10
* Vytvoríme si polia pre x-ovú a y-ovú súradnicu kruhu.
+
    int N;
* Potrebujeme aj pole, do ktorého si budeme dávať celočíselné identifikátory nakreslených kruhov, aby sme ich neskôr mohli zmazať.
+
    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;
 +
    }
  
<pre>
+
    cout << "Zadavajte " << N << "cisel z intervalu 0-9: ";
#include "../SVGdraw.h"
+
    for (int i = 0; i < N; i++) {
#include <cstdlib>
+
        int x;
#include <ctime>
+
        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
 +
        }
 +
    }
  
int main(void) {
+
     cout << endl;
     const int count = 10; /* počet kruhov */
+
     for (int i = 0; i < 10; i++) {
     const int size = 300; /* veľkosť obrázku */
+
        cout << "Pocet vyskytov " << i << " je " << p[i] << endl; // výpis
    const int radius = 15; /* polomer kruhu */
+
     }
    const int step = 4; /* o kolko padne dolu v jednom kroku */
+
}</syntaxhighlight>
     const int repeat = 100; /* pocet iteracii */
 
    double wait = 0.2; /* cakaj po kazdej iteracii */
 
  
    /* inicializácia generátora pseudonáhodných čísel */
+
== Odbočka: grafická knižnica SVGdraw ==
    srand(time(NULL));
+
 +
[[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"
  
    SVGdraw drawing(size, size, "kruhy.svg");
+
int main() {
    drawing.setFillColor("lightblue");
+
  /* Vytvor obrázok s šírkou 150 a výškou 100 a
 +
  * ulož ho do súboru stvorec.svg*/
 +
  SVGdraw drawing(150, 100, "stvorec.svg");
  
    int x[count]; /* x-ova poloha kruzku */
+
  /* Nastav farbu vyfarbovania na zelenú. */
    int y[count]; /* y-ove poloha kruzku */
+
  drawing.setFillColor("green");
    int id[count]; /* id objektu na obrazovke */
+
  /* 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);
  
    /* kazdemu kruzku vygeneruj nahodnu polohu */
+
  /* Ukonči vypisovanie obrázka. */
    for (int i = 0; i < count; i++) {
+
  drawing.finish();
        x[i] = rand() % (size - 2*radius) + radius;
+
}
        y[i] = rand() % (size - 2*radius) + radius;
+
</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 - radius) {
 
                y[i] = radius;
 
                x[i] = rand() % (size - 2*radius) + radius;
 
            }
 
            /* vykresli kruzok na novom mieste */
 
            id[i] = drawing.drawEllipse(x[i], y[i], radius, radius);
 
        }
 
        /* na konci iteracie chvilu pockaj */
 
        drawing.wait(wait);
 
    }
 
}</pre>
 
  
* Takéto polia nie sú ideálne, lebo údaje o jednom kruhu sú 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 <tt>struct</tt>
+
* Ú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-kruhy2.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 size, int radius) {
+
void generujKruh(kruh &k, int velkost, int polomer) {
     k.x = rand() % (size-2*radius) + radius;
+
    /* inicializuj kruh s nahodnou polohou
     k.y = rand() % (size-2*radius) + radius;
+
    * 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;
 +
}
 +
 
 +
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(void) {
+
int main() {
     const int count = 10; /* počet kruhov */
+
     const int pocet = 10; /* počet kruhov */
     const int size = 300; /* veľkosť obrázku */
+
     const int velkost = 300; /* veľkosť obrázku */
     const int radius = 15; /* polomer kruhu */
+
     const int polomer = 15; /* polomer kruhu */
    const int step = 4; /* o kolko padne dolu v jednom kroku */
 
    const int repeat = 100; /* pocet iteracii */
 
    const double wait = 0.2; /* cakaj po kazdej iteracii */
 
  
     /* 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], size, radius);
+
         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 >= size - radius) {
 
                generujKruh(kruhy[i], size, radius);
 
                kruhy[i].y = radius;
 
            }
 
            /* 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,
 
                    radius, radius);
 
 
         }
 
         }
        /* na konci iteracie chvilu pockaj */
 
        drawing.wait(wait);
 
 
     }
 
     }
}
 
</pre>
 
  
* Pomocou <tt>struct</tt> vytvoríme nový dátový typ <tt>kruh</tt>, ktorý má niekoľko zložiek x, y,...
+
    /* vykresluj kruhy */
** v programe vyššie sú všetky typu <tt>int</tt>, mohli by však byť aj rôznych typov (napr. <tt>struct kruh { double x,y; int id; bool visible; };</tt>)
+
    drawing.setFillColor("gray");
* Môžeme vytvárať premenné typu <tt>kruh</tt> aj polia kruhov, napr. <tt>kruh jedenKruh; kruh poleKruhov[10];</tt>
+
     for (int i = 0; i < pocet; i++) {
* K položkám kruhu pristupujeme pomocou bodky, napr. <tt>jedenKruh.x = 4; if(polekruhov[2].visible) { ... }</tt>
+
         if (kruhy[i].pretinaSa) {
 
+
             drawing.setLineColor("red");
== Eratostenovo sito ==
+
        } else {
 
+
             drawing.setLineColor("black");
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 <tt>true</tt>. Toto číslo je prvočíslo (vypíšeme ho) a vyškrtáme jeho násobky.
 
 
 
<pre>
 
#include <iostream>
 
using namespace std;
 
 
 
int main(void) {
 
    const int N = 25;
 
    bool A[N + 1];
 
 
 
     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
+
== Polia: zhrnutie ==
<pre>
 
2 3 5 7 11 13 17 19 23
 
</pre>
 
  
Priebeh programu:
+
* Pole je tabuľka hodnôt. V poli dĺžky ''n'' máme hodnoty ''p[0], p[1], ..., p[n-1]''
<pre>
+
* Kopírovanie a porovnávanie polí si musíme naprogramovať
0 1 2  3  4  5  6  7  8  9 10 11 12 ...
+
* C resp. C++ nekontrolujú, či index nie je mimo rozsahu poľa
?  ?  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).
 
  
 
== Ďalšie príklady na prácu s poľom ==
 
== Ďalšie príklady na prácu s poľom ==
Riadok 478: Riadok 549:
  
 
* Načítajte pole čísel, náhodne ich premiešajte a zase vypíšte.
 
* Načítajte pole čísel, náhodne ich premiešajte a zase vypíšte.
 
== 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
 

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.