Čítanie čísel
Predtavme si, že máme súbor numbers.txt
s obsahom:
123 456
789 4444
10 11
A chceme tieto čísla prečítať a vypísať na konzolu oddelené novým riadkom. Najjednoduchší spôsob je použiť operator>>
, ktorý sme si už ukázali. Môžeme s ním čítať jednotlivé čísla, pričom ignoruje whitespace.
#include <fstream>
#include <iostream>
int main() {
std::ifstream f("numbers.txt");
int n;
while (f >> n){
std::cout << n << '\n';
}
}
Kontrola stavu súboru
Program funguje, ale má jeden problém. Ak sa niečo nepodarí, tak sa f
dostane do fail
stavu a cyklus skončí. To znamená, že ak by sme v súbore mali nejaký nečíselný znak, tak program skončí skôr ako prečíta všetky čísla. Preto je dobré na konci skontrolovať či sme naozaj došli až na koniec súboru.
#include <fstream>
#include <iostream>
int main() {
std::ifstream f("numbers.txt");
int n;
while (f >> n) {
std::cout << n << '\n';
}
if (!f.eof()) {
std::cerr << "Cannot read whole input file.\n";
return 1;
}
}
Teraz ak by sme mali v súbore napríklad 123 456 abc
, tak program vypíše 123
a 456
, ale na konci vypíše chybu, že sa nepodarilo prečítať celý súbor. Čo je správne, keďže abc
nie je číslo.
int
overflow
Ďalší problém môže nastať ak by sme mali v súbore číslo, ktoré je väčšie ako INT_MAX
alebo menšie ako INT_MIN
. Vtedy sa f
dostane do fail
stavu a program skončí. Je tu ale jeden problém ako je to číslo, ktore pretečie na konci súboru, tak to nevieme úple dobre detekovať.
Súbor numbers.txt
(nakonci je nový riadok):
123 456
789 4444
21474836479999
Na tomto súbore program skončí s chybou, teda správne. Ale ak nový riadok na konci súboru odstránime, program skončí bez chyby, čo je zlé a vypíše prvé 4 čísla. Dôvod je ten, že síce na konci bude mať nastevený fail
stav, ale zároveň bude aj eof
stav, takže kontrola na konci prejde. Mohlo by nás napadnúť, že by sme na konci skontrolovali či je fail
(namiesto eof
) stav nastavený a ak áno, tak by sme vrátili chybu.
#include <fstream>
#include <iostream>
int main() {
std::ifstream f("numbers.txt");
int n;
while (f >> n) {
std::cout << n << '\n';
}
if (f.fail()) {
std::cerr << "Cannot read whole input file.\n";
return 1;
}
}
Toto funguje celkom dobre, jediný problém sú whitespace na konci súboru. Ak by sme mali na konci súboru napríklad medzeru, tak program skončí s chybou, čo je zlé, keďže whitespace na konci súboru by nemal robiť problém. Riešenie je trochu komplikovanejšie ako len check stavu.
Čítanie stringov
Môžeme si pomôcť tak, že budeme čítať stringy a tie potom skonvertujeme na čísla. Toto riešenie funguje, je ale o niečo komplikovanejšie.
#include <fstream>
#include <iostream>
#include <string>
#include <climits> // for INT_MIN, INT_MAX
#include <cstdlib> // for std::strtol
int main() {
std::ifstream f("numbers.txt");
std::string s;
while (f >> s) {
char* end;
errno = 0; // reset errno before call
long val = std::strtol(s.c_str(), &end, 10);
if (*end != '\0' // check if whole string was converted
|| errno == ERANGE // check for long overflow
|| val < INT_MIN || val > INT_MAX) { // clamp to int range
std::cerr << "Invalid number: " << s << '\n';
return 1;
}
std::cout << static_cast<int>(val) << '\n';
}
if (!f.eof()) {
std::cerr << "Cannot read whole input file.\n";
return 1;
}
}
Ignorovanie whitespace
Ďalšie riešenie je trochu zmeniť vyčítanie, aby sme vždy najprv vyčítali všetky whitespace znaky. To zabezpečíme pomocou std::ws
, ktorý je špeciálny manipulator, ktorý vyčítava všetky whitespace znaky.
#include <fstream>
#include <iostream>
int main() {
std::ifstream f("numbers.txt");
while (!(f >> std::ws).eof()) {
int n;
f >> n;
if (f.fail()) {
return EXIT_FAILURE;
}
std::cout << n << '\n';
}
if (!f.eof()) {
std::cerr << "Cannot read whole input file.\n";
return 1;
}
}
C spôsob pomocou fscanf
tu robiť nebudeme, tam je priveľa nedefinováneho a nešpecifikovaného správania, aby sme to mohli odporučiť.