Prednáška 15
- Oznamy
- Smerníková aritmetika
- Práca s konzolou na spôsob jazyka C:
printfascanf - Textové súbory
- Spracovanie vstupu pozostávajúceho z postupnosti čísel
- Zhrnutie
Oznamy
- Miniprojekt odovzdávajte do budúceho štvrtka 22:00, posledný miniprojekt bude zverejnený budúci týždeň.
- Úlohy z tohto týždňa majú termín odovzdania až utorok večer, lebo v pondelok je rektorské voľno. V utorok na cvičeniach však pribudnú nové úlohy, takže odporúčame tie z tohto týždňa dokončiť skôr.
- Na dnešných cvičeniach bude rozcvička za jeden bonusový bod.
- Nerobte zbytočné zmeny v poskytnutej kostre, stratíte za to body.
- 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 + nje smerník nan-té políčko za adresoup, pričom veľkosť políčka je daná typomTp + nje teda to isté ako&(p[n])a*(p+n)je to isté akop[n].p++je skratkou prep = p + 1, posunie nám teda smerníkpo políčko doprava v rámci poľa.- Výraz
p[n]je len skratkou pre*(p+n). p[n]ajp + nteda chceme používať iba akpukazuje na prvok poľa, za ktorým v poli ide ešte aspoňnďalších políčok
- Podobne
p - nje smerník nan-té políčko pred adresoup.p - nteda chceme používať iba akpukazuje na prvok poľa, pred ktorým v poli ide ešte aspoňnďalších políčok
- Ak
paqsú adresami prvkov v tom istom poli,p - qje celé čísloktaké, žep == q + k, t.j. o koľko políčok jepďalej vpravo odq. - Ak
paqsú 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 nacharNULLak 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í
strstrspočítali počet výskytov vzorky v texte? - Podobne
strchrhľ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údycinacout. - 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
cstdiomožno na konzolu písať pomocou funkcieprintf. - 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 (typint).%ld: dlhé celé číslo (typlong int).%f: reálne číslo (typdouble).%e: reálne číslo vo vedeckej notácii, napr.5.4e7(typdouble).%c: znak (typchar).%s: reťazec (typchar *).%%: 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íklad0(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, alebohprelong,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 premennejx. - Zatiaľ čo argumentmi
printfsú priamo hodnoty,scanfpotrebuje 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.%dnačítaint%lfnačítadouble(pozor, tu je rozdiel odprintf, kde sa double vypisuje pomocou%f)%snačíta reťazec, konkrétne jedno slovo, t.j. postupnosť nebielych znakov. Ako argument sa zadá hodnota typuchar*, ktorá má ukazovať na dostatočne veľké pole.%100snačíta slovo, ale najviac 100 znakov. Pole má mať veľkosť aspoň 101.%cnačí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+Dresp. pod Windowsom akoCtrl+ZaEnter
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 >>acout <<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
frpriradíNULL. - Z otvoreného súboru môžeme čítať napríklad pomocou
fscanf, ktorá je analógiou kscanf. - 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ífopennamiesto"w"použili"a", pridávalo by sa na koniec existujúceho súboru). - Ak sa nepodarí súbor otvoriť, do
fwpriradíNULL. - Do otvoreného súboru môžeme zapisovať napr. pomocou funkcie
fprintf, ktorá je analógiou kprintf. - 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
cstdioje definovaná symbolická konštantaEOF, ktorá má väčšinou hodnotu -1. Ak sa funkciifscanfnepodarí načítať žiadnu hodnotu, pretože načítavanie dospelo ku koncu súboru, vráti konštantuEOFako svoj výstup. - Funkcia
feof(subor)vrátitruepráve vtedy, keď sa funkciafscanf(alebo nejaká iná funkcia) už niekedy pokúšala čítať za koncom súborusubor.
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,
fscanfsa 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
assertukončí program s chybovou hláškou. - Aj bez príkazu
assertby 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
cinacoutz knižniceiostream, ktorá patrí do C++. - V jazyku C sa na načítavanie čísel a slov používajú funkcie
scanfaprintfz knižnicecstdio.- Funkcia
scanfumožňuje tiež testovať koniec vstupu a správnosť prečítaných hodnôt. - Funkcia
printfumožňuje pohodlne vypísať viacero hodnôt aj s nastavením formátovania ako napr. počet desatinných miest alebo zarovnávanie.
- Funkcia
- Nekombinujte v jednom programe prácu s konzolou pomocou
iostreamacstdio. Každá knižnica si časť vstupu alebo výstupu ukladá do svojich interných premenných a môže dôjsť k strate alebo preusporiadaniu dát. - Pri práci so súbormi súbor typu
FILE *otvoríme funkcioufopen, zatvorímefclose. - Namiesto
scanf,printfpri súboroch použijemefprintf,fscanf.