Programovanie (1) v C/C++
1-INF-127, ZS 2024/25
Prednáška 5: Rozdiel medzi revíziami
(→Oznamy) |
|||
(41 medziľahlých úprav od 3 ďalších používateľov nie je zobrazených) | |||
Riadok 1: | Riadok 1: | ||
== Oznamy == | == Oznamy == | ||
+ | * Dokončenie predchádzajúcej [[Prednáška 4b|prednášky (funkcie)]]. | ||
+ | * 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. | ||
+ | * 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 | |
− | * | + | ** 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 == | == Záznam typu struct == | ||
Riadok 27: | Riadok 33: | ||
V príkladoch s obvodom trojuholníka a stredom úsečky sme funkciám posielali veľa parametrov (súradnice x a y) | 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++"> | <syntaxhighlight lang="C++"> | ||
− | void stred(double x1, double y1, double x2, double y2, double & | + | void stred(double x1, double y1, double x2, double y2, double &xm, double &ym) { |
− | + | xm = (x1 + x2) / 2; | |
− | + | ym = (y1 + y2) / 2; | |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
Program bude krajší, ak si údaje o jednom bode spojíme do jedného záznamu | Program bude krajší, ak si údaje o jednom bode spojíme do jedného záznamu | ||
Riadok 40: | Riadok 45: | ||
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * Pomocou <tt>struct</tt> vytvoríme nový dátový typ <tt>bod</tt>, ktorý má zložky x a y | + | * 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> | * 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> | * K položkám bodu pristupujeme pomocou bodky, napr. <tt>a.x = 4.0; </tt> | ||
Riadok 70: | Riadok 81: | ||
void vypisBod(bod &b) { | void vypisBod(bod &b) { | ||
− | // funkcia vypise | + | // funkcia vypise suradnice bodu v zatvorke a koniec riadku |
cout << "(" << b.x << "," << b.y << ")" << endl; | cout << "(" << b.x << "," << b.y << ")" << endl; | ||
} | } | ||
− | int main( | + | int main() { |
// nacitame suradnice vrcholov trojuholnika | // nacitame suradnice vrcholov trojuholnika | ||
bod A, B, C; | bod A, B, C; | ||
Riadok 121: | Riadok 132: | ||
== Spracovanie väčšieho množstva dát == | == 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 | + | 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 | + | * 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 | V nasledujúcich príkladoch užívateľ zadá číslo ''N'' a potom ''N'' celých čísel | ||
Riadok 134: | Riadok 145: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
int N; | int N; | ||
cout << "Zadaj pocet cisel: "; | cout << "Zadaj pocet cisel: "; | ||
Riadok 153: | Riadok 164: | ||
* Čo by sa stalo, keby sme vo výpočte priemeru vynechali <tt>(double)</tt>? | * Čo by sa stalo, keby sme vo výpočte priemeru vynechali <tt>(double)</tt>? | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
=== Maximum === | === Maximum === | ||
Riadok 189: | Riadok 171: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
int max, x, N; | int max, x, N; | ||
Riadok 210: | Riadok 192: | ||
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. | + | * 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 | + | * 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) | + | * Ď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). |
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
Riadok 218: | Riadok 200: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
int max, x, N; | int max, x, N; | ||
Riadok 241: | Riadok 223: | ||
'''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? |
− | |||
− | == | + | == 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é. | ||
Riadok 257: | Riadok 238: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | 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 a[N]; | int a[N]; | ||
Riadok 302: | Riadok 283: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
const int NMax = 1000; | const int NMax = 1000; | ||
int p[NMax]; | int p[NMax]; | ||
Riadok 333: | Riadok 314: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | int A[4]={3, 6, 8, 10}; //spravne | + | // spravne: inicializacia zoznamom |
− | int B[4]; // | + | int A[4] = {3, 6, 8, 10}; |
− | B[4]={3, 6, 8, 10}; // | + | // spravne: definicia bez inicializacie |
− | B[0]=3; B[1]=6; B[2]=8; B[3]=10; | + | 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; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Riadok 355: | Riadok 340: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | + | for (int i=0; i < 10; i++) { | |
+ | b[i] = a[i]; | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Polia sa tiež nedajú porovnávať pomocou operátora <tt>==</tt>. Podmienku <tt>if (a==b) cout << " | + | 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. | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | + | bool rovne = true; | |
− | + | for (int i = 0; i < 10; i++) { | |
− | + | rovne = rovne && a[i] == b[i]; | |
− | + | } | |
− | + | if (rovne) { | |
+ | cout << "Rovnaju sa" << endl; | ||
+ | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Riadok 373: | Riadok 362: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | + | 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> | </syntaxhighlight> | ||
== Výskyty čísel 0...9 == | == 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. | + | 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ý | + | Prvý prístup: |
* vstupné čísla uložíme do poľa | * 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'' | * pre každú hodnotu ''i'' od 0 po 9 prejdeme pole a spočítame počet výskytov ''i'' | ||
− | Druhý prístup | + | Druhý prístup: |
* samotné vstupné čísla neukladáme do poľa, spracovávame ich po jednom | * 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'' | * vytvoríme si pole počítadiel dĺžky 10, v ktorom p[i] bude počet výskytov čísla ''i'' | ||
Riadok 399: | Riadok 390: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
int p[10]; // pole dlzky 10 | int p[10]; // pole dlzky 10 | ||
int N; | int N; | ||
Riadok 423: | Riadok 414: | ||
} | } | ||
}</syntaxhighlight> | }</syntaxhighlight> | ||
− | |||
− | |||
== Odbočka: grafická knižnica SVGdraw == | == Odbočka: grafická knižnica SVGdraw == | ||
Riadok 430: | Riadok 419: | ||
[[Image:P5-svgdraw.png|frame|Výsledný obrázok]] | [[Image:P5-svgdraw.png|frame|Výsledný obrázok]] | ||
* Aplikácie s grafickým rozhraním budeme programovať až budúci semester | * 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 [[ | + | * 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 [[ | + | * 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> | * 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: | * Tu je malý ukážkový program, ktorý vykreslí zelený štvorec a v ňom červený kruh: | ||
Riadok 437: | Riadok 426: | ||
#include "SVGdraw.h" | #include "SVGdraw.h" | ||
− | int main( | + | int main() { |
/* Vytvor obrázok s šírkou 150 a výškou 100 a | /* Vytvor obrázok s šírkou 150 a výškou 100 a | ||
* ulož ho do súboru stvorec.svg*/ | * ulož ho do súboru stvorec.svg*/ | ||
Riadok 444: | Riadok 433: | ||
/* Nastav farbu vyfarbovania na zelenú. */ | /* Nastav farbu vyfarbovania na zelenú. */ | ||
drawing.setFillColor("green"); | drawing.setFillColor("green"); | ||
− | /* Vykresli štvorec s ľavým horným rohom v bode (20,10) | + | /* Vykresli štvorec s ľavým horným |
− | * a s dĺžkou strany 80, t.j. pravým dolným rohom 100,90 */ | + | * 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); | drawing.drawRectangle(20,10,80,80); | ||
− | /* Nastav farbu vyfarbovania na červenú | + | /* Nastav farbu vyfarbovania na červenú */ |
drawing.setFillColor("red"); | drawing.setFillColor("red"); | ||
− | /* Vykresli kruh so stredom v bode (60,50) a polomerom 40. */ | + | /* Vykresli kruh so stredom |
+ | * v bode (60,50) a polomerom 40. */ | ||
drawing.drawEllipse(60,50,40,40); | drawing.drawEllipse(60,50,40,40); | ||
Riadok 461: | Riadok 453: | ||
[[Image:P5-kruhy.png|thumb|150px]] | [[Image:P5-kruhy.png|thumb|150px]] | ||
− | Nasledujúci program náhodne vygeneruje súradnice niekoľkých kruhov a vykreslí ich pomocou | + | 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 <tt>struct kruh</tt>, v programe používame pole takýchto kruhov. | * Ú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. | * 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. | ||
Riadok 474: | Riadok 466: | ||
int x, y; /* suradnice stredu */ | int x, y; /* suradnice stredu */ | ||
int polomer; /* polomer kruhu */ | int polomer; /* polomer kruhu */ | ||
− | bool pretinaSa; /* pretína sa s iným | + | bool pretinaSa; /* pretína sa s iným? */ |
}; | }; | ||
void generujKruh(kruh &k, int velkost, int polomer) { | void generujKruh(kruh &k, int velkost, int polomer) { | ||
− | /* inicializuj kruh s nahodnou polohou a danym polomerom */ | + | /* inicializuj kruh s nahodnou polohou |
− | k.x = rand() % (velkost - 2 * polomer) + polomer; | + | * a danym polomerom */ |
− | k.y = rand() % (velkost - 2 * polomer) + polomer; | + | k.x = rand() % (velkost - 2 * polomer) |
+ | + polomer; | ||
+ | k.y = rand() % (velkost - 2 * polomer) | ||
+ | + polomer; | ||
k.polomer = polomer; | k.polomer = polomer; | ||
} | } | ||
Riadok 492: | Riadok 487: | ||
} | } | ||
− | int main( | + | int main() { |
const int pocet = 10; /* počet kruhov */ | const int pocet = 10; /* počet kruhov */ | ||
const int velkost = 300; /* veľkosť obrázku */ | const int velkost = 300; /* veľkosť obrázku */ | ||
const int polomer = 15; /* polomer kruhu */ | 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)); | ||
Riadok 515: | Riadok 511: | ||
kruhy[i].pretinaSa = false; | kruhy[i].pretinaSa = false; | ||
for (int j = 0; j < pocet; j++) { | for (int j = 0; j < pocet; j++) { | ||
− | if (i != j && pretinajuSa(kruhy[i], kruhy[j])) { | + | if (i != j && pretinajuSa(kruhy[i], |
+ | kruhy[j])) { | ||
kruhy[i].pretinaSa = true; | kruhy[i].pretinaSa = true; | ||
} | } | ||
Riadok 529: | Riadok 526: | ||
drawing.setLineColor("black"); | drawing.setLineColor("black"); | ||
} | } | ||
− | drawing.drawEllipse(kruhy[i].x, kruhy[i].y, | + | drawing.drawEllipse(kruhy[i].x, |
− | kruhy[i].polomer, kruhy[i].polomer); | + | kruhy[i].y, |
+ | kruhy[i].polomer, | ||
+ | kruhy[i].polomer); | ||
} | } | ||
Riadok 538: | Riadok 537: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == | + | == 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 == | == Ďalšie príklady na prácu s poľom == | ||
Riadok 592: | 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. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− |
Verzia zo dňa a času 17:55, 3. október 2023
Obsah
Oznamy
- Dokončenie predchádzajúcej prednášky (funkcie).
- 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.
- 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
- Pomáha 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)
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
- 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
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.