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
-
Textové řetězce se ve zdrojovém kódu zadávají pomocí dvojitých uvozovek, jednotlivé znaky se zadávají pomocí jednoduchých uvozovek. Je tedy rozdíl mezi následujícími příkazy (všechny půjdou zkompilovat, ale některé nedělají to, co byste na první pohled čekali):
char znak1 = "\0"; char znak2 = '\0'; char znak3 = '0'; char znak4 = 0;
-
Pro výpis textových řetězců pomocí funkce
printf
je k dispozici formátovací symbol%s
, pro výpis jednoho znaku je k dispozici formátovací symbol%c
.
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
- Pro zpřístupnění typu
bool
je potřeba includovat hlavičkový souborstdbool.h
. - Pro ukončení víceřádkového vstupu v terminálu se používají klávesové zkratky
Ctrl+z
(Windows) neboCtrl+d
(Linux). - K funkci
getchar
existuje analogická funkce putchar, která na terminál vytiskne zadaný znak. Tedyputchar(znak)
je jednoduchá alternativa ke kanónu jménemprintf("%c", znak)
.
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:
- strlen – vrací délku řetězce
- strcmp – porovná dva řetězce
- strchr – vrací ukazatel na první výskyt daného znaku v daném řetězci
- strstr – vrací ukazatel na první výskyt podřetězce v daném řetězci
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
- Program demonstrující základní práci s textovými řetězci (viz jednotlivé příkazy výše).
- Funkce pro určení délky nejdelšího slova v textovém řetězci.
- 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;
}