|
| 1 | +# Čítanie čísel |
| 2 | + |
| 3 | +Predtavme si, že máme súbor `numbers.txt` s obsahom: |
| 4 | + |
| 5 | +``` |
| 6 | +123 456 |
| 7 | +789 4444 |
| 8 | +
|
| 9 | +10 11 |
| 10 | +``` |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +```cpp |
| 15 | +#include <fstream> |
| 16 | +#include <iostream> |
| 17 | + |
| 18 | +int main() { |
| 19 | + std::ifstream f("numbers.txt"); |
| 20 | + |
| 21 | + int n; |
| 22 | + while (f >> n){ |
| 23 | + std::cout << n << '\n'; |
| 24 | + } |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +## Kontrola stavu súboru |
| 29 | + |
| 30 | +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. |
| 31 | + |
| 32 | +```cpp |
| 33 | +#include <fstream> |
| 34 | +#include <iostream> |
| 35 | + |
| 36 | +int main() { |
| 37 | + std::ifstream f("numbers.txt"); |
| 38 | + |
| 39 | + int n; |
| 40 | + while (f >> n) { |
| 41 | + std::cout << n << '\n'; |
| 42 | + } |
| 43 | + |
| 44 | + if (!f.eof()) { |
| 45 | + std::cerr << "Cannot read whole input file.\n"; |
| 46 | + return 1; |
| 47 | + } |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +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. |
| 52 | + |
| 53 | +## `int` overflow |
| 54 | + |
| 55 | +Ď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ť. |
| 56 | + |
| 57 | +Súbor `numbers.txt` (nakonci **je** nový riadok): |
| 58 | + |
| 59 | +``` |
| 60 | +123 456 |
| 61 | +789 4444 |
| 62 | +21474836479999 |
| 63 | +
|
| 64 | +``` |
| 65 | + |
| 66 | +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. |
| 67 | + |
| 68 | +```cpp |
| 69 | +#include <fstream> |
| 70 | +#include <iostream> |
| 71 | + |
| 72 | +int main() { |
| 73 | + std::ifstream f("numbers.txt"); |
| 74 | + |
| 75 | + int n; |
| 76 | + while (f >> n) { |
| 77 | + std::cout << n << '\n'; |
| 78 | + } |
| 79 | + |
| 80 | + if (f.fail()) { |
| 81 | + std::cerr << "Cannot read whole input file.\n"; |
| 82 | + return 1; |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +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. |
| 88 | + |
| 89 | +## Čítanie stringov |
| 90 | + |
| 91 | +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. |
| 92 | + |
| 93 | +```cpp |
| 94 | +#include <fstream> |
| 95 | +#include <iostream> |
| 96 | +#include <string> |
| 97 | +#include <climits> // for INT_MIN, INT_MAX |
| 98 | +#include <cstdlib> // for std::strtol |
| 99 | + |
| 100 | +int main() { |
| 101 | + std::ifstream f("numbers.txt"); |
| 102 | + |
| 103 | + std::string s; |
| 104 | + while (f >> s) { |
| 105 | + char* end; |
| 106 | + errno = 0; // reset errno before call |
| 107 | + long val = std::strtol(s.c_str(), &end, 10); |
| 108 | + if (*end != '\0' // check if whole string was converted |
| 109 | + || errno == ERANGE // check for long overflow |
| 110 | + || val < INT_MIN || val > INT_MAX) { // clamp to int range |
| 111 | + std::cerr << "Invalid number: " << s << '\n'; |
| 112 | + return 1; |
| 113 | + } |
| 114 | + std::cout << static_cast<int>(val) << '\n'; |
| 115 | + } |
| 116 | + |
| 117 | + if (!f.eof()) { |
| 118 | + std::cerr << "Cannot read whole input file.\n"; |
| 119 | + return 1; |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +## Ignorovanie whitespace |
| 125 | + |
| 126 | +Ď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. |
| 127 | + |
| 128 | +```cpp |
| 129 | +#include <fstream> |
| 130 | +#include <iostream> |
| 131 | + |
| 132 | +int main() { |
| 133 | + std::ifstream f("numbers.txt"); |
| 134 | + |
| 135 | + while (!(f >> std::ws).eof()) { |
| 136 | + int n; |
| 137 | + f >> n; |
| 138 | + if (f.fail()) { |
| 139 | + return EXIT_FAILURE; |
| 140 | + } |
| 141 | + std::cout << n << '\n'; |
| 142 | + } |
| 143 | + |
| 144 | + if (!f.eof()) { |
| 145 | + std::cerr << "Cannot read whole input file.\n"; |
| 146 | + return 1; |
| 147 | + } |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +*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ť.* |
0 commit comments