Ošetření uživatelských vstupů
Dnes to bude v C++ tutoriálu takové oddechové, dokončíme si totiž naši kalkulačku, dále už ji nebudeme potřebovat a bylo by hezké ji dotáhnout do konce. Asi tušíte, že u ni chybí zabezpečení vstupů od uživatele, tím se bude zabývat dnešní lekce.
Připomeňme si kód naší kalkulačky:
cout << "Vitejte v kalkulacce" << endl; string pokracovat = "ano"; while (pokracovat == "ano") { cout << "Zadejte prvni cislo:" << endl; float a; cin >> a; cout << "Zadejte druhe cislo:" << endl; float b; cin >> b; cout << "Zvolte si operaci:" << endl; cout << "1 - scitani" << endl; cout << "2 - odcitani" << endl; cout << "3 - nasobeni" << endl; cout << "4 - deleni" << endl; int volba; cin >> volba; float vysledek = 0.0f; switch(volba) { case 1: vysledek = a + b; break; case 2: vysledek = a - b; break; case 3: vysledek = a * b; break; case 4: vysledek = a / b; break; } if ((volba > 0) && (volba < 5)) cout << "Vysledek: " << vysledek << endl; else cout << "Neplatna volba" << endl; cout << "Prejete si zadat dalsi priklad? [ano/ne]" << endl; cin >> pokracovat; } cout << "Dekuji za pouziti kalkulacky, aplikaci ukoncite libovolnou klavesou." << endl; cin.get(); cin.get();
Vstupy od uživatele bychom měli vždy ošetřovat. Řeknu vám tajemství úspěšných a oblíbených aplikací, je velmi jednoduché: Počítají s tím, že je uživatel naprostý hlupák . Čím hloupějšího uživatele budete předpokládat, tím větší úspěch budou vaše aplikace mít. Pokud zde uživatel zadá místo "ano" např. "ano." (ano tečka) nebo "Ano" (s velkým písmenem), program stejně skončí. To ještě nemusí být kvůli hlouposti, ale proto, že se překlepl. Může nám však zadat i něco úplně nesmyslného, např. "možná".
To však není největší problém našeho programu, když uživatel nezadá číslo, ale nějaký nesmysl, celý program se zastaví a spadne s chybou. Pojďme nyní tyto dva problémy opravit.
K ověření správnosti vstupu použijeme funkci stoi(). Tu najdeme v knihovně string. Postup bude následující:
- požádáme uživatele o zadání hodnoty
- místo do číselné proměnné načteme vstup do stringu
- pokusíme se text převést na číslo
- v případě neúspěchu se vrátíme k bodu 1
Jediná potíž je v tom, jak poznat neúspěch. K tomu slouží výjimky. Podrobně se jimi budeme zabývat až u objektů. Prozatím bude muset stačit to, že výjimka je způsob, jak vyslat zprávu o chybě. Pokud tuto zprávu nikde nezachytíme, ukončí program. K jejímu zachycení slouží blok try-catch. Ten vypadá takto:
try { // příkazy, které mohou výjimku způsobit } catch (prvniTypVyjimky v1) { // ošetření chyby typu prvniTypVyjimky } catch (druhyTypVyjimky v2) { // ošetření výjimky typu druhyTypVyjimky }
Částí catch může být libovolný počet. Pokud místo proměnné u catch napíšeme tři tečky (...) zachytíme zde libovolnou výjimku (pokud je částí catch více, tečky smí být jen u té poslední). Jak výjimku vyvolat si vysvětlíme později, bude nám je nyní stačit umět zachytávat.
Funkce stoi() vyvolává 2 typy výjimek:
- invalid_argument pokud nedokáže ze stringu přečíst číslo
- out_of_range pokud se přečtené číslo nevejde do typu int
Náš kód může tedy vypadat třeba takto:
int i; string s; while (true) { try { cin >> s; i = stoi(s); break; } catch (invalid_argument& exception) { cout << "Nebylo zadano cislo" << endl; } catch (out_of_range& exception) { cout << "Cislo je prilis velke (nebo prilis male)" << endl; } }
Pokud se převod nepovede, skočí se rovnou na příslušného
catch
bloku a příkaz break
se tím pádem
přeskočí. Jakmile se převod povede, vyskočíme z cyklu a pokračujeme za
ním.
Funkce pro další datové typy
Jak vás již asi napadlo, typ int
není jediným typem, který
se dá ze stringu číst (tzv. parsovat). Konkrétní funkce pro načítání
dalších typů máte níže v tabulce:
Datový typ | Funkce | Počet parametrů |
---|---|---|
float | stof() | 1 - 2 |
double | stod() | 1 - 2 |
long double | stold() | 1 - 2 |
int | stoi() | 1 - 3 |
long | stol() | 1 - 3 |
long long | stoll() | 1 - 3 |
unsigned long | stoul() | 1 - 3 |
unsigned long long | stoull() | 1 - 3 |
Kromě stringu k převedení můžeme těmto funkcím předat ještě ukazatel na typ size_t (číselný typ). Po převedení se nám do tohoto ukazatele uloží pozice, kde převod skončil. To je užitečné třeba pro čtení několika čísel oddělených např. mezerami. Pokud místo něj uvedeme nullptr, bude ignorován.
U funkcí pro celočíselné typy můžeme uvést ještě číselnou soustavu, která se má použít. Např. čtení čísla zapsaného v šestnáctkové soustavě do typu unsigned long by tedy mohlo vypadat takto:
unsigned long cislo = stoul(str, nullptr, 16);
Jdeme tedy konečně upravit naši kalkulačku:
cout << "Vitejte v kalkulacce" << endl; string pokracovat = "ano"; string s; while (pokracovat == "ano") { cout << "Zadejte prvni cislo:" << endl; float a; while (true) { try { cin >> s; a = stoi(s); break; } catch (invalid_argument& exception) { cout << "Nebylo zadano cislo" << endl; } catch (out_of_range& exception) { cout << "Cislo je prilis velke (nebo prilis male)" << endl; } } cout << "Zadejte druhé číslo:" << endl; float b; while (true) { try { cin >> s; b = stoi(s); break; } catch (invalid_argument& exception) { cout << "Nebylo zadano cislo" << endl; } catch (out_of_range& exception) { cout << "Cislo je prilis velke (nebo prilis male)" << endl; } } // ... // zbytek programu // ... } cout << "Dekuji za pouziti kalkulacky, aplikaci ukoncite libovolnou klavesou." << endl; cin.get(); cin.get();
Nyní se ještě podíváme na výběr operace a pokračování. Obě volby
nyní načítáme jako string. To ale není nutné, pro výběr z menu nám
stačí pouze jeden znak. Jeden znak přečteme již známou konstrukcí
cin.get(). Pokud byla zadána špatná hodnota, odchytíme to v
samotném switch
i. Problémem je, že cin.get() přečte i
enter z minulého zadání. Musíme tedy vstup opět přečíst vícekrát.
char volba; do { volba = cin.get(); } while (volba == '\n'); switch (volba) { case '1': cout << "Vysledek: " << (a + b) << endl; break; case '2': cout << "Vysledek: " << (a - b) << endl; break; case '3': cout << "Vysledek: " << (a * b) << endl; break; case '4': if (b != 0) cout << "Vysledek: " << (a / b) << endl; else cout << "Nulou nelze delit!" << endl; break; default: cout << "Neplatna volba" << endl; break; }
Do proměnné volba si uložíme stisknutý znak jako
char
. Protože se rozsah znaků nedá již tak jednoduše otestovat
podmínkou, uděláme kontrolu jiným způsobem, než když jsme načítali
volbu jako int
. Připravíme si proměnnou platnaVolba
typu bool
, kterou nastavíme na true
(budeme
předpokládat, že je volba správná). Switch zůstane podobný, jen čísla
dáme nyní do apostrofů, protože se jedná o jednotlivé znaky. Přidáme
možnost default, která v případě jiné hodnoty než jmenovaných nastaví
námi připravenou proměnnou platnaVolba na false
. Potom
není nic jednoduššího, než proměnnou otestovat. Vyzkoušejte si to,
program se používá nyní pohodlněji.
K tomu ještě upravíme počítání výsledku a ošetříme dělení nulou.
Nakonec upravíme i výzvu k pokračování, zadávat budeme opět char A/N,
budeme tolerovat různou velikost písmen a reagovat na špatné zadání. Opět
použijeme switch
, naši proměnnou pokracovat změníme
na typ bool
. Kód je asi zbytečné více popisovat, za zmínku
stojí jen funkce tolower()
, která převádí velká písmena na
malá.
cout << "Vitejte v kalkulacce" << endl; bool pokracovat = true; while (pokracovat) { // nacteni cisel string s; cout << "Zadejte prvni cislo:" << endl; float a; while (true) { try { cin >> s; a = stoi(s); break; } catch (invalid_argument& exception) { cout << "Nebylo zadano cislo" << endl; } catch (out_of_range& exception) { cout << "Cislo je prilis velke (nebo prilis male)" << endl; } } cout << "Zadejte druhe cislo:" << endl; float b; while (true) { try { cin >> s; b = stoi(s); break; } catch (invalid_argument& exception) { cout << "Nebylo zadano cislo" << endl; } catch (out_of_range& exception) { cout << "Cislo je prilis velke (nebo prilis male)" << endl; } } // volba operace a výpočet cout << "Zvolte si operaci:" << endl; cout << "1 - scitani" << endl; cout << "2 - odcitani" << endl; cout << "3 - nasobeni" << endl; cout << "4 - deleni" << endl; char volba; do { volba = cin.get(); } while (volba == '\n'); switch (volba) { case '1': cout << "Vysledek: " << (a + b) << endl; break; case '2': cout << "Vysledek: " << (a - b) << endl; break; case '3': cout << "Vysledek: " << (a * b) << endl; break; case '4': if (b != 0) cout << "Vysledek: " << (a / b) << endl; else cout << "Nulou nelze delit!" << endl; break; default: cout << "Neplatna volba" << endl; break; } // dotaz na pokračování cout << "Prejete si zadat dalsi priklad? [a/n]" << endl; bool platnaVolba = false; while (!platnaVolba) { char volba; do { volba = cin.get(); } while (volba == '\n'); switch (volba) { case 'a': pokracovat = true; platnaVolba = true; break; case 'n': pokracovat = false; platnaVolba = true; break; case 224: _getch(); default: cout << "Neplatna volba, zadejte prosim a/n" << endl; break; } } } cout << "Dekuji za pouziti kalkulacky, aplikaci ukoncite libovolnou klavesou." << endl; cin.get(); cin.get(); return 0;
Gratuluji, právě jste vytvořili svůj první blbovzdorný program Kód se nám trochu zkomplikoval, snad jste to všechno pochytili. Někdy v budoucnu to napravíme a rozdělíme ho do přehledných funkcí.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 204x (8.83 kB)
Aplikace je včetně zdrojových kódů v jazyce C++