Programare Competitivă

Șiruri de caractere

Până acum am lucrat cu numere. Dar în multe probleme trebuie să prelucrăm text: cuvinte, propoziții, nume.

În C++, un text se stochează ca un șir de caractere - un vector de tip char terminat cu un caracter special.


Ce este un caracter?

Un caracter este o singură literă, cifră sau simbol. În C++ îl declarăm cu tipul char și îl scriem între apostrofuri:

char c = 'A';
char cifra = '5';
char spatiu = ' ';
char newline = '\n';

Fiecare caracter are un cod numeric (codul ASCII). Câteva exemple:

Caracter Cod ASCII
'A' 65
'B' 66
'Z' 90
'a' 97
'z' 122
'0' 48
'9' 57
' ' (spațiu) 32

Observații importante: literele mari (A-Z) au codurile 65-90, literele mici (a-z) au codurile 97-122. Diferența între o literă mică și litera mare corespunzătoare este mereu 32: 'a' - 'A' = 32.


Operații pe caractere

Deoarece caracterele sunt de fapt numere (coduri ASCII), putem face operații cu ele:

char c = 'A';
c = c + 1;      // c devine 'B' (65 + 1 = 66)

Verificare: este literă mare?

if (c >= 'A' && c <= 'Z') {
    // c este literă mare
}

Verificare: este cifră?

if (c >= '0' && c <= '9') {
    // c este cifră
}

Conversie majusculă - minusculă

char mare = 'A';
char mica = mare + 32;  // 'a'

char mica2 = 'g';
char mare2 = mica2 - 32; // 'G'
De ce 32?

În tabelul ASCII, literele mari și mici sunt aranjate în aceeași ordine, dar la o distanță de 32:

  • 'A' = 65, 'a' = 97, diferența = 32
  • 'B' = 66, 'b' = 98, diferența = 32
  • …și tot așa

Deci: literă_mică = literă_mare + 32 și literă_mare = literă_mică - 32.

O altă formă, mai elegantă: literă_mică = literă_mare + ('a' - 'A').


Ce este un șir de caractere?

Un șir de caractere (string) este un vector de char terminat cu caracterul special '\0' (null terminator, codul ASCII 0).

char salut[10] = "Buna";

În memorie arată așa:

indice:  0    1    2    3    4    5    6   ...
       +----+----+----+----+----+----+----+
       | B  | u  | n  | a  | \0 |    |    |
       +----+----+----+----+----+----+----+
  • Cuvântul "Buna" are 4 caractere vizibile
  • Pe poziția 4 este '\0' care marchează sfârșitul șirului
  • Pozițiile 5, 6, … nu sunt folosite

'\0' este esențial. Fără el, programul nu știe unde se termină textul.


Declararea șirurilor

char s[101];          // șir gol, poate stoca până la 100 de caractere + '\0'
char s[] = "Salut";   // compilatorul calculează dimensiunea automat (6: 5 + '\0')
char s[20] = "Test";   // 20 de poziții, doar primele 5 folosite

Dimensiunea: dacă vrem să stocăm maxim n caractere, declarăm vectorul de n + 1 poziții (pentru '\0').


Citirea și afișarea

Citirea unui cuvânt (fără spații)

#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");

char s[101];

int main()
{
    fin >> s;
    fout << s;

    return 0;
}

fin >> s citește caractere până la primul spațiu sau sfârșit de linie.

date.in:

Informatica

date.out:

Informatica

Citirea unei linii întregi (cu spații)

fin.getline(s, 101);

getline citește toată linia, inclusiv spațiile, până la '\n' sau până la limita dată (101).

#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");

char s[101];

int main()
{
    fin.getline(s, 101);
    fout << s;

    return 0;
}

date.in:

Salut lume frumoasa

date.out:

Salut lume frumoasa

Lungimea unui șir

Lungimea se calculează numărând caracterele până la '\0':

char s[] = "Algoritm";
int lung = 0;

while (s[lung] != '\0') {
    lung++;
}

fout << lung; // 8

Parcurgerea unui șir

Parcurgem caracter cu caracter, de la 0 până la '\0':

for (int i = 0; s[i] != '\0'; i++) {
    fout << s[i] << " ";
}

Exemplu: pentru "ABC" afișează: A B C


Probleme clasice cu șiruri

Problema 1: câte vocale are un cuvânt?

#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");

char s[101];

int main()
{
    fin >> s;

    int cnt = 0;

    for (int i = 0; s[i] != '\0'; i++) {
        char c = s[i];
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
            c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
            cnt++;
        }
    }

    fout << cnt;

    return 0;
}

date.in:

Informatica

date.out:

5

Problema 2: inversarea unui șir

Interschimbăm primul caracter cu ultimul, al doilea cu penultimul, etc.

#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");

char s[101];

int main()
{
    fin >> s;

    // Aflăm lungimea
    int lung = 0;
    while (s[lung] != '\0') lung++;

    // Inversăm
    int st = 0, dr = lung - 1;

    while (st < dr) {
        char aux = s[st];
        s[st] = s[dr];
        s[dr] = aux;
        st++;
        dr--;
    }

    fout << s;

    return 0;
}

date.in:

algoritm

date.out:

mtirogla

Problema 3: este palindrom?

Un cuvânt este palindrom dacă citit invers este identic.

#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");

char s[101];

int main()
{
    fin >> s;

    int lung = 0;
    while (s[lung] != '\0') lung++;

    int palindrom = 1;
    int st = 0, dr = lung - 1;

    while (st < dr) {
        if (s[st] != s[dr]) {
            palindrom = 0;
        }
        st++;
        dr--;
    }

    if (palindrom == 1) {
        fout << "DA";
    } else {
        fout << "NU";
    }

    return 0;
}

date.in:

abacaba

date.out:

DA

Problema 4: transformare în majuscule

for (int i = 0; s[i] != '\0'; i++) {
    if (s[i] >= 'a' && s[i] <= 'z') {
        s[i] = s[i] - 32;
    }
}

Problema 5: numărarea cuvintelor dintr-o propoziție

Cuvintele sunt separate prin spații. Numărăm tranzițiile de la spațiu la non-spațiu.

#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");

char s[1001];

int main()
{
    fin.getline(s, 1001);

    int cnt = 0;

    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] != ' ' && (i == 0 || s[i - 1] == ' ')) {
            cnt++;
        }
    }

    fout << cnt;

    return 0;
}

date.in:

Ana are mere multe

date.out:

4

Cum funcționează?

Un cuvânt nou începe când: - Caracterul curent nu este spațiu (s[i] != ' ') - Și fie suntem la începutul șirului (i == 0), fie caracterul anterior era spațiu (s[i-1] == ' ')


Compararea a două șiruri

Nu putem compara șiruri cu == (aceasta compară adresele, nu conținutul). Trebuie comparate caracter cu caracter:

char a[] = "abc";
char b[] = "abd";

int i = 0;
while (a[i] != '\0' && b[i] != '\0' && a[i] == b[i]) {
    i++;
}

if (a[i] == b[i]) {
    fout << "Egale";
} else if (a[i] < b[i]) {
    fout << "a < b (ordine lexicografica)";
} else {
    fout << "a > b";
}

Compararea se face lexicografic (ca în dicționar): se caută primul caracter diferit, iar cel cu codul ASCII mai mic este “mai mic”.


Greșeli frecvente

1. Depășirea vectorului

char s[5] = "Salut"; // GREȘIT! "Salut" are 5 caractere + '\0' = 6
char s[6] = "Salut"; // CORECT

2. Compararea cu ==

if (s == "test") // GREȘIT - compară adresele, nu conținutul

Trebuie comparat caracter cu caracter (sau cu strcmp din cstring, vezi lecția următoare).


3. Uitarea lui '\0'

Dacă construim un șir manual (caracter cu caracter), trebuie să punem '\0' la final:

char s[10];
s[0] = 'A';
s[1] = 'B';
s[2] = '\0'; // OBLIGATORIU!

Fără '\0', funcțiile care lucrează cu șiruri nu știu unde se termină textul.


4. fin >> s nu citește spații

Dacă intrarea este "Ana are", fin >> s citește doar "Ana". Pentru toată linia, folosim fin.getline(s, dimensiune).


Ce să reții

  • Un caracter (char) este o literă/cifră/simbol cu un cod ASCII numeric.
  • Un șir de caractere este un vector de char terminat cu '\0'.
  • Dimensiunea vectorului trebuie să fie cu 1 mai mare decât lungimea maximă a textului.
  • Parcurgem cu for (int i = 0; s[i] != '\0'; i++).
  • fin >> s citește un cuvânt, fin.getline(s, dim) citește o linie întreagă.
  • Caracterele sunt numere - putem face operații aritmetice și comparații pe ele.
  • Nu comparăm șiruri cu == - comparăm caracter cu caracter.
  • Mereu punem '\0' la finalul unui șir construit manual.