Skip to the content.

Čí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ť.