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

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(29 medziľahlých úprav od rovnakého používateľa nie je zobrazených.)
Riadok 1: Riadok 1:
 
== Oznamy ==
 
== Oznamy ==
* Na piatkových doplnkových cvičeniach bude bonusová rozcvička (za 1 bonusový bod). Zvyšné úlohy z tohto týždňa treba odovzdať do ''stredy 18. novembra, 22:00''.
+
* DÚ2 odovzdávajte do zajtra 22:00, posledná DÚ bude zverejnená v stredu.
* Druhú domácu úlohu treba odovzdať do ''piatku 13. novembra, 22:00''.
+
* Rozcvička bude z dnešnej prednášky, zvyšok cvičení hlavne spájané zoznamy z minulého pondelka.
* Budúci týždeň budú kvôli štátnemu sviatku iba piatkové cvičenia. V ''pondelok 16. novembra'' bude zverejnených niekoľko úloh na cvičenia č. 9 (menej, než obvykle).
+
* Piatkové cvičenia sú povinné pre tých, ktorí nedokončia načas rozcvičku.
* V ''piatok 20. novembra'' bude na začiatku doplnkových cvičení krátky test, body za ktorý budú riadnou súčasťou hodnotenia z cvičení č. 9. Pokyny ohľadom technickej realizácie testu budú upresnené neskôr.
+
* Nerobte zbytočné zmeny v poskytnutej kostre.
 +
* Pri práci so smerníkmi odporúčame nakresliť si obrázok.
 +
 
 +
== Smerníková aritmetika ==
 +
 
 +
Na smerníkoch možno vykonávať určité operácie, ktoré sa zvyknú nazývať smerníková aritmetika. Uvažujme číslo <tt>n</tt> typu <tt>int</tt> a smerníky <tt>p</tt> a <tt>q</tt> na nejaký typ <tt>T</tt>.
 +
<syntaxhighlight lang="C++">
 +
int n;
 +
T * p, * q;
 +
</syntaxhighlight>
 +
* <tt>p + n</tt> je smerník na <tt>n</tt>-té políčko za adresou <tt>p</tt>, pričom veľkosť políčka je daná typom <tt>T</tt>
 +
** <tt>p + n</tt> je teda to isté ako <tt>&(p[n])</tt> a <tt>*(p+n)</tt> je to isté ako <tt>p[n]</tt>.
 +
** <tt>p++</tt> je skratkou pre <tt>p = p + 1</tt>, posunie nám teda smerník <tt>p</tt> o políčko doprava.
 +
** Výraz <tt>p[n]</tt> je len skratkou pre <tt>*(p+n)</tt>.
 +
** <tt>p[n]</tt> aj <tt>p + n</tt> teda chceme používať iba ak <tt>p</tt> ukazuje na prvok poľa, za ktorým v poli ide ešte aspoň <tt>n</tt> ďalších políčok
 +
* Podobne <tt>p - n</tt> je smerník na <tt>n</tt>-té políčko pred adresou <tt>p</tt>.
 +
** <tt>p - n</tt> teda chceme používať iba ak <tt>p</tt> ukazuje na prvok poľa, pred ktorým v poli ide ešte aspoň <tt>n</tt> ďalších políčok
 +
* Ak <tt>p</tt> a <tt>q</tt> sú adresami prvkov v tom istom poli, <tt>p - q</tt> je celé číslo <tt>k</tt> také, že <tt>p == q + k</tt>, t.j. o koľko políčok je <tt>p</tt> ďalej vpravo od <tt>q</tt>.
 +
* Ak <tt>p</tt> a <tt>q</tt> sú adresami prvkov v tom istom poli, môžeme ich tiež porovnávať pomocou <tt><, >, <=, >=</tt>
 +
* Ľubovoľné dva smerníky toho istého typu vieme porovnávať pomocou <tt>==, !=</tt>.
 +
 
 +
Tu je napr. zvláštny spôsob ako vypísať pole <tt>a</tt>:
 +
<syntaxhighlight lang="C++">
 +
const int n = 4;
 +
int a[n] = {4, 3, 2, 1};
 +
for (int * smernik = a; smernik < a + n; smernik++) {
 +
    cout << "Prvok " << smernik - a << " je " << *smernik << endl;
 +
}
 +
</syntaxhighlight>
 +
 
 +
Podobný kód sa ale občas používa na prechádzanie reťazcov. Napríklad nasledujúca funkcia spočíta počet medzier v reťazci:
 +
 
 +
<syntaxhighlight lang="C++">
 +
int zratajMedzery(char str[]) { // mohli by sme dať aj char *str
 +
  int pocet = 0;
 +
  while(*str != 0) {  // kým nenájdeme ukončovaciu nulu
 +
    if(*str == ' ') { // skontroluj znak, na ktorý ukazuje str
 +
        pocet++;
 +
    }
 +
    str++;          // posuň str na ďalší znak
 +
  }
 +
  return pocet;
 +
}
 +
</syntaxhighlight>
 +
 
 +
=== Funkcie z knižnice cstring so smerníkovou aritmetikou===
 +
 
 +
* <tt>strstr(text, vzorka)</tt> vracia smerník na <tt>char</tt>
 +
** <tt>NULL</tt> ak sa vzorka nenachádza v texte, smerník na začiatok prvého výskytu inak
 +
** pozíciu výskytu zistíme smerníkovou aritmetikou:
 +
<pre>
 +
char * text = "Hello world!";
 +
char * vzorka = "or";
 +
char * where = strstr(text, vzorka);
 +
if(where != NULL) {
 +
  int position = where - text;
 +
}
 +
</pre>
 +
* Ako by ste pomocou volaní <tt>strstr</tt> spočítali počet výskytov vzorky v texte?
 +
* Podobne <tt>strchr</tt> hľadá prvý výskyt znaku v texte.
  
 
== Práca s konzolou na spôsob jazyka C: <tt>printf</tt> a <tt>scanf</tt> ==
 
== Práca s konzolou na spôsob jazyka C: <tt>printf</tt> a <tt>scanf</tt> ==
  
Doposiaľ sme s konzolou pracovali prostredníctvom knižnice <tt>[http://www.cplusplus.com/reference/iostream/ iostream]</tt>, ktorá patrí medzi štandardné knižnice jazyka C++ a v ktorej sú definované štandardné vstupno-výstupné prúdy <tt>cin</tt> a <tt>cout</tt>. V nasledujúcom si ukážeme alternatívny prístup k práci s konzolou založený na knižnici [http://www.cplusplus.com/reference/cstdio/ <tt>cstdio</tt>], ktorá je štandardnou knižnicou jazyka C. Podobne ako nižšie sa tak so vstupom a výstupom dá pracovať aj v jazyku C.  
+
* Doposiaľ sme s konzolou pracovali prostredníctvom knižnice <tt>[http://www.cplusplus.com/reference/iostream/ iostream]</tt>, ktorá patrí medzi štandardné knižnice jazyka C++ a v ktorej sú definované prúdy <tt>cin</tt> a <tt>cout</tt>.  
 +
* Dnes si ukážeme alternatívny prístup k práci s konzolou založený na knižnici [http://www.cplusplus.com/reference/cstdio/ <tt>cstdio</tt>], ktorá je štandardnou knižnicou jazyka C.  
  
 
=== Výpis formátovaných dát na konzolu: <tt>printf</tt> ===
 
=== Výpis formátovaných dát na konzolu: <tt>printf</tt> ===
  
S použitím knižnice <tt>cstdio</tt> možno na konzolu písať pomocou funkcie <tt>[http://www.cplusplus.com/reference/cstdio/printf/ printf]</tt>. Jej základné použitie môže vyzerať napríklad takto:
+
* S použitím knižnice <tt>cstdio</tt> možno na konzolu písať pomocou funkcie <tt>[http://www.cplusplus.com/reference/cstdio/printf/ printf]</tt>.  
 +
* Tu sú príklady jej použitia:
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
 +
#include <cmath>
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 +
    int x = 10;
 +
    double y = sqrt(x);
 
     printf("Ahoj svet, este raz!\n");
 
     printf("Ahoj svet, este raz!\n");
     return 0;
+
     printf("Odmocnina cisla %d je %f.\n", x, y);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Funkciu <tt>printf</tt> možno volať aj s viac ako jedným argumentom. Vo všeobecnosti vyzerá jej volanie nasledovne:
+
Tento program vypíše:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
printf(format, hodnota1, hodnota2, ...)
+
Ahoj svet, este raz!
 +
Odmocnina cisla 10 je 3.162278.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Prvým argumentom je takzvaný ''formátovací reťazec'', za ním nasleduje niekoľko ďalších argumentov (prípadne aj žiaden). Formátovací reťazec pozostáva z dvoch druhov znakov: bežné znaky, ktoré sa priamo vypíšu na výstup a takzvané ''špecifikácie konverzií'' začínajúce symbolom <tt>%</tt> a končiace tzv. ''znakom konverzie'', ktoré majú za následok vypísanie niektorého z ďalších argumentov funkcie <tt>printf</tt> (presnejšie prvého ešte nevypísaného argumentu). V rámci špecifikácie konverzie možno zadať formát, v ktorom sa má ten-ktorý argument vypísať.
+
Vo všeobecnosti vyzerá volanie <tt>printf</tt> nasledovne:
 
 
Napríklad
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
#include <cstdio>
+
printf(format, hodnota1, hodnota2, ...)
using namespace std;
+
</syntaxhighlight>
  
int main(void) {
+
* Prvý argument je '''formátovací reťazec''', za ním môže nasledovať niekoľko ďalších argumentov.
    int n = 7;
+
* Bežné znaky z formátovacieho reťazca sa priamo vypíšu na výstup.
    printf("Prve cislo je %d a druhe cislo je %d.\n",1+1,n);
+
* Koniec riadku sa píše pomocou <tt>"\n"</tt>.
    return 0;
+
* Symbol <tt>%</tt> začína takzvanú '''špecifikáciu konverzie''' a má za následok vypísanie ďalšieho argumentu funkcie (prvého, ktorý sa nepoužil).
}
+
* V jednoduchých príkladoch za znakom <tt>%</tt> nasleduje znak reprezentujúci typ vypísanej hodnoty.
</syntaxhighlight>
+
* Pozor, typy jednotlivých argumentov musia byť v súlade s formátovacím reťazcom.
vypíše
 
<pre>
 
Prve cislo je 2 a druhe cislo je 7.
 
</pre>
 
Špecifikácia <tt>%d</tt> tu pozostáva iba zo znaku konverzie <tt>d</tt>, ktorý zodpovedá výpisu celého čísla v desiatkovej sústave.
 
  
Ďalšie príklady znakov konverzie:
+
* <tt>%d</tt>: celé číslo (typ <tt>int</tt>).
* <tt>%f</tt>: reálne číslo.  
+
* <tt>%ld</tt>: dlhé celé číslo (typ <tt>long int</tt>).
* <tt>%e</tt>: reálne čislo vo vedeckej notácii, napr. <tt>5.4e7</tt>.
+
* <tt>%f</tt>: reálne číslo (typ <tt>double</tt>).  
* <tt>%x</tt>: celé číslo v šestnástkovej sústave.  
+
* <tt>%e</tt>: reálne číslo vo vedeckej notácii, napr. <tt>5.4e7</tt> (typ <tt>double</tt>).  
* <tt>%c</tt>: znak (<tt>char</tt>).
+
* <tt>%c</tt>: znak (typ <tt>char</tt>).
* <tt>%s</tt>: reťazec (<tt>char *</tt>).
+
* <tt>%s</tt>: reťazec (typ <tt>char *</tt>).
 
* <tt>%%</tt>: vypíše samotný znak <tt>%</tt>.
 
* <tt>%%</tt>: vypíše samotný znak <tt>%</tt>.
 
''Pozor'': typy jednotlivých argumentov musia byť v súlade s formátovacím reťazcom (pričom vo všeobecnosti ''nedôjde'' k automatickému pretypovaniu).
 
 
Pred samotný znak konverzie možno pridávať aj modifikátory <tt>l</tt>, <tt>ll</tt>, resp. <tt>h</tt> zodpovedajúce modifikátorom typov <tt>long</tt>, <tt>long long</tt>, resp. <tt>short</tt>. Napríklad
 
* <tt>%lld</tt>: vypíše &bdquo;veľmi dlhé&rdquo; celé číslo.
 
* <tt>%lf</tt>: pri <tt>printf</tt> by sa nemalo používať, zato však veľmi podstatné pri načítavaní.
 
  
 
=== Formátovanie výstupu ===
 
=== Formátovanie výstupu ===
  
Formát vypísania daného argumentu možno zadať niekoľkými nepovinnými parametrami medzi symbolom <tt>%</tt> a znakom konverzie. Napríklad:
+
* Formát vypísania daného argumentu možno upraviť nepovinnými parametrami medzi symbolom <tt>%</tt> a znakom konverzie.  
 +
* Napríklad:
 +
** <tt>%.2f</tt>: vypíše reálne číslo na 2 desatinné miesta,
 +
** <tt>%4d</tt>: ak má celé číslo menej ako 4 cifry, doplní vľavo medzery,
 +
** <tt>%04d</tt>: podobne, ale dopĺňa nuly.
  
* <tt>%.2f</tt>: vypíše reálne číslo na 2 desatinné miesta.
+
Nasledujúci program vo vhodnom formáte vypíše hodnoty faktoriálu prirodzených čísel od 1 po 20 použitím typu <tt>long long int</tt>, ktorý garantuje aspoň 64 bitové číslo:
* <tt>%4d</tt>: ak má celé číslo menej ako 4 cifry, doplní vľavo medzery.
 
* <tt>%04d</tt>: podobne, ale dopĺňa nuly.
 
 
 
Nasledujúci program vo vhodnom formáte vypíše hodnoty faktoriálu prirodzených čísel od 1 po 20:
 
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
Riadok 82: Riadok 134:
 
}
 
}
  
int main(void) {
+
int main() {
 
     for (int i = 1; i <= 20; i++) {                   
 
     for (int i = 1; i <= 20; i++) {                   
         printf("%2d! = %22lld\n",i,factorial(i));
+
         printf("%2d! = %22lld\n", i, factorial(i));
 
     }
 
     }
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Nasledujúci program vypíše vo vhodnom formáte zadaný dátum:
+
<syntaxhighlight lang="C++">
 +
1! =                      1
 +
2! =                      2
 +
3! =                      6
 +
4! =                    24
 +
5! =                    120
 +
6! =                    720
 +
7! =                  5040
 +
8! =                  40320
 +
9! =                362880
 +
10! =                3628800
 +
11! =              39916800
 +
12! =              479001600
 +
13! =            6227020800
 +
14! =            87178291200
 +
15! =          1307674368000
 +
16! =        20922789888000
 +
17! =        355687428096000
 +
18! =      6402373705728000
 +
19! =    121645100408832000
 +
20! =    2432902008176640000
 +
</syntaxhighlight>
 +
 
 +
 
 +
Nasledujúci program vypíše zadaný dátum vo formáte typu 02.01.2019:
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 97: Riadok 172:
  
 
void vypisDatum(int d, int m, int r) {
 
void vypisDatum(int d, int m, int r) {
     printf("%02d.%02d.%04d\n",d,m,r);
+
     printf("%02d.%02d.%04d\n", d, m, r);
 
}
 
}
  
int main(void) {
+
int main() {
     vypisDatum(2,1,2019);
+
     vypisDatum(2, 1, 2019);
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
  
 
Celá špecifikácia konverzie pozostáva z nasledujúcich častí:
 
Celá špecifikácia konverzie pozostáva z nasledujúcich častí:
 
* Z povinného úvodného znaku <tt>%</tt>.
 
* Z povinného úvodného znaku <tt>%</tt>.
* Z jedného alebo niekoľkých nepovinných ''príznakov'', ako napríklad <tt>-</tt>, ktorého použitie vyústi v zarovnanie vypisovaného textu vľavo (bez jeho použitia sa text zarovná vpravo). Ďalšími príznakmi sú napríklad <tt>0</tt> (dopĺňanie núl naľavo), <tt>+</tt> (vypíše znamienko ''+'' pri kladných číslach), atď.
+
* Z nepovinných ''príznakov'', napríklad <tt>-</tt>, ktorého použitie vyústi v zarovnanie vypisovaného textu vľavo (bez jeho použitia sa text zarovná vpravo). Ďalšími príznakmi sú napríklad <tt>0</tt> (dopĺňanie núl naľavo), <tt>+</tt> (vypíše znamienko ''+'' pri kladných číslach), atď.
 
* Z nepovinného celého čísla udávajúceho minimálnu ''šírku'' výpisu (minimálny počet &bdquo;políčok&rdquo;, do ktorých sa text vypíše).
 
* Z nepovinného celého čísla udávajúceho minimálnu ''šírku'' výpisu (minimálny počet &bdquo;políčok&rdquo;, do ktorých sa text vypíše).
 
* Z nepovinnej bodky nasledovanej celým číslom udávajúcim ''presnosť'' výpisu (pri reálnych číslach napríklad počet desatinných miest; presnosť má však svoju interpretáciu aj pri iných typoch dát).
 
* Z nepovinnej bodky nasledovanej celým číslom udávajúcim ''presnosť'' výpisu (pri reálnych číslach napríklad počet desatinných miest; presnosť má však svoju interpretáciu aj pri iných typoch dát).
Riadok 115: Riadok 190:
  
 
=== Načítanie formátovaných dát z konzoly: <tt>scanf</tt> ===
 
=== Načítanie formátovaných dát z konzoly: <tt>scanf</tt> ===
Funkciu [http://www.cplusplus.com/reference/cstdio/scanf/ <tt>scanf</tt>] s typickým volaním
+
 
 +
Vstup z konzoly sa dá načítať funkciou [http://www.cplusplus.com/reference/cstdio/scanf/ <tt>scanf</tt>] s typickým volaním
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
scanf(format, adresa1, adresa2, ...)  
 
scanf(format, adresa1, adresa2, ...)  
 
</syntaxhighlight>
 
</syntaxhighlight>
možno využiť na načítanie dát z konzoly.
 
 
* Napríklad <tt>scanf("%d", &x)</tt> načíta celočíselnú hodnotu do premennej <tt>x</tt>.
 
* Napríklad <tt>scanf("%d", &x)</tt> načíta celočíselnú hodnotu do premennej <tt>x</tt>.
 
* Zatiaľ čo argumentmi <tt>printf</tt> sú priamo hodnoty, <tt>scanf</tt> potrebuje adresy premenných, pretože ich modifikuje.
 
* Zatiaľ čo argumentmi <tt>printf</tt> sú priamo hodnoty, <tt>scanf</tt> potrebuje adresy premenných, pretože ich modifikuje.
  
Jednoduchý príklad použitia:
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
using namespace std;
 
 
void vypisDatum(int d, int m, int r) {
 
    printf("%02d.%02d.%04d\n",d,m,r);
 
}
 
 
int main(void) {
 
    int r;
 
 
    printf("Zadaj rok: "); 
 
    scanf("%d", &r);       
 
    vypisDatum(1,1,r);                 
 
    return 0;
 
}
 
</syntaxhighlight>
 
 
Pomocou <tt>scanf</tt> možno načítať aj viacero premenných naraz:
 
Pomocou <tt>scanf</tt> možno načítať aj viacero premenných naraz:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 147: Riadok 204:
  
 
void vypisDatum(int d, int m, int r) {
 
void vypisDatum(int d, int m, int r) {
     printf("%02d.%02d.%04d\n",d,m,r);
+
     printf("%02d.%02d.%04d\n", d, m, r);
 
}
 
}
  
int main(void) {
+
int main() {
     int d,m,r;
+
     int d, m, r;
 
 
 
     printf("Zadaj den, mesiac a rok: ");   
 
     printf("Zadaj den, mesiac a rok: ");   
 
     scanf("%d %d %d", &d, &m, &r);         
 
     scanf("%d %d %d", &d, &m, &r);         
     vypisDatum(d,m,r);                     
+
     vypisDatum(d, m, r);                     
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Formátovací reťazec sa teraz interpretuje nasledovne:
+
Formátovací reťazec sa v <tt>scanf</tt> interpretuje nasledovne:
* Špecifikácie formátov načítavaných premenných (začínajúce znakom <tt>%</tt>) možno zadávať podobne ako pri funkcii <tt>printf</tt>.
+
* Špecifikácia typu načítavaných premenných je podobná ako pri funkcii <tt>printf</tt>.
** ''Pozor'': na načítanie reálneho čísla typu <tt>double</tt> je potrebné namiesto <tt>%f</tt> použiť <tt>%lf</tt>. Použitie <tt>%f</tt> zodpovedá načítavaniu hodnoty &bdquo;kratšieho&rdquo; typu <tt>float</tt>. Pri funkcii <tt>printf</tt> je však žiadúce pre <tt>double</tt> aj <tt>float</tt> používať <tt>%f</tt> (hoci na mnohých systémoch bude fungovať aj <tt>%lf</tt>).
+
** <tt>%d</tt> načíta <tt>int</tt>
* Biele znaky (angl. ''whitespace''; t.j. medzery, konce riadkov, tabulátory) vo formátovacom reťazci spôsobia, že funkcia <tt>scanf</tt> číta a ignoruje všetky biele znaky pred ďalším nebielym znakom. Jeden biely znak vo formátovacom reťazci tak umožní ľubovoľný počet bielych znakov na vstupe.
+
** <tt>%lf</tt> načíta <tt>double</tt> (pozor, tu je rozdiel od <tt>printf</tt>, kde sa double vypisuje pomocou <tt>%f</tt>)
 +
** <tt>%s</tt> načíta reťazec, konkrétne jedno slovo, t.j. postupnosť nebielych znakov. Ako argument sa zadá hodnota typu <tt>char*</tt>, ktorá má ukazovať na dostatočne veľké pole.
 +
** <tt>%100s</tt> načíta slovo, ale najviac 100 znakov. Pole má mať veľkosť aspoň 101.
 +
** <tt>%c</tt> načíta jeden znak. Na rozdiel od všetkých predchádzajúcich typov, tu sa nepreskakujú biele znaky pred prvým nebielym.
 +
* Biele znaky (angl. ''whitespace'', t.j. medzery, konce riadkov, tabulátory) vo formátovacom reťazci spôsobia, že funkcia <tt>scanf</tt> číta a ignoruje všetky biele znaky pred ďalším nebielym znakom. Jeden biely znak vo formátovacom reťazci tak umožní ľubovoľný počet bielych znakov na vstupe.
 
* Ostatné znaky formátovacieho reťazca musia presne zodpovedať vstupu.
 
* Ostatné znaky formátovacieho reťazca musia presne zodpovedať vstupu.
  
Riadok 173: Riadok 232:
 
=== Kontrola správnosti vstupu ===
 
=== Kontrola správnosti vstupu ===
  
Funkcia <tt>scanf</tt> vracia na výstupe počet úspešne načítaných hodnôt zo vstupu. V prípade chyby hneď na začiatku vstupu tak napríklad vráti <tt>0</tt>. V prípade, že hneď na začiatku narazí na koniec súboru (ktorý na konzole možno zadať pod Linuxom ako <tt>Ctrl+D</tt> resp. pod Windowsom ako <tt>Ctrl+Z</tt> a <tt>Enter</tt>), vráti hodnotu <tt>EOF</tt> (typicky -1).
+
Funkcia <tt>scanf</tt> vracia počet úspešne načítaných hodnôt zo vstupu.  
 +
* V prípade chyby hneď na začiatku vstupu tak napríklad vráti <tt>0</tt>.  
 +
* Ak hneď na začiatku narazí na koniec vstupu, vráti hodnotu <tt>EOF</tt> (typicky -1).
 +
* Vstup z konzoly sa dá ukončiť pod Linuxom ako <tt>Ctrl+D</tt> resp. pod Windowsom ako <tt>Ctrl+Z</tt> a <tt>Enter</tt>
  
 
Príklad: zadávanie dátumu vo formáte <tt>deň.mesiac.rok</tt> s kontrolou vstupu:  
 
Príklad: zadávanie dátumu vo formáte <tt>deň.mesiac.rok</tt> s kontrolou vstupu:  
Riadok 182: Riadok 244:
  
 
void vypisDatum(int d, int m, int r) {
 
void vypisDatum(int d, int m, int r) {
     printf("%02d.%02d.%04d\n",d,m,r);
+
     printf("%02d.%02d.%04d\n", d, m, r);
 
}
 
}
  
int main(void) {
+
int main() {
     int d,m,r;
+
     int d, m, r;
     printf("Zadaj datum: ");
+
     printf("Zadaj datum: ");
     if (scanf("%d.%d.%d", &d, &m, &r) == 3) {
+
     int uspech = scanf("%d.%d.%d", &d, &m, &r);
 +
    if (uspech == 3) {
 
         printf("Datum je ");
 
         printf("Datum je ");
         vypisDatum(d,m,r);
+
         vypisDatum(d, m, r);
 
     } else {
 
     } else {
 
         printf("Nebol zadany korektny datum.\n");
 
         printf("Nebol zadany korektny datum.\n");
 +
        printf("Pocet uspesne nacitanych poloziek: %d.\n", uspech);
 
     }
 
     }
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Ďalším príkladom môže byť program, ktorý počíta súčet postupne zadávaných čísel, až kým je zadané nekorektné číslo alebo koniec súboru:
+
Ďalší program počíta súčet postupne zadávaných čísel, až kým je zadané nekorektné číslo alebo koniec súboru:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     double sum = 0;
 
     double sum = 0;
 
     double x;
 
     double x;
Riadok 210: Riadok 273:
 
     }
 
     }
 
     printf("Sucet je %.2f\n", sum);
 
     printf("Sucet je %.2f\n", sum);
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 217: Riadok 279:
  
 
Na načítavanie a vypisovanie dát sme doposiaľ používali výhradne konzolu.
 
Na načítavanie a vypisovanie dát sme doposiaľ používali výhradne konzolu.
V praxi však často vzniká potreba spracovávať dáta uložené v súboroch. Zameriame sa teraz na súbory v textovom formáte, s ktorými sa pracuje podobne ako s konzolou.
+
V praxi však často potrebujeme spracovávať dáta uložené v súboroch.  
<!--* V C++ existujú ekvivalenty <tt>cin >></tt> a <tt>cout << </tt> aj pre súbory, nájdete ich v knižnici [http://www.cplusplus.com/reference/fstream/fstream/ fstream].-->
+
* Zameriame sa na súbory v textovom formáte, s ktorými sa pracuje podobne ako s konzolou.
 +
* V C++ existujú ekvivalenty <tt>cin >></tt> a <tt>cout << </tt> aj pre súbory, nájdete ich v knižnici [http://www.cplusplus.com/reference/fstream/fstream/ fstream].
  
 
===Základy: typ <tt>FILE *</tt> a funkcie <tt>fopen</tt>, <tt>fclose</tt>, <tt>fprintf</tt>, <tt>fscanf</tt> ===
 
===Základy: typ <tt>FILE *</tt> a funkcie <tt>fopen</tt>, <tt>fclose</tt>, <tt>fprintf</tt>, <tt>fscanf</tt> ===
  
So súbormi sa pri použití knižnice <tt>cstdio</tt> pracuje pomocou typu <tt>FILE *</tt>. Ide tu o smerník na štruktúru typu <tt>FILE</tt>, ktorá obsahuje nejaké (pre programátora zväčša nepodstatné) informácie o súbore, s ktorým sa práve pracuje. Premenné pre prácu so súbormi tak možno definovať napríklad takto:
+
So súbormi sa pri použití knižnice <tt>cstdio</tt> pracuje pomocou typu <tt>FILE *</tt> (veľkými písmenami).  
 +
* Je to smerník na štruktúru typu <tt>FILE</tt>, ktorá obsahuje informácie o súbore, s ktorým sa práve pracuje.  
 +
* Premenné pre prácu so súbormi tak možno definovať napríklad takto:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
FILE *f;
 
 
FILE *fr, *fw;
 
FILE *fr, *fw;
 
</syntaxhighlight>  
 
</syntaxhighlight>  
 
''Pozor'': v názve typu <tt>FILE</tt> treba dodržať veľké písmená (čiže treba písať <tt>FILE *</tt>, nie <tt>file *</tt>).
 
  
 
'''Otvorenie súboru pre čítanie'''
 
'''Otvorenie súboru pre čítanie'''
Riadok 234: Riadok 296:
 
* Otvorí súbor s názvom <tt>vstup.txt</tt> (prípadne možno zadať kompletnú cestu k súboru).
 
* Otvorí súbor s názvom <tt>vstup.txt</tt> (prípadne možno zadať kompletnú cestu k súboru).
 
* Ak taký súbor neexistuje alebo sa nedá otvoriť, do <tt>fr</tt> priradí <tt>NULL</tt>.
 
* Ak taký súbor neexistuje alebo sa nedá otvoriť, do <tt>fr</tt> priradí <tt>NULL</tt>.
* Z takto otvoreného súboru môžeme čítať napríklad pomocou <tt>fscanf</tt>, ktorá je analógiou k <tt>scanf</tt>.
+
* Z otvoreného súboru môžeme čítať napríklad pomocou <tt>fscanf</tt>, ktorá je analógiou k <tt>scanf</tt>.
 
* Napríklad <tt>fscanf(fr, "%d", &x);</tt>
 
* Napríklad <tt>fscanf(fr, "%d", &x);</tt>
  
Riadok 241: Riadok 303:
 
* Vytvorí súbor s názvom <tt>vystup.txt</tt>. Ak už existoval, zmaže jeho obsah (keby sme vo volaní <tt>fopen</tt> namiesto <tt>"w"</tt> použili <tt>"a"</tt>, pridávalo by sa na koniec existujúceho súboru).
 
* Vytvorí súbor s názvom <tt>vystup.txt</tt>. Ak už existoval, zmaže jeho obsah (keby sme vo volaní <tt>fopen</tt> namiesto <tt>"w"</tt> použili <tt>"a"</tt>, pridávalo by sa na koniec existujúceho súboru).
 
* Ak sa nepodarí súbor otvoriť, do <tt>fw</tt> priradí <tt>NULL</tt>.
 
* Ak sa nepodarí súbor otvoriť, do <tt>fw</tt> priradí <tt>NULL</tt>.
* Do takto otvoreného súboru môžeme zapisovať napr. pomocou funkcie <tt>fprintf</tt>, ktorá je analógiou k <tt>printf</tt>.
+
* Do otvoreného súboru môžeme zapisovať napr. pomocou funkcie <tt>fprintf</tt>, ktorá je analógiou k <tt>printf</tt>.
 
* Napr. <tt>fprintf(fw, "%d", x);</tt>
 
* Napr. <tt>fprintf(fw, "%d", x);</tt>
  
Riadok 256: Riadok 318:
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
     FILE *fr = fopen("vstup.txt", "r");
+
    // otvorime vstupny subor a skontrolujeme, ze sa podarilo
    FILE *fw = fopen("vystup.txt", "w");
+
     FILE *fr = fopen("vstup.txt", "r");  
     assert(fr != NULL && fw != NULL);
+
     assert(fr != NULL);
 
      
 
      
     int n,r;
+
    // nacitame pocet cisel
     r = fscanf(fr, "%d", &n);
+
     int n, uspech;
     assert(r == 1 && n >= 0);
+
     uspech = fscanf(fr, "%d", &n);
 +
     assert(uspech == 1 && n >= 0);
 +
 
 +
    // alokujeme pole a nacitame cisla
 
     int *a = new int[n];
 
     int *a = new int[n];
   
+
     for (int i = 0; i < n; i++) {
     for (int i = 0; i <= n-1; i++) {
+
         uspech = fscanf(fr, "%d", &a[i]);
         r = fscanf(fr, "%d", &a[i]);
+
         assert(uspech == 1);
         assert(r == 1);
 
 
     }
 
     }
 
     fclose(fr);
 
     fclose(fr);
 +
 +
    // otvorime vystupny subor a skontrolujeme, ze sa podarilo
 +
    FILE *fw = fopen("vystup.txt", "w");
 +
    assert(fw != NULL);
 +
 +
    // vypiseme cisla odzadu
 
     for (int i = n-1; i >= 0; i--) {
 
     for (int i = n-1; i >= 0; i--) {
 
         fprintf(fw, "%d ", a[i]);
 
         fprintf(fw, "%d ", a[i]);
Riadok 276: Riadok 346:
 
     fclose(fw);
 
     fclose(fw);
 
     delete[] a;     
 
     delete[] a;     
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 286: Riadok 355:
 
FILE *stdin, *stdout;
 
FILE *stdin, *stdout;
 
</syntaxhighlight>
 
</syntaxhighlight>
pre štandardný vstupný a výstupný prúd. Tie tak môžu byť použité v ľubovoľnom kontexte, v ktorom sa očakáva súbor. Napríklad volanie <tt>fscanf(stdin,"%d",&x)</tt> je ekvivalentné volaniu <tt>scanf("%d",&x)</tt>.
+
pre štandardný vstupný a výstupný prúd. Tie tak môžu byť použité v ľubovoľnom kontexte, v ktorom sa očakáva súbor. Napríklad volanie <tt>fscanf(stdin, "%d", &x)</tt> je ekvivalentné volaniu <tt>scanf("%d", &x)</tt>.
  
 
Ten istý kód sa tak dá použiť na prácu so súbormi aj so štandardným vstupom resp. výstupom &ndash; stačí len podľa potreby nastaviť premennú typu <tt>FILE *</tt>. Typické použitie je napríklad nasledovné:
 
Ten istý kód sa tak dá použiť na prácu so súbormi aj so štandardným vstupom resp. výstupom &ndash; stačí len podľa potreby nastaviť premennú typu <tt>FILE *</tt>. Typické použitie je napríklad nasledovné:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
     FILE *fr, *fw;
+
     // do retazca str nacitame z konzoly meno suboru alebo pomlcku
     ...
+
     char str[101];
     fscanf(fr, "%s", str);
+
     scanf("%100s", str);
     if (strcmp(str,"-") == 0) {
+
    // do fw otvorime subor alebo pouzijeme konzolu
 +
    FILE *fw;   
 +
     if (strcmp(str, "-") == 0) {
 
         fw = stdout;
 
         fw = stdout;
 
     } else {
 
     } else {
 
         fw = fopen(str, "w");
 
         fw = fopen(str, "w");
 
     }
 
     }
 +
    // zapiseme nieco do fw
 
     fprintf(fw, "Hello world!\n");
 
     fprintf(fw, "Hello world!\n");
     ...
+
     // ak treba, zatvorime subor
 +
    if (strcmp(str, "-") != 0) {
 +
        fclose(fw);
 +
    }
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
=== Testovanie konca súboru ===
 
=== Testovanie konca súboru ===
  
Existujú dve možnosti testovania &bdquo;nárazu&rdquo; na koniec súboru:
+
Existujú dve možnosti testovania, či sme dosiahli koniec súboru:
 
* V knižnici <tt>cstdio</tt> je definovaná symbolická konštanta <tt>EOF</tt>, ktorá má väčšinou hodnotu -1. Ak sa funkcii <tt>fscanf</tt> nepodarí načítať žiadnu hodnotu, pretože načítavanie dospelo ku koncu súboru, vráti konštantu <tt>EOF</tt> ako svoj výstup.
 
* V knižnici <tt>cstdio</tt> je definovaná symbolická konštanta <tt>EOF</tt>, ktorá má väčšinou hodnotu -1. Ak sa funkcii <tt>fscanf</tt> nepodarí načítať žiadnu hodnotu, pretože načítavanie dospelo ku koncu súboru, vráti konštantu <tt>EOF</tt> ako svoj výstup.
 
* Funkcia <tt>feof(subor)</tt> vráti <tt>true</tt> práve vtedy, keď sa funkcia <tt>fscanf</tt> (alebo nejaká iná funkcia) už niekedy pokúšala čítať za koncom súboru <tt>subor</tt>.
 
* Funkcia <tt>feof(subor)</tt> vráti <tt>true</tt> práve vtedy, keď sa funkcia <tt>fscanf</tt> (alebo nejaká iná funkcia) už niekedy pokúšala čítať za koncom súboru <tt>subor</tt>.
Riadok 318: Riadok 393:
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     FILE *f;
 
     FILE *f;
 
     const int MAXN = 100;
 
     const int MAXN = 100;
     int a[MAXN], N, kod;
+
     int a[MAXN], N, uspech;
  
 
     f = fopen("vstup.txt", "r");
 
     f = fopen("vstup.txt", "r");
 
     assert(f != NULL);
 
     assert(f != NULL);
  
     kod = fscanf(f, "%d ", &N);
+
     uspech = fscanf(f, "%d ", &N);
     assert(kod == 1 && N >= 0 && N < MAXN);
+
     assert(uspech == 1 && N >= 0 && N <= MAXN);
  
 
     for (int i = 0; i < N; i++) {
 
     for (int i = 0; i < N; i++) {
         kod = fscanf(f, "%d ", &a[i]);
+
         uspech = fscanf(f, "%d ", &a[i]);
         assert(kod == 1);
+
         assert(uspech == 1);
 
     }
 
     }
 
     fclose(f);
 
     fclose(f);
Riadok 344: Riadok 419:
 
     N = 0;
 
     N = 0;
 
     int x;
 
     int x;
     kod = fscanf(f, "%d ", &x);
+
     uspech = fscanf(f, "%d ", &x);
     assert(kod == 1);
+
     assert(uspech == 1);
 
     while (x != -1) {
 
     while (x != -1) {
 
         assert(N < MAXN);
 
         assert(N < MAXN);
 
         a[N] = x;
 
         a[N] = x;
 
         N++;
 
         N++;
         kod = fscanf(f, "%d ", &x);
+
         uspech = fscanf(f, "%d ", &x);
         assert(kod == 1);
+
         assert(uspech == 1);
 
     }
 
     }
 
     // zatvorime subor a spracujeme data
 
     // zatvorime subor a spracujeme data
Riadok 364: Riadok 439:
 
     while (!feof(f)) {
 
     while (!feof(f)) {
 
         assert(N < MAXN);
 
         assert(N < MAXN);
         kod = fscanf(f, "%d", &a[N]);
+
         uspech = fscanf(f, "%d", &a[N]);
         assert(kod == 1);
+
         assert(uspech == 1); // bude tu padat
 
         N++;
 
         N++;
 
     }
 
     }
Riadok 371: Riadok 446:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Po poslednom čísle v súbore často nasleduje ešte koniec riadku, v dôsledku čoho môže posledné volanie funkcie <tt>fscanf</tt> vyústiť v návratovú hodnotu <tt>-1</tt> (predchádzajúce volanie <tt>fscanf</tt> totiž ešte &bdquo;nenarazilo&rdquo; na koniec súboru, v dôsledku čoho je pred čítaním posledného riadku hodnota <tt>feof(f)</tt> stále rovná <tt>false</tt>). Tým pádom program zlyhá na riadku <tt>assert(kod == 1)</tt>. Tento nedostatok môžeme napraviť napríklad tak, že vo volaní funkcie <tt>fscanf</tt> dáme vo formátovacom reťazci za <tt>%d</tt> medzeru. Tá sa bude pokúšať preskočiť všetky biele znaky až po najbližší nebiely; pritom natrafí na koniec súboru a <tt>feof(f)</tt> už bude vracať <tt>true</tt>.
+
* Tento program nefunguje, ak po poslednom čísle v súbore nasleduje ešte koniec riadku (čo je obvyklé).
 +
* Pri načítaní tohto čísla skončíme na symbole konca riadku, <tt>fscanf</tt> sa teda nepokúsi čítať za koncom súboru.
 +
* Spustí sa teda ďalšie opakovanie cyklu, v ktorom sa už ale nepodarí ďalšie číslo načítať a <tt>assert</tt> ukončí program s chybovou hláškou.
 +
* Aj bez príkazu <tt>assert</tt> by sme mali problém, že do poľa by sa uložila nezmyselná hodnota.
 +
 
 +
Tento nedostatok môžeme napraviť napríklad tak, že vo volaní funkcie <tt>fscanf</tt> dáme vo formátovacom reťazci za <tt>%d</tt> medzeru. Tá sa bude pokúšať preskočiť všetky biele znaky až po najbližší nebiely; pritom natrafí na koniec súboru a <tt>feof(f)</tt> už bude vracať <tt>true</tt>.
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
Riadok 377: Riadok 457:
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     FILE *f;
 
     FILE *f;
 
     const int MAXN = 100;
 
     const int MAXN = 100;
     int a[MAXN], N, kod;
+
     int a[MAXN], N, uspech;
  
 
     f = fopen("vstup.txt", "r");
 
     f = fopen("vstup.txt", "r");
Riadok 388: Riadok 468:
 
     while (!feof(f)) {
 
     while (!feof(f)) {
 
         assert(N < MAXN);
 
         assert(N < MAXN);
         kod = fscanf(f, "%d ", &a[N]);
+
         uspech = fscanf(f, "%d ", &a[N]);
         assert(kod == 1);
+
         assert(uspech == 1);
 
         N++;
 
         N++;
 
     }
 
     }
Riadok 397: Riadok 477:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
Cvičenie: upravte úryvky programu vyššie tak, aby pracoval s číslami typu <tt>double</tt> alebo so slovami.
 +
 +
==Zhrnutie==
 +
* Doteraz sme s konzolou pracovali pomocou <tt>cin</tt> a <tt>cout</tt> z knižnice <tt>iostream</tt>, ktorá patrí do C++.
 +
* V jazyku C sa na načítavanie čísel a slov používajú funkcie <tt>scanf</tt> a <tt>printf</tt> z knižnice <tt>cstdio</tt>.
 +
** Funkcia <tt>scanf</tt> umožňuje tiež testovať koniec vstupu a správnosť prečítaných hodnôt.
 +
** Funkcia <tt>printf</tt> umožňuje pohodlne vypísať viacero hodnôt aj s nastavením formátovania ako napr. počet desatinných miest alebo zarovnávanie.
 +
* Pri práci so súbormi súbor typu <tt>FILE *</tt> otvoríme funkciou <tt>fopen</tt>, zatvoríme <tt>fclose</tt>.
 +
* Namiesto <tt>scanf</tt>, <tt>printf</tt> pri súboroch použijeme <tt>fprintf</tt>, <tt>fscanf</tt>.

Aktuálna revízia z 13:06, 18. november 2024

Oznamy

  • DÚ2 odovzdávajte do zajtra 22:00, posledná DÚ bude zverejnená v stredu.
  • Rozcvička bude z dnešnej prednášky, zvyšok cvičení hlavne spájané zoznamy z minulého pondelka.
  • Piatkové cvičenia sú povinné pre tých, ktorí nedokončia načas rozcvičku.
  • Nerobte zbytočné zmeny v poskytnutej kostre.
  • Pri práci so smerníkmi odporúčame nakresliť si obrázok.

Smerníková aritmetika

Na smerníkoch možno vykonávať určité operácie, ktoré sa zvyknú nazývať smerníková aritmetika. Uvažujme číslo n typu int a smerníky p a q na nejaký typ T.

int n;
T * p, * q;
  • p + n je smerník na n-té políčko za adresou p, pričom veľkosť políčka je daná typom T
    • p + n je teda to isté ako &(p[n]) a *(p+n) je to isté ako p[n].
    • p++ je skratkou pre p = p + 1, posunie nám teda smerník p o políčko doprava.
    • Výraz p[n] je len skratkou pre *(p+n).
    • p[n] aj p + n teda chceme používať iba ak p ukazuje na prvok poľa, za ktorým v poli ide ešte aspoň n ďalších políčok
  • Podobne p - n je smerník na n-té políčko pred adresou p.
    • p - n teda chceme používať iba ak p ukazuje na prvok poľa, pred ktorým v poli ide ešte aspoň n ďalších políčok
  • Ak p a q sú adresami prvkov v tom istom poli, p - q je celé číslo k také, že p == q + k, t.j. o koľko políčok je p ďalej vpravo od q.
  • Ak p a q sú adresami prvkov v tom istom poli, môžeme ich tiež porovnávať pomocou <, >, <=, >=
  • Ľubovoľné dva smerníky toho istého typu vieme porovnávať pomocou ==, !=.

Tu je napr. zvláštny spôsob ako vypísať pole a:

const int n = 4;
int a[n] = {4, 3, 2, 1};
for (int * smernik = a; smernik < a + n; smernik++) {
     cout << "Prvok " << smernik - a << " je " << *smernik << endl;
}

Podobný kód sa ale občas používa na prechádzanie reťazcov. Napríklad nasledujúca funkcia spočíta počet medzier v reťazci:

int zratajMedzery(char str[]) { // mohli by sme dať aj char *str
  int pocet = 0;
  while(*str != 0) {   // kým nenájdeme ukončovaciu nulu
     if(*str == ' ') { // skontroluj znak, na ktorý ukazuje str
         pocet++; 
     }
     str++;           // posuň str na ďalší znak 
  }
  return pocet;
}

Funkcie z knižnice cstring so smerníkovou aritmetikou

  • strstr(text, vzorka) vracia smerník na char
    • NULL ak sa vzorka nenachádza v texte, smerník na začiatok prvého výskytu inak
    • pozíciu výskytu zistíme smerníkovou aritmetikou:
char * text = "Hello world!";
char * vzorka = "or";
char * where = strstr(text, vzorka);
if(where != NULL) {
  int position = where - text;
}
  • Ako by ste pomocou volaní strstr spočítali počet výskytov vzorky v texte?
  • Podobne strchr hľadá prvý výskyt znaku v texte.

Práca s konzolou na spôsob jazyka C: printf a scanf

  • Doposiaľ sme s konzolou pracovali prostredníctvom knižnice iostream, ktorá patrí medzi štandardné knižnice jazyka C++ a v ktorej sú definované prúdy cin a cout.
  • Dnes si ukážeme alternatívny prístup k práci s konzolou založený na knižnici cstdio, ktorá je štandardnou knižnicou jazyka C.

Výpis formátovaných dát na konzolu: printf

  • S použitím knižnice cstdio možno na konzolu písať pomocou funkcie printf.
  • Tu sú príklady jej použitia:
#include <cstdio>
#include <cmath>
using namespace std;

int main() {
    int x = 10;
    double y = sqrt(x);
    printf("Ahoj svet, este raz!\n");
    printf("Odmocnina cisla %d je %f.\n", x, y);
}

Tento program vypíše:

Ahoj svet, este raz!
Odmocnina cisla 10 je 3.162278.

Vo všeobecnosti vyzerá volanie printf nasledovne:

printf(format, hodnota1, hodnota2, ...)
  • Prvý argument je formátovací reťazec, za ním môže nasledovať niekoľko ďalších argumentov.
  • Bežné znaky z formátovacieho reťazca sa priamo vypíšu na výstup.
  • Koniec riadku sa píše pomocou "\n".
  • Symbol % začína takzvanú špecifikáciu konverzie a má za následok vypísanie ďalšieho argumentu funkcie (prvého, ktorý sa nepoužil).
  • V jednoduchých príkladoch za znakom % nasleduje znak reprezentujúci typ vypísanej hodnoty.
  • Pozor, typy jednotlivých argumentov musia byť v súlade s formátovacím reťazcom.
  • %d: celé číslo (typ int).
  • %ld: dlhé celé číslo (typ long int).
  • %f: reálne číslo (typ double).
  • %e: reálne číslo vo vedeckej notácii, napr. 5.4e7 (typ double).
  • %c: znak (typ char).
  • %s: reťazec (typ char *).
  • %%: vypíše samotný znak %.

Formátovanie výstupu

  • Formát vypísania daného argumentu možno upraviť nepovinnými parametrami medzi symbolom % a znakom konverzie.
  • Napríklad:
    • %.2f: vypíše reálne číslo na 2 desatinné miesta,
    • %4d: ak má celé číslo menej ako 4 cifry, doplní vľavo medzery,
    • %04d: podobne, ale dopĺňa nuly.

Nasledujúci program vo vhodnom formáte vypíše hodnoty faktoriálu prirodzených čísel od 1 po 20 použitím typu long long int, ktorý garantuje aspoň 64 bitové číslo:

#include <cstdio>
using namespace std;

long long int factorial(int n) {                      
    if (n == 0) {
        return 1;
    } else {
        return n * factorial(n-1);
    }
}

int main() {
    for (int i = 1; i <= 20; i++) {                   
        printf("%2d! = %22lld\n", i, factorial(i));
    }
}
 1! =                      1
 2! =                      2
 3! =                      6
 4! =                     24
 5! =                    120
 6! =                    720
 7! =                   5040
 8! =                  40320
 9! =                 362880
10! =                3628800
11! =               39916800
12! =              479001600
13! =             6227020800
14! =            87178291200
15! =          1307674368000
16! =         20922789888000
17! =        355687428096000
18! =       6402373705728000
19! =     121645100408832000
20! =    2432902008176640000


Nasledujúci program vypíše zadaný dátum vo formáte typu 02.01.2019:

#include <cstdio>
using namespace std;

void vypisDatum(int d, int m, int r) {
    printf("%02d.%02d.%04d\n", d, m, r);
}

int main() {
    vypisDatum(2, 1, 2019);
}


Celá špecifikácia konverzie pozostáva z nasledujúcich častí:

  • Z povinného úvodného znaku %.
  • Z nepovinných príznakov, napríklad -, ktorého použitie vyústi v zarovnanie vypisovaného textu vľavo (bez jeho použitia sa text zarovná vpravo). Ďalšími príznakmi sú napríklad 0 (dopĺňanie núl naľavo), + (vypíše znamienko + pri kladných číslach), atď.
  • Z nepovinného celého čísla udávajúceho minimálnu šírku výpisu (minimálny počet „políčok”, do ktorých sa text vypíše).
  • Z nepovinnej bodky nasledovanej celým číslom udávajúcim presnosť výpisu (pri reálnych číslach napríklad počet desatinných miest; presnosť má však svoju interpretáciu aj pri iných typoch dát).
  • Z nepovinného modifikátora l, ll, alebo h pre long, long long, resp. short.
  • Z povinného symbolu konverzie (napr. d, f, s, ...).

Načítanie formátovaných dát z konzoly: scanf

Vstup z konzoly sa dá načítať funkciou scanf s typickým volaním

scanf(format, adresa1, adresa2, ...)
  • Napríklad scanf("%d", &x) načíta celočíselnú hodnotu do premennej x.
  • Zatiaľ čo argumentmi printf sú priamo hodnoty, scanf potrebuje adresy premenných, pretože ich modifikuje.

Pomocou scanf možno načítať aj viacero premenných naraz:

#include <cstdio>
using namespace std;

void vypisDatum(int d, int m, int r) {
    printf("%02d.%02d.%04d\n", d, m, r);
}

int main() {
    int d, m, r;
    printf("Zadaj den, mesiac a rok: ");  
    scanf("%d %d %d", &d, &m, &r);        
    vypisDatum(d, m, r);                    
}

Formátovací reťazec sa v scanf interpretuje nasledovne:

  • Špecifikácia typu načítavaných premenných je podobná ako pri funkcii printf.
    • %d načíta int
    • %lf načíta double (pozor, tu je rozdiel od printf, kde sa double vypisuje pomocou %f)
    • %s načíta reťazec, konkrétne jedno slovo, t.j. postupnosť nebielych znakov. Ako argument sa zadá hodnota typu char*, ktorá má ukazovať na dostatočne veľké pole.
    • %100s načíta slovo, ale najviac 100 znakov. Pole má mať veľkosť aspoň 101.
    • %c načíta jeden znak. Na rozdiel od všetkých predchádzajúcich typov, tu sa nepreskakujú biele znaky pred prvým nebielym.
  • Biele znaky (angl. whitespace, t.j. medzery, konce riadkov, tabulátory) vo formátovacom reťazci spôsobia, že funkcia scanf číta a ignoruje všetky biele znaky pred ďalším nebielym znakom. Jeden biely znak vo formátovacom reťazci tak umožní ľubovoľný počet bielych znakov na vstupe.
  • Ostatné znaky formátovacieho reťazca musia presne zodpovedať vstupu.

Nasledujúci príkaz tak napríklad načíta dátum vo formáte deň.mesiac.rok:

scanf("%d.%d.%d", &d, &m, &r);

Kontrola správnosti vstupu

Funkcia scanf vracia počet úspešne načítaných hodnôt zo vstupu.

  • V prípade chyby hneď na začiatku vstupu tak napríklad vráti 0.
  • Ak hneď na začiatku narazí na koniec vstupu, vráti hodnotu EOF (typicky -1).
  • Vstup z konzoly sa dá ukončiť pod Linuxom ako Ctrl+D resp. pod Windowsom ako Ctrl+Z a Enter

Príklad: zadávanie dátumu vo formáte deň.mesiac.rok s kontrolou vstupu:

#include <cstdio>
using namespace std;

void vypisDatum(int d, int m, int r) {
    printf("%02d.%02d.%04d\n", d, m, r);
}

int main() {
    int d, m, r;
    printf("Zadaj datum: ");
    int uspech = scanf("%d.%d.%d", &d, &m, &r);
    if (uspech == 3) {
        printf("Datum je ");
        vypisDatum(d, m, r);
    } else {
        printf("Nebol zadany korektny datum.\n");
        printf("Pocet uspesne nacitanych poloziek: %d.\n", uspech);
    }
}

Ďalší program počíta súčet postupne zadávaných čísel, až kým je zadané nekorektné číslo alebo koniec súboru:

#include <cstdio>
using namespace std;

int main() {
    double sum = 0;
    double x;
    while (scanf("%lf", &x) == 1) {
         sum += x;
    }
    printf("Sucet je %.2f\n", sum);
}

Textové súbory

Na načítavanie a vypisovanie dát sme doposiaľ používali výhradne konzolu. V praxi však často potrebujeme spracovávať dáta uložené v súboroch.

  • Zameriame sa na súbory v textovom formáte, s ktorými sa pracuje podobne ako s konzolou.
  • V C++ existujú ekvivalenty cin >> a cout << aj pre súbory, nájdete ich v knižnici fstream.

Základy: typ FILE * a funkcie fopen, fclose, fprintf, fscanf

So súbormi sa pri použití knižnice cstdio pracuje pomocou typu FILE * (veľkými písmenami).

  • Je to smerník na štruktúru typu FILE, ktorá obsahuje informácie o súbore, s ktorým sa práve pracuje.
  • Premenné pre prácu so súbormi tak možno definovať napríklad takto:
FILE *fr, *fw;

Otvorenie súboru pre čítanie

  • fr = fopen("vstup.txt", "r");
  • Otvorí súbor s názvom vstup.txt (prípadne možno zadať kompletnú cestu k súboru).
  • Ak taký súbor neexistuje alebo sa nedá otvoriť, do fr priradí NULL.
  • Z otvoreného súboru môžeme čítať napríklad pomocou fscanf, ktorá je analógiou k scanf.
  • Napríklad fscanf(fr, "%d", &x);

Otvorenie súboru pre zápis

  • fw = fopen("vystup.txt", "w");
  • Vytvorí súbor s názvom vystup.txt. Ak už existoval, zmaže jeho obsah (keby sme vo volaní fopen namiesto "w" použili "a", pridávalo by sa na koniec existujúceho súboru).
  • Ak sa nepodarí súbor otvoriť, do fw priradí NULL.
  • Do otvoreného súboru môžeme zapisovať napr. pomocou funkcie fprintf, ktorá je analógiou k printf.
  • Napr. fprintf(fw, "%d", x);

Zatvorenie súboru

  • Po ukončení práce so súborom je ho potrebné zavrieť pomocou fclose(f);
  • Počet súčasne otvorených súborov je obmedzený.

Príklad

Nasledujúci program načíta číslo n a následne n celých čísel zo súboru vstup.txt. Do súboru vystup.txt vypíše vstupné čísla v opačnom poradí.

#include <cstdio>
#include <cassert>
using namespace std;

int main() {
    // otvorime vstupny subor a skontrolujeme, ze sa podarilo
    FILE *fr = fopen("vstup.txt", "r");   
    assert(fr != NULL);
    
    // nacitame pocet cisel
    int n, uspech;
    uspech = fscanf(fr, "%d", &n);
    assert(uspech == 1 && n >= 0);

    // alokujeme pole a nacitame cisla
    int *a = new int[n];
    for (int i = 0; i < n; i++) {
        uspech = fscanf(fr, "%d", &a[i]);
        assert(uspech == 1);
    }
    fclose(fr);

    // otvorime vystupny subor a skontrolujeme, ze sa podarilo
    FILE *fw = fopen("vystup.txt", "w");
    assert(fw != NULL);

    // vypiseme cisla odzadu
    for (int i = n-1; i >= 0; i--) {
        fprintf(fw, "%d ", a[i]);
    }
    fclose(fw);
    delete[] a;    
}

Štandardný vstup a výstup ako súbor

So štandardným vstupom a výstupom sa pracuje rovnako ako so súborom. V cstdio sú definované dva konštantné smerníky

FILE *stdin, *stdout;

pre štandardný vstupný a výstupný prúd. Tie tak môžu byť použité v ľubovoľnom kontexte, v ktorom sa očakáva súbor. Napríklad volanie fscanf(stdin, "%d", &x) je ekvivalentné volaniu scanf("%d", &x).

Ten istý kód sa tak dá použiť na prácu so súbormi aj so štandardným vstupom resp. výstupom – stačí len podľa potreby nastaviť premennú typu FILE *. Typické použitie je napríklad nasledovné:

    // do retazca str nacitame z konzoly meno suboru alebo pomlcku
    char str[101];
    scanf("%100s", str);
    // do fw otvorime subor alebo pouzijeme konzolu
    FILE *fw;    
    if (strcmp(str, "-") == 0) {
        fw = stdout;
    } else {
        fw = fopen(str, "w");
    }
    // zapiseme nieco do fw
    fprintf(fw, "Hello world!\n");
    // ak treba, zatvorime subor
    if (strcmp(str, "-") != 0) {
        fclose(fw);
    }

Testovanie konca súboru

Existujú dve možnosti testovania, či sme dosiahli koniec súboru:

  • V knižnici cstdio je definovaná symbolická konštanta EOF, ktorá má väčšinou hodnotu -1. Ak sa funkcii fscanf nepodarí načítať žiadnu hodnotu, pretože načítavanie dospelo ku koncu súboru, vráti konštantu EOF ako svoj výstup.
  • Funkcia feof(subor) vráti true práve vtedy, keď sa funkcia fscanf (alebo nejaká iná funkcia) už niekedy pokúšala čítať za koncom súboru subor.

Spracovanie vstupu pozostávajúceho z postupnosti čísel

Často na vstupe očakávame postupnosť číselných hodnôt oddelených bielymi znakmi. Pozrime sa na tri obvyklé možnosti, ako môže byť takýto vstup zadaný a spracovaný pomocou funkcie fscanf.

Formát 1: N (počet čísel) a následne N ďalších čísel.

#include <cstdio>
#include <cassert>
using namespace std;

int main() {
    FILE *f;
    const int MAXN = 100;
    int a[MAXN], N, uspech;

    f = fopen("vstup.txt", "r");
    assert(f != NULL);

    uspech = fscanf(f, "%d ", &N);
    assert(uspech == 1 && N >= 0 && N <= MAXN);

    for (int i = 0; i < N; i++) {
        uspech = fscanf(f, "%d ", &a[i]);
        assert(uspech == 1);
    }
    fclose(f);

    // tu pride spracovanie dat v poli a
}

Formát 2: postupnosť čísel ukončená číslom -1 alebo inou špeciálnou hodnotu.

    // otvorime subor f ako vyssie
    N = 0;
    int x;
    uspech = fscanf(f, "%d ", &x);
    assert(uspech == 1);
    while (x != -1) {
        assert(N < MAXN);
        a[N] = x;
        N++;
        uspech = fscanf(f, "%d ", &x);
        assert(uspech == 1);
    }
    // zatvorime subor a spracujeme data

Formát 3: čísla, až kým neskončí súbor (najtypickejší prípad v praxi).

Priamočiary prístup nefunguje vždy správne:

    // otvorime subor f ako vyssie
    N = 0;
    while (!feof(f)) {
        assert(N < MAXN);
        uspech = fscanf(f, "%d", &a[N]);
        assert(uspech == 1); // bude tu padat
        N++;
    }
    // zatvorime subor a spracujeme data
  • Tento program nefunguje, ak po poslednom čísle v súbore nasleduje ešte koniec riadku (čo je obvyklé).
  • Pri načítaní tohto čísla skončíme na symbole konca riadku, fscanf sa teda nepokúsi čítať za koncom súboru.
  • Spustí sa teda ďalšie opakovanie cyklu, v ktorom sa už ale nepodarí ďalšie číslo načítať a assert ukončí program s chybovou hláškou.
  • Aj bez príkazu assert by sme mali problém, že do poľa by sa uložila nezmyselná hodnota.

Tento nedostatok môžeme napraviť napríklad tak, že vo volaní funkcie fscanf dáme vo formátovacom reťazci za %d medzeru. Tá sa bude pokúšať preskočiť všetky biele znaky až po najbližší nebiely; pritom natrafí na koniec súboru a feof(f) už bude vracať true.

#include <cstdio>
#include <cassert>
using namespace std;

int main() {
    FILE *f;
    const int MAXN = 100;
    int a[MAXN], N, uspech;

    f = fopen("vstup.txt", "r");
    assert(f != NULL);

    N = 0;
    while (!feof(f)) {
        assert(N < MAXN);
        uspech = fscanf(f, "%d ", &a[N]);
        assert(uspech == 1);
        N++;
    }
    fclose(f);

    // tu pride spracovanie dat v poli a
}

Cvičenie: upravte úryvky programu vyššie tak, aby pracoval s číslami typu double alebo so slovami.

Zhrnutie

  • Doteraz sme s konzolou pracovali pomocou cin a cout z knižnice iostream, ktorá patrí do C++.
  • V jazyku C sa na načítavanie čísel a slov používajú funkcie scanf a printf z knižnice cstdio.
    • Funkcia scanf umožňuje tiež testovať koniec vstupu a správnosť prečítaných hodnôt.
    • Funkcia printf umožňuje pohodlne vypísať viacero hodnôt aj s nastavením formátovania ako napr. počet desatinných miest alebo zarovnávanie.
  • Pri práci so súbormi súbor typu FILE * otvoríme funkciou fopen, zatvoríme fclose.
  • Namiesto scanf, printf pri súboroch použijeme fprintf, fscanf.