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.
Pokud definujeme řetězec v jazyce Python, nelze jej změnit. Následující sekvence příkazů vyvolá chybu:
abc = 'Python' abc[0] = 'K'
V grafické studiu kreslí vektorové obrázky. Odčítají potřebné souřadnice z předem nakreslených obrázků a přidávají mezi ně speciální značky. Výsledek může vypadat takto:
stroke="#DD7700" stroke-width="8" fill="none" M 65 20 a 15 15 0 1 1 15 15 h -60 a 15 15 0 1 1 15 -15 v 60 a 15 15 0 1 1 -15 -15 h 60 a 15 15 0 1 1 -15 15 stroke="#FF0000" stroke-width="10" M 45 50 h 10
Řádky začínající na M popisují souřadnice jednotlivých objektů. Ostatní řádky popisují formát pro vykreslení následujících řádků (jednoho nebo více). Vytvořte obrázek typu SVG ze zadaných popisů. Postup znázorňuje výsledek:
M
SVG
<svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'> <path d="M 65 20 a 15 15 0 1 1 15 15 h -60 a 15 15 0 1 1 15 -15 v 60 a 15 15 0 1 1 -15 -15 h 60 a 15 15 0 1 1 -15 15z" stroke="#DD7700" stroke-width="8" fill="none"/> <path d="M 45 50 h 10" stroke="#FF0000" stroke-width="10"/> </svg>
Písmeno z na konci řetězce doplňte tehdy, když řetězec souřadnic obsahuje víc než jedno písmeno (mimo M – viz první řádek, který obsahuje písmena a, h, a, v, a, h a).
z
a, h, a, v, a, h a
Další testovací vstupy
Možnosti úpravy programu: Pokuste se ze vstupu načíst jednotlivá čísla (typu int), čísla vypište do sloupce pod sebe. Program upravte pro čtení čísel zadaných hexadecimálně (barvy objektů).
int
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; } }
Zkompilujte a spusťte následující kód. Jsou vypsané příklady při použití kompilátoru clang++ a g++ vyřešeny správně? Jsou stejné?
#include <iostream> int f() { std::cout << "In f\n"; return rand() % 100; // 4 } int g() { std::cout << "In g\n"; return rand() % 100; // 3 } int diff(int i, int j) { std::cout << j << " - " << i << " = "; return j - i; } int main() { std::cout << diff(f(), g()) << std::endl; return 0; }
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'; }
Překlad pomocí překladače g++ (bez ohledu na optimalizaci):
g++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 main.cpp -o main g++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 –O3 main.cpp -o main
make_point
Překlad pomocí překladače clang++ (bez ohledu na optimalizaci):
clang++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 main.cpp -o main clang++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 –O3 main.cpp -o main
Při překladu pomocí jednotlivých kompilátorů dostáváme následující výsledky:
In f In g 86 - 83 = 3
In g In f 83 - 86 = -3
Funkce f a g jsou volány v opačném pořadí, příklady jsou rozličné, přestože jsou vypsány a vyřešeny správně.
f
g
Pokud je návratovou hodnotou funkcí f a g konstanta, výsledky jsou následující:
In f In g 3 - 4 = -1
In g In f 3 - 4 = -1
Vygenerované příklady jsou stejné i přesto, že funkce f a g jsou volány v rozličném pořadí. K prohození zpracovaných argumentů funkce diff nedošlo.
diff
Problém tohoto příkladu je dnes již celkem obtížné demonstrovat. V novějších verzích jazyka C++ a odpovídajících překladačů je problém vyřešen - viz překlad pomocí clang++ na Coliru. Pokud chceme vidět, k jakým problémům může dojít, musíme použít starší překladače, například překladač g++-4.8 a verzi jazyka C++11:
g++-4.8 -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++11 main.cpp -o main
Výstupem takto zkompilovaného programu (viz i příklad na Corilu) je neočekáváný řetězec I have heard it works evenonlyyou donieve in it
Jakým způsobem došlo k zpracování vstupního řetězce do této podoby? Nejprve jsou vyhodnoceny výsledky funkcí find, tj. původní výraz pro úpravu vstupního řetězce
find
s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(" don't"), 6, "");
s.replace(0, 4, "").replace(26, 4, "only").replace(37, 6, "");
replace
std::istringstream
std::ostringstream