Lekce 9 - Textové řetězce v jazyce C podruhé - Práce se znaky
V minulé lekci, Textové řetězce v jazyce C, jsme si uvedli, že textový řetězec je vlastně pouze pole znaků ukončené nulovým znakem.
Dnes budeme v tutoriálu pracovat právě s jednotlivými znaky, naučíme se používat ASCII hodnoty a vytvoříme analyzátor vět a šifrovací program.
Výpis textu po znacích
Nejprve si ověříme, že můžeme opravdu přistupovat k textu jako k poli
char
ů. Pro začátek si pouze vypíšeme jednotlivá písmena
nějakého řetězce:
{C_CONSOLE}
int i;
char veta[] = "Hello ITnetwork";
for (i = 0; veta[i] != '\0'; i++)
printf("%c ", veta[i]);
{/C_CONSOLE}
Cyklus projede všechny znaky řetězce, až narazí na nulový znak, kterým je řetězec zakončený. Ve výsledku jsou opravdu vypsány jednotlivé znaky řetězce, pro názornost jsem za každý znak vypsal ještě mezeru:
Konzolová aplikace
Hello ITnetwork
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 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:
{C_CONSOLE}
char c; // znak
int i; // ordinální (ASCII) hodnota znaku
// převedeme znak na jeho ASCII hodnotu
c = 'a';
i = (int)c;
printf("Znak %c jsme převedli na ASCII hodnotu %d\n", c, i);
// Převedeme ASCII hodnotu na znak
i = 98;
c = (char)i;
printf("ASCII hodnotu %d jsme převedli na znak %c", c, i);
{/C_CONSOLE}
Konzolová aplikace
Znak a jsme převedli na ASCII hodnotu 97
ASCII hodnotu 98 jsme převedli 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, čísel a počet zbylých znaků
(např. mezera nebo !
).
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 scanf()
. Ř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 čísla. 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.
// inicializace pocitadel int pocet_samozhlasek = 0; int pocet_souhlasek = 0; int pocet_cisel = 0; // retezec, ktery chceme analyzovat char retezec[] = "Mount Everest"; // definice typu znaku char samohlasky[] = "aeiouyAEUOUY"; char souhlasky[] = "bcdfghijklmnpqrstvwxzBCDFGHIJKLMNPQRSTVWXZ"; char cisla[] = "0123456789"; // indexy int i; printf("Puvodni zprava: %s\n",retezec); // hlavni cyklus pokracuje, dokud nenarazi na znak konce retezce for (i = 0; retezec[i] != '\0'; 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 (i = 0; retezec[i] != '\0'; i++) { if (obsahuje_znak(retezec[i], samohlasky) == 1) pocet_samohlasek++; else if (obsahuje_znak(retezec[i], souhlasky) == 1) pocet_souhlasek++; else if (obsahuje_znak(retezec[i], cisla) == 1) pocet_cisel++; }
Všimněte si, že využíváme funkci obsahuje_znak()
, která
zjistí zda řetězec obsahuje určitý znak. K funkcím se sice dostaneme až
na konci kurzu, nicméně dnes předběhneme a přidáme si právě funkci
obsahuje_znak()
, abychom mohli vytvořit nějaké zajímavé
programy.
Následující blok kódu vložte nad funkci main()
, pokud
budete mít s jeho začleněním problémy, podívejte se na přiložené
zdrojové kódy.
int obsahuje_znak(char znak,char pole[]) { int i; for (i = 0; pole[i] != '\0'; i++) if (pole[i] == znak) return 1; return 0; }
Funkci si zatím popisovat nebudeme, vraťme se k našemu kódu ve funkci
main()
. 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. To samé provedeme s čísly. Nyní nám chybí
již jen výpis na konec:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int obsahuje_znak(char znak,char pole[])
{
int i;
for (i = 0; pole[i] != '\0'; i++)
if (pole[i] == znak)
return 1;
return 0;
}
int main(int argc, char** argv)
{
// inicializace pocitadel
int pocet_samohlasek = 0;
int pocet_souhlasek = 0;
int pocet_cisel = 0;
// retezec, ktery chceme analyzovat
char retezec[] = "Mount Everest";
// definice typu znaku
char samohlasky[] = "aeiouyAEUOUY";
char souhlasky[] = "bcdfghijklmnpqrstvwxzBCDFGHIJKLMNPQRSTVWXZ";
char cisla[] = "0123456789";
// indexy
int i;
printf("Puvodni zprava: %s\n",retezec);
// hlavni cyklus pokracuje, dokud nenarazi na znak konce retezce
for (i = 0; retezec[i] != '\0'; i++)
{
if (obsahuje_znak(retezec[i], samohlasky) == 1)
pocet_samohlasek++;
else if (obsahuje_znak(retezec[i], souhlasky) == 1)
pocet_souhlasek++;
else if (obsahuje_znak(retezec[i], cisla) == 1)
pocet_cisel++;
}
printf("Pocet samohlasek: %d\n", pocet_samohlasek);
printf("Pocet souhlasek: %d\n", pocet_souhlasek);
printf("Pocet cisel: %d\n", pocet_cisel);
printf("Pocet zbylych znaku: %d\n",
strlen(retezec) - pocet_samohlasek - pocet_souhlasek - pocet_cisel);
return (EXIT_SUCCESS);
}
Výsledek:
Konzolová aplikace
Puvodni zprava: Mount Everest
Pocet samohlasek: 5
Pocet souhlasek: 7
Pocet cisel: 0
Pocet zbylych znaku: 1
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é funkcí scanf()
. Š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 char s[] = "gaiusjuliuscaesar"; int posun = 1; int i; printf("Puvodni zprava: %s\n", s); // hlavni cyklus for (i = 0; s[i] != '\0'; i++) { } printf("Zasifrovana zprava: %s\n", s);
Nyní se přesuneme dovnitř cyklu. Hodnotu aktuálního znaku zvýšíme o posun a uložíme místo původního znaku.
{C_CONSOLE}
// retezec k zasifrovani
char s[] = "gaiusjuliuscaesar";
int posun = 1;
int i;
printf("Puvodni zprava: %s\n", s);
// hlavni cyklus
for (i = 0; s[i] != '\0'; i++)
{
s[i] = s[i] + posun;
}
printf("Zasifrovana zprava: %s\n", s);
{/C_CONSOLE}
Konzolová aplikace
Puvodni zprava: gaiusjuliuscaesar
Zasifrovana zprava: hbjvtkvmjvtdbftbs
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 (i = 0; s[i] != '\0'; 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 následujícím cvičení, Řešené úlohy k 8.-9. lekci Céčka, si procvičíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 126x (126.1 kB)
Aplikace je včetně zdrojových kódů v jazyce C