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

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(20 medziľahlých úprav od rovnakého používateľa nie je zobrazených.)
Riadok 1: Riadok 1:
 
== Oznamy ==
 
== Oznamy ==
  
* Dnes budú zverejnené úlohy na cvičenia č. 9 (menej, než obvykle).
+
* DÚ3 bude zverejnená dnes po prednáške, odovzdajte do '''piatku''' 6.12. 22:00. Zahŕňa dvojrozmerné polia a spracovanie vstupu a výstupu, súbory. Je to dobrý tréning na prvý príklad na skúške.
* Na testovači pribudlo zadanie tretej (a poslednej) domácej úlohy s odovzdaním ''do 4. decembra, 22:00''.
 
* V úvode piatkových doplnkových cvičení bude krátka písomka zameraná predovšetkým na rekurziu a smerníky. Riešenia budete písať priamo do editovateľných zadaní vo formáte pdf a body za písomku budú riadnou súčasťou hodnotenia tohtotýždňových cvičení. Bližšie informácie k technickej relizácii písomky v stredu.
 
  
 
== Príklad na prácu s textovými súbormi ==
 
== Príklad na prácu s textovými súbormi ==
  
Na minulej prednáške sme sa zaoberali základnými technikami práce s textovými súbormi s využitím knižnice <tt>cstdio</tt>. V rámci ich opakovania uvažujme nasledujúci problém: máme daných niekoľko &bdquo;čiastkových&rdquo; textových súborov, z ktorých každý obsahuje postupnosť niekoľkých celých čísel. Hlavný vstupný súbor <tt>vstup.txt</tt> potom pozostáva z:
+
* Prácu s textovými súbormi si zopakujeme na nasledujúcom príklade.
* Prvého riadku obsahujúceho názov výstupného súboru.
+
* Hlavný vstupný súbor <tt>vstup.txt</tt> má na prvom riadku názov výstupného súboru.
* Niekoľkých ďalších riadkov zakaždým obsahujúcich názov niektorého &bdquo;čiastkového&rdquo; súboru nasledovaný celým číslom.  
+
* Každý z niekoľkých ďalších riadkov obsahuje názov jedného vstupného súboru nasledovaný počtom čísel, ktoré z neho chceme načítať.
 +
* Úlohou je prekopírovať z každého súboru zadaný počet čísel.  
  
Úlohou je pre každú dvojicu tvorenú názvom &bdquo;čiastkového&rdquo; súboru a číslom ''N'', uvedenú v súbore <tt>vstup.txt</tt>, prekopírovať z daného &bdquo;čiastkového&rdquo; súboru do výstupného súboru prvých ''N'' čísel.  
+
Napríklad <tt>vstup.txt</tt>
 
+
<pre>
Napríklad pre &bdquo;čiastkový&rdquo; súbor <tt>a.txt</tt> pozostávajúci z čísel
+
vystup.txt
 +
a.txt 2
 +
b.txt 1
 +
a.txt 3
 +
</pre>
 +
Súbor <tt>a.txt</tt>
 
<pre>
 
<pre>
 
1 2 3 4 5 6 7 8 9
 
1 2 3 4 5 6 7 8 9
 
</pre>
 
</pre>
a &bdquo;čiastkový&rdquo; súbor <tt>b.txt</tt> pozostávajúci z čísel
+
Súbor <tt>b.txt</tt>
 
<pre>
 
<pre>
 
10 20 30 40 50 60 70 80 90
 
10 20 30 40 50 60 70 80 90
 
</pre>
 
</pre>
sa pre hlavný vstupný súbor <tt>vstup.txt</tt> daný ako
+
Výsledný súboru <tt>vystup.txt</tt>
<pre>
 
vystup.txt
 
a.txt 2
 
b.txt 1
 
a.txt 3
 
</pre>
 
majú do výstupného súboru <tt>vystup.txt</tt> nakopírovať hodnoty
 
 
<pre>
 
<pre>
 
1 2 10 1 2 3
 
1 2 10 1 2 3
 
</pre>
 
</pre>
  
Túto úlohu realizuje program uvedený nižšie, ktorý pracuje v nasledujúcich krokoch:
+
Program uvedený nižšie pracuje v nasledujúcich krokoch:
 
* Otvorí hlavný vstupný súbor <tt>vstup.txt</tt> a prečíta z neho názov výstupného súboru.
 
* Otvorí hlavný vstupný súbor <tt>vstup.txt</tt> a prečíta z neho názov výstupného súboru.
 
* Otvorí výstupný súbor.
 
* Otvorí výstupný súbor.
* Následne, až kým nenarazí na koniec hlavného vstupného súboru, opakuje nasledujúce:
+
* Následne opakovane prečíta názov súboru a počet čísel ''N''.
** Z hlavného vstupného súboru prečíta názov &bdquo;čiastkového&rdquo; súboru a prirodzené číslo ''N''.
+
* Otvorí súbor s práve načítaným názvom, prekopíruje z neho ''N'' čísel do výstupného súboru a následne tento súbor zatvorí.
** Otvorí &bdquo;čiastkový&rdquo; súbor s práve načítaným názvom, prekopíruje z neho ''N'' čísel do výstupného súboru a následne tento &bdquo;čiastkový&rdquo; súbor zatvorí.
 
 
* Zatvorí hlavný vstupný súbor aj výstupný súbor.
 
* Zatvorí hlavný vstupný súbor aj výstupný súbor.
  
Dĺžka načítavaných reťazcov bude vo volaniach funkcie <tt>fscanf</tt> obmedzená na 19 znakov (to teda bude maximálna dĺžka názvu súboru, s ktorou bude program vedieť pracovať).
+
Dĺžka načítavaných reťazcov bude vo volaniach funkcie <tt>fscanf</tt> obmedzená na 19 znakov
 +
* to teda je maximálna dĺžka názvu súboru, s ktorou bude program vedieť pracovať
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
Riadok 47: Riadok 44:
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     FILE *fr_main, *fr_part, *fw;
 
     FILE *fr_main, *fr_part, *fw;
     int N,r,num;
+
     int N, success, num;
 
     fr_main = fopen("vstup.txt", "r");
 
     fr_main = fopen("vstup.txt", "r");
 
     assert(fr_main != NULL);
 
     assert(fr_main != NULL);
 
      
 
      
 
     char filename[20];
 
     char filename[20];
     r = fscanf(fr_main,"%19s",filename);
+
     success = fscanf(fr_main, "%19s", filename);
     assert(r == 1);  
+
     assert(success == 1);  
 
      
 
      
 
     fw = fopen(filename, "w");
 
     fw = fopen(filename, "w");
 
     assert(fw != NULL);
 
     assert(fw != NULL);
 
     while (!feof(fr_main)) {
 
     while (!feof(fr_main)) {
         r = fscanf(fr_main,"%19s %d ",filename,&N);
+
         success = fscanf(fr_main, "%19s %d ", filename, &N);
         assert(r == 2);
+
         assert(success == 2);
 
         fr_part = fopen(filename, "r");
 
         fr_part = fopen(filename, "r");
 
         assert(fr_part != NULL);
 
         assert(fr_part != NULL);
         for (int i = 0; i <= N-1; i++) {
+
         for (int i = 0; i < N; i++) {
             r = fscanf(fr_part, "%d ", &num);
+
             success = fscanf(fr_part, "%d ", &num);
             assert(r == 1);
+
             assert(success == 1);
 
             fprintf(fw, "%d ", num);
 
             fprintf(fw, "%d ", num);
 
         }
 
         }
Riadok 73: Riadok 70:
 
     fclose(fw);
 
     fclose(fw);
 
     fclose(fr_main);  
 
     fclose(fr_main);  
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 79: Riadok 75:
 
== Čítanie a zapisovanie po znakoch==
 
== Čítanie a zapisovanie po znakoch==
  
Knižnica <tt>cstdio</tt> obsahuje funkciu
+
Knižnica <tt>cstdio</tt> obsahuje aj funkcie na čítanie a zapisovanie súboru po znakoch.
<syntaxhighlight lang="C++">
+
 
int getc(FILE *f);
+
* Funkcia <tt>int getc(FILE *f)</tt> načíta a vráti jeden znak zo súboru
</syntaxhighlight>
+
** Ak načítanie neprebehne úspešne, výsledkom je špeciálna konštanta <tt>EOF</tt>, ktorá je vždy rôzna od ľubovoľnej hodnoty typu <tt>char</tt>
ktorá načíta jeden znak zo súboru, na ktorý odkazuje smerník <tt>f</tt>. V prípade, že načítanie prebehne úspešne, je výstupom funkcie <tt>getc</tt> kód tohto znaku. V opačnom prípade je výstupom špeciálna konštanta <tt>EOF</tt>, ktorá je vždy rôzna od ľubovoľnej hodnoty typu <tt>char</tt>; to je aj dôvod, prečo funkcia <tt>getc</tt> nevracia hodnotu typu <tt>char</tt>, ale hodnotu typu <tt>int</tt>. Je preto dôležité vyvarovať sa ukladania výstupnej hodnoty funkcie <tt>getc</tt> do premennej typu <tt>char</tt> &ndash; v takom prípade nie je možné rozoznať koniec súboru. Funkcia
+
** Neukladajte výstupnú hodnotu funkcie <tt>getc</tt> do premennej typu <tt>char</tt>, lebo nebudete vedieť rozoznať koniec súboru.  
<syntaxhighlight lang="C++">
+
* Funkcia <tt>int getchar()</tt> je skratka pre <tt>getc(stdin)</tt>, načíta teda jeden znak z konzoly
int getchar(void);
+
** Avšak rovnako ako pri <tt>scanf</tt> sa vstup začne spracovávať až potom, ako používateľ stlačí <tt>Enter</tt>, nie je takto možné reagovať priamo na stlačenie nejakej klávesy.
</syntaxhighlight>
 
je skratkou pre <tt>getc</tt> s parametrom <tt>stdin</tt>; načíta tak jeden znak z konzoly (rovnako ako napríklad pri <tt>scanf</tt> sa však vstup začne spracovávať až potom, ako používateľ stlačí <tt>Enter</tt> &ndash; nie je takto možné reagovať priamo na stlačenie nejakej klávesy).  
 
  
Výstupným náprotivkom k funkcii <tt>getc</tt> je funkcia
+
* Funkcia <tt>int putc(int c, FILE *f)</tt> zapíše znak <tt>c</tt> do súboru.
<syntaxhighlight lang="C++">
+
* Funkcia <tt>int putchar(int c)</tt> je skratkou pre <tt>putc(c, stdout)</tt>, vypíše teda daný znak na konzolu.
int putc(int c, FILE *f);
 
</syntaxhighlight>
 
zapíše znak <tt>c</tt> do súboru, na ktorý odkazuje smerník <tt>f</tt>. Funkcia
 
<syntaxhighlight lang="C++">
 
int putchar(int c);
 
</syntaxhighlight>
 
je skratkou pre <tt>putc</tt> s parametrami <tt>c</tt> a <tt>stdout</tt>; vypíše teda daný znak na konzolu.
 
  
 
=== Príklad: kopírovanie súboru ===
 
=== Príklad: kopírovanie súboru ===
Riadok 107: Riadok 94:
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     FILE *fr = fopen("original.txt", "r");
 
     FILE *fr = fopen("original.txt", "r");
 
     FILE *fw = fopen("kopia.txt", "w");
 
     FILE *fw = fopen("kopia.txt", "w");
Riadok 119: Riadok 106:
 
     fclose(fr);
 
     fclose(fr);
 
     fclose(fw);
 
     fclose(fw);
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Načítavanie pritom možno realizovať aj priamo v podmienke cyklu <tt>while</tt> &ndash; výstupom priradenia <tt>c = getc(fr)</tt> je nová hodnota premennej <tt>c</tt>, ktorú možno hneď porovnať s <tt>EOF</tt>. Kľúčová časť horeuvedeného programu sa tak dá kratšie, hoci menej čitateľne, prepísať takto:
 
<syntaxhighlight lang="C++">
 
    int c;
 
    while ((c = getc(fr)) != EOF) {
 
        putc(c, fw);
 
    }
 
</syntaxhighlight>
 
 
=== Konce riadkov ===
 
 
Koniec riadku je reprezentovaný znakom <tt>'\n'</tt>. Pri čítaní alebo zápise sa môže prekladať na jeden alebo dva znaky v závislosti od operačného systému (<tt><LF></tt>, <tt><CR><LF></tt>, alebo <tt><CR></tt>).
 
  
 
''Cvičenie:'' čo robí nasledujúci program?
 
''Cvičenie:'' čo robí nasledujúci program?
 +
* výsledkom priradenia <tt>c = getc(fr)</tt> je hodnota priradená do premennej <tt>c</tt>
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     FILE *fr;
 
     FILE *fr;
 
     int c;
 
     int c;
Riadok 149: Riadok 123:
 
         putchar(c);
 
         putchar(c);
 
     }
 
     }
     putchar(c);           // vypis \n
+
     putchar(c);        
 
     fclose(fr);
 
     fclose(fr);
 
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 158: Riadok 130:
 
=== Funkcia <tt>ungetc</tt> ===
 
=== Funkcia <tt>ungetc</tt> ===
  
Signálom na ukončenie načítavania znakov často býva &bdquo;náraz&rdquo; na znak, ktorý už nie je žiadúce prečítať. V takom prípade je užitočné &bdquo;posunúť sa v načítavaní o jeden krok nazad&rdquo;. Túto úlohu realizuje funkcia
+
* Často sa stáva, že pri načítavaní znakov nájdeme koniec práve načítavaného úseku po načítaní znaku, ktorý už nie je žiadúce prečítať.  
 
+
* Vtedy je užitočné posunúť sa v načítavaní o jeden krok nazad.  
<syntaxhighlight lang="C++">
+
* Túto úlohu realizuje funkcia <tt>int ungetc(int c, FILE * f)</tt>.
int ungetc(int c, FILE * f);
+
* Väčšinou ako <tt>c</tt> použijeme posledne načítaným znak zo súboru <tt>f</tt>.
</syntaxhighlight>
+
* Môžeme však použiť aj iný znak, ktorý bude virtuálne pridaný na začiatok neprečítanej časti súboru. Súbor sa reálne nemení, ale pri nasledujúcom čítaní z neho sa ako prvý prečíta znak <tt>c</tt>.  
 
+
* V prípade úspechu <tt>ungetc(c,f)</tt> vráti hodnotu <tt>c</tt>; v prípade neúspechu je výstupom <tt>EOF</tt>.  
Ak bol posledným načítaným znakom (zo súboru, na ktorý odkazuje smerník <tt>f</tt>) znak <tt>c</tt>, volanie <tt>ungetc(c,f)</tt> má skutočne efekt &bdquo;kroku nazad&rdquo;. Ako parameter funkcie <tt>ungetc</tt> však možno okrem naposledy prečítaného znaku použiť aj ľubovoľný iný znak <tt>c</tt> &ndash; funkcia <tt>ungetc</tt> potom tento znak &bdquo;virtuálne&rdquo; pridá na začiatok neprečítanej časti súboru. Súbor sa teda reálne nemení, ale pri nasledujúcom čítaní z neho sa ako prvý prečíta znak <tt>c</tt>. V prípade úspechu sa po volaní <tt>ungetc(c,f)</tt> vráti hodnota <tt>c</tt>; v prípade neúspechu je výstupom konštanta <tt>EOF</tt>.  
+
* Takéto správanie funkcie <tt>ungetc</tt> je však garantované len ak sa táto funkcia nevolá viackrát za sebou.
 
 
Takéto správanie funkcie <tt>ungetc</tt> je však ''garantované len v prípade, že sa'' táto funkcia ''nevolá viackrát za sebou''.
 
  
''Príklad č. 1:'' Nasledujúci kus programu skonvertuje reťazec pozostávajúci zo znakov <tt>'0'</tt> až <tt>'9'</tt> na zodpovedajúcu číselnú hodnotu. Keď narazí na prvý znak, ktorý nie je cifra, vráti ho, aby sa dal použiť pri ďalšom spracovávaní.
+
'''Príklad č. 1:''' Nasledujúci kus programu skonvertuje reťazec pozostávajúci zo znakov <tt>'0'</tt> až <tt>'9'</tt> na zodpovedajúcu číselnú hodnotu. Keď narazí na prvý znak, ktorý nie je cifra, vráti ho, aby sa dal použiť pri ďalšom spracovávaní.
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
     int hodnota = 0;
 
     int hodnota = 0;
Riadok 179: Riadok 149:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
''Príklad č. 2:'' Nasledujúci program prečíta číslo pomocou funkcie <tt>fscanf</tt>, predtým však musí prečítať neznámy počet znakov <tt>'$'</tt>.
+
'''Príklad č. 2:''' Nasledujúci program prečíta číslo pomocou funkcie <tt>fscanf</tt>, predtým však musí prečítať neznámy počet znakov <tt>'$'</tt>.
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     FILE *fr = fopen("vstup.txt", "r");
 
     FILE *fr = fopen("vstup.txt", "r");
 
     int c = getc(fr);
 
     int c = getc(fr);
Riadok 197: Riadok 167:
 
    
 
    
 
     fclose(fr);
 
     fclose(fr);
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 204: Riadok 172:
 
== Čítanie a zapisovanie po riadkoch ==
 
== Čítanie a zapisovanie po riadkoch ==
  
V knižnici <tt>cstdio</tt> je definovaná funkcia
+
Riadok vieme načítať zo súboru nasledujúcou funkciou
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
char *fgets(char *str, int n, FILE * f);
 
char *fgets(char *str, int n, FILE * f);
 
</syntaxhighlight>
 
</syntaxhighlight>
pomocou ktorej možno načítať zo súboru, na ktorý ukazuje smerník <tt>f</tt>, práve jeden riadok (alebo nejakú jeho časť, ak je tento riadok príliš dlhý). Vstupnými argumentmi funkcie <tt>fgets</tt> :
+
Jej argumenty
 
* Pole znakov <tt>str</tt>, do ktorého sa v prípade úspechu riadok načíta.
 
* Pole znakov <tt>str</tt>, do ktorého sa v prípade úspechu riadok načíta.
* Číslo <tt>n</tt> určujúce maximálny počet znakov skopírovaných do poľa <tt>str</tt>. ''Presnejšie:'' do poľa <tt>str</tt> sa z daného riadku súboru skopíruje najviac <tt>n-1</tt> znakov a reťazec <tt>str</tt> sa následne ukončí znakom <tt>\0</tt>. Pri typickom volaní funkcie <tt>fgets</tt> je teda <tt>n</tt> rovné dĺžke poľa <tt>str</tt>.
+
* Číslo <tt>n</tt> je typicky dĺžka poľa <tt>str</tt>  
 +
** Funkcia do poľa <tt>str</tt> z daného riadku súboru načíta a uloží najviac <tt>n-1</tt> znakov a reťazec <tt>str</tt> následne ukončí znakom <tt>0</tt>.  
 
* Smerník <tt>f</tt> na súbor, z ktorého sa má riadok prečítať.
 
* Smerník <tt>f</tt> na súbor, z ktorého sa má riadok prečítať.
  
Funkcia <tt>fgets</tt> na týchto argumentoch postupne načítava znaky zo súboru, na ktorý ukazuje smerník <tt>f</tt>, pričom ich ukladá do <tt>str</tt>. To robí až dovtedy, kým narazí na koniec riadku (<tt>\n</tt>) alebo koniec súboru, prípadne kým sa zo súboru neprečíta <tt>n-1</tt> znakov. Prípadný znak <tt>\n</tt> na konci riadku sa (pokiaľ nebolo načítaných príliš veľa znakov) nezahodí, ale pridá sa na koniec reťazca <tt>str</tt>. Výstupom funkcie <tt>fgets</tt> je v prípade načítania aspoň jedného znaku načítaný reťazec <tt>str</tt>; v prípade &bdquo;nárazu&rdquo; na koniec súboru je výstupom <tt>NULL</tt> a reťazec <tt>str</tt> ostáva nezmenený.
 
  
''Príklad:'' nasledujúci program spočíta počet riadkov v súbore <tt>vstup.txt</tt> (za predpokladu, že žiaden z týchto riadkov nie je dlhší ako 100 znakov vrátane znaku <tt>\n</tt> na konci riadku):
+
* Funkcia <tt>fgets</tt> teda načítava znaky dovtedy, kým narazí na koniec riadku (<tt>\n</tt>) alebo koniec súboru alebo kým zo súboru neprečíta <tt>n-1</tt> znakov.
 +
* Prípadný znak <tt>\n</tt> na konci riadku sa nezahodí, ale pridá sa na koniec reťazca <tt>str</tt> (pokiaľ nebolo načítaných príliš veľa znakov).
 +
* Výstupom funkcie je v prípade načítania aspoň jedného znaku načítaný reťazec <tt>str</tt>; inak je výstupom <tt>NULL</tt> a reťazec <tt>str</tt> ostáva nezmenený.
 +
 
 +
Funkcia <tt>int fputs(const char *str, FILE *f)</tt> vypíše reťazec <tt>str</tt> do súboru
 +
* <tt>str</tt> môže obsahovať hocikoľko koncov riadkov (aj nula)
 +
* pri chybe vráti <tt>EOF</tt>, inak vráti nezáporné celé číslo
 +
 
 +
 
 +
'''Príklad:''' nasledujúci program spočíta počet riadkov v súbore <tt>vstup.txt</tt> (za predpokladu, že žiaden z týchto riadkov nie je dlhší ako 100 znakov vrátane znaku <tt>\n</tt> na konci riadku):
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
Riadok 222: Riadok 199:
 
const int maxN = 101;
 
const int maxN = 101;
  
int main(void) {
+
int main() {
 
     char str[maxN];
 
     char str[maxN];
 
     int num = 0;
 
     int num = 0;
Riadok 232: Riadok 209:
 
     fclose(fr);
 
     fclose(fr);
 
      
 
      
     printf("%d\n",num);
+
     printf("Pocet riadkov je %d\n", num);
   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
''Cvičenie:'' Zistite, ako sa správa uvedený program, keď posledným znakom v súbore je resp. nie je znak <tt>\n</tt>. Zistite, čo program vypíše na výstup pre súbor, ktorý obsahuje jediný riadok o 200 znakoch.
+
'''Cvičenie:'''
 +
* Zistite, ako sa správa uvedený program, keď posledným znakom v súbore je resp. nie je znak <tt>\n</tt>.  
 +
* Zistite, čo program vypíše na výstup pre súbor, ktorý obsahuje jediný riadok o 200 znakoch.
  
Výstupným náprotivkom funkcie <tt>fgets</tt> je funkcia
+
== Prístupy k spracovaniu textového vstupu ==
<syntaxhighlight lang="C++">
 
int fputs(const char *str, FILE *f);
 
</syntaxhighlight>
 
ktorá do súboru, na ktorý ukazuje smerník <tt>f</tt>, vypíše reťazec <tt>str</tt>. Vypisovaný reťazec <tt>str</tt> pritom môže obsahovať ľubovoľný (aj nulový) počet výskytov symbolu <tt>\n</tt> pre koniec riadku. V prípade úspechu vráti funkcia <tt>fputs</tt> nezáporné celé číslo; v prípade neúspechu vráti konštantu <tt>EOF</tt>.
 
  
== Prístupy k spracovaniu textového vstupu  ==
+
Časté schémy spracovania textového súboru:
 +
* Pomocou <tt>fscanf</tt> načítavame jednotlivé čísla, slová a pod. (vhodné, keď všetky biele znaky považujeme za ekvivalentné oddeľovače)
 +
* Čítame po znakoch pomocou <tt>getc</tt> (pomerne univerzálne, ale niekedy prácne)
 +
* Čítame po riadkoch pomocou <tt>fgets</tt> do reťazca, potom reťazec spracovávame (vhodné, keď riadok je ucelená časť súboru, ktorá nás zaujíma)
  
Vstup môže byť v textovom súbore zadaný v rôznych formátoch. V závislosti od formátu potom môžu byť výhodnými rôzne spôsoby jeho spracovania. Často používanými prístupmi k spracovaniu textového vstupu sú napríklad nasledujúce:
+
Niekedy je užitočné tieto prístupy aj kombinovať.
* Pomocou funkcie <tt>fscanf</tt> postupne načítať jednotlivé čísla, slová, a podobne. Tento prístup býva zvyčajne výhodný vtedy, keď sa všetky biele znaky považujú za ekvivalentné oddeľovače.
 
* Pomocou funkcie <tt>getc</tt> spracovať vstupný súbor po znakoch. Tu ide o relatívne univerzálny spôsob spracovania vstupu, ktorý je však v niektorých situáciách pomerne prácny.
 
* Pomocou funkcie <tt>fgets</tt> postupne prečítať jednotlivé riadky do reťazca a tento reťazec následne spracovať. Tento prístup je výhodný najmä vtedy, keď má koniec riadku funkciu prirodzeného oddeľovača vstupov a keď je dĺžka riadku predom obmedzená.
 
  
Často môže byť užitočné horeuvedené prístupy aj navzájom kombinovať.
 
  
''Príklad č. 1:'' predpokladajme, že potrebujeme nájsť dĺžku najdlhšieho riadku v súbore (vrátane symbolu <tt>\n</tt>, ktorý sa môže vyskytovať na jeho konci). Nasledujúce dva programy túto úlohu riešia dvoma odlišnými spôsobmi:
+
'''Príklad:''' chceme nájsť dĺžku najdlhšieho riadku v súbore
* Prvý program postupne načítava riadky do reťazca, ktorý následne spracúva (problém, ak je riadok príliš dlhý).
+
* Prvá možnosť je čítanie riadkov do reťazca a ich spracovanie (problém, ak je riadok príliš dlhý)
* Druhý program číta súbor po znakoch, pričom si udržiava premennú <tt>pocet</tt> uchovávajúcu informáciu o tom, koľko písmen sa už v momentálne spracovávanom riadku načítalo.
+
* Druhá možnosť je čítať súbor po znakoch, pričom si potrebujeme udržiavať "stav": koľko písmen sme videli v aktuálnom riadku
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 265: Riadok 237:
 
const int maxN = 100;
 
const int maxN = 100;
  
int main(void) {
+
int main() {
 
     FILE *fr = fopen("vstup.txt", "r");
 
     FILE *fr = fopen("vstup.txt", "r");
 
     int maxDlzka = 0;
 
     int maxDlzka = 0;
Riadok 277: Riadok 249:
 
     fclose(fr);
 
     fclose(fr);
 
     printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
 
     printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 285: Riadok 256:
 
using namespace std;
 
using namespace std;
  
int main(void) {
+
int main() {
 
     FILE *fr = fopen("vstup.txt", "r");
 
     FILE *fr = fopen("vstup.txt", "r");
 
     int maxDlzka = 0;
 
     int maxDlzka = 0;
Riadok 305: Riadok 276:
 
     fclose(fr);
 
     fclose(fr);
 
     printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
 
     printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
Cvičenie: čo ak chceme zistiť, koľký riadok v súbore bol ten najdlhší?
  
''Príklad č. 2:'' nasledujúci program spracúva vstupný súbor obsahujúci čísla oddelené bielymi znakmi (medzery, tabulátory, konce riadkov,...), pričom medzi dvoma číslami môže byť aj viac ako jeden oddeľovač. Pre každý riadok program vypíše súčet čísel, ktoré sa v tomto riadku vyskytujú (predpokladá pritom, že každý &ndash; t. j. aj posledný &ndash; riadok vstupného súboru je ukončený symbolom <tt>\n</tt>).
+
'''Príklad 2:''' máme súbor s číslami oddelenými bielymi znakmi (medzery, tabulátory, konce riadkov,...), pričom medzi dvoma číslami môže byť aj viac ako jeden oddeľovač. Chceme spočítať súčet čísel na každom riadku.
 +
* Nepríjemná kombinácia rozlišovania koncov riadku od iných bielych znakov a čítania formátovaných hodnôt (čísel)
 +
* Môžeme prečítať riadok do reťazca a rozložiť na čísla (napr. funkciou <tt>[https://en.cppreference.com/w/c/io/fscanf sscanf]</tt> a so špecifikáciou konverzie <tt>%n</tt>, ktorá uloží počet načítaných znakov).
 +
* Program nižšie však používa kombináciu <tt>getc</tt>, <tt>ungetc</tt> a <tt>fscanf</tt>
 +
** Biele znaky spracúva pomocou funkcie <tt>getc</tt>. Ak je niektorý z týchto znakov koncom riadku, vypíše zistený súčet čísel.
 +
** Po nájdení nebieleho znaku ho vráti do vstupného prúdu funkciou <tt>ungetc</tt>, prečíta číslo pomocou funkcie <tt>fscanf</tt> a aktualizuje súčet.
 +
** Na zistenie, či je prečítaný znak biely, využíva program funkciu <tt>isspace</tt> z knižnice <tt>cctype</tt>.
 +
** Program predpokladá, že aj posledný riadok vstupného súboru je ukončený symbolom <tt>\n</tt>.
  
Vzhľadom na to, že tu ide o pomerne nepríjemnú kombináciu rozlišovania koncov riadku od iných bielych znakov a čítania formátovaných hodnôt (čísel), kombinuje nasledujúci program čítanie po znakoch s využívaním funkcie <tt>fscanf</tt>. Pracuje pritom nasledovne:
 
* Kým sú na vstupe biele znaky, spracúva ich pomocou funkcie <tt>getc</tt>. Ak je niektorý z týchto znakov koncom riadku, vypíše zistený súčet čísel.
 
* Po &bdquo;náraze&rdquo; na prvý nebiely znak použije funkciu <tt>ungetc</tt> na jeho vrátenie do vstupného prúdu. Následne prečíta číslo pomocou funkcie <tt>fscanf</tt> a na základe prečítanej hodnoty aktualizuje súčet.
 
* Na zistenie, či je prečítaný znak biely, využíva nasledujúci program funkciu <tt>isspace</tt> z knižnice <tt>cctype</tt>.
 
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 327: Riadok 301:
 
     while (!feof(fr)) {
 
     while (!feof(fr)) {
 
         int c = getc(fr);
 
         int c = getc(fr);
         while (c != EOF && isspace(c)) { // Precitaj biele znaky po najblizsi nebiely.
+
        // Precitaj biele znaky po najblizsi nebiely.
             if (c == '\n') {             // Na konci riadku vypis sucet.
+
         while (c != EOF && isspace(c)) {  
 +
             if (c == '\n') {   // Na konci riadku vypis sucet.
 
                 printf("Sucet %d\n", sucet);
 
                 printf("Sucet %d\n", sucet);
 
                 sucet = 0;
 
                 sucet = 0;
Riadok 334: Riadok 309:
 
             c = getc(fr);
 
             c = getc(fr);
 
         }
 
         }
         if (c == EOF) {                 // Pri naraze na koniec suboru nepokracuj dalej.
+
         if (c == EOF) { // Koniec suboru
 
             break;
 
             break;
 
         }                 
 
         }                 
         ungetc(c, fr);                   // Posledny precitany znak nebol biely; vrat ho do vstupneho prudu.
+
         ungetc(c, fr);  
         fscanf(fr, "%d", &hodnota);     // Precitaj cislo a pripocitaj ho k suctu.
+
         fscanf(fr, "%d", &hodnota);
 
         sucet += hodnota;
 
         sucet += hodnota;
 
     }
 
     }
 
     fclose(fr);
 
     fclose(fr);
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
''Cvičenie:'' upravte program tak, aby pracoval správne aj v prípade, že posledný riadok nie je ukončený symbolom <tt>\n</tt>.
+
'''Cvičenia:'''
 +
* Čo program spraví, ak vstup obsahuje aj prázdne riadky?
 +
* Upravte program, aby pracoval správne aj v prípade, že posledný riadok nie je ukončený symbolom <tt>\n</tt>.
 +
** Čo vlastne chceme považovať za posledný riadok?
 +
* Upravte program, aby na výstupe vypisoval aj čísla na riadkoch oddelené medzerami.
  
 
== Jednoduché šifrovanie ==
 
== Jednoduché šifrovanie ==
Prácu so súbormi si v nasledujúcom precvičíme na dvoch jednoduchých šifrách.  
+
Prácu so súbormi si teraz precvičíme na dvoch jednoduchých šifrách.  
  
 
=== Caesarova šifra ===
 
=== Caesarova šifra ===
  
[https://en.wikipedia.org/wiki/Caesar_cipher Caesarova šifra] je šifra, pri ktorej sa každé písmeno vstupného reťazca posunie cyklicky o ''K'' miest v abecednom poradí, kde ''K'' je zadaný parameter šifry (tzv. posun).  
+
[https://en.wikipedia.org/wiki/Caesar_cipher Caesarova šifra] je šifra, pri ktorej sa každé písmeno vstupného reťazca posunie cyklicky o ''K'' miest v abecednom poradí, kde ''K'' je zadaný parameter šifry.  
 
* Napríklad pre ''K=2'' sa písmeno <tt>A</tt> zmení na <tt>C</tt>, písmeno <tt>b</tt> sa zmení na <tt>d</tt> a písmeno <tt>Z</tt> sa zmení na <tt>B</tt>.
 
* Napríklad pre ''K=2'' sa písmeno <tt>A</tt> zmení na <tt>C</tt>, písmeno <tt>b</tt> sa zmení na <tt>d</tt> a písmeno <tt>Z</tt> sa zmení na <tt>B</tt>.
 
* Ukážeme si jej použitie pre anglickú abecedu (t. j. znaky <tt>'a'</tt> až <tt>'z'</tt> a <tt>'A'</tt> až <tt>'Z'</tt> bez diakritiky); je ju ale možné upraviť napríklad aj tak, aby pracovala s ASCII kódmi.
 
* Ukážeme si jej použitie pre anglickú abecedu (t. j. znaky <tt>'a'</tt> až <tt>'z'</tt> a <tt>'A'</tt> až <tt>'Z'</tt> bez diakritiky); je ju ale možné upraviť napríklad aj tak, aby pracovala s ASCII kódmi.
  
''Zašifrovanie súboru'' realizuje nasledujúci program:
+
Napríklad pre ''K=3'':
 +
<pre>
 +
Pôvodný text:  HelloWorld
 +
Šifrovaný text: KhoorZruog
 +
</pre>
 +
 
 +
Nasledujúci program zašifruje súbor.
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
Riadok 381: Riadok 365:
 
}
 
}
  
int main(void) {
+
int main() {
 
     int shift;
 
     int shift;
     scanf("%d", &shift);
+
     scanf("%d", &shift); // nacitame parameter o kolko posuvat pismena
 
      
 
      
 
     FILE *fr = fopen("plaintext.txt", "r");
 
     FILE *fr = fopen("plaintext.txt", "r");
Riadok 392: Riadok 376:
 
     fclose(fr);
 
     fclose(fr);
 
     fclose(fw);
 
     fclose(fw);
   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
''Dešifrovanie súboru'' zašifrovaného Caesarovou šifrou realizuje tento program:
+
* Čo program spraví so znakmi, ktoré nie sú písmená?
 +
* Dešifrovanie by fungovalo podobne, akurát by sa posun odpočítaval a ako špeciálny prípad by sa riešilo, keď nový znak má kód menší ako 'a'. Môžeme ale tiež použiť program na šifrovanie a posun 26-K, kde K je posun použitý na šifrovanie.
  
<syntaxhighlight lang="C++">
+
=== Vigenèrova šifra ===
#include <cstdio>
 
#include <cassert>
 
using namespace std;
 
  
void decryptCaesar(FILE *fr, FILE *fw, int shift) {
+
[https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher Vigenèrova šifra] je veľmi podobná Caesarovej; posun však nie je konštantný, ale podľa kľúča.
    assert(shift >= 0 && shift <= 25);
+
* Kľúč je reťazec zložený z písmen <tt>A</tt> <tt>Z</tt>, pričom tieto predstavujú posuny o <tt>0</tt> <tt>25</tt> pozícií v abecede.
    int c;
+
* Prvý symbol otvoreného textu je zašifrovaný podľa prvého symbolu kľúča, druhý symbol podľa druhého symbolu kľúča, atď. Po vyčerpaní celého kľúča sa pokračuje cyklicky, opäť od jeho začiatku.
    while ((c = getc(fr)) != EOF) {
 
        if ((c >= 'A') && (c <= 'Z')) {
 
            c = c - shift;
 
            if (c < 'A') {
 
                c += 26;
 
            }
 
        } else if ((c >= 'a') && (c <= 'z')) {
 
            c = c - shift;
 
            if (c < 'a') {
 
                c += 26;
 
            }
 
        }
 
        putc(c, fw);
 
    }
 
}
 
  
int main(void) {
+
Napríklad pre heslo AHOJ:
    int shift;
+
<pre>
    scanf("%d", &shift);
+
Pôvodný text:  HelloWorld
   
+
Heslo:          AHOJAHOJAH
    FILE *fr = fopen("ciphertext.txt", "r");
+
Šifrovaný text: HlzuoDcalk
    FILE *fw = fopen("plaintext2.txt", "w");
+
</pre>
   
 
    decryptCaesar(fr, fw, shift);
 
   
 
    fclose(fr);
 
    fclose(fw);
 
   
 
    return 0;
 
}
 
</syntaxhighlight>
 
  
=== Vigenèrova šifra ===
+
Očíslované písmená abecedy:
 
+
<pre>
[https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher Vigenèrova šifra] je veľmi podobná Caesarovej; posun už ale nie je konštantný a realizuje sa podľa kľúča.
+
0:A, 1:B,  2:C,  3:D,  4:E,  5:F,  6:G
* Kľúčom je reťazec zložený z písmen <tt>A</tt> až <tt>Z</tt>, pričom tieto predstavujú posuny o <tt>0</tt> až <tt>25</tt> pozícií v abecede.
+
7:H,  8:I,  9:J, 10:K, 11:L, 12:M, 13:N
* Pri šifrovaní aj dešifrovaní sa jednotlivé abecedné posuny realizujú podľa kľúča. Prvý symbol otvoreného textu je tak zašifrovaný podľa prvého symbolu kľúča, druhý symbol podľa druhého symbolu kľúča, atď. Po vyčerpaní celého kľúča sa pokračuje cyklicky, opäť od jeho začiatku.
+
14:O, 15:P, 16:Q, 17:R, 18:S, 19:T, 20:U
 
+
21:V, 22:W, 23:X, 24:Y, 25:Z
''Zašifrovanie súboru'' realizuje nasledujúci program:
+
</pre>
  
 +
Nasledujúci program zašifruje súbor.
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
Riadok 477: Riadok 434:
 
}
 
}
  
int main(void) {
+
int main() {
 +
    // nacitame kluc z konzoly
 
     char key[maxKeyLength];
 
     char key[maxKeyLength];
 
     scanf("%s", key);
 
     scanf("%s", key);
 
      
 
      
 +
    // otvorime subory
 
     FILE *fr = fopen("plaintext.txt", "r");
 
     FILE *fr = fopen("plaintext.txt", "r");
 
     FILE *fw = fopen("ciphertext.txt", "w");
 
     FILE *fw = fopen("ciphertext.txt", "w");
 
      
 
      
 +
    // sifrujeme
 
     encryptVigenere(fr, fw, key);
 
     encryptVigenere(fr, fw, key);
 
      
 
      
 +
    // zatvorime subory
 
     fclose(fr);
 
     fclose(fr);
 
     fclose(fw);
 
     fclose(fw);
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
''Dešifrovanie súboru'' zašifrovaného Vigenèrovou šifrou realizuje tento program:
+
Nasledujúci program potom dešifruje súbor.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 526: Riadok 485:
 
}
 
}
  
int main(void) {
+
int main() {
 
     char key[maxKeyLength];
 
     char key[maxKeyLength];
 
     scanf("%s", key);
 
     scanf("%s", key);
Riadok 537: Riadok 496:
 
     fclose(fr);
 
     fclose(fr);
 
     fclose(fw);
 
     fclose(fw);
   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
 +
* Caesarova aj Vigenèrova šifra s krátkym kľúčom sa dajú ľahko prelomiť.
 +
* O šifrách, ktoré sa v súčasnosti používajú, sa môžete dozvedieť vo vyšších ročníkoch na predmete Kryptológia.
 +
 +
== Zhrnutie práce so súbormi ==
 +
* Ukázali sme si prácu so súbormi pomocou knižnice <tt>cstdio</tt> z jazyka C.
 +
* Súbor je reprezentovaný smerníkom typu <tt>FILE *</tt>.
 +
* Na otváranie a zatváranie súborov slúžia funkcie <tt>fopen</tt>, <tt>fclose</tt>.
 +
* Na vypísanie kombinácie čísel a textov slúži funkcia <tt>fprintf</tt>, čísla a slová načítavame pomocou <tt>fscanf</tt>.
 +
* Po znakoch pracujeme funkciami <tt>putc</tt> a <tt>getc</tt>, niekedy sa zíde aj <tt>ungetc</tt>.
 +
* Po riadkoch pracujeme pomocou <tt>fgets</tt> a <tt>fputs</tt>.
 +
* V programoch, ktoré sa majú reálne používať, je vhodné kontrolovať, či tieto operácie úspešne zbehli. Tiež pozor na to, aby pri načítaní reťazcov nenastalo pretečenie poľa.
 +
* K niektorým funkciám existujú aj verzie pre konzolu, napr. <tt>printf</tt> a <tt>scanf</tt>, <tt>putchar</tt>, <tt>getchar</tt>.
 +
* S konzolou tiež môžeme pracovať pomocou premenných <tt>stdin</tt>, <tt>stdout</tt>.
 +
 +
 +
Ďalšie možnosti
 +
* Okrem textových súborov existujú aj binárne, kde nie je číslo zapísané ako postupnosť cifier, ale priamo ako bajty.
 +
** Binárne súbory majú často menšiu veľkosť a rýchlejšie sa s nimi pracuje, ťažšie však ručne skontrolujete, čo v súbore je.
 +
** V knižnici <tt>cstdio</tt> existujú na prácu s binárnymi súbormi funkcie <tt>fwrite</tt> a <tt>fread</tt>.
 +
* V C++ sa dá so súbormi pracovať podobne, ako sme používali <tt>cin</tt> a <tt>cout</tt>. Potrebné funkcie nájdete v knižnici <tt>fstream</tt>.
 +
* V jednom programe '''nekombinujte''' prácu s konzolou pomocou <tt>cstdio</tt> a <tt>iostream</tt>, môže dôjsť k strate dát.
 +
* V mnohých programovacích jazykoch existujú knižnice na načítavanie a zapisovanie často používaných formátov súborov.

Aktuálna revízia z 09:37, 19. november 2024

Oznamy

  • DÚ3 bude zverejnená dnes po prednáške, odovzdajte do piatku 6.12. 22:00. Zahŕňa dvojrozmerné polia a spracovanie vstupu a výstupu, súbory. Je to dobrý tréning na prvý príklad na skúške.

Príklad na prácu s textovými súbormi

  • Prácu s textovými súbormi si zopakujeme na nasledujúcom príklade.
  • Hlavný vstupný súbor vstup.txt má na prvom riadku názov výstupného súboru.
  • Každý z niekoľkých ďalších riadkov obsahuje názov jedného vstupného súboru nasledovaný počtom čísel, ktoré z neho chceme načítať.
  • Úlohou je prekopírovať z každého súboru zadaný počet čísel.

Napríklad vstup.txt

vystup.txt
a.txt 2
b.txt 1
a.txt 3

Súbor a.txt

1 2 3 4 5 6 7 8 9

Súbor b.txt

10 20 30 40 50 60 70 80 90

Výsledný súboru vystup.txt

1 2 10 1 2 3

Program uvedený nižšie pracuje v nasledujúcich krokoch:

  • Otvorí hlavný vstupný súbor vstup.txt a prečíta z neho názov výstupného súboru.
  • Otvorí výstupný súbor.
  • Následne opakovane prečíta názov súboru a počet čísel N.
  • Otvorí súbor s práve načítaným názvom, prekopíruje z neho N čísel do výstupného súboru a následne tento súbor zatvorí.
  • Zatvorí hlavný vstupný súbor aj výstupný súbor.

Dĺžka načítavaných reťazcov bude vo volaniach funkcie fscanf obmedzená na 19 znakov

  • to teda je maximálna dĺžka názvu súboru, s ktorou bude program vedieť pracovať
#include <cstdio>
#include <cassert>
using namespace std;

int main() {
    FILE *fr_main, *fr_part, *fw;
    int N, success, num;
    fr_main = fopen("vstup.txt", "r");
    assert(fr_main != NULL);
    
    char filename[20];
    success = fscanf(fr_main, "%19s", filename);
    assert(success == 1); 
    
    fw = fopen(filename, "w");
    assert(fw != NULL);
    while (!feof(fr_main)) {
        success = fscanf(fr_main, "%19s %d ", filename, &N);
        assert(success == 2);
        fr_part = fopen(filename, "r");
        assert(fr_part != NULL);
        for (int i = 0; i < N; i++) {
            success = fscanf(fr_part, "%d ", &num);
            assert(success == 1);
            fprintf(fw, "%d ", num);
        }
        fclose(fr_part);
    }
    fclose(fw);
    fclose(fr_main); 
}

Čítanie a zapisovanie po znakoch

Knižnica cstdio obsahuje aj funkcie na čítanie a zapisovanie súboru po znakoch.

  • Funkcia int getc(FILE *f) načíta a vráti jeden znak zo súboru
    • Ak načítanie neprebehne úspešne, výsledkom je špeciálna konštanta EOF, ktorá je vždy rôzna od ľubovoľnej hodnoty typu char
    • Neukladajte výstupnú hodnotu funkcie getc do premennej typu char, lebo nebudete vedieť rozoznať koniec súboru.
  • Funkcia int getchar() je skratka pre getc(stdin), načíta teda jeden znak z konzoly
    • Avšak rovnako ako pri scanf sa vstup začne spracovávať až potom, ako používateľ stlačí Enter, nie je takto možné reagovať priamo na stlačenie nejakej klávesy.
  • Funkcia int putc(int c, FILE *f) zapíše znak c do súboru.
  • Funkcia int putchar(int c) je skratkou pre putc(c, stdout), vypíše teda daný znak na konzolu.

Príklad: kopírovanie súboru

Nasledujúci program skopíruje obsah súboru original.txt do súboru kopia.txt.

#include <cstdio>
using namespace std;

int main() {
    FILE *fr = fopen("original.txt", "r");
    FILE *fw = fopen("kopia.txt", "w");

    int c = getc(fr);
    while (c != EOF) {
        putc(c, fw);
        c = getc(fr);
    }

    fclose(fr);
    fclose(fw);
}

Cvičenie: čo robí nasledujúci program?

  • výsledkom priradenia c = getc(fr) je hodnota priradená do premennej c
#include <cstdio>
using namespace std;

int main() {
    FILE *fr;
    int c;

    fr = fopen("vstup.txt", "r");
    while ((c = getc(fr)) != '\n') {
        putchar(c);
    }
    putchar(c);         
    fclose(fr);
}

Funkcia ungetc

  • Často sa stáva, že pri načítavaní znakov nájdeme koniec práve načítavaného úseku až po načítaní znaku, ktorý už nie je žiadúce prečítať.
  • Vtedy je užitočné posunúť sa v načítavaní o jeden krok nazad.
  • Túto úlohu realizuje funkcia int ungetc(int c, FILE * f).
  • Väčšinou ako c použijeme posledne načítaným znak zo súboru f.
  • Môžeme však použiť aj iný znak, ktorý bude virtuálne pridaný na začiatok neprečítanej časti súboru. Súbor sa reálne nemení, ale pri nasledujúcom čítaní z neho sa ako prvý prečíta znak c.
  • V prípade úspechu ungetc(c,f) vráti hodnotu c; v prípade neúspechu je výstupom EOF.
  • Takéto správanie funkcie ungetc je však garantované len ak sa táto funkcia nevolá viackrát za sebou.

Príklad č. 1: Nasledujúci kus programu skonvertuje reťazec pozostávajúci zo znakov '0''9' na zodpovedajúcu číselnú hodnotu. Keď narazí na prvý znak, ktorý nie je cifra, vráti ho, aby sa dal použiť pri ďalšom spracovávaní.

    int hodnota = 0;
    int c = getchar();
    while (c >= '0' && c <= '9') {
        hodnota = hodnota * 10 + (c - '0');
        c = getchar();
    }
    ungetc(c, stdin);

Príklad č. 2: Nasledujúci program prečíta číslo pomocou funkcie fscanf, predtým však musí prečítať neznámy počet znakov '$'.

#include <cstdio>
using namespace std;

int main() {
    FILE *fr = fopen("vstup.txt", "r");
    int c = getc(fr);
    while (c == '$') {
        c = getc(fr);
    }
    ungetc(c, fr);

    int hodnota;
    fscanf(fr, "%d", &hodnota);
    printf("%d\n", hodnota);
   
    fclose(fr);
}

Čítanie a zapisovanie po riadkoch

Riadok vieme načítať zo súboru nasledujúcou funkciou

char *fgets(char *str, int n, FILE * f);

Jej argumenty sú

  • Pole znakov str, do ktorého sa v prípade úspechu riadok načíta.
  • Číslo n je typicky dĺžka poľa str
    • Funkcia do poľa str z daného riadku súboru načíta a uloží najviac n-1 znakov a reťazec str následne ukončí znakom 0.
  • Smerník f na súbor, z ktorého sa má riadok prečítať.


  • Funkcia fgets teda načítava znaky dovtedy, kým narazí na koniec riadku (\n) alebo koniec súboru alebo kým zo súboru neprečíta n-1 znakov.
  • Prípadný znak \n na konci riadku sa nezahodí, ale pridá sa na koniec reťazca str (pokiaľ nebolo načítaných príliš veľa znakov).
  • Výstupom funkcie je v prípade načítania aspoň jedného znaku načítaný reťazec str; inak je výstupom NULL a reťazec str ostáva nezmenený.

Funkcia int fputs(const char *str, FILE *f) vypíše reťazec str do súboru

  • str môže obsahovať hocikoľko koncov riadkov (aj nula)
  • pri chybe vráti EOF, inak vráti nezáporné celé číslo


Príklad: nasledujúci program spočíta počet riadkov v súbore vstup.txt (za predpokladu, že žiaden z týchto riadkov nie je dlhší ako 100 znakov vrátane znaku \n na konci riadku):

#include <cstdio>
using namespace std;

const int maxN = 101;

int main() {
    char str[maxN];
    int num = 0;
    
    FILE *fr = fopen("vstup.txt", "r");
    while (fgets(str, maxN, fr) != NULL) {
        num++;
    }
    fclose(fr);
    
    printf("Pocet riadkov je %d\n", num);
}

Cvičenie:

  • Zistite, ako sa správa uvedený program, keď posledným znakom v súbore je resp. nie je znak \n.
  • Zistite, čo program vypíše na výstup pre súbor, ktorý obsahuje jediný riadok o 200 znakoch.

Prístupy k spracovaniu textového vstupu

Časté schémy spracovania textového súboru:

  • Pomocou fscanf načítavame jednotlivé čísla, slová a pod. (vhodné, keď všetky biele znaky považujeme za ekvivalentné oddeľovače)
  • Čítame po znakoch pomocou getc (pomerne univerzálne, ale niekedy prácne)
  • Čítame po riadkoch pomocou fgets do reťazca, potom reťazec spracovávame (vhodné, keď riadok je ucelená časť súboru, ktorá nás zaujíma)

Niekedy je užitočné tieto prístupy aj kombinovať.


Príklad: chceme nájsť dĺžku najdlhšieho riadku v súbore

  • Prvá možnosť je čítanie riadkov do reťazca a ich spracovanie (problém, ak je riadok príliš dlhý)
  • Druhá možnosť je čítať súbor po znakoch, pričom si potrebujeme udržiavať "stav": koľko písmen sme už videli v aktuálnom riadku
#include <cstdio>
#include <cstring>
using namespace std;

const int maxN = 100;

int main() {
    FILE *fr = fopen("vstup.txt", "r");
    int maxDlzka = 0;
    char str[maxN];
    while (fgets(str, maxN, fr) != NULL) {
        int dlzka = strlen(str);
        if (dlzka > maxDlzka) {
            maxDlzka = dlzka;
        }
    }
    fclose(fr);
    printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
}
#include <cstdio>
using namespace std;

int main() {
    FILE *fr = fopen("vstup.txt", "r");
    int maxDlzka = 0;
    int dlzka = 0;
    int c = getc(fr);
    while (c != EOF) {
        dlzka++;
        if (c == '\n') {
            if (dlzka > maxDlzka) {
                maxDlzka = dlzka;
            }
            dlzka = 0;
        }
        c = getc(fr);
    }
    if (dlzka > maxDlzka) { // Posledny riadok nemusi koncit symbolom \n.
        maxDlzka = dlzka;
    }
    fclose(fr);
    printf("Najdlhsi riadok ma dlzku %d\n", maxDlzka);
}

Cvičenie: čo ak chceme zistiť, koľký riadok v súbore bol ten najdlhší?

Príklad 2: máme súbor s číslami oddelenými bielymi znakmi (medzery, tabulátory, konce riadkov,...), pričom medzi dvoma číslami môže byť aj viac ako jeden oddeľovač. Chceme spočítať súčet čísel na každom riadku.

  • Nepríjemná kombinácia rozlišovania koncov riadku od iných bielych znakov a čítania formátovaných hodnôt (čísel)
  • Môžeme prečítať riadok do reťazca a rozložiť na čísla (napr. funkciou sscanf a so špecifikáciou konverzie %n, ktorá uloží počet načítaných znakov).
  • Program nižšie však používa kombináciu getc, ungetc a fscanf
    • Biele znaky spracúva pomocou funkcie getc. Ak je niektorý z týchto znakov koncom riadku, vypíše zistený súčet čísel.
    • Po nájdení nebieleho znaku ho vráti do vstupného prúdu funkciou ungetc, prečíta číslo pomocou funkcie fscanf a aktualizuje súčet.
    • Na zistenie, či je prečítaný znak biely, využíva program funkciu isspace z knižnice cctype.
    • Program predpokladá, že aj posledný riadok vstupného súboru je ukončený symbolom \n.


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

int main(void) {
    FILE *fr = fopen("vstup.txt", "r");
    int sucet = 0;
    int hodnota;
    while (!feof(fr)) {
        int c = getc(fr);
        // Precitaj biele znaky po najblizsi nebiely.
        while (c != EOF && isspace(c)) { 
            if (c == '\n') {   // Na konci riadku vypis sucet.
                printf("Sucet %d\n", sucet);
                sucet = 0;
            }
            c = getc(fr);
        }
        if (c == EOF) { // Koniec suboru
            break;
        }                
        ungetc(c, fr);    
        fscanf(fr, "%d", &hodnota);
        sucet += hodnota;
    }
    fclose(fr);
}

Cvičenia:

  • Čo program spraví, ak vstup obsahuje aj prázdne riadky?
  • Upravte program, aby pracoval správne aj v prípade, že posledný riadok nie je ukončený symbolom \n.
    • Čo vlastne chceme považovať za posledný riadok?
  • Upravte program, aby na výstupe vypisoval aj čísla na riadkoch oddelené medzerami.

Jednoduché šifrovanie

Prácu so súbormi si teraz precvičíme na dvoch jednoduchých šifrách.

Caesarova šifra

Caesarova šifra je šifra, pri ktorej sa každé písmeno vstupného reťazca posunie cyklicky o K miest v abecednom poradí, kde K je zadaný parameter šifry.

  • Napríklad pre K=2 sa písmeno A zmení na C, písmeno b sa zmení na d a písmeno Z sa zmení na B.
  • Ukážeme si jej použitie pre anglickú abecedu (t. j. znaky 'a''z' a 'A''Z' bez diakritiky); je ju ale možné upraviť napríklad aj tak, aby pracovala s ASCII kódmi.

Napríklad pre K=3:

Pôvodný text:   HelloWorld
Šifrovaný text: KhoorZruog

Nasledujúci program zašifruje súbor.

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

void encryptCaesar(FILE *fr, FILE *fw, int shift) {
    assert(shift >= 0 && shift <= 25);
    int c;
    while ((c = getc(fr)) != EOF) {
        if ((c >= 'A') && (c <= 'Z')) {
            c = c + shift;
            if (c > 'Z') {
                c -= 26;
            }
        } else if ((c >= 'a') && (c <= 'z')) {
            c = c + shift;
            if (c > 'z') {
                c -= 26;
            }
        }
        putc(c, fw);
    }
}

int main() {
    int shift;
    scanf("%d", &shift);  // nacitame parameter o kolko posuvat pismena
    
    FILE *fr = fopen("plaintext.txt", "r");
    FILE *fw = fopen("ciphertext.txt", "w");
    
    encryptCaesar(fr, fw, shift);
    
    fclose(fr);
    fclose(fw);
}
  • Čo program spraví so znakmi, ktoré nie sú písmená?
  • Dešifrovanie by fungovalo podobne, akurát by sa posun odpočítaval a ako špeciálny prípad by sa riešilo, keď nový znak má kód menší ako 'a'. Môžeme ale tiež použiť program na šifrovanie a posun 26-K, kde K je posun použitý na šifrovanie.

Vigenèrova šifra

Vigenèrova šifra je veľmi podobná Caesarovej; posun však nie je konštantný, ale podľa kľúča.

  • Kľúč je reťazec zložený z písmen AZ, pričom tieto predstavujú posuny o 025 pozícií v abecede.
  • Prvý symbol otvoreného textu je zašifrovaný podľa prvého symbolu kľúča, druhý symbol podľa druhého symbolu kľúča, atď. Po vyčerpaní celého kľúča sa pokračuje cyklicky, opäť od jeho začiatku.

Napríklad pre heslo AHOJ:

Pôvodný text:   HelloWorld
Heslo:          AHOJAHOJAH
Šifrovaný text: HlzuoDcalk

Očíslované písmená abecedy:

 0:A,  1:B,  2:C,  3:D,  4:E,  5:F,  6:G
 7:H,  8:I,  9:J, 10:K, 11:L, 12:M, 13:N
14:O, 15:P, 16:Q, 17:R, 18:S, 19:T, 20:U
21:V, 22:W, 23:X, 24:Y, 25:Z

Nasledujúci program zašifruje súbor.

#include <cstdio>
using namespace std;

const int maxKeyLength = 100;

void encryptVigenere(FILE *fr, FILE *fw, char *key) {
    int c;
    int i = 0;
    while ((c = getc(fr)) != EOF) {
        if ((c >= 'A') && (c <= 'Z')) {
            c = c + (key[i] - 'A');
            if (c > 'Z') {
                c -= 26;
            }
            i++;
        } else if ((c >= 'a') && (c <= 'z')) {
            c = c + (key[i] - 'A');
            if (c > 'z') {
                c -= 26;
            }
            i++;
        }
        if (key[i] == 0) {
            i = 0;
        }
        putc(c, fw);
    }
}

int main() {
    // nacitame kluc z konzoly
    char key[maxKeyLength];
    scanf("%s", key);
    
    // otvorime subory
    FILE *fr = fopen("plaintext.txt", "r");
    FILE *fw = fopen("ciphertext.txt", "w");
    
    // sifrujeme 
    encryptVigenere(fr, fw, key);
    
    // zatvorime subory
    fclose(fr);
    fclose(fw);
}

Nasledujúci program potom dešifruje súbor.

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

const int maxKeyLength = 100;

void decryptVigenere(FILE *fr, FILE *fw, char *key) {
    int c;
    int i = 0;
    while ((c = getc(fr)) != EOF) {
        if ((c >= 'A') && (c <= 'Z')) {
            c = c - (key[i] - 'A');
            if (c < 'A') {
                c += 26;
            }
            i++;
        } else if ((c >= 'a') && (c <= 'z')) {
            c = c - (key[i] - 'A');
            if (c < 'a') {
                c += 26;
            }
            i++;
        }
        if (key[i] == 0) {
            i = 0;
        }
        putc(c, fw);
    }
}

int main() {
    char key[maxKeyLength];
    scanf("%s", key);
    
    FILE *fr = fopen("ciphertext.txt", "r");
    FILE *fw = fopen("plaintext2.txt", "w");
    
    decryptVigenere(fr, fw, key);
    
    fclose(fr);
    fclose(fw);
}


  • Caesarova aj Vigenèrova šifra s krátkym kľúčom sa dajú ľahko prelomiť.
  • O šifrách, ktoré sa v súčasnosti používajú, sa môžete dozvedieť vo vyšších ročníkoch na predmete Kryptológia.

Zhrnutie práce so súbormi

  • Ukázali sme si prácu so súbormi pomocou knižnice cstdio z jazyka C.
  • Súbor je reprezentovaný smerníkom typu FILE *.
  • Na otváranie a zatváranie súborov slúžia funkcie fopen, fclose.
  • Na vypísanie kombinácie čísel a textov slúži funkcia fprintf, čísla a slová načítavame pomocou fscanf.
  • Po znakoch pracujeme funkciami putc a getc, niekedy sa zíde aj ungetc.
  • Po riadkoch pracujeme pomocou fgets a fputs.
  • V programoch, ktoré sa majú reálne používať, je vhodné kontrolovať, či tieto operácie úspešne zbehli. Tiež pozor na to, aby pri načítaní reťazcov nenastalo pretečenie poľa.
  • K niektorým funkciám existujú aj verzie pre konzolu, napr. printf a scanf, putchar, getchar.
  • S konzolou tiež môžeme pracovať pomocou premenných stdin, stdout.


Ďalšie možnosti

  • Okrem textových súborov existujú aj binárne, kde nie je číslo zapísané ako postupnosť cifier, ale priamo ako bajty.
    • Binárne súbory majú často menšiu veľkosť a rýchlejšie sa s nimi pracuje, ťažšie však ručne skontrolujete, čo v súbore je.
    • V knižnici cstdio existujú na prácu s binárnymi súbormi funkcie fwrite a fread.
  • V C++ sa dá so súbormi pracovať podobne, ako sme používali cin a cout. Potrebné funkcie nájdete v knižnici fstream.
  • V jednom programe nekombinujte prácu s konzolou pomocou cstdio a iostream, môže dôjsť k strate dát.
  • V mnohých programovacích jazykoch existujú knižnice na načítavanie a zapisovanie často používaných formátov súborov.