{{page>courses:b6b36pjc:styles#common&noheader&nofooter}}
{{page>courses:b6b36pjc:styles#cviceni&noheader&nofooter}}
===== Cvičení 2: Vstup a výstup =====
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ý.
==== Řádkové čtení ====
Č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
[[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í ====
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
* [[http://en.cppreference.com/w/cpp/header/iomanip|iomanip]], a
* [[http://en.cppreference.com/w/cpp/header/ios|ios]].
Potřebujete o nich vědět 2 důležité věci
- Některé se dají použít nejen pro zápis, ale i pro čtení
- Některé modifikátory mění stav proudu trvale, některé ne
Syntaxe viz příklad.
#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 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''.
=== 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 napodobuje utilitu ''tee'': zapíše vše ze standardního vstupu
na standardní výstup a do souboru, který dostala jako argument.
#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::cout << 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);
}
}
==== Možnosti dalšího procvičení ====
To co jsme vám zde ukázali stačí na napodobení několika dalších linuxích
utilit:
* ''wc'' -- spočítá a vytiskne množství řádků/slov v souborech, které byly předány jako argumenty
* ''uniq'' -- Vyfiltruje za sebou se opakující řádky ze stdin na stdout
* ''paste'' -- Vezme jako argument sadu souborů a pak vytiskne na stdout jejich 1. řádky spojené ''\t'', 2. řádky spojené ''\t'', ...
===== Seznámení se s Brute =====
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
[[http://cw.fel.cvut.cz/b181/courses/b6b36pjc/ukoly/test_upload|tato
stránka na CW]].
===== 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áno, 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';
}