Python týden ITnetwork summer 2020
Pouze tento týden sleva až 80 % na e-learning týkající se Pythonu
80 % bodů zdarma na online výuku díky naší Letní akci!

Lekce 10 - Textové řetězce v jazyce C++ podruhé - Práce se znaky

V minulé lekci, Textové řetězce v jazyce C++, jsme si představili datový typ string a funkce, které můžeme pro práci s textovými řetězci používat. Dnes budeme v C++ tutoriálu pokračovat v práci s textem a pobavíme se o tzv. ASCII hodnotě. Vytvoříme také další praktické programy.

ASCII hodnota

Možná jste již někdy slyšeli o ASCII tabulce. Zejména v éře operačního systému MS-DOS prakticky nebyla jiná možnost, jak zaznamenávat text. Jednotlivé znaky byly uloženy jako čísla typu byte (1 byte, v C/C++ typ char), tedy s rozsahem hodnot od 0 do 255. V systému byla uložena tzv. ASCII tabulka, která měla 256 znaků a každému ASCII kódu (číselnému kódu) přiřazovala jeden znak.

Asi je vám jasné, proč tento způsob nepřetrval dodnes. Do tabulky se jednoduše nevešly všechny znaky všech národních abeced, nyní se používá unicode (UTF-8) kódování, kde jsou znaky reprezentovány trochu jiným způsobem. Nicméně v C++ se ve výchozím nastavení stále pracuje s ASCII hodnotami jednotlivých znaků. Pokud bychom chtěli pracovat s UNICODE znaky (tedy i UTF8), museli bychom použít takzvané wide znaky. Hlavní výhoda ASCII zápisu je v tom, že znaky jsou uloženy v tabulce za sebou, podle abecedy. Např. na pozici 97 nalezneme 'a', 98 'b' a podobně. Podobně je to s čísly, diakritické znaky tam budou bohužel jen nějak rozházeny.

Zkusme si nyní převést znak do jeho ASCII hodnoty a naopak podle ASCII hodnoty daný znak vytvořit:

char c; // znak
int i; // ordinální (ASCII) hodnota znaku
// převedeme znak na jeho ASCII hodnotu
c = 'a';
i = (int)c;
cout << "Znak " << c << " jsme prevedli na ASCII hodnotu "<< i << endl;

// Převedeme ASCII hodnotu na znak
i = 98;
c = (char)i;
cout << "ASCII hodnotu " << i << " jsme prevedli na znak " << c << endl;
cin.get();

Výsledek:

Konzolová aplikace
Znak a jsme prevedli na ASCII hodnotu 97
ASCII hodnotu 98 jsme prevedli na znak b

Analýza výskytů ve větě

Napišme si jednoduchý program, který nám analyzuje zadanou větu. Bude nás zajímat počet samohlásek, souhlásek, písmen a počet zbylých znaků (např. mezera nebo !).

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Daný textový řetězec si nejprve v programu zadáme napevno, abychom ho nemuseli při každém spuštění psát. Až bude program hotový, nahradíme řetězec za cin. Řetězec budeme projíždět cyklem po jednom znaku. Rovnou zde říkám, že neapelujeme na rychlost programu a budeme volit názorná a jednoduchá řešení.

Nejprve si připravme kód, definujme si samohlásky, souhlásky a písmena. Počet zbylých znaků nemusíme počítat, bude to délka řetězce mínus součet samohlásek, souhlásek a písmen. Připravíme si proměnné, do kterých budeme ukládat jednotlivé počty. Protože se jedná o složitější kód, nebudeme zapomínat na komentáře. Také si nezapomeňte includovat knihovnu string.

// inicializace pocitadel
int pocetSamohlasek = 0;
int pocetSouhlasek = 0;
int pocetCisel = 0;

// retezec, ktery chceme analyzovat
string retezec = "Programator se zasekne ve sprse, protoze instrukce na samponu byly: Namydlit, omyt, opakovat.";

// definice typu znaku
string samohlasky = "aeiouyAEIOUY";
string souhlasky = "bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ";
string cisla = "0123456789";

cout << "Puvodni zprava: " << retezec << endl;

// hlavni cyklus pokracuje, dokud nenarazi na znak konce retezce
for (int i = 0; i < retezec.length(); i++)
{

}

Nejdříve počítadlo vynulujeme. Na definice znaků nám postačí obyčejné pole znaků jak jej známe. Hlavní cyklus nám projede jednotlivé znaky v řetězci. Pojďme plnit počítadla, pro jednoduchost již nebudu opisovat zbytek kódu a přesunu se jen k cyklu:

// hlavni cyklus pokracuje, dokud nenarazi na znak konce retezce
for (int i = 0; i < retezec.length(); i++)
{
    if (samohlasky.find(retezec[i]) < samohlasky.length())
        pocetSamohlasek++;
    else if (souhlasky.find(retezec[i]) < souhlasky.length())
        pocetSouhlasek++;
    else if (cisla.find(retezec[i]) < cisla.length())
        pocetCisel++;
}

Všimněte si, že využíváme metodu find(), která zjistí zda řetězec obsahuje určitý znak.

Aktuální znak naší věty tedy nejprve zkusíme vyhledat v řetězci samohlasky a případně zvýšit jejich počítadlo. Pokud v samohláskách není, podíváme se do souhlásek a případně opětovně zvýšíme jejich počítadlo. Vraťme se k našemu kódu ve funkci main(). Nyní nám chybí již jen výpis na konec:

cout << "Pocet samohlasek: " << pocetSamohlasek << endl;
cout << "Pocet souhlasek: " << pocetSouhlasek << endl;
cout << "Pocet cisel: " << pocetCisel << endl;
cout << "Pocet zbylych znaku: " <<
        retezec.length() - pocetSamohlasek - pocetSouhlasek - pocetCisel << endl;

Výsledek:

Konzolová aplikace
Puvodni zprava: Programator se zasekne ve sprse, protoze instrukce na samponu by
ly: Namydlit, omyt, opakovat.
Pocet samohlasek: 31
Pocet souhlasek: 45
Pocet cisel: 0
Pocet zbylych znaku: 17

Cézarova šifra

Vytvoříme si jednoduchý program pro šifrování textu. Pokud jste někdy slyšeli o Cézarově šifře, bude to přesně to, co si zde naprogramujeme. Šifrování textu spočívá v posouvání znaku v abecedě o určitý, pevně stanovený počet znaků. Například slovo "ahoj" se s posunem textu o 1 přeloží jako "bipk". Posun umožníme uživateli vybrat. Algoritmus zde máme samozřejmě opět vysvětlený a to v článku Cézarova šifra. Program si dokonce můžete vyzkoušet v praxi - Online cézarova šifra.

Vraťme se k programování a připravme si kód. Budeme potřebovat proměnné pro původní text a pro posun. Zašifrovaným textem nahradíme původní zprávu. Dále cyklus projíždějící jednotlivé znaky a výpis zašifrované zprávy. Zprávu si necháme zapsanou napevno v kódu, abychom ji nemuseli při každém spuštění programu psát. Po dokončení nahradíme obsah proměnné tím co zadal uživatel pomocí objektu cin. Šifra nepočítá s diakritikou, mezerami a interpunkčními znaménky. Diakritiku budeme bojkotovat a budeme předpokládat, že ji uživatel nebude zadávat. Pro zjednodušení vynecháme i velká písmena. Ideálně bychom měli diakritiku před šifrováním odstranit, stejně tak cokoli kromě písmen.

// retezec k zasifrovani
string s = "cernediryjsoutamkdebuhdelilnulou"; // cerne diry jsou tam, kde buh delil nulou
int posun = 1;

cout << "Puvodni zprava: " << s << endl;

// hlavni cyklus
for (int i = 0; i < s.length(); i++)
{

}

cout << "Zasifrovana zprava: " << s << endl;
cin.get();

Nyní se přesuneme dovnitř cyklu. Hodnotu aktuálního znaku zvýšíme o posun a uložíme místo původního znaku.

// hlavni cyklus
for (int i = 0; i < s.length(); i++)
{
    s[i] = s[i] + posun;
}

Konzolová aplikace
Puvodni zprava: cernediryjsoutamkdebuhdelilnulou
Zasifrovana zprava: dfsofejszktpvubnlefcviefmjmovmpv

Program si vyzkoušíme. Výsledek vypadá docela dobře. Zkusme si však zadat vyšší posun nebo napsat slovo "zebra". Vidíme, že znaky mohou po 'z' přetéct do ASCII hodnot dalších znaků, v textu tedy již nemáme jen písmena, ale další ošklivé znaky. Uzavřeme znaky do kruhu tak, aby posun plynule po 'z' přešel opět k 'a' a dále. Postačí nám k tomu jednoduchá podmínka, která od nové ASCII hodnoty odečte celou abecedu tak, abychom začínali opět na 'a'.

// hlavni cyklus
for (int i = 0; i < s.length(); i++)
{
    s[i] = s[i] + posun;
    if (s[i] > 'z') // kontrola preteceni
        s[i] = s[i] - 26;
}

Pokud hodnota přesáhne ASCII hodnotu 'z', snížíme ji o 26 znaků (tolik znaků má anglická abeceda). Je to jednoduché a náš program je nyní funkční. Všimněme si, že nikde nepoužíváme přímé kódy znaků, v podmínce je 'z', i když bychom tam mohli napsat rovnou 122. Je to z důvodu, aby byl náš program plně odstíněn od explicitních ASCII hodnot a bylo lépe viditelné, jak funguje. Cvičně si zkuste udělat dešifrování.

V příštím lekci, Řešené úlohy k 9.-10. lekci C++, se podíváme na vícerozměrná pole v C++.

V následujícím cvičení, Řešené úlohy k 9.-10. lekci C++, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Stáhnout

Staženo 56x (4.29 MB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

Předchozí článek
Textové řetězce v jazyce C++
Všechny články v sekci
Základní konstrukce jazyka C++
Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
6 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity (16)

 

 

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

Avatar
Ing. Petr Štechmüller:14.11.2018 20:39

If no matches were found, the function returns string::npos

Odpovědět
14.11.2018 20:39
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Avatar
Lukáš Labor:30.11.2018 14:55

Ahoj zkusil jsem si vytvořit svou verzi programu caesarova šifra

int main()
{
cout << "Zadej zpravu:" << endl;
string a;
string b;
int c;
char d;
getline(cin,a);
cout<<"zadej posuv"<<endl;
cin>>c;
for(int i=0;i<a.length();i++)
if(a[i]=='z'|­|a[i]=='Z')
{d=a[i]-26+c;
b[i]=d;
cout<<d;}
else
{d=a[i]+c;
b[i]=d;
cout<<d;}

a program přestane fungovat pokud zadám řetězec delší než 17 znaků, nemáte nějaké vysvětlení?

 
Odpovědět
30.11.2018 14:55
Avatar
pgarsky
Člen
Avatar
pgarsky:2.2.2019 17:06

Prosím o ohodnocení mojí jednoduché aplikace pro šifrování:

#include <iostream>
// #include <cmath>
#include <string>
// #include <algorithm>
// #include <ctime>

using namespace std;

int main(void) {
        setlocale(LC_ALL, "");

        unsigned int posun;

        string text;
        string moznost;
        string povolenaPismena = "abcdefghijklmnopqrstuvwxyz";

        bool vyskocit = false;
        bool spatne = false;

        cout << "Zadejte text: " << endl;
        getline(cin, text);

        do {
                if (spatne == true) {
                        getline(cin, moznost);
                        spatne = false;
                }

                for (int i = 0; i < text.length(); i++) {
                        if (povolenaPismena.find(text[i]) < povolenaPismena.length()) {
                                if (i == text.length() - 1) {
                                        vyskocit = true;
                                        break;
                                }
                        }
                        else {
                                cout << "Zadejte pouze text s malymi pismeny a bez mezer.";
                                i = 0;
                                spatne = true;
                                break;
                        }
                }

                if (vyskocit == true) {
                        break;
                }

        } while (1);

        cout << "Chcete text sifrovat nebo rozsifrovat? (s/r): ";
        do {
                getline(cin, moznost);

                if (moznost == "s" || moznost == "r") {
                        break;
                }
                else {
                        cout << "Zadejte pouze \"s\" nebo \"r\": ";
                }

        } while (1);

        cout << "O kolik mist chcete text sifrovat nebo rozsifrovat?: ";
        cin >> posun;

        if (moznost == "s") {
                for (int i = 0; i < text.length(); i++) {
                        text[i] = text[i] + posun;
                        if (text[i] > 'z') {
                                text[i] = text[i] - 26;
                        }
                }
        }
        else if (moznost == "r") {
                for (int i = 0; i < text.length(); i++) {
                        text[i] = text[i] - posun;
                        if (text[i] < 'a') {
                                text[i] = text[i] + 26;
                        }
                }
        }

        cout << "Zasifrovany/odsifrovany text: " << text << endl;
        cin.ignore(0xFF, '\n');

        cin.get();
        return 0;
}
Editováno 2.2.2019 17:06
Odpovědět
2.2.2019 17:06
Pokud ti něco jde těžko, znamená to, že jdeš tou správnou cestou...
Avatar
Tomáš Lipert:20.3.2019 12:54

Chtěl bych ohlási, že u analýzi ve větě máte v samohláskách dvakrát velké u a ve souhláskách tam máte velké a malé i

 
Odpovědět
20.3.2019 12:54
Avatar
Odpovídá na Tomáš Lipert
Patrik Valkovič:20.3.2019 13:20

Díky, opraveno.

Odpovědět
20.3.2019 13:20
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Branislav Mráz:18. dubna 10:03

ASCII hodnota: Prosim, mohli by ste mi naznacit, kde ste vysvetlovali takuto inicializaciu: "i = (int)c;" Asi som nepozorny, lebo si neviem spomenut. A este vysvetlit, preco mi to funguje aj bez "(int)", resp. "(char)". Vdaka B.

 
Odpovědět
18. dubna 10:03
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Branislav Mráz
DarkCoder:18. dubna 14:22

Jazyk C se při míchání datových typů řídí striktními konverzními pravidly. Jedním z takových pravidel je tzv. Podpora Celých Čísel. To říká, že kdykoli je ve výrazu použita proměnná typu char nebo short int, je její hodnota během vyhodnocování vyrazu automaticky rozšířena na int. Proto se může proměnná typu char používat tam, kde lze použít proměnnou typu int.

Odpovědět
18. dubna 14:22
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Branislav Mráz:19. dubna 22:35

ok, dakujem za pokus o vysvetlenie druhej polovice mojho dotazu.

 
Odpovědět
19. dubna 22:35
Avatar
David K
Člen
Avatar
David K:12. května 10:34

Ja jsem si udělal takový malý šifrovací prográmek, bohužel si příliš nevím rady s tím, jak ho odšifrovat:

int main(void){
    string s;
    string t;
    cout << "Zadejte zpavu pro sifrovani:";
    getline(cin, s);
    cout << "Vysledek je:";
    for (int i = 0; i < s.length(); i++)
    {
        t[i] = s[i] + t[i - 1] + 1;
        for (;t[i] > 'z';)
        {
            t[i] = t[i] - 26;
        }
        for (; t[i] < 'a';)
        {
            t[i] = t[i] + 26;
        }
        cout << t[i];
    }
    cout << endl;
    cin.get();
    return 0;
}

Neměl by prosím někdo nápad, co s tím?

 
Odpovědět
12. května 10:34
Avatar
DarkCoder
Člen
Avatar
Odpovídá na David K
DarkCoder:12. května 14:55

Ja jsem si udělal takový malý šifrovací prográmek, bohužel si příliš nevím rady s tím, jak ho odšifrovat:

V šifrování docela zásadní věc. Ty jako tvůrce bys měl nejlépe vědět, jak šifra funguje. Tedy měl bys vědět, jaké kroky si provedl k modifikaci řetězce. Je třeba postupovat v obráceném pořadí s opačným funkčním efektem. Pokud např. k písmenu přičtu jedna abych ho pozměnil na následují písmeno, pak k tomu, abych získal původní písmeno je zase třeba jedna odečíst. Pro zprávu zakódovanou pomocí XOR je třeba pro její dekódování XOR volat znovu, apod.

Když budeš vědět jak má šifra fungovat, můžeš začít tvořit program k zašifrování zprávy. Tvůj program obsahuje chyby, takže tvým úkolem je poladit program k šifrování dřív než se vrhneš na dešifrovací část.

Program nemůže být úplný pokud mu chybí nějaká část. V tomto případě kompletní hlavička, funkce které používáš jsou někde definované. Hlavní cyklus for provádí traversování celého řetězce, nač tedy pak využíváš vnitřní for cykly.

t[i] = s[i] + t[i - 1] + 1;

Tento příkaz má dvě zásadní chyby. Ta první je, že pracuješ s indexem pole aniž bys znal jeho obsah. Ta druhá je že pracuješ s indexem pole jehož hodnota je při první iterakci cyklu for záporná (pro i=0 je index -1) a tudíž Ti program musí hodit běhovou chybu.

Pozn: V C++ je definování funkce která nemá parametry pomocí klíčového slova void zbytečné. (Není to však chyba)

Odpovědět
12. května 14:55
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
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 17. Zobrazit vše