8. díl - Ošetření uživatelských vstupů

C++ Základní konstrukce C++ Ošetření uživatelských vstupů

Minule jsme se zabývali cykly. Dnes to bude 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í tutoriál.

Připomeňme si kód naší kalkulačky:

cout << "Vítejte v kalkulačce" << endl;
string pokracovat = "ano";
while (pokracovat == "ano")
{
        cout << "Zadejte první číslo:" << endl;
        float a;
        cin >> a;
        cout << "Zadejte druhé číslo:" << endl;
        float b;
        cin >> b;
        cout << "Zvolte si operaci:" << endl;
        cout << "1 - sčítání" << endl;
        cout << "2 - odčítání" << endl;
        cout << "3 - násobení" << endl;
        cout << "4 - dělení" << endl;
        int volba;
        cin >> volba;
        float vysledek = 0;
        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 << "Výsledek: " << vysledek << endl;
        else
                cout << "Neplatná volba" << endl;
        cout << "Přejete si zadat další příklad? [ano/ne]" << endl;
        cin >> pokracovat;
}
cout << "Děkuji za použití kalkulačky, aplikaci ukončíte libovolnou klávesou." << 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í:

  1. požádáme uživatele o zadání hodnoty
  2. místo do číselné proměnné načteme vstup do stringu
  3. pokusíme se text převést na číslo
  4. v případě neúspěchu se vrátíme k 1 bodu

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í).

Výjimku vyvoláme takto:

throw x;

kde x je výjimka. Jako výjimku můžeme použít libovolnou proměnnou - klidně i číslo nebo znak ( throw 'd'; ). Většinou se ale používají objektové typy výjimek.

Funkce stoi způsobuje 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 vypadat třeba takto:

int i;
string s;
while (true) {
        try {
                cin >> s;
                i = stoi(s);
                break;
        } catch (invalid_argument& exception) {
                // nebylo zadáno číslo
        } catch (out_of_range& exception) {
                // číslo je moc velké (nebo moc malé)
        }
}

Pokud se převod nepovede, skočí se rovnou na příslušné catch 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.

funce pro další datové typy

Jak už vás asi napadlo typ int není jediným typem, který se dá ze stringu číst (tzv. parsovat). Konkrétní funkce 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 << "Vítejte v kalkulačce" << endl;
string pokracovat = "ano";
string s;
while (pokracovat == "ano")
{
        cout << "Zadejte první číslo:" << endl;
        float a;
        while (true) {
                try {
                        cin >> s;
                        a = stoi(s);
                        break;
                } catch (invalid_argument& exception) {
                        cout << "Neplatné zadání!" << endl;
                } catch (out_of_range& exception) {
                        cout << "Číslo je moc velké (nebo moc malé)!" << endl;
                }
        }
        cout << "Zadejte druhé číslo:" << endl;
        float b;
        while (true) {
                try {
                        cin >> s;
                        b = stoi(s);
                        break;
                } catch (invalid_argument& exception) {
                        cout << "Neplatné zadání!" << endl;
                } catch (out_of_range& exception) {
                        cout << "Číslo je moc velké (nebo moc malé)!" << endl;
                }
        }

        // ...
        // atd.
        // ...

}
cout << "Děkuji za použití kalkulačky, aplikaci ukončíte libovolnou klávesou." << 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 switchi. Problémem je, že cin.get() přečte i enter z minulého zadání. Musíme tedy vstup přečíst vícekrát.

char volba;
do
{
        volba = cin.get();
} while (volba == '\n');
switch (volba) {
case '1':
        cout << "Výsledek: " << (a + b) << endl;
        break;
case '2':
        cout << "Výsledek: " << (a - b) << endl;
        break;
case '3':
        cout << "Výsledek: " << (a * b) << endl;
        break;
case '4':
        if (b != 0)
                cout << "Výsledek: " << (a / b) << endl;
        else cout << "Nulou nelze dělit!" << endl;
        break;
default:
        cout << "Neplatná 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 ještě 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á.

setlocale(LC_ALL, "");
cout << "Vítejte v kalkulačce" << endl;
bool pokracovat = true;
while (pokracovat) {
        // načtení čísel
        string s;
        cout << "Zadejte první číslo:" << endl;
        float a;
        while (true) {
                try {
                        cin >> s;
                        a = stoi(s);
                        break;
                }
                catch (invalid_argument& exception) {
                        cout << "Neplatné zadání!" << endl;
                }
                catch (out_of_range& exception) {
                        cout << "Číslo je moc velké (nebo moc malé)!" << endl;
                }
        }
        cout << "Zadejte druhé číslo:" << endl;
        float b;
        while (true) {
                try {
                        cin >> s;
                        b = stoi(s);
                        break;
                }
                catch (invalid_argument& exception) {
                        cout << "Neplatné zadání!" << endl;
                }
                catch (out_of_range& exception) {
                        cout << "číslo je moc velké (nebo moc malé)!" << endl;
                }
        }


        // volba operace a výpočet
        cout << "Zvolte si operaci:" << endl;
        cout << "1 - sčítání" << endl;
        cout << "2 - odčítání" << endl;
        cout << "3 - násobení" << endl;
        cout << "4 - dělení" << endl;
        char volba;
        do
        {
                volba = cin.get();
        } while (volba == '\n');
        switch (volba) {
        case '1':
                cout << "Výsledek: " << (a + b) << endl;
                break;
        case '2':
                cout << "Výsledek: " << (a - b) << endl;
                break;
        case '3':
                cout << "Výsledek: " << (a * b) << endl;
                break;
        case '4':
                if (b != 0)
                        cout << "Výsledek: " << (a / b) << endl;
                else cout << "Nulou nelze dělit!" << endl;
                break;
        default:
                cout << "Neplatná volba" << endl;
                break;
        }

        // dotaz na pokračování
        cout << "Přejete si zadat další příklad? [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 << "Neplatná volba, zadejte prosím a/n" << endl;
                        break;
                }
        }
}
cout << "Děkuji za použití kalkulačky, aplikaci ukončíte libovolnou klávesou." << endl;
cin.get();cin.get();
return 0;
Jednoduchá konzolová kalkulačka v C++ s ošetřením vstupů

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í.

Příště se opět ponoříme do nových konstrukcí, čekají nás pole. Pak už jen práce s textem a funkce a bude to z této sekce vše, blížíme se ke konci :)


 

Stáhnout

Staženo 93x (8.83 kB)

 

  Aktivity (5)

Článek pro vás napsal Zdeněk Pavlátka
Avatar
Autor se věnuje spoustě zajímavých věcí :)

Jak se ti líbí článek?
Celkem (3 hlasů) :
4444 4


 


Miniatura
Předchozí článek
Cvičení k 6.-7. lekci C++
Miniatura
Všechny články v sekci
Základy C++
Miniatura
Následující článek
Pole v C++

 

 

Komentáře
Zobrazit starší komentáře (2)

Avatar
coells
Redaktor
Avatar
coells:

Tady v tutoriálu, ve dvou příkladech.

 
Odpovědět 4.5.2014 12:54
Avatar
Lukáš Hruda (Luckin):

Měl bys stream inicializovat na nulu. Operátor delete pokud vím už pak sám kontroluje jestli je adresa nulová, takhle ale používáš delete na nějakou neznámou adresu, v C++ se neinicializované proměnné nenastavují na nulu ale mají všeobecně nedefinovanou (neznámou) hodnotu, tudíž by ti takhle mohl operátor delete dealokovat něco co nechceš.

 
Odpovědět 4.5.2014 15:40
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na Lukáš Hruda (Luckin)
Zdeněk Pavlátka:

Jo takhle. Nějak jsem na to zapoměl :[ co nejdřív to opravím.

Odpovědět 4.5.2014 15:51
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Tlapka
Člen
Avatar
Tlapka:

Kód mi zprvu nefungoval, hlásilo mi to chyby na stringstreamu. Tak jsem po chvíli pátrání, pokusů a omylů přišla na to, že to opraví tento řádek, přidaný na začátek programu mezi importy:

#include <sstream>

tak to tu píšu pro ty, kterým by to také nefungovalo. ;-)

Jinak tutoriál je fajn, i když v této lekci je dost ne úplně vysvětlených věcí (new, delete, stream), ale jinak mi pomohl, díky za něj. ;-)

 
Odpovědět 11.10.2014 22:31
Avatar
Odpovídá na Zdeněk Pavlátka
Libor Šimo (libcosenior):

Zdeňku, c++ sa pýši tým, že je to multiplatformový jazyk a ty tu používaš knižnicu conio.h a funkciu _getch(), ktoré idú iba na windows. Nebolo by vhodné zvoliť niečo iné? Napríklad getchar() a knižnicu cstdio?

Ináč článok sa mi páči. :-)

Editováno 11.3.2015 11:46
Odpovědět  +1 11.3.2015 11:46
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
pradedadedymraze:

Tak jsem z toho krapet zmatený. Kapitolou 8. začíná jakoby jiný kurz (to stejný v bledě modrým) a na pole zmiňované v kapitole 5 se asi zapomělo - což mě celkem mrzí...

 
Odpovědět 27.9.2015 13:54
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na pradedadedymraze
Zdeněk Pavlátka:

Články co následují po tomhle jsou staré (jsou z původního krátkého seriálu)

Odpovědět 27.9.2015 17:36
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Odpovídá na Zdeněk Pavlátka
pradedadedymraze:

Ta novější část mi přišla dobrá, škoda že tam chybí ty pole - myslím to do základů patří a bylo by to komplet. V té starší části je lehce nastíněno v k čemu je dobré to dokazování na proměnné (což by stálo za to zapracovat i do té nové části). :) Každopádně díky za kurz, mě to pomohlo moc.

 
Odpovědět 27.9.2015 19:52
Avatar
Roman
Člen
Avatar
Roman:

Ahoj, netreba na začiatku #include <sstream> aby:

stringstream stream(str);

fungoval ???
Lebo mne to bez toho nešlo :)
Ale inak PERFECT článok :) 8-)

Editováno 27. února 18:52
 
Odpovědět 27. února 18:51
Avatar
Jan Doskočil:

nějak to pořád nechápu a to už jsem to přečetl několikrát... nemohl bych se s někým z vás domluvit na hangouts aby mi to vysvětlil? (prosíííím??)

Odpovědět 18. dubna 19:13
"Existuje pouze 10 typů lidí, ti kteří strojovému kódu rozumí a ti kteří ne."
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 12. Zobrazit vše