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ý.
Č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 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 <iostream> #include <string> 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.
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 <iostream> #include <fstream> #include <stdexcept> 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'; }
Tzv. I/O manipulátory nám umožňují upravit vzhled našeho výstupu.
#include <iostream> #include <iomanip> 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); // -------- }
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ála1) 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 jsou ve standardní hlavičce <iostream>
a existují 4:
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 existují 3 a jsou ve standardní hlavičce <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
2).
#include <fstream> #include <iostream> #include <string> 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 <fstream> #include <iostream> #include <string> 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ň.
Existuje též řetězcový proud, std::stringstream
3), 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 <iostream> #include <sstream> #include <string> #include <vector> int main(){ std::string line; std::getline(std::cin, line); std::stringstream sstream(line); std::string word; std::vector<std::string> words; while (sstream >> word){ words.push_back(word); } }
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.
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 <iostream> #include <vector> 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<point> 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?
Zkuste si zkompilovat a spustit následující kód. Dává výstup smysl?
#include <iostream> #include <string> 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'; }
cat
například hlásí chyby a bere více argumentů než jenom jeden.std::istringstream
, std::ostringstream
, std::stringstream
, ale není dobrý důvod používat jinou variantu než std::stringstream