Search
Cílem dnešního cvičení je seznámit se s komplikovanějším použití standardních proudů 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, namísto čtení 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.
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'; }
Občas můžeme chtít zapsat výstup ve specifickém formátu. Pro formátování zápisu do C++ proudů slouží tzv. I/O manipulátory, z hlaviček
Potřebujete o nich vědět 2 důležité věci
Syntaxe viz příklad.
#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:
<iostream>
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 může dostat na stderr až „někdy časem“ (není přesně řečeno, kdy; nejpozději s ukončením programu). Proto zabere zápis na std::clog výrazně méně času než zápis na std::cerr.
std::cerr
stderr
std::clog
Souborové proudy existují 3 a jsou ve standardní hlavičce <fstream>.
<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.
std::ifstream
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ší cat2).
cat
#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.
std::ofstream
std::cout
Tato ukázka napodobuje utilitu tee: zapíše vše ze standardního vstupu na standardní výstup a do souboru, který dostala jako argument.
tee
#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::cout << line << '\n'; } }
std::fstream samotný pak umožňuje otevřít soubor pro zápis i pro čtení zároveň.
std::fstream
Existuje též řetězcový proud, std::stringstream3), který umožňuje formátované čtení z/do řetězce.
std::stringstream
Ř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); } }
To co jsme vám zde ukázali stačí na napodobení několika dalších linuxích utilit:
wc
uniq
paste
\t
Během tohoto cvičení byste se též měli seznámit s odevzdáváním úloh do Brute. Jsou v něm otevřeny dvě testovací úlohy, viz tato stránka na CW.
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áno, 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í.
n
#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; } }
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'; }
std::istringstream
std::ostringstream