11. cvičení

Textové řetězce

Textový řetěžec (anglicky string) je v podstatě pole znaků (typ char) s tím rozdílem, že textový řetězec je dle konvence ukončen speciálním znakem '\0'. Řetězce lze zapisovat ve dvojitých uvozovkách, např. "Hello, world!" je řetězec délky 13 znaků, ale pro jeho uložení v paměti počítače je třeba pole délky alespoň 14 znaků, protože 14. znak bude '\0'. Znak '\0' nemusí být pouze na konci daného pole znaků, např. "Hello,\0 world!" je platný textový řetězec o délce 6 znaků a je ekvivalentní s textovým řetězcem "Hello,".

S řetězci zadanými přímo ve zdrojovém kódu pomocí dvojitých uvozovek můžeme pracovat s využitím ukazatelů typu const char*:

const char* string = "Hello, world!";
printf("string = \"%s\"\n", string);
printf("prvni pismeno = '%c'\n", string[0]);

Tyto řetězce však nelze modifikovat, protože jsou po spuštění programu uloženy v části paměti, kam program nemá oprávnění zapisovat. To je vyjádřeno pomocí kvalifikátoru const. Pokud bychom v předchozím vynechali const a přidali např. příkaz string[6] = '\0', program by sice šel zkompilovat, ale při spuštění by skončil s chybovou hláškou segmentation fault.

Deklaraci modifikovatelného textového řetězce je třeba provést trochu jinak, aby kompilátor vytvořil pole v modifikovatelné oblasti paměti a textový řetězec ze dvojitých uvozovek do něj překopíroval:

char string[] = "Hello, world!";
string[6] = '\0';
printf("string = \"%s\"\n", string);

Předchozí příkazy by měly vypsat text string = "Hello,".

Důležité poznámky

Načítání textových řetězců z terminálu

Pro načtení textového řetězce z terminálu je sice možné použít funkci scanf a formátovací symbol %s, ale tento přístup je velmi nebezpečný, protože funkce scanf nekontroluje délku výstupního pole znaků. Proto například tyto příkazy způsobí segmentation fault, pokud uživatel zadá řetězec délky 10 znaků nebo více:

char string[10];
scanf("%s", string);

Bezpečnější způsob je použít funkci getchar, která přečte právě jeden znak z terminálu, a v cyklu ukládat přečtené znaky do řetězce. Navíc si můžeme vybrat, jestli načítání ukončíme znakem pro konec řádku ('\n'), nebo ne (pokud chceme načíst více řádků současně). V následující funkci pro přečtení jednoho řádku ukončujeme načítání buď konstantou EOF (zkráceně end of file) reprezentující konec vstupu nebo znakem '\n':

// funkce pro nacteni jednoho radku textu z terminalu
bool getLine(char* string, int maxDelka)
{
    for (int i = 0; i < maxDelka; i++) {
        // precti znak z terminalu
        int znak = getchar();

        // zkontroluj, jestli neni konec vstupu
        if (znak != EOF && znak != '\n')
            string[i] = znak;
        else {
            // konec vstupu - ukonci string a prerus cyklus
            string[i] = '\0';
            return true;
        }
    }

    // v cyklu jsme nedosli ke znaku EOF ani k '\n' - vstup je delsi nez (maxDelka - 1)
    // (vypisujeme maxDelka - 1, protoze posledni znak musi byt '\0', ktery ale uzivatel nezadava)
    printf("Chyba: vstup je delsi nez maximalni povolena delka %d znaku.\n", maxDelka - 1);
    return false;
}

Poznámky

Funkce pro práci s textovými řetězci

Ve standardní knihovně jazyka C existuje řada funkcí pro práci s textovými řetězci. Ze seznamu všech funkcí zmíním zejména tyto:

Pro použití těchto funkcí je třeba includovat hlavičkový soubor string.h.

V hlavičkovém souboru ctype.h jsou k dispozici další funkce pro klasifikaci jednotlivých znaků: isspace, isdigit, isupper, islower, atd.

Probrané příklady

  1. Program demonstrující základní práci s textovými řetězci (viz jednotlivé příkazy výše).
  2. Funkce pro určení délky nejdelšího slova v textovém řetězci.
  3. Funkce pro určení počtu slov v textovém řetězci. Dejte si pozor na několikanásobné mezery, mezery na začátku řetězce a mezery na konci řetězce.

Celý program pro poslední dva příklady:

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

// funkce pro nacteni jednoho radku textu z terminalu
bool getLine(char* string, int maxDelka)
{
    for (int i = 0; i < maxDelka; i++) {
        // precti znak z terminalu
        int znak = getchar();

        // zkontroluj, jestli neni konec vstupu
        if (znak != EOF && znak != '\n')
            string[i] = znak;
        else {
            // konec vstupu - ukonci string a prerus cyklus
            string[i] = '\0';
            return true;
        }
    }

    // v cyklu jsme nedosli ke znaku EOF ani k '\n' - vstup je delsi nez (maxDelka - 1)
    // (vypisujeme maxDelka - 1, protoze posledni znak musi byt '\0', ktery ale uzivatel nezadava)
    printf("Chyba: vstup je delsi nez maximalni povolena delka %d znaku.\n", maxDelka - 1);
    return false;
}

int nejdelsi_slovo(const char* string)
{
    int max_delka = 0;
    int delka = 0;
    int i = 0;
    while (string[i] != '\0') {
        // pouzit funkci isspace(string[i])
        if (isspace(string[i])) {
            if (delka > max_delka)
                max_delka = delka;
            delka = 0;
        }
        else {
            delka++;
        }

        i++;
    }

    if (delka > max_delka)
        max_delka = delka;

    return max_delka;
}

int pocet_slov(const char* string)
{
    // POZOR: na cviceni bylo  predchozi = '\0';  coz nefunguje pro mezery na zacatku
    //        (pokud se necha  predchozi = '\0';  je potreba zacit pocitat az za prvnim
    //        slovem -> pomocna promenna na zacatku false, prvnim ne-space znakem se
    //        zmeni na true)
    char predchozi = string[0];
    int pocet = 0;
    int i = 0;
    while (string[i] != '\0') {
        // pokud predchozi neni mezera a aktualni je mezera, skoncilo slovo
        if (isspace(predchozi) == 0 && isspace(string[i]) > 0)
            pocet++;

        predchozi = string[i];
        i++;
    }

    // pokud na konci neni mezera, tak jeste pricist 1
    // POZOR: na cviceni chybela podminka i > 0 (pricteni nema smysl pro prazdny
    //        retezec) a pouziti isspace bylo opacne (je potreba znegovat)
    if (i > 0 && !isspace(predchozi))
        pocet++;

    return pocet;
}

int main()
{
    char retezec[1000];
    printf("Zadej text: ");
    getLine(retezec, 1000);

    printf("Zadany text je \"%s\".\n", retezec);

    int d = nejdelsi_slovo(retezec);
    printf("Delka nejdelsiho slova je %d znaku.\n", d);

    int p = pocet_slov(retezec);
    printf("Pocet slov je %d.\n", p);

    return 0;
}