Lucrul cu Șiruri de Caractere

Iulian Oleniuc

🧊 Tipul char — Recapitulare

  • Tipul char este folosit pentru stocarea caracterelor.
  • Caracterele ce pot fi reprezentate pe tipul char sunt cele 27=1282^7 = 128 din codul ASCII.

ascii

  • În mod intern, tipul char stochează de fapt întregi (coduri ASCII, nu caractere propriu-zise).
  • Fiind un tip întreg cu semn, pe 88 biți, char stochează valori cuprinse între 128-128 și 127127 (în total 28=2562^8 = 256), însă pe noi ne interesează doar cele pozitive.

Operații pe Tipul char

  • În codul ASCII, literele ocupă poziții consecutive, în ordinea dată de alfabet.
  • Acest lucru este valabil atât pentru cele mici, cât și pentru cele mari; inclusiv pentru cifre.
  • Astfel, putem efectua adunări, scăderi și comparații.
'c' < 'p'       // true
'F' > 'X'       // false
'd' + 3         // 'g'
'E' - 'A' + 'a' // 4 + 'a' == 'e'
  • Operațiile aritmetice ce implică un char transformă automat rezultatul într-un int.
  • Prin urmare, ocazional, la afișări, vom avea nevoie de o conversie explicită la char.
cout << 'A' + 3 << '\n';       // 68
cout << char('A' + 3) << '\n'; // D

🏹 Pointeri — Noțiuni de Bază

  • Pointerii sunt un tip (de tipuri) de date special, ce stochează adrese de memorie către variabile de diverse tipuri.
  • Orice tip de date obișnuit T are asociat un tip de date pointer T* ce stochează adrese către variabile de tipul T.
  • Un pointer poate să nu indice către nicio variabilă, caz în care are valoarea NULL.

  • Operatorul & se numește operatorul de referențiere și returnează un pointer către o variabilă dată (nu neapărat obișnuită, poate fi și ea la rândul ei pointer).
  • Operatorul * se numește operatorul de dereferențiere și returnează variabila de la o adresă dată.
int var, *ptr;
var = 618;
ptr = &var;
cout << *ptr << '\n';           // 618
var = 314;
cout << *ptr << '\n';           // 314
*ptr = 111;
cout << var << '\n';            // 111
cout << (&*ptr == ptr) << '\n'; // 1
  • Motivația inițială din spatele pointerilor:
    1. Abilitatea de a modifica variabile stocate în afara funcției curente.
    2. Lucrul cu variabile alocate dinamic.
  • În continuare, pe noi ne va interesa legătura dintre pointeri și vectori, chiar dacă C++ ne permite să facem chestii mult mai complexe cu ajutorul pointerilor.

meme

Legătura dintre Pointeri și Vectori

  • În cod, numele unui vector este de fapt un pointer constant către primul element din vectorul respectiv.
int vec[] = {6, 1, 8, 3, 1, 4}, var = 10;
cout << *vec << '\n';   // 6
*vec = 2;
cout << vec[0] << '\n'; // 2
vec = &var;             // eroare
  • La un pointer p de tipul T*, putem aduna (sau scădea) un scalar întreg i.
  • Rezultatul va fi un pointer către zona de memorie aflată cu i poziții de tipul T (deci i * sizeof(T) bytes) în fața (sau spatele) lui p.
  • Având în vedere că vectorii sunt secvențe continue de memorie, deducem că vec[i] este de fapt doar un alias pentru *(vec + i).
int vec[] = {6, 1, 8, 3, 1, 4}, *ptr;
ptr = vec;
cout << *ptr << '\n';       // 6
cout << *(ptr + 2) << '\n'; // 8
*(ptr + 3) = 7;
cout << vec[3] << '\n';     // 7
cout << *(vec + 3) << '\n'; // 7

🦕 Șiruri de Caractere în C

  • În C, un șir de caractere este o secvență de variabile de tip char, situate pe poziții consecutive în memorie (deci care, adesea, formează un întreg vector), terminată prin caracterul \0 (cu codul ASCII 0).
char str1[100] = "InfoGym";
char str2[] = "InfoGym";           // 7 + 1 == 8 elemente
cout << (str2[7] == '\0') << '\n'; // 1

Citire și Afișare

  • Pentru șirurile ce nu conțin caractere albe, folosim operatorii >> și <<.
cin >> str;
cout << str;
  • Pentru a citi o linie întreagă, fără stocarea caracterului \n de la final, folosim funcția getline.
cin.getline(str, maxLen + 1);
  • Reamintim modalitățile de citire a unui caracter.
cin >> chr;   // se sare peste caracterele albe
cin.get(chr); // se citește următorul caracter în chr
cin.get();    // se sare peste următorul caracter

Funcții Predefinite

  • Biblioteca <cstring> ne pune la dispoziție diverse funcții pentru a efectua operații elementare pe șiruri de caractere.

1. Funcția strlen

int strlen(const char* str);
  • Funcția strlen returnează lungimea șirului str.
  • Complexitatea este O(n)\mathcal{O}(n), deoarece se parcurg caracterele lui str până la întâlnirea lui \0.
char str[] = "C++";
cout << strlen(str) << '\n';      // 3
cout << strlen(str + 2) << '\n';  // 1
cout << strlen("string") << '\n'; // 6

Întrebare: De ce nu este OK să parcurgem un șir în modul următor?

for (int i = 0; i < strlen(str); i++)

2. Funcția strcpy

char* strcpy(char* dst, const char* src);
  • Funcția strcpy copiază conținutul șirului src în șirul dst și returnează un pointer către rezultat.
char a[10], b[10] = "InfoGym";
strcpy(a, b + 4);
cout << a << '\n'; // Gym

Întrebare: De ce nu putem folosi operatorul = pentru copierea unui șir în alt șir?

  • Cazul în care cei doi parametri pointează către zone din același vector poate produce unexpected behavior. Așadar, nu putem șterge o secvență dintr-un șir ca în primul exemplu, ci ca în al doilea.
strcpy(str + 3, str + 10);
strcpy(tmp, str + 10);
strcpy(str + 3, tmp);

3. Funcția strcat

char* strcat(char* dst, const char* src);
  • Funcția strcat concatenează șirurile dst și src, stocând rezultatul în dst, și returnează un pointer către acesta.
char a[] = "Info", b[] = "Gym";
strcat(a, b);
cout << a << '\n'; // InfoGym

4. Funcția strcmp

int strcmp(const char* str1, const char* str2);
  • Funcția strcmp compară șirurile str1 și str2 din punct de vedere lexicografic, returnând un număr negativ pentru mai mic, un număr pozitiv pentru mai mare și 0 pentru egal.
cout << (strcmp("interval", "integral") > 0) << '\n'; // 1 ('r' > 'g')
cout << (strcmp("C", "C++") < 0) << '\n';             // 1 ('\0' < '+')
cout << !strcmp("string", "string") << '\n';          // 1

5. Funcția strchr

char* strchr(const char* src, char chr);
  • Funcția strchr returnează un pointer către prima apariție a caracterului chr în șirul src, sau NULL dacă aceasta nu există.
char str[] = "calculator";
cout << (strchr(str, 'l') - str == 2) << '\n'; // 1
cout << !strchr(str, 'x') << '\n';             // 1
cout << strchr(str, 'o') << '\n';              // or

6. Funcția strstr

char* strstr(const char* src, const char* str);
  • Funcția strstr returnează un pointer către prima apariție a șirului str în șirul src, sau NULL dacă aceasta nu există.
char *ptr = strstr(a, b);
while (ptr) {
    cout << ptr - a << '\n';
    ptr = strstr(ptr + 1, b);
}

7. Funcția strtok

char* strtok(char* str, const char* sep);
  • Funcția strtok este folosită pentru împărți șirul str în tokeni, adică în subșiruri separate prin caractere din șirul de separatori sep.
  • Mai precis, strtok pune \0 pe fiecare poziție din str ce conține un separator și returnează, pe rând, câte un pointer la fiecare token obținut, sau NULL dacă aceștia s-au terminat.
char str[] = "Cee, asta a fost totul despre string-uri?";
char *ptr = strtok(str, " ,.?!");
while (ptr) {
    cout << ptr << '\n';
    ptr = strtok(NULL, " ,.?!");
}
// Cee
// asta
// a
// fost
// totul
// despre
// string-uri

🦖 Șiruri de Caractere în C++

  • În C++, pe lângă șirurile de caractere din C, le putem folosi și pe cele din STL: obiectele de tipul std::string din biblioteca <string>.
string a = "Info";
cout << a.length() << '\n';     // 4
cout << a[3] << '\n';           // f
a += "Gym 7-8";
cout << a << '\n';              // InfoGym 7-8
cout << a.substr(4, 3) << '\n'; // Gym
string b = "Inginer";
cout << (a < b) << '\n';        // 1

Întrebare: De ce codul de mai jos nu compilează?

string str = "abc" + "def";
  • String-urile din STL sunt deosebit de utile atunci când trebuie, spre exemplu, să sortăm o listă de string-uri, căci putem scrie pur și simplu sort(vec, vec + len), unde vec este un vector de tipul string[].

Întrebare: Cum putem sorta o listă de string-uri C-style folosind funcția std::sort?

  • Singurul mic neajuns al string-urilor din STL este lipsa unei funcții echivalente cu strtok.

  • Aici ne ajută expresia str.c_str(), care returnează un pointer către reprezentarea C-style a lui str (da, cu tot cu terminatorul nul).

🧠 Probleme

  1. Anagrame
  2. FormNr
  3. Dubluri
  4. Eliminare
  5. ÎnlocuireCuvânt
  6. SortareCuvinte

  1. Litere
  2. Masca
  3. Rețeta

📚 Resurse

  1. Șiruri de caractere în C++
  2. Probleme simple cu șiruri de caractere în C++
  3. Referință string-uri C-style
  4. Referință string-uri STL