Programovanie (1) v C/C++
1-INF-127, ZS 2024/25
Prednáška 15: Rozdiel medzi revíziami
(23 medziľahlých úprav od rovnakého používateľa nie je zobrazených.) | |||
Riadok 1: | Riadok 1: | ||
== Oznamy == | == 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 == | == 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> | + | 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++"> | <syntaxhighlight lang="C++"> | ||
int n; | int n; | ||
− | T *p, *q; | + | T * p, * q; |
</syntaxhighlight> | </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 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> | ||
Riadok 25: | Riadok 26: | ||
Tu je napr. zvláštny spôsob ako vypísať pole <tt>a</tt>: | Tu je napr. zvláštny spôsob ako vypísať pole <tt>a</tt>: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | int a[ | + | const int n = 4; |
− | for (int *smernik = a; smernik < a + | + | int a[n] = {4, 3, 2, 1}; |
+ | for (int * smernik = a; smernik < a + n; smernik++) { | ||
cout << "Prvok " << smernik - a << " je " << *smernik << endl; | cout << "Prvok " << smernik - a << " je " << *smernik << endl; | ||
} | } | ||
Riadok 34: | Riadok 36: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | int zratajMedzery(char str[]) { // mohli by sme | + | int zratajMedzery(char str[]) { // mohli by sme dať aj char *str |
int pocet = 0; | int pocet = 0; | ||
− | while(*str != 0) { // | + | while(*str != 0) { // kým nenájdeme ukončovaciu nulu |
− | if(*str == ' ') { // skontroluj znak, na | + | if(*str == ' ') { // skontroluj znak, na ktorý ukazuje str |
pocet++; | pocet++; | ||
} | } | ||
− | str++; // | + | str++; // posuň str na ďalší znak |
} | } | ||
return pocet; | return pocet; | ||
Riadok 52: | Riadok 54: | ||
** pozíciu výskytu zistíme smerníkovou aritmetikou: | ** pozíciu výskytu zistíme smerníkovou aritmetikou: | ||
<pre> | <pre> | ||
− | char *text = "Hello world!"; | + | char * text = "Hello world!"; |
− | char *vzorka = "or"; | + | char * vzorka = "or"; |
− | char *where = strstr(text, vzorka); | + | char * where = strstr(text, vzorka); |
if(where != NULL) { | if(where != NULL) { | ||
int position = where - text; | int position = where - text; | ||
} | } | ||
</pre> | </pre> | ||
− | * Ako by ste spočítali počet výskytov vzorky v texte? | + | * 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 | + | * 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> == | ||
Riadok 202: | 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() { | 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); |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Formátovací reťazec sa v <tt>scanf</tt> interpretuje nasledovne: | Formátovací reťazec sa v <tt>scanf</tt> interpretuje nasledovne: | ||
− | * Špecifikácia typu načítavaných premenných | + | * Špecifikácia typu načítavaných premenných je podobná ako pri funkcii <tt>printf</tt>. |
− | ** | + | ** <tt>%d</tt> načíta <tt>int</tt> |
− | + | ** <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. | * 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 229: | Riadok 232: | ||
=== Kontrola správnosti vstupu === | === Kontrola správnosti vstupu === | ||
− | Funkcia <tt>scanf</tt> vracia | + | 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 238: | 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( | + | int main() { |
− | int d,m,r; | + | int d, m, r; |
− | printf("Zadaj datum: "); | + | printf("Zadaj datum: "); |
− | + | 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); | ||
} | } | ||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Ď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( | + | int main() { |
double sum = 0; | double sum = 0; | ||
double x; | double x; | ||
Riadok 266: | Riadok 273: | ||
} | } | ||
printf("Sucet je %.2f\n", sum); | printf("Sucet je %.2f\n", sum); | ||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Riadok 273: | 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 | + | 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 <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>. | + | 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 *fr, *fw; | FILE *fr, *fw; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
'''Otvorenie súboru pre čítanie''' | '''Otvorenie súboru pre čítanie''' | ||
Riadok 290: | 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 | + | * 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 297: | 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 | + | * 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 312: | Riadok 318: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
− | FILE *fr = fopen("vstup.txt", "r"); | + | // otvorime vstupny subor a skontrolujeme, ze sa podarilo |
− | + | FILE *fr = fopen("vstup.txt", "r"); | |
− | assert(fr | + | assert(fr != NULL); |
− | int n, | + | // nacitame pocet cisel |
− | + | int n, uspech; | |
− | assert( | + | 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 < | + | uspech = fscanf(fr, "%d", &a[i]); |
− | + | assert(uspech == 1); | |
− | assert( | ||
} | } | ||
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 332: | Riadok 346: | ||
fclose(fw); | fclose(fw); | ||
delete[] a; | delete[] a; | ||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Riadok 342: | 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 – 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 – stačí len podľa potreby nastaviť premennú typu <tt>FILE *</tt>. Typické použitie je napríklad nasledovné: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | + | // do retazca str nacitame z konzoly meno suboru alebo pomlcku | |
− | + | char str[101]; | |
− | + | 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 | + | 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 374: | Riadok 393: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
FILE *f; | FILE *f; | ||
const int MAXN = 100; | const int MAXN = 100; | ||
− | int a[MAXN], N, | + | int a[MAXN], N, uspech; |
f = fopen("vstup.txt", "r"); | f = fopen("vstup.txt", "r"); | ||
assert(f != NULL); | assert(f != NULL); | ||
− | + | uspech = fscanf(f, "%d ", &N); | |
− | assert( | + | assert(uspech == 1 && N >= 0 && N <= MAXN); |
for (int i = 0; i < N; i++) { | for (int i = 0; i < N; i++) { | ||
− | + | uspech = fscanf(f, "%d ", &a[i]); | |
− | assert( | + | assert(uspech == 1); |
} | } | ||
fclose(f); | fclose(f); | ||
Riadok 400: | Riadok 419: | ||
N = 0; | N = 0; | ||
int x; | int x; | ||
− | + | uspech = fscanf(f, "%d ", &x); | |
− | assert( | + | assert(uspech == 1); |
while (x != -1) { | while (x != -1) { | ||
assert(N < MAXN); | assert(N < MAXN); | ||
a[N] = x; | a[N] = x; | ||
N++; | N++; | ||
− | + | uspech = fscanf(f, "%d ", &x); | |
− | assert( | + | assert(uspech == 1); |
} | } | ||
// zatvorime subor a spracujeme data | // zatvorime subor a spracujeme data | ||
Riadok 420: | Riadok 439: | ||
while (!feof(f)) { | while (!feof(f)) { | ||
assert(N < MAXN); | assert(N < MAXN); | ||
− | + | uspech = fscanf(f, "%d", &a[N]); | |
− | assert( | + | assert(uspech == 1); // bude tu padat |
N++; | N++; | ||
} | } | ||
Riadok 427: | Riadok 446: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | * 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 433: | Riadok 457: | ||
using namespace std; | using namespace std; | ||
− | int main( | + | int main() { |
FILE *f; | FILE *f; | ||
const int MAXN = 100; | const int MAXN = 100; | ||
− | int a[MAXN], N, | + | int a[MAXN], N, uspech; |
f = fopen("vstup.txt", "r"); | f = fopen("vstup.txt", "r"); | ||
Riadok 444: | Riadok 468: | ||
while (!feof(f)) { | while (!feof(f)) { | ||
assert(N < MAXN); | assert(N < MAXN); | ||
− | + | uspech = fscanf(f, "%d ", &a[N]); | |
− | assert( | + | assert(uspech == 1); |
N++; | N++; | ||
} | } | ||
Riadok 453: | 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
Obsah
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.