Geek tričko zdarma Geek tričko zdarma
Tričko zdarma! Stačí před dobitím bodů použít kód TRIKO15. Více informací zde

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

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

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:

int i;
char veta[] = "Hello ITnetwork";

for (i = 0; veta[i] != '\0'; i++)
    printf("%c ", veta[i]);

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 byte, 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;
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);

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ě

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

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[] = "Programator se zasekne ve sprse, protoze instrukce na samponu byly: Namydlit, omyt, opakovat.";

// 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[] = "Programator se zasekne ve sprse, protoze instrukce na samponu byly: Namydlit, omyt, opakovat.";

    // 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: Programator se zasekne ve sprse, protoze instrukce na samponu byly: 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é funkcí scanf(). Šifra nepočítá s diakritikou, mezerami a interpunkčními znaménky. Diakritiku budeme bojkovat 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[] = "cernediryjsoutamkdebuhdelilnulou"; // cerne diry jsou tam, kde buh delil nulou
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.

// hlavni cyklus
for (i = 0; s[i] != '\0'; 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 (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 příští lekci, Vícerozměrná pole v jazyce C, si řekneme o jedné z nejzákladnějších konstrukcí jazyka C - o funkcích :)


 

Stáhnout

Staženo 81x (126.1 kB)
Aplikace je včetně zdrojových kódů v jazyce c

 

 

Článek pro vás napsal Patrik Valkovič
Avatar
Jak se ti líbí článek?
2 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Předchozí článek
Textové řetězce v jazyce C
Všechny články v sekci
Základní konstrukce jazyka C
Miniatura
Následující článek
Cvičení k 8.-9. lekci Céčka
Aktivity (13)

 

 

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

Avatar
DarkCoder
Člen
Avatar
DarkCoder:24. srpna 22:06

Jak funguje dereferencování, podívej na následující příklad:

#include <stdio.h>

int main(void) {
        int i = 0, j, *pi = &i;

        *pi = 10; // přiřazení hodnoty 10 proměnné i nepřímo
        printf("%d\n", *pi); // výpis proměnné i nepřímo

        i = 100; // přiřazení hodnoty 100 proměnné i přímo
        printf("%d\n", i); // výpis proměnné i přímo

        j = *pi; // přiřazení proměnné i proměnné j nepřímo
        printf("%d\n", j); // výpis proměnné j přímo

        return 0;
}

Dereferencováním ukazatele na typ je získána hodnota proměnné, na kterou ukazatel ukazuje.
Lze použít jak na levé tak pravé straně přiřazovacího příkazu.

Editováno 24. srpna 22:07
Odpovědět 24. srpna 22:06
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:24. srpna 22:29

tak proc to nefungovalo v mem pripade? kde jsem mel *strchr(s,'z') = 'a', kde s je pole. Taky jsem chtel ziskat hodnotu adresy charu 'z', ale spadlo mi to, proc, kdyz delam to co jsi ted vyse popsla?

 
Odpovědět 24. srpna 22:29
Avatar
Patrik Pastor:24. srpna 22:32

for (i = 0; s[i] != '\0'; i++)
{
s[i] = s[i] - posun;
}
*(strchr(s, 'z')) = 'a';

Editováno 24. srpna 22:32
 
Odpovědět 24. srpna 22:32
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:24. srpna 23:07

Třeba proto, že strchr() je funkce z knihovny string.h a nikoli proměnná.

Jak se pracuje s dereferencováním u pole ukazuje následující příklad:

#include <stdio.h>

#define SIZE 10

int main(void) {
        int pole[SIZE], *pp = pole;

        // přiřazení hodnoty 10 druhému prvku pole 4 způsoby
        pole[1] = 10; // indexace pole
        *(pole + 1) = 100; // konstatní ukazatel
        *(pp + 1) = 10; // ukazatel
        pp[1] = 100; // indexace ukazatele

        // výpis hodnoty druhého prvku pole 4 způsoby
        printf("%d\n", pole[1]);
        printf("%d\n", *(pole + 1));
        printf("%d\n", *(pp + 1));
        printf("%d\n", pp[1]);

        return 0;
}

Zde je vidět, jak úzce jsou pole a ukazatele svázány.

Odpovědět 24. srpna 23:07
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:25. srpna 2:24

a jak by to bylo konkretne s mym prikladem s strchr()? prirazeni hodnot poli umim taky, ale kdyz je funkce typu *chat, tak proc ji nemuzu dereferencovat a priradit ji hodnotu?

 
Odpovědět 25. srpna 2:24
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:25. srpna 4:10

Funkce strchr() vrací ukazatel na první výskyt dolního bytu v řetězci, za předpokladu, že hledaný znak se v řetězci nachází. Není-li žádný nalezen, vrací se nulový ukazatel. Z tohoto důvodu nemůžeš vrácenou hodnotu ihned dereferencovat, pokud nevíš, jaká hodnota byla funkcí strchr() vrácena. Pokud by daný znak v řetězci nebyl, došlo by při dereferencování nulového ukazatele k chybě a program by tak spadnul. Proto musíš nejprve tento ukazatel otestovat.

Podívej na následující příklad. V něm mám nějaký řetězec, ve kterém chci najít první znak (v tomto případě X), vypsat jeho hodnotu a jeho adresu. Pokud existuje, zaměnit ho za znak Y a nakonec znovu vypsat řetězec.

#include <stdio.h>
#include <string.h>

int main(void){

        char text[] = "Pan X znovu uderil.";
        char *p = strchr(text, 'X');

        if (p) {
                printf("%c - %p\n", *p, p);
                *p = 'Y';
        }
        printf(text);

        return 0;
}

To nejpodstatnější na celém příkladu je to, že nejprve získám ukazatel a poté ho otestuji. V případě že znakový ukazatel je platný (má adresu), vypíši potřebné údaje a poté hodnotu znaku, kterou získám dereferencováním ukazatele, pozměním na novou. Nakonec vypíši řetězec na obrazovku.

Odpovědět 25. srpna 4:10
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:25. srpna 9:58

chapu, takze je tam ten mezistupen, kdy overis, ze je poitlnter platny. a nemohla byt podminka neco jako if (p* != NULL) ...

Je NULL vubec legalni hodnota prazdne promenne? (to.je ale asi void, spis jestli muze byt NULL, jako chybejici hodnota, nebo tak neco)

 
Odpovědět 25. srpna 9:58
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:25. srpna 12:28

To jsou ale naprosté základy které by si měl umět. Žádné if(p* != NULL) není!

Proměnná má vždy nějakou hodnotu (ať už garbage, 0, jinou), neexistuje žádná prázdná proměnná. Void jako datový typ bez hodnoty se používá u funkce která nevrací hodnotu, která nemá parametry, u obecného ukazatele.

Pro proměnnou platí:

int i;

if(i) je totéž co if(i != 0)
if(!i) je totéž co if(i == 0)

pro ukazatele platí:

int *p;

if(p) je totéž co if(p != NULL)
if(!p) je totéž co if(p == NULL)

Pro proměnnou pomocí ukazatele:

int i, *p=&i;

if(*p) je totéž co if(*p != 0)
if(!*p) je totéž co if(*p == 0)

Pokud něco nevíš a nesouvisí to s tématem článku, založ nové téma.

Pokud Ti odpovím na dotazy, označuj je jako řešením, umožníš mi tím koupit si prémiový článek a tak pomůžeš nejen sobě ale i ostatním, kteří potřebují poradit.

Odpovědět  +1 25. srpna 12:28
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:25. srpna 18:43

no clanky by potrebovaly aktualizovat, protoze pokud toto jsou zaklady, potom to tady neni ani v 1\2\3 lekce, ktere tady povazujou za zaklady. Defaultnuti hodnoty datovych typu, by mohly byt zmineny

 
Odpovědět 25. srpna 18:43
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:25. srpna 18:52

Toto zkrácení zápisu by mělo být uvedeno v článku o relačních operátorech a prvním článku o ukazatelích. Defaultní hodnoty datových typů by mělo být v článku o lokálních a globálních proměnných a ve specifikátorech paměťových tříd, konkrétně static. Podívej se zda-li tu něco takového je.

Odpovědět 25. srpna 18:52
"„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 25. Zobrazit vše