===== Vstup a výstup ===== Cílem dnešního cvičení je seznámit se s komplikovanějším vstupem a výstupem v C%%++%%, protože program, který nekomunikuje s okolím, není zrovna užitečný. ==== Řádkové čtení ==== Často je užitečné číst ze vstupu po celých řádcích, místo po řetězcích/číslech. K tomuto účelu poslouží funkce [[http://en.cppreference.com/w/cpp/string/basic_string/getline|std::getline]], která, jak název napovídá, čte ze vstupu, dokud nenarazí na konec řádku, a následně přečtená data uloží do ''std::string''. Nyní bychom mohli upravit ukázku z minulého cvičení, kde chceme po uživateli, aby nám napsal své jméno, kterým ho pak pozdravíme. #include #include int main() { std::cout << "Napis sve jmeno:\n"; std::string jmeno; std::getline(std::cin, jmeno); std::cout << "Ahoj " << jmeno << '\n'; } Tentokrát načteme a uživateli vytiskneme všechen text, který napsal až do chvíle, kdy zmáčkl Enter. ==== Ošetření chyb ==== Doteď jsme možnost výskytu chyb při čtení vstupu prostě ignorovali a předpokládali, že nám uživatel vždy dá správné vstupy. To je samozřejmě z dlouhodobého hlediska neudržitelné. #include #include #include double readDouble() { double d; std::cin >> d; if (std::cin.good()) { return d; } else if (std::cin.bad() || std::cin.eof()) { throw std::runtime_error("readDouble() failed"); } else { std::cin.clear(); std::cin.ignore(1, '\n'); return readDouble(); } } int main() { double a = readDouble(); double b = readDouble(); double c = readDouble(); std::cout << (a + b + c) << '\n'; } ==== Formátování ==== Tzv. I/O manipulátory nám umožňují upravit vzhled našeho výstupu. #include #include void hline(int W) { for (int i = 0; i < W; ++i) { std::cout << '-'; } std::cout << '\n'; } int main() { const int W = 8; hline(W); // -------- std::cout << std::setw(W) << 1234 << '\n'; // 1234 std::cout << std::setw(W) << 1.2 << '\n'; // 1.2 std::cout << std::setw(W) << 1.2e10 << '\n'; // 1.2e+10 std::cout << std::setw(W) << "abcd" << '\n'; // abcd hline(W); // -------- std::cout << std::hex; std::cout << std::setw(W) << 255 << '\n'; // ff std::cout << std::showbase; std::cout << std::setw(W) << 255 << '\n'; // 0xff std::cout << std::dec; std::cout << std::setw(W) << 255 << '\n'; // 255 hline(W); // -------- } ==== Druhy proudů ==== Ve standardní knihovně se dají najít 3 druhy proudů -- konzolové, souborové a řetězcové. S těmi prvními jsme se již potkali na minulém cvičení. Protože jsou proudy jedno z mála((Nevíme o dalším, ale též nenosíme v hlavě celou standardní knihovnu)) míst, kde standardní knihovna C++ používá dědičnost a virtuální funkce, typ (objekt), který se umí vypsat na konzolový výstup, se umí též vypsat do souboru nebo do řetězce. To samé samozřejmě platí i o načítání. === Konzolové proudy === Konzolové proudy jsou ve standardní hlavičce '''' a existují 4: * std::cin * std::cout * std::cerr * std::clog S prvními dvěma jsme se již setkali na minulém cvičení, s posledními dvěma ne. Dle jména není těžké si domyslet, že ''std::cerr'' vypisuje na ''stderr''. Co ale dělá ''std::clog''? Též vypisuje na ''stderr'', ale výstup bufferuje, to jest, výstup zapsaný do ''std::clog'' se dostane na ''stderr'' až spolu s dalším výstupem, aby bylo vypisování efektivnější. === Souborové proudy === Souborové proudy existují 3 a jsou ve standardní hlavičce ''''. * std::ifstream * std::ofstream * std::fstream ''std::ifstream'' slouží ke čtení ze souboru (i - input, f - file, stream - proud). Po jeho konstrukci se s ním pracuje stejně jako se ''std::cin''. Tato ukázka otevře soubor, který dostala jako argument, a celý ho vypíše na standardní výstup. Jedná se vlastně o (hodně) horší ''cat''((Klasický ''cat'' například hlásí chyby a bere více argumentů než jenom jeden.)). #include #include #include int main(int argc, char** argv) { std::ifstream infile(argv[1]); std::string line; while (std::getline(infile, line)) { std::cout << line << '\n'; } } ''std::ofstream'' pak slouží k zápisu do souboru (o - output, f - file, stream - proud). Po jeho konstrukci se s ním pracuje stejně jako se ''std::cout''. Tato ukázka otevře soubor, který dostala jako argument, a zapíše do něj vše co dostane na standardním vstupu. #include #include #include int main(int argc, char** argv) { std::ofstream outfile(argv[1]); std::string line; while (std::getline(std::cin, line)) { outfile << line << '\n'; } } ''std::fstream'' samotný pak umožňuje otevřít soubor pro zápis i pro čtení zároveň. === Řetězcové proudy === Existuje též řetězcový proud, ''std::stringstream''((Ve skutečnosti existují zase 3 varianty, ''std::istringstream'', ''std::ostringstream'', ''std::stringstream'', ale není dobrý důvod používat jinou variantu než ''std::stringstream'')), který umožňuje formátované čtení z/do řetězce. Řetězcové proudy se obvykle používají, pokud máme ''std::string'' a chceme z něj formátovaně číst, nebo do něj dále formátovaně psát. Ukázka načte řádek ze stdin a následně ten řádek rozdělí na slova, která uloží. #include #include #include #include int main(){ std::string line; std::getline(std::cin, line); std::stringstream sstream(line); std::string word; std::vector words; while (sstream >> word){ words.push_back(word); } } ===== Nespecifikované chování ===== Krom nedefinovaného chování existuje i tzv. nespecifikované chování. Zatímco nedefinované chování "nesmí nastat", k nespecifikovanému chování dojít může, pouze... není přesně specifikována co se má dít. ==== Příklad 1 ==== Zkuste této ukázce dát nějaká data... očekává nejdříve jedno číslo, udávající množství bodů, a pak ''n'' bodů, [x, y], které dané body definují. #include #include struct point { int x, y; }; int get_int(std::istream& in) { int temp; in >> temp; return temp; } point make_point(int x, int y) { return{ x, y }; } int main() { std::vector points; int n = get_int(std::cin); for (int i = 0; i < n; ++i) { points.push_back(make_point(get_int(std::cin), get_int(std::cin))); } for (auto& p : points) { std::cout << p.x << ' ' << p.y << std::endl; } } Vypsaly se vám zpátky body stejně, jako jste je načítali? ==== Příklad 2 ==== Zkuste si zkompilovat a spustit následující kód. Dává výstup smysl? #include #include int main(){ std::string s = "but I have heard it works even if you don't believe in it"; s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(" don't"), 6, ""); std::cout << s << '\n'; }