Prednáška 5
- Oznamy
- Hľadanie chýb v programe
- Prvé použitie poľa: podpriemer / nadpriemer
- Polia
- Výskyty čísel 0…9
- Odbočka: grafická knižnica SVGdraw
- Kreslíme kruhy
- Polia: zhrnutie
- Ďalšie príklady na prácu s poľom
Oznamy
- Body z prvých dvoch cvičení aj papierovej rozcvičky máte na testovači v záložke Body.
- Opakujeme, že účasť na cvičení je povinná a že ak nie je povedané inak, máte používať počítače v učebni. Pri nedodržaní týchto podmienok a ďalších pokynov vyučujúcich na cvičeniach môžete stratiť body za rozcvičku.
- Dnes na prednáške polia, budúci pondelok hlavne ďalšie príklady a algoritmy s použitím tých častí Cčka, ktoré už poznáte.
- Budúci pondelok aj utorok na cvičeniach práca na počítači, príklady na polia.
- Niektoré príklady budú mať bonus, ak ich odovzdáte hneď prvýkrát správne. Dôvod je, aby ste si precvičili kompilovanie a spúšťanie programov na počítači.
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.
- 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.
- V našich programoch dostávate varovanie, že návratová hodnota funkcie
scanfsa ignoruje. Táto návratová hondota sa používa na kontrolu správnosti vstupu, ale my zatiaľ budeme predpokladať, že vstup je vždy správny. Toto varovanie teda môžete ignorovať.
- V našich programoch dostávate varovanie, že návratová hodnota funkcie
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 v pokročilejších prostrediach, prípadne existuje textový debugger gdb).
- 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.
Prvé použitie poľa: podpriemer / nadpriemer
Na minulej prednáške sme videli príklady, kde spracovávame veľa čísel bez toho, aby sme si ich všetky uložili do pamäte. Počítali sme tak maximum alebo priemer týchto čísel. Teraz si ukážme príklad, kde to takto nevieme.
Chceme spočítať priemer a o každom vstupnom čísle vypísať, či je nadpriemerné alebo podpriemerné.
- Priemer vieme až vtedy, 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
Nje napr. 20 (Nteda nenačítavame) - Príkaz
int a[20];vytvorí pole s 20 premennými typuint, ku ktorým pristupujemea[0],a[1], …,a[19]- pozor,
a[20]neexistuje
- pozor,
- Aby sme nemuseli všade písať 20, vytvoríme si konštantu
Ns hodnotou 20 a použijeme ju v definícii poľa aj v cykle. Na to použijeme príkaz#define N 20. V programe sa potom zaNdosadí hodnota 20.
#include <stdio.h>
/* Tento príkaz vytvorí konštantu N s hodnotou 20,
* ktorá bude predstavovať veľkosť poľa. */
#define N 20
int main() {
int a[N];
double sucet = 0;
printf("Zadavajte %d cisel: ", N);
for (int i = 0; i < N; i++) {
scanf("%d", &(a[i]));
sucet += a[i];
}
double priemer = sucet / N;
printf("Priemer je %lf.\n", priemer);
for (int i = 0; i < N; i++) {
if (a[i] > priemer) {
printf("%d: vacsie ako priemer.\n", a[i]);
} else if (a[i] < priemer) {
printf("%d: mensie ako priemer.\n", a[i]);
} else {
printf("%d: priemer.\n", a[i]);
}
}
}
Na zamyslenie:
- 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
inta nerobil žiadnu chybu v zaokrúhľovaní? - Môže aj po takejto zmene niekedy dať zlú odpoveď?
- Vedeli by ste program prerobiť tak, aby používal iba premenné
typu
Polia
Rozsah poľa je konštantný výraz väčší ako 0. Prvky sa indexujú od 0 po počet - 1
#define N 20
int a[10];
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ýchNhodnôt.
#include <stdio.h>
#define NMAX 1000
int main() {
int a[NMAX];
int N;
printf("Zadaj pocet cisel: ");
scanf("%d", &N);
if (N > NMAX) {
printf("Prilis velke N (max. %d)\n", NMAX);
return 1; // ukončíme funkciu main a tým aj program
}
printf("Zadavajte %d cisel: ", N);
int sucet = 0;
for (int i = 0; i < N; i++) {
scanf("%d", &(a[i]));
sucet += a[i];
}
double priemer = (double)sucet / N;
printf("Priemer je %lf.\n", priemer);
for (int i = 0; i < N; i++) {
if (a[i] > priemer) {
printf("%d: vacsie ako priemer.\n", a[i]);
} else if (a[i] < priemer) {
printf("%d: mensie ako priemer.\n", a[i]);
} else {
printf("%d: priemer.\n", a[i]);
}
}
}
Č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;
printf("Zadaj pocet cisel: ");
scanf("%d", &N);
int a[N];
- V starších verziách 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.
// správne: inicializácia zoznamom
int a[4] = {3, 6, 8, 10};
// správne: definícia bez inicializácie
int b[4];
// nesprávne, pole tu nedefinujeme:
// b = {3, 6, 8, 10};
// správne - meníme prvky existujúceho poľa:
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 tesne
za pole
a(čo môže byť napr. iná premenná) - Prepísanie obsahu iných miest v pamäti môže viesť k chybným výsledkom a „nevysvetliteľnému“ správaniu sa 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 < N; i++) {
b[i] = a[i];
}
Polia sa tiež nedajú porovnávať pomocou operátora ==. Podmienku if
(a==b) { printf("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 < N; i++) {
rovne = rovne && a[i] == b[i];
}
if (rovne) {
printf("Rovnaju sa\n");
}
Ten istý kúsok programu môžeme napísať napr. aj takto:
bool rovne = true;
for (int i = 0; i < N; i++) {
if(a[i] != b[i]) {
rovne = false;
break;
}
}
if (rovne) {
printf("Rovnaju sa\n");
}
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
pocet[i]bude počet výskytov čísla i
#include <stdio.h>
int main() {
int pocet[10]; // pole dlzky 10
int N;
printf("Zadajte pocet cisel: ");
scanf("%d", &N);
// inicializácia poľa pocet[0]=0; pocet[1]=0; ... pocet[9]=0;
for (int i = 0; i < 10; i++) {
pocet[i] = 0;
}
printf("Zadavajte %d cisel z intervalu 0-9: ", N);
for (int i = 0; i < N; i++) {
int x;
scanf("%d", &x);
if (x >= 0 && x < 10) { // test, či je číslo z požadovaného rozsahu
pocet[x]++; // zvýšime počítadlo pre hodnotu x
}
}
for (int i = 0; i < 10; i++) {
printf("Pocet vyskytov %d je %d\n", i, pocet[i]); // výpis
}
}
Poučenie:
- Keď máme na vstupe postupnosť čísel, často ju chceme celú uložiť do poľa a potom spracovať.
- Niekedy je ale lepšie spracovávať čísla po jednom a ukladať si len to, čo potrebujeme (napr. počítadlá výskytov).
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 so šírkou 150 a výškou 100 a
* ulož ho do súboru stvorec.svg*/
Drawing drawing = drawingInit(150, 100, "stvorec.svg");
/* Nastav farbu vyfarbovania na zelenú. */
setFillColor(drawing, "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 */
drawRectangle(drawing, 20, 10, 80, 80);
/* Nastav farbu vyfarbovania na červenú */
setFillColor(drawing, "red");
/* Vykresli kruh so stredom
* v bode (60,50) a polomerom 40. */
drawEllipse(drawing, 60, 50, 40, 40);
/* Ukonči vypisovanie obrázka. */
drawingFinish(drawing);
}
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
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 <stdlib.h>
#include <time.h>
#include <math.h>
#include <stdbool.h>
typedef struct {
int x, y; /* suradnice stredu */
int polomer; /* polomer kruhu */
bool pretinaSa; /* pretína sa s iným? */
} kruh;
/* vytvorenie kruhu s náhodnou polohou */
kruh generujKruh(int velkost, int polomer) {
kruh k;
k.x = rand() % (velkost - 2 * polomer)
+ polomer;
k.y = rand() % (velkost - 2 * polomer)
+ polomer;
k.polomer = polomer;
return k;
}
/* zistí, či sa dva kruhy pretínajú */
bool pretinajuSa(kruh k1, kruh k2) {
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;
}
/* počet kruhov */
#define POCET 10
/* veľkosť obrázku */
#define VELKOST 300
/* polomer kruhu */
#define POLOMER 15
int main() {
/* inicializácia generátora
* pseudonáhodných čísel */
srand(time(NULL));
/* inicializácia obrázku */
Drawing drawing = drawingInit(VELKOST, VELKOST, "kruhy.svg");
/* pole kruhov */
kruh kruhy[POCET];
for (int i = 0; i < POCET; i++) {
/* každému kruhu vygeneruj náhodnu polohu */
kruhy[i] = generujKruh(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;
}
}
}
/* vykresli kruhy */
setFillColor(drawing, "gray");
for (int i = 0; i < POCET; i++) {
if (kruhy[i].pretinaSa) {
setLineColor(drawing, "red");
} else {
setLineColor(drawing, "black");
}
drawEllipse(drawing, kruhy[i].x, kruhy[i].y,
kruhy[i].polomer, kruhy[i].polomer);
}
/* ukončenie vykresľovania */
drawingFinish(drawing);
}
Polia: zhrnutie
- Pole je tabuľka hodnôt. V poli
adĺžkynmáme hodnotya[0],a[1], …,a[n-1] - Kopírovanie a porovnávanie polí si musíme naprogramovať
- C nekontroluje, č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.