[Cvičení 7] | [Obsah] | [Cvičení 9] |
string
v jiných jazycích) neexistuje
(až v C++ v knihovně STL je typ string implementován).
Existují řetězcové konstanty, které jsou uzavřeny v uvozovkách, např. "Ahoj"
. Řetězcové
konstanty jsme využívali jako formátovací řetězec ve funkcích printf
a scanf
,
např. printf("Soucet: %d",a+b);
.null
), který se zapisuje ve zdrojovém kódu
pomocí lomítka jako prefixu: '\0'
. Z toho výplývá, že paměť potřebná pro uložení řetězu má velikost o 1 slabiku větší,
než je délka řetězce.
Tedy, řetězec Ahoj
má délku 4 znaky, ale pro jeho uložení je potřeba paměť o velikosti 5 bytů, viz obrázek 1.
Obrázek 1: Uložení řetězce v paměti
char ret1[10]; char *ret2; char *ret3; ret1 = "Ahoj"; // zde překladač ohlásí chybu ret2 = "Ahoj"; // zde nekopírujeme vlastní řetězec, ale pouze ukazatel na konstatní řetězecPrvní přiřazovací příkaz je chybný a překladač zde ohlásí chybu typu „konstantní ukazatel nelze přepsat“. U druhého přiřazovacího příkazu překladač chybu neohlásí, ale přiřazení v tomto případě také není zcela správné. Nejde totiž o přiřazení vlastního řetězce či kopii řetězce, ale do ukazatele
ret2
se přiřadí adresa paměti, kde je překladačem umístěna řetězcová
konstanta "Ahoj"
(řetězcové funkce budou s tímto řetězcem správně pracovat, potíže mohou nastat, budeme-li do řetězce
zapisovat).
Vrátíme se k poli ret1
. Správné nastavení hodnoty řetězce pole ret1
je možné provést dvěma způsoby:
char ret1[10] = "Ahoj";
strcpy
z knihovny
string.h
(odstavec o zmíněné knihovně viz Knihovna string.h):char ret1[10]; ... ... strcpy(ret1,"Ahoj");
ret1
délky 10 znaků, můžeme kopírovat do tohoto pole řetězce o maximální délce 9 znaků
(poslední položka pole je využita pro ukončující znak '\0'
).
Pro řetězec ret2
je potřeba nejprve dynamicky alokovat paměť (pole). Do tohoto pole budeme opět kopírovat řetězec
"Ahoj"
, délku alokovaného určíme přesně podle délky pozdravu "Ahoj"
. K tomu využijeme funkci
strlen
, která vrací délku řetězce bez ukončujícího znaku! Při alokaci musíme tedy zvětšit požadavek na velikost
paměti o 1:
ret2 = (char*)malloc(strlen("Ahoj")+1); strcpy(ret2,"Ahoj");Pokud napíšeme do kódu následně přiřazení
ret3 = ret2;
, přiřadili jsme ukazatel na začátek dynamického pole také do proměnné
ret3
. Oba ukazatelé ukazují na stejný řetězec (opět nejde tedy o kopii řetězce, pouze o kopii ukazatelů); při změně
řetězce ret2
se mění i řetězec ret3
.
printf
, kde ve formátovacím řetězci uvádíme specifikátor
%s
. Následující fragment kódu vytiskne na dva řádky pozdrav:
char pozdrav[10] = "Ahoj"; printf("%s\n%s",pozdrav,pozdrav);Vstup řetězců z klávesnice je možné provádět pomocí funkce
scanf
, např. scanf("%s",pozdrav)
.
Chování funkce má jeden „háček“. Víme, že funkce scanf konvertuje znaky ze vstupního bufferu, dokud nenarazí na
tzv. bílé znaky (white characters), což jsou: mezera, tabulátor či konec řádku (ev. souboru). Zadáme-li z klávesnice text
„Ahoj, Pepiku“, načte se do pole pozdrav
pouze text „Ahoj,“,
protože za ním následuje mezera.
V takovém případě je lépe použít funkci char *gets(char *s)
z knihovny stdio.h
, která načte
celý zadaný řádek včetně mezer (až do stisku klávesy Enter). Znak konce řádku není do řetězce vložen, na konec je automaticky
přidán ukončující znak '\0'
. Funkce gets
neprovádí kontrolu přetečení, zda načtený řetězec není
delší nez alokované pole. Načtení celého řádku z klávesnice do pole ukazuje následující fragment kódu:
char radek[80]; gets(radek);Nevýhoda spočívající v možnosti přetečení se odstraní použitím funkce
fgets
, která se používá pro čtení celého
řádku ze souboru. Tato funkce má za parametr maximální délku pole a hlídá přetečení. Jako identifikace souboru se používá
označení standardního vstupu stdin
. Tedy, řetězec z klávesnice do pole o alokované délce 80 znaků načteme
pomocí funkce fgets
následovně:
char radek[80]; fgets(radek,80,stdin);Funkce přečte z klávesnice řádek textu. Čte tak dlouho, dokud nenarazí na znak konce řádku nebo nepřečetla 79 (obecně n-1) znaků. Na rozdíl od gets funkce ponechá znak konce řádku v řetězci a za něj umístí ukončující znak NULL. Více v kapitole o souborech.
string.h
. Identifikátory funkcí začínají vždy předponou str,
další část identifikátoru je zkratkou operace, např. strcpy
pro kopii řetězců (jako copy),
strcmp
pro porovnání řetězců (jako compare).
V tabulce uvedeme přehled nejpoužívanějších funkcí, pro získání detailních informací odkazujeme čtenáře na manuály překladače a hlavičkový souborstring.h
. Upozorníme jen, že většina funkcí předpokládá, že všechny alokace paměti jsou provedeny před voláním funkce, tj. např. před voláním funkcestrcpy
musí být paměť pro cílový řetězec již alokována, funkce sama žádnou alokaci neprovádí.
Funkce pracující s řetězci Hlavička funkce Význam int strlen(const char *s)
Vrátí délku řetězce bez ukončujícího znaku '\0'
.char *strcpy(char *s1, const char *s2)
Zkopíruje obsah řetězce s2 do s1. Vrátí ukazatel na počátek řetězce s1 (paměť pro s1 musí být již alokována, přetečení se nekontroluje). char *strcat(char *s1, const char *s2)
Připojí obsah řetězce s2 za konec řetězce s1. Vrátí ukazatel na počátek řetězce s1 (paměť pro s1 musí být alokována po velikost řetězce po spojení, přetečení se nekontroluje). char *strchr(const char *s, char c)
Nalezení znaku c v řetězci. Pokud se znak c vyskytuje v řetězci s, pak funkce vrátí ukazatel na jeho první výskyt. V případě neúspěchu (znak v řetězci není) je vráceno NULL. int strcmp(const char *s1, const char *s2)
Porovnání dvou řetězců. Funkce vrátí 0, jsou-li oba řetězce stejné. Vrátí záporné číslo, je-li s1 lexikograficky menší než s2 a kladné číslo v opačném případě. char *strstr(const char *s1, const char *s2)
Nalezení podřetězce v řetězci. Nalezne první výskyt řetězce s2 v podřetězci s1 a vrátí pointer na tento výskyt nebo vrátí NULL v případě neúspěchu. char *strset(char *s, int c)
Nastaví všechny znaky řetězce s na hodnotu c. Pracuje, dokud nenarazí v řetězci na znak '\0'. char *strlwr(char *s)
Převede řetězec na malá písmena (bez českých znaků). Vrací ukazatel na konvertovaný řetězec. char *strupr(char *s)
Převede řetězec na velká písmena (bez českých znaků). Vrací ukazatel na konvertovaný řetězec. char *strtok(char *s1, const char *s2)
Postupně vrací ukazatele na části řetězce s1, které jsou odděleny.řetězcem s2. Tabulka 1: Funkce z knihovny string.h
Všimněte si, že v hlavičkách funkcí je formální parametr řetězec s2 deklarován jako
const char *s2
. To znamená, že funkce nemění řetězec s2.Chování funkce
strtok
(tok jako token) nejlépe vysvětlí příklad (převzatý z manuálu překladače firmy Borland):int main(void) { char input[16] = "abc,d,e"; char *p; /* odělovač je řetězec ",", při prvním volání vrátí ukazatel na počátek řetězce input, první výskyt "," je nahrazen '\0' */ p = strtok(input, ","); /* vytiskne se abc */ if (p) printf("%s\n", p); /* Druhé volání s parametrem NULL vrátí ukazatel na další část řetězce za prvním '\0', další výskyt "," je nahrazen '\0' */ p = strtok(NULL, ","); /* vytiskne d */ if (p) printf("%s\n", p); p = strtok(NULL, ","); /* vytiskne e */ if (p) printf("%s\n", p); return 0; }
V knihovně jsou také implementovány funkce, které nemusí pracovat s celým řetězcem, ale pouze s jeho částí, resp. s prvými n znaky. Přesněji, funkce ukončí svoji činnost, zpracují-li n znaků řetězce nebo narazí na ukončující znak'\0'
(je-li řetězec kratší než n znaků). Identifikátory funkcí jsou odvozeny od výše zmíněných, mají pouze uprostřed identifikátoru navíc písmeno n a také jeden parametr navíc - počet znaků n, např.:strncpy
,strncmp
atd.Za všechny uvedeme funkci:
char *strncpy(char *s1, char *s2, int n),která zkopíruje nejvýše n znaků řetězce s2 do s1, např.strncpy(str,"napodobenina",5)
zkopíruje do řetězcestr
řetězec"napod"
, dálestrncpy(str,"ahoj",7)
zkopíruje do řetězcestr
řetězec"ahoj"
.
Další množina funkcí pracuje s řetězcem od konce. Ve svém identifikátoru mají funkce uprostřed písmeno r (reverse).
Zpracovávají řetězec od ukončujícího znaku '\0' směrem k počátku,
např. int strrchr(char *s, char c)
vrátí ukazatel na poslední výskyt znaku c v řetězci s, pokud
tento znak řetězec obsahuje, jinak vrátí NULL.
Napište vlastní implementaci funkce pro kopii řetězceŘešení:
char *kopie(char *s1, const char *s2) /* kopíruje s2 do s1, vrací ukazatel na počátek s1 */ { char *ps; // pomocný ukazatel ps = s1; // do něj uschovám počátek na řetězec s1, do kterého se kopíruje while (*s2 != '\0') // dokud nenarazím na konec *s1++ = *s2++; // kopíruji znak po znaku // ukončující znak se již nezkopíroval, nesmím zapomenout jej na konec řetězce s1 přidat *s1 = '\0'; return ps; } /* implementace cyklem for a pomocí práce s poli */ char *kopie2(char *s1, const char *s2) { int i; int delka = strlen(s2); for(i=0;i<delka;i++) s1[i] = s2[i]; // kopíruji znak po znaku // ukončující znak se již nezkopíroval, nesmím zapomenout jej na konec řetězce s1 přidat s1[delka] = '\0'; return s1; } char *kopie3(char *s1, const char *s2) { int i; int delka = strlen(s2); for(i=0;i<=delka;i++) s1[i] = s2[i]; // kopíruji znak po znaku // ukončující znak se tentokrát zkopíroval return s1; } int main(int argc, char **argv) { char ret1[10]="Ahoj"; char ret2[10]; kopie(ret2,ret1); printf("%s\n",ret2); system("pause"); return 0; }V kódu ke stažení je ještě další řešení.
Dev C++: kopie.dev, kopie.c CodeBlocks: kopie.cbp, kopie.c
Napište program, který načte z klávesnice dva řetězce a vytvoří třetí řetězec (dynamickou alokací), do kterého spojí oba načtené řetězce spojkou a. Výsledný řetězec vytiskne na obrazovku. Program napište nejprve s využitím standardních funkcístrcpy
astrcat
, pak naprogramujte kopii a připojení řetězce sami pomocí cyklů jako v předešlé úloze. Oba vstupní řetězce čtěte pomocí funkcegets(char *s)
, uložte je do statických polí pro max. 80 znaků.
Příklad vstupu:
Jan NovákOdpovídající výstup:
Lucie Nováková
Jan Novák a Lucie Nováková
Řešení:
Dev C++: spojeni1.dev, spojeni1.c CodeBlocks: spojeni1.cbp, spojeni1.c Dev C++: spojeni2.dev, spojeni2.c CodeBlocks: spojeni2.cbp, spojeni2.c Dev C++: spojeni3.dev, spojeni3.c CodeBlocks: spojeni3.cbp, spojeni3.c
[Cvičení 7] | [Obsah] | [Cvičení 9] |