Prednáška 3
- Oznamy
- Opakovanie
- Ďalšie príklady na cyklus
for - Úprava a čitateľnosť programov
- Cyklus
while - Príkazy
breakacontinue, nekonečný cyklus - Viac o cykle
for - Vnorené cykly
- Cykly: zhrnutie
Oznamy
Cvičenia tento týždeň
- Na začiatku cvičení v pondelok aj utorok sa na testovači objaví nová sada úloh.
- Prvá úloha je rozcvička, treba ju odovzdať do konca cvičenia, začnite teda touto úlohou.
- Zvyšok cvičenia riešte ďalšie úlohy.
- Čo nestihnete na cvičení, môžete dokončiť doma.
- Termín odovzdania je ďalší pondelok večer 22:00.
- Úlohy sa dajú odovzdávať aj po termíne, ale nedostanete za ne body, ak sme vám neudelili výnimku.
- Ak vám testovač k príkladu vypíše zelené OK, prešiel testami. Ak vypíše oranžový kód chyby, niečo je zle, treba opraviť a odovzdať znovu.
- Do dnes večera 22:00 je možné odovzdávať aj úlohy z predchádzajúceho týždňa, ak ste ich nestihli odovzdať.
Opakovanie
Videli sme:
- základy použitia funkcií
printfascanfna načítanie a výpis - premenné typu
int,double,boola operátory, ktoré s nimi vedia počítať - podmienku
if - cyklus
for
Tu je program, ktorý vypisoval čísla od 1 po n pre zadané n
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
printf(" %d", i);
}
printf("\n");
}
Ďalšie príklady na cyklus for
Výpočet faktoriálu
Nasledujúci program si od používateľa vypýta číslo n a vypočíta n!, t.j. súčin celých čísel od 1 po n.
#include <stdio.h>
int main() {
int n;
printf("Zadajte n: ");
scanf("%d", &n);
int vysledok = 1;
for (int i = 1; i <= n; i++) {
vysledok = vysledok * i;
}
printf("%d! = %d\n", n, vysledok);
}
Príklad behu programu pre n=4 (1*2*3*4=24):
Zadajte n: 4
4! = 24
- Program používa premennú
vysledok, ktorú na začiatku inicializuje hodnotou 1 a postupne ju násobí číslami 1, 2, …, n. - Riadok
vysledok = vysledok * i;zoberie pôvodnú hodnotu premennejvysledok, vynásobí ju hodnotou premenneji(t.j. jedným z čísel 1, 2, …, n) a výsledok uloží naspäť do premennejvysledok(prepíše pôvodnú hodnotu). To isté sa dá napísať akovysledok *= i;
Tento program ale funguje správne iba pre n<13, lebo n! veľmi rýchlo rastie a už pre n=13 sa výsledok nezmestí do premennej typu int. Dostávame nezmyselné hodnoty:
12! = 479001600
13! = 1932053504
14! = 1278945280
15! = 2004310016
16! = 2004189184
17! = -288522240
Správne hodnoty (ktoré možno získať zmenou typu premennej vysledok na
long long int) sú:
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
16! = 20922789888000
17! = 355687428096000
(Aj typ long long int má však svoj limit, ktorý pri faktoriále rýchlo prekročíme.)
Cvičenie: rozšírte program tak, aby okrem výpisu výsledku aj rozpísal faktoriál ako súčin:
Zadajte n: 4
4! = 1*2*3*4 = 24
Simulovanie hodov kocky
Nasledujúci program od používateľa načíta číslo n a vypíše n
simulovaných hodov kocky (každý na samostatný riadok).
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
// Inicializácia generátora pseudonáhodných čísel
srand(time(NULL));
int n;
printf("Zadajte pocet hodov: ");
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
// Vygenerovanie a vypisanie hodu kockou
printf("%d\n", rand() % 6 + 1);
}
}
Príklad behu programu:
Zadajte pocet hodov: 5
2
6
1
2
1
- Program využíva funkciu
rand(), ktorá generuje pseudonáhodné celé čísla.- Nie sú v skutočnosti náhodné, lebo ide o pevne definovanú matematickú postupnosť, ktorá však má mnohé vlastnosti náhodných čísel.
- Aby bolo možné použiť túto funkciu, treba do hlavičky pridať
#include <stdlib.h>. - Výstupom funkcie
rand()je celé číslo medzi nulou a nejakou veľkou konštantou. - Zvyšok po delení tohto čísla šiestimi, t.j.
rand() % 6, je potom číslo medzi 0 a 5. Ak k tomu pripočítame 1, dostaneme číslo od 1 po 6.
- Funkcia
srandinicializuje generátor pseudonáhodných čísel na základe parametra určujúceho počiatočný bod pseudonáhodnej postupnosti.- My ako tento parameter používame aktuálny čas (v sekundách od začiatku roku 1970), čo pre našu ukážku vytvorí dostatočný efekt náhodnosti.
- Aby bolo možné použiť funkciu
time, je treba do hlavičky pridať#include <time.h>.
Vypisovanie deliteľov (podmienka v cykle)
Nasledujúci program načíta od používateľa prirodzené číslo n a vypíše zoznam jeho deliteľov.
- číslo i delí číslo n práve vtedy, keď zvyšok po delení čísla n číslom i je 0
#include <stdio.h>
int main() {
int n;
printf("Zadajte cislo: ");
scanf("%d", &n);
printf("Delitele cisla %d:", n);
for (int i = 1; i <= n; i++) {
if (n % i == 0) {
printf(" %d", i);
}
}
printf("\n");
}
Príklad behu programu:
Zadajte cislo: 30
Delitele cisla 30: 1 2 3 5 6 10 15 30
Úprava a čitateľnosť programov
Pri písaní programov myslite na to, že ich často budú okrem počítača čítať aj ľudia (napríklad vy po dlhšom čase, učitelia, alebo kolegovia na väčšom projekte). Je preto zvykom dodržiavať určité zásady, ktoré čitateľnosť zdrojového kódu zlepšujú:
- Odsadzovanie: príkazy vykonávané v cykle, či v podmienke (alebo
vo všeobecnosti v ľubovoľnom bloku medzi
{a}) odsadzujeme o niekoľko pozícií doprava (napríklad o 4 medzery).- Pri vnorených cykloch, podmienkach a podobne odsadzujeme o ďalšie štyri pozície, atď.
- Väčšina editorov pre C nejakým spôsobom odsadzovanie podporuje
- Voľné riadky: ucelené časti programu je kvôli prehľadnosti často dobré oddeliť prázdnym riadkom.
- Medzery: odporúča sa písať okolo operátorov medzery.
- Napríklad zápis
for (int i = 1; i <= n; i++) {
sucet += i;
}
je o dosť prehľadnejší ako
for(int i=1;i<=n;i++){sucet+=i;}
- Dĺžka riadku: odporúča sa vyhýbať sa riadkom dlhším ako 80 znakov. S dlhými riadkami sú problémy pri tlači alebo zobrazovaní v menších oknách; aj na veľkom monitore čitateľa zbytočne namáhajú. V prípade potreby je možné dlhšiu podmienku alebo iný výraz rozdeliť na viac riadkov.
- Názvy premenných: najmä pri rozsiahlejších programoch je vhodné
používať názvy premenných, ktoré vyjadrujú ich obsah (napríklad
userCountalebopocet_pouzivatelovnamiestoh). Premenné v kratších programoch, alebo premenné používané iba lokálne v kratšom kuse programu možno označovať aj krátkymi zaužívanými názvami, ako napríkladiajpre premenné v cykloch,npre počet, aleboapre pole. - Komentáre: význam jednotlivých úsekov kódu je najmä pri rozsiahlejších programoch dobré popísať v komentároch.
Pri známkovaní budeme brať do úvahy aj prehľadnosť vašich programov.
Cyklus while
Okrem cyklu for možno v jazyku C používať aj cyklus while s
nasledujúcou schémou:
while (podmienka) {
telo cyklu
}
Telo takéhoto cyklu sa vykonáva, kým je podmienka cyklu splnená.
Presnejšie sa pri vykonávaní cyklu while typicky deje nasledovné:
- Overí sa, či je podmienka cyklu splnená.
- Ak áno, vykoná sa telo cyklu a celý proces sa opakuje (čiže sa opätovne vykoná overenie podmienky z bodu 1, atď.).
- Ak nie, vykonávanie programu pokračuje prvým príkazom nasledujúcim
za cyklom
while.
Úroky v banke
- Predpokladajme, že na začiatku každého roku uložíme na účet nejakú pevnú sumu (napríklad 1000 EUR).
- Na konci každého roku sa vklad zúročí ročným úrokom (napríklad 5%).
- Za koľko rokov úspory dosiahnu danú cieľovú čiastku (napríklad 10000 EUR)?
Túto úlohu budeme riešiť programom pracujúcim nasledovne:
- V premennej
ucetbudeme uchovávať aktuálny stav účtu - V každom roku túto premennú zvýšime o vklad a úrok
- Toto opakujeme, kým suma uložená v premennej nie je rovná aspoň cieľovej čiastke.
To môžeme vyjadriť pomocou cyklu while.
#include <stdio.h>
int main(void) {
double vklad, ciel, urok;
printf("Zadaj kazdorocny vklad: ");
scanf("%lf", &vklad);
printf("Zadaj cielovu ciastku: ");
scanf("%lf", &ciel);
printf("Zadaj rocny urok (v %%): ");
scanf("%lf", &urok);
int rok = 0;
double ucet = 0;
while (ucet < ciel) {
rok++;
ucet = (ucet + vklad) * (1 + urok / 100);
printf("Na konci roku %d je na ucte %lf EUR.\n", rok, ucet);
}
}
Ukážkový vstup a výstup:
Zadaj kazdorocny vklad: 1000
Zadaj cielovu ciastku: 10000
Zadaj rocny urok (v %): 5
Na konci roku 1 je na ucte 1050 EUR
Na konci roku 2 je na ucte 2152.5 EUR
Na konci roku 3 je na ucte 3310.12 EUR
Na konci roku 4 je na ucte 4525.63 EUR
Na konci roku 5 je na ucte 5801.91 EUR
Na konci roku 6 je na ucte 7142.01 EUR
Na konci roku 7 je na ucte 8549.11 EUR
Na konci roku 8 je na ucte 10026.6 EUR
Euklidov algoritmus
Euklidov algoritmus slúži na hľadanie najväčšieho spoločného deliteľa dvojice kladných celých čísel a, b.
- To znamená: hľadáme najväčšie kladné prirodzené číslo d, ktoré delí súčasne a aj b.
- Najväčší spoločný deliteľ čísel a, b označujeme gcd(a,b) (z angl. greatest common divisor). V slovenčine sa používa aj s označenie nsd(a,b).
- Ide o jeden z najstarších známych algoritmov vôbec. Jeho variant popísal už Euklides v diele Základy okolo roku 300 pred Kr.
Príklad:
- Delitele čísla 12 sú 1, 2, 3, 4, 6, 12.
- Delitele čísla 8 sú 1, 2, 4, 8.
- Spoločné delitele 8 a 12 teda sú 1, 2, 4.
- Najväčší spoločný deliteľ čísel 8 a 12 je teda gcd(8,12) = 4.
Euklidov algoritmus je založený na platnosti nasledujúcej lemy.
Lema. Pre všetky kladné celé čísla a, b platí:
- gcd(a,b) = gcd(b, a mod b).
Dôkaz.
- Nech X je množina spoločných deliteľov a a b, nech Y je množina spoločných deliteľov b a a mod b. Dokážeme rovnosť X = Y.
- Ak označíme r := a mod b, tak existuje celé číslo q také, že a = q b + r.
- Ak x ∈ Y, číslo x delí b aj r a z rovnosti a = q b + r vyplýva, že delí aj a. Teda x ∈ X.
- Ak naopak x ∈ X, číslo x delí a aj b a z rovnosti r = a - qb vyplýva, že delí aj r. Preto x ∈ Y.
- Dokázali sme teda, že platí X ⊆ Y a súčasne X ⊆ Y. Preto X = Y. ◻
Poznámka: môže sa stať, že a mod b je 0. Nakoľko ale každé celé číslo delí nulu, gcd(b, 0) = b pre každé kladné b.
- Euklidov algoritmus opakovane používa lemu: gcd(12,8) = gcd(8, 4) = gcd(4, 0) = 4
- Dostáva sa k stále menším číslam, až kým b neklesne na nulu
Implementácia tohto algoritmu teda môže vyzerať nasledovne:
#include <stdio.h>
int main() {
int a, b;
printf("Zadaj dvojicu kladnych celych cisel: ");
scanf("%d %d", &a, &b);
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
printf("Najvacsi spolocny delitel je %d.\n", a);
}
Príklad behu programu:
Zadaj dvojicu kladnych celych cisel: 30 8
Najvacsi spolocny delitel je 2.
Tento výpočet prešiel cez dvojice:
30 8
8 6
6 2
2 0
Cvičenie: Ako bude fungovať Euklidov algoritmus pre vstupné čísla 8, 30?
Hra „hádaj číslo”
V nasledujúcom programe si počítač „myslí” číslo od 1 do 100 a užívateľ háda, o ktoré číslo ide (až kým nakoniec neuhádne).
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
int main() {
srand(time(NULL));
int num = rand() % 100 + 1;
printf("Myslim si cislo od 1 po 100. Tvoj tip: ");
bool correct = false;
while (!correct) {
int guess;
scanf("%d", &guess);
if (guess < num) {
printf("Prilis nizko. Iny tip: ");
} else if (guess > num) {
printf("Prilis vysoko. Iny tip: ");
} else {
correct = true;
printf("Spravne!\n");
}
}
}
Príklad behu programu:
Myslim si cislo od 1 po 100. Tvoj tip: 50
Prilis nizko. Iny tip: 70
Prilis nizko. Iny tip: 90
Prilis vysoko. Iny tip: 80
Spravne!
Príkazy break a continue, nekonečný cyklus
V C existuje dvojica príkazov umožňujúcich umelo prerušiť vykonávanie cyklu resp. jednej jeho iterácie:
- Príkaz
breakukončí cyklus, v ktorom sa program práve nachádza; vykonávanie programu pokračuje prvým príkazom za cyklom. - Príkaz
continue„skočí” na ďalšiu iteráciu cyklu, pričom nevykoná zvyšok tela cyklu.
Tieto príkazy treba používať s mierou, keďže robia program menej prehľadným a sú častým zdrojom chýb.
Nekonečný cyklus
V cykle while typu
while (true) {
telo cyklu
}
je podmienka stále splnená; telo cyklu sa teda opakuje donekonečna resp.
kým program nezastavíme (v prípade, že cyklus neukončíme umelo príkazom
break).
Napríklad môžeme donekonečna niečo vypisovať:
#include <stdio.h>
int main() {
while (true) {
printf("Hello, World!\n");
}
}
Hra „hádaj číslo” s príkazom break
Program uvedený vyššie môžeme prepísať bez boolovskej premennej s
použitím nekonečného cyklu, z ktorého ale „vyskočíme” príkazom
break, keď používateľ uhádne.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
int main() {
srand(time(NULL));
int num = rand() % 100 + 1;
printf("Myslim si cislo od 1 po 100. Tvoj tip: ");
while (true) {
int guess;
scanf("%d", &guess);
if (guess < num) {
printf("Prilis nizko. Iny tip: ");
} else if (guess > num) {
printf("Prilis vysoko. Iny tip: ");
} else {
printf("Spravne!\n");
break;
}
}
}
Viac o cykle for
Cyklus for sme zatiaľ používali iba základným spôsobom; jeho
možnosti sú väčšie. Vo všeobecnosti možno schému cyklu for
popísať nasledovne:
for (prikaz1; podmienka; prikaz2) {
postupnost_prikazov
}
Takýto cyklus potom pracuje nasledovne:
- Vykoná sa príkaz
prikaz1. - Kým platí podmienka
podmienka, vykonáva sa telo cyklupostupnost_prikazovzakaždým nasledované príkazomprikaz2.
Uvedený cyklus for je teda typicky ekvivalentný nasledujúcemu cyklu
while:
prikaz1;
while (podmienka) {
postupnost_prikazov
prikaz2;
}
(Drobnou výnimkou je prípad, keď telo cyklu
for obsahuje príkaz continue. V takom prípade sa príkaz prikaz2 aj
tak vykoná.)
Nasledujúce kúsky kódu napríklad obidva vypisujú čísla 1 až 9:
for (int i = 1; i <= 9; i++) {
printf(" %d", i);
}
int i = 1;
while (i <= 9) {
printf(" %d", i);
i++;
}
Cyklus for spravidla používame, ak má náš cyklus krátky a jednoduchý
iterátor (prikaz2) a jednoduchú podmienku. V opačnom prípade väčšinou
používame cyklus while.
Vypisovanie deliteľov od najväčších
Vráťme sa k programu na vypisovanie všetkých deliteľov čísla n z úvodu tejto prednášky. Tam sme deliteľov vypisovali v poradí od najmenšieho po najväčší, a to použitím cyklu
for (int i = 1; i <= n; i++)
Nahradením tohto cyklu cyklom
for (int i = n; i >= 1; i--)
získame program vypisujúci delitele v poradí od najväčšieho po najmenší.
#include <stdio.h>
int main() {
int n;
printf("Zadajte cislo: ");
scanf("%d", &n);
printf("Delitele cisla %d:", n);
for (int i = n; i >= 1; i--) {
if (n % i == 0) {
printf(" %d", i);
}
}
printf("\n");
}
Príklad behu programu:
Zadajte cislo: 30
Delitele cisla 30: 30 15 10 6 5 3 2 1
Rýchlejšie hľadanie deliteľov
Program na vypisovanie deliteľov môžeme o niečo urýchliť:
- Stačí si všimnúť, že i je deliteľ čísla n práve vtedy, keď n/i je deliteľom čísla n.
- Číslo n/i teda môžeme rovno vypísať spoločne s i.
- Aspoň jedno z tejto dvojice čísel je navyše menšie alebo rovné odmocnine z n, čo znamená, že stačí prehľadať iba čísla i spĺňajúce túto podmienku.
#include <stdio.h>
int main() {
int n;
printf("Zadajte cislo: ");
scanf("%d", &n);
printf("Delitele cisla %d:", n);
for (int i = 1; i*i <= n; i++) {
if (n % i == 0) {
printf(" %d %d", i, n / i);
}
}
printf("\n");
}
Cvičenie 1: Občas sa môže stať, že program vypíše niektorého deliteľa
dvakrát. Kedy? Modifikujte telo cyklu for tak, aby program každého
deliteľa vypísal práve raz.
Cvičenie 2: Vyskúšajte si rýchlosť rôznych variantov programu na vypisovanie deliteľov na veľkom vstupe, napríklad pre n = 1234567890.
Nekonečný cyklus for
V cykle
for (prikaz1; podmienka; prikaz2) {
postupnost_prikazov
}
môžu byť príkazy prikaz1 a prikaz2 aj prázdne – v takom prípade sa
na ich mieste nič nevykoná. Podobne môže byť prázdna aj podmienka
podmienka, ktorá sa v takom prípade interpretuje ako true.
Nekonečný cyklus možno teda napísať aj ako
for ( ; true; ) {
printf("Hello, World!" "\n");
}
prípadne ako
for ( ; ; ) {
printf("Hello, World!" "\n");
}
Vnorené cykly
Vypíšeme tabuľku násobilky, v ktorej bude v riadku i a stĺpci j súčin i ⋅ j.
- Použijeme dva vnorené cykly: jeden pôjde cez riadky, druhý cez stĺpce.
#include <stdio.h>
int main() {
int n; // pokial ma ist nasobilka
scanf("%d", &n);
for (int riadok = 1; riadok <= n; riadok++) {
for (int stlpec = 1; stlpec <= n; stlpec++) {
printf(" %d", riadok * stlpec);
}
printf("\n");
}
}
Ukážka výstupu pre n=5:
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
Ak pred každé číslo vypíšeme namiesto medzery " " tabulátor "\t",
dostaneme krajší výstup
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
Cvičenie: pridajme jednotlivým riadkom a stĺpcom hlavičku
Cykly: zhrnutie
- Videli sme niekoľko príkladov využitia cyklov
forawhile. - Cyklus
forje možné zapísať akowhile(a naopak). - Z cyklu vieme vyskočiť príkazom
break, prejsť na ďalšiu iteráciu príkazomcontinue. Používať s mierou. - Euklidov algoritmus rýchlo nájde najväčšieho spoločného deliteľa dvoch čísel.