Sari la conținut
EduCamp

Tematică științifică · 2.3

Tipuri de date structurate

Tablourile unidimensionale — declararea, parcurgerea, copierea, inversarea, ștergerea și inserarea, permutarea circulară, vectorul de frecvență —, matricele și bordarea lor, și șirurile de caractere, cu citirea cu getline și prelucrarea caracter cu caracter, în C++ și în Python.

∗ Și la proba practică — Titularizare

Datele simple, cum sunt un număr întreg, un număr real sau un caracter, păstrează câte o singură valoare. Când o problemă prelucrează multe valori, de pildă notele tuturor elevilor unei clase, acestea se organizează într-o structură de date.

Programa cuprinde tabloul, înregistrarea și șirul de caractere.

Tablouri unidimensionale

Fiecare element se identifică prin numele tabloului și prin poziția lui, numită indice. Tabloul cu o singură dimensiune se numește vector.

Se deosebesc lungimea fizică și lungimea logică.

Lungimea logică nu poate depăși lungimea fizică. Enunțul precizează numărul maxim de elemente, de exemplu „cel mult 100 de numere”, iar vectorul se declară cu atâtea elemente.

În C++

Declararea unui vector are forma:

tip nume[nr_elemente];
ParteaCe este
tiptipul elementelor
numeidentificatorul vectorului
nr_elementeo constantă întreagă, lungimea fizică
int a[20];          // 20 de elemente de tip int
float b[10];        // 10 elemente de tip float
const int DIM = 100;
int v[DIM];         // lungimea data de o constanta simbolica

Constanta simbolică DIM se poate folosi apoi în program, de exemplu la verificarea lungimii logice.

Elementele pot primi valori chiar la declarare, printr-o listă scrisă între acolade:

int a[5] = {10, 20, 30, 40, 50};
int b[] = {10, 20, 30};       // 3 elemente, cate valori are lista
int c[5] = {10, 20};          // c[2], c[3], c[4] primesc 0
int f[10] = {0};              // toate elementele primesc 0

Dacă se inițializează toate elementele, numărul lor poate lipsi: compilatorul îl socotește din listă. Dacă lista are mai puține valori decât elemente, celelalte elemente primesc valoarea 0.

Accesul la un element se face prin nume[indice]. Indicii pornesc de la 0, deci pentru un vector cu n elemente indicele ia valori de la 0 la n − 1. Pentru vectorul a de mai sus, a[3] este al patrulea element și are valoarea 40.

În Python

Vectorului din C++ îi corespunde lista. Nu se declară și nu are lungime fizică: lista crește când i se adaugă elemente, iar lungimea ei se află cu len.

a = [10, 20, 30, 40, 50]
f = [0] * 10           # zece elemente egale cu 0
print(len(a))          # 5
print(a[3])            # 40
print(a[-1])           # 50, ultimul element

Indicii pornesc tot de la 0. Un indice negativ numără de la sfârșit: a[-1] este ultimul element. Un indice în afara listei, de exemplu a[5], oprește programul cu IndexError.

Elementele unei liste pot avea tipuri diferite, dar în problemele cu vectori se folosesc liste cu elemente de același tip.

Parcurgerea unui vector

Se face în ordinea indicilor, de la primul element la ultimul sau de la ultimul la primul, cu structura for.

În C++

void citire(int a[], int &n) {
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
}

void afisare(int a[], int n) {
    for (int i = 0; i < n; i++)
        cout << a[i] << " ";
    cout << '\n';
}

void afisareInversa(int a[], int n) {
    for (int i = n - 1; i >= 0; i--)
        cout << a[i] << " ";
    cout << '\n';
}

Un tablou transmis ca parametru nu se copiază: subprogramul primește adresa lui și lucrează pe tabloul din programul principal. De aceea citire umple vectorul a din main. Lungimea n se transmite prin referință, int &n, numai la subprogramele care o schimbă.

În Python

def citire():
    n = int(input())
    return [int(input()) for i in range(n)]

def afisare(a):
    for x in a:
        print(x, end=" ")
    print()

def afisare_inversa(a):
    for i in range(len(a) - 1, -1, -1):
        print(a[i], end=" ")
    print()

Când numerele se dau pe un singur rând, despărțite prin spații, lista se citește cu a = list(map(int, input().split())).

Operații cu vectori

Copierea. Vectorul a se copiază în vectorul b element cu element, cu același indice: b[i] ← a[i], pentru i de la 0 la n − 1.

Divizarea. Primele m elemente ale lui a trec în vectorul b, iar celelalte n − m în vectorul c. Elementul a[i], cu i ≥ m, ajunge în c[i - m].

Concatenarea. Vectorul c primește întâi cele n elemente ale lui a, apoi cele m elemente ale lui b. Elementul b[i] ajunge în c[n + i], iar c are n + m elemente.

Inversarea. Ordinea elementelor se inversează în două feluri:

  • în alt vector: b[i] ← a[n - 1 - i]; suma indicilor celor două elemente este mereu n − 1;
  • în același vector: se interschimbă elementele simetrice, a[i] cu a[n - 1 - i], numai pentru i < n / 2. Dacă parcurgerea ar continua până la capăt, fiecare pereche s-ar interschimba de două ori, iar vectorul ar reveni la forma inițială.

Ștergerea elementului de pe poziția k. Elementele de după poziția k se deplasează cu o poziție spre stânga, începând cu a[k + 1], iar lungimea logică scade cu 1.

Inserarea valorii x pe poziția k. Elementele de la poziția k până la sfârșit se deplasează cu o poziție spre dreapta, începând cu ultimul, apoi a[k] primește valoarea x, iar lungimea logică crește cu 1. Deplasarea începe de la ultimul element: pornită de la a[k], fiecare atribuire ar acoperi elementul următor înainte ca acesta să fie mutat.

Permutarea circulară. La permutarea spre stânga cu o poziție, primul element se salvează, celelalte se deplasează cu o poziție spre stânga, iar elementul salvat trece pe ultima poziție. La permutarea spre dreapta, ultimul element se salvează și trece pe prima poziție. Permutarea cu k poziții se obține repetând de k ori permutarea cu o poziție. După n permutări, vectorul revine la forma inițială.

În C++

void copiere(int a[], int n, int b[]) {
    for (int i = 0; i < n; i++)
        b[i] = a[i];
}

void divizare(int a[], int n, int m, int b[], int c[]) {
    for (int i = 0; i < m; i++)
        b[i] = a[i];
    for (int i = m; i < n; i++)
        c[i - m] = a[i];
}

void concatenare(int a[], int n, int b[], int m, int c[]) {
    for (int i = 0; i < n; i++)
        c[i] = a[i];
    for (int i = 0; i < m; i++)
        c[n + i] = b[i];
}

void inversareInAltVector(int a[], int n, int b[]) {
    for (int i = 0; i < n; i++)
        b[i] = a[n - 1 - i];
}

void inversare(int a[], int n) {
    for (int i = 0; i < n / 2; i++) {
        int aux = a[i];
        a[i] = a[n - 1 - i];
        a[n - 1 - i] = aux;
    }
}

void stergere(int a[], int &n, int k) {
    for (int i = k; i < n - 1; i++)
        a[i] = a[i + 1];
    n--;
}

void inserare(int a[], int &n, int k, int x) {
    for (int i = n; i > k; i--)
        a[i] = a[i - 1];
    a[k] = x;
    n++;
}

void permutareStanga(int a[], int n) {
    int x = a[0];
    for (int i = 1; i < n; i++)
        a[i - 1] = a[i];
    a[n - 1] = x;
}

void permutareDreapta(int a[], int n) {
    int x = a[n - 1];
    for (int i = n - 1; i > 0; i--)
        a[i] = a[i - 1];
    a[0] = x;
}

Atribuirea b = a nu copiază un vector în altul: pentru tablouri, compilatorul o semnalează ca eroare.

În Python

Operațiile au echivalente scrise direct în limbaj:

b = a[:]                 # copierea
b, c = a[:m], a[m:]      # divizarea
c = a + b                # concatenarea
b = a[::-1]              # inversarea in alta lista
a.reverse()              # inversarea in aceeasi lista
del a[k]                 # stergerea de pe pozitia k
a.insert(k, x)           # inserarea lui x pe pozitia k
a.append(a.pop(0))       # permutarea spre stanga
a.insert(0, a.pop())     # permutarea spre dreapta

Algoritmii de mai sus se pot scrie și element cu element, ca în C++. Inversarea în aceeași listă, de exemplu:

def inversare(a):
    n = len(a)
    for i in range(n // 2):
        a[i], a[n - 1 - i] = a[n - 1 - i], a[i]

Programul principal.

Pseudocod

citire(a, n)
afisareInversa(a, n)
copiere(a, n, b); afisare(b, n)
concatenare(a, n, b, n, c)
afisare(c, 2 * n)
inversare(a, n); afisare(a, n)
inversare(a, n)
stergere(a, n, 1); afisare(a, n)
inserare(a, n, 1, 20); afisare(a, n)
permutareStanga(a, n); afisare(a, n)
permutareDreapta(a, n); afisare(a, n)
divizare(a, n, 2, b, c)
afisare(b, 2); afisare(c, n - 2)

Implementare în C++

#include <iostream>
using namespace std;

const int DIM = 100;

// subprogramele de mai sus

int main() {
    int a[DIM], b[DIM], c[2 * DIM], n;

    citire(a, n);
    afisareInversa(a, n);

    copiere(a, n, b);
    afisare(b, n);
    concatenare(a, n, b, n, c);
    afisare(c, 2 * n);

    inversare(a, n);
    afisare(a, n);
    inversare(a, n);

    stergere(a, n, 1);
    afisare(a, n);
    inserare(a, n, 1, 20);
    afisare(a, n);

    permutareStanga(a, n);
    afisare(a, n);
    permutareDreapta(a, n);
    afisare(a, n);

    divizare(a, n, 2, b, c);
    afisare(b, 2);
    afisare(c, n - 2);
    return 0;
}

Implementare în Python

# citire, afisare si afisare_inversa,
# scrise ca mai sus

a = citire()
afisare_inversa(a)

b = a[:]
afisare(b)
afisare(a + b)

a.reverse()
afisare(a)
a.reverse()

del a[1]
afisare(a)
a.insert(1, 20)
afisare(a)

a.append(a.pop(0))
afisare(a)
a.insert(0, a.pop())
afisare(a)

afisare(a[:2])
afisare(a[2:])

Pentru n = 5 și valorile 10 20 30 40 50, programul scrie pe rânduri separate:

50 40 30 20 10
10 20 30 40 50
10 20 30 40 50 10 20 30 40 50
50 40 30 20 10
10 30 40 50
10 20 30 40 50
20 30 40 50 10
10 20 30 40 50
10 20
30 40 50

Vectorul de frecvență

Elementul f[v] este contorul valorii v: la fiecare apariție a lui v se execută f[v] ← f[v] + 1. Valoarea prelucrată este folosită chiar ca indice.

Pentru cifrele unui număr, vectorul are 10 elemente, câte unul pentru fiecare cifră. Pentru n = 432352 se obține f[2] = 2, f[3] = 2, f[4] = 1, f[5] = 1, iar celelalte elemente rămân 0.

În C++

void frecventaCifrelor(long long n) {
    int f[10] = {0};
    do {
        f[n % 10]++;
        n = n / 10;
    } while (n != 0);
    for (int c = 0; c <= 9; c++)
        if (f[c] != 0)
            cout << c << " apare de " << f[c] << " ori\n";
}

În Python

def frecventa_cifrelor(n):
    f = [0] * 10
    while True:
        f[n % 10] += 1
        n = n // 10
        if n == 0:
            break
    for c in range(10):
        if f[c] != 0:
            print(c, "apare de", f[c], "ori")

Cifrele nu trebuie memorate întâi într-un alt vector: fiecare cifră se numără imediat după ce a fost extrasă.

Același procedeu se aplică numerelor citite dintr-un fișier. Programul următor afișează numărul de două cifre care apare de cele mai multe ori în fișierul numere.txt. Dacă mai multe numere apar de același număr maxim de ori, se afișează cel mai mic dintre ele, fiindcă un număr mai mare îl înlocuiește numai dacă are frecvența strict mai mare. Dacă fișierul nu conține niciun număr de două cifre, se afișează 0.

În C++

#include <iostream>
#include <fstream>
using namespace std;

ifstream f("numere.txt");

int main() {
    int x, fr[100] = {0};
    while (f >> x)
        if (x >= 10 && x <= 99)
            fr[x]++;

    int maxim = 0, nr = 0;
    for (int i = 10; i <= 99; i++)
        if (fr[i] > maxim) {
            maxim = fr[i];
            nr = i;
        }
    cout << nr;
    return 0;
}

În Python

fr = [0] * 100
with open("numere.txt") as f:
    for x in map(int, f.read().split()):
        if 10 <= x <= 99:
            fr[x] += 1

maxim = 0
nr = 0
for i in range(10, 100):
    if fr[i] > maxim:
        maxim = fr[i]
        nr = i
print(nr)

Căutarea unei valori într-un vector, sortarea și interclasarea a doi vectori ordonați sunt tratate la Algoritmi de căutare, Algoritmi de sortare și Algoritmul de interclasare.

Tablouri bidimensionale

În C++

int a[101][101];    // indicii 0..100; numerotate de la 1, cel mult 100 de linii si 100 de coloane

Elementul de pe linia i și coloana j se scrie a[i][j]. Matricea se parcurge cu două structuri for imbricate: cea exterioară trece prin linii, cea interioară prin coloanele liniei curente. În problemele de examen, liniile și coloanele se numerotează de obicei de la 1.

for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
        cin >> a[i][j];

În Python

Matricea se păstrează ca listă de liste: fiecare linie este o listă. Elementul de pe linia i și coloana j se scrie a[i][j], cu indicii numărați de la 0.

n, m = map(int, input().split())
a = [list(map(int, input().split())) for i in range(n)]

Bordarea unei matrice

Enunțul stabilește ce valori primesc elementele noi și, uneori, numai pe care laturi se adaugă.

Când liniile și coloanele matricei se numerotează de la 1 la n, respectiv de la 1 la m, bordura ocupă:

  • linia 0, deasupra, și linia n + 1, dedesubt;
  • coloana 0, la stânga, și coloana m + 1, la dreapta.

Matricea se declară deci cu două linii și două coloane în plus față de dimensiunea maximă din enunț. Pentru cel mult 100 de linii și 100 de coloane, indicii iau valori de la 0 la 101, iar declararea este int a[102][102].

Bordarea cu o valoare dată. Toate elementele bordurii primesc aceeași valoare. În desen, matricea cu 3 linii și 3 coloane este bordată cu 0, iar celulele adăugate sunt colorate.

Matricea cu liniile 2 4 1, 3 0 5, 6 2 2, bordată cu 0: liniile 0 și 4 și coloanele 0 și 4 au toate elementele 0 0 1 2 3 4 0 1 2 3 4 0 0 0 0 0 0 2 4 1 0 0 3 0 5 0 0 6 2 2 0 0 0 0 0 0
Indicii liniilor sunt scriși la stânga, iar ai coloanelor deasupra. Bordura ocupă liniile 0 și n + 1 și coloanele 0 și m + 1.

O matrice bordată se folosește când fiecare element trebuie comparat cu vecinii lui de sus, de jos, din stânga și din dreapta. Elementele de pe marginea matricei inițiale au și ele acum patru vecini, deci nu mai trebuie tratate separat. Valoarea bordurii se alege astfel încât să nu schimbe rezultatul: la căutarea elementelor mai mari decât toți vecinii, dintr-o matrice de numere naturale, bordura primește −1.

Bordarea cu sume. Coloana m + 1 primește suma elementelor de pe fiecare linie, iar linia n + 1 suma elementelor din fiecare coloană. Colțul a[n+1][m+1] primește suma tuturor elementelor, care este și suma coloanei din dreapta și suma liniei de jos. Bordura este aici numai pe două laturi.

Matricea bordată cu sume: coloana 4 conține sumele liniilor, 7, 8, 10; linia 4 conține sumele coloanelor, 11, 6, 8; colțul conține suma totală, 25 1 2 3 4 1 2 3 4 2 4 1 7 3 0 5 8 6 2 2 10 11 6 8 25
Suma totală, 25, se obține și adunând sumele liniilor, și adunând sumele coloanelor.

Bordarea cu maxime și minime. Coloana 0 primește maximul fiecărei linii, iar linia 0 minimul fiecărei coloane. Colțul a[0][0] nu se folosește.

Matricea bordată cu maxime și minime: coloana 0 conține maximele liniilor, 4, 5, 6; linia 0 conține minimele coloanelor, 2, 0, 1; colțul din stânga sus nu se folosește 0 1 2 3 0 1 2 3 2 0 1 4 2 4 1 5 3 0 5 6 6 2 2
Pe linia 1, maximul este 4; în coloana 2, minimul este 0. Colțul cu contur punctat nu primește nicio valoare.

În C++

Un tablou bidimensional transmis ca parametru se scrie cu a doua dimensiune precizată, int a[][DIM]: compilatorul are nevoie de ea ca să afle unde începe fiecare linie.

#include <iostream>
using namespace std;

const int DIM = 102;

void bordeaza(int a[][DIM], int n, int m, int v) {
    for (int j = 0; j <= m + 1; j++)
        a[0][j] = a[n + 1][j] = v;
    for (int i = 1; i <= n; i++)
        a[i][0] = a[i][m + 1] = v;
}

void bordeazaSume(int a[][DIM], int n, int m) {
    for (int j = 1; j <= m + 1; j++)
        a[n + 1][j] = 0;
    for (int i = 1; i <= n; i++) {
        a[i][m + 1] = 0;
        for (int j = 1; j <= m; j++) {
            a[i][m + 1] += a[i][j];
            a[n + 1][j] += a[i][j];
        }
        a[n + 1][m + 1] += a[i][m + 1];
    }
}

void bordeazaMaxMin(int a[][DIM], int n, int m) {
    for (int i = 1; i <= n; i++) {
        a[i][0] = a[i][1];
        for (int j = 2; j <= m; j++)
            if (a[i][j] > a[i][0])
                a[i][0] = a[i][j];
    }
    for (int j = 1; j <= m; j++) {
        a[0][j] = a[1][j];
        for (int i = 2; i <= n; i++)
            if (a[i][j] < a[0][j])
                a[0][j] = a[i][j];
    }
}

int varfuri(int a[][DIM], int n, int m) {
    int nr = 0;
    bordeaza(a, n, m, -1);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (a[i][j] > a[i - 1][j] && a[i][j] > a[i + 1][j] &&
                a[i][j] > a[i][j - 1] && a[i][j] > a[i][j + 1])
                nr++;
    return nr;
}

void afisare(int a[][DIM], int i1, int i2, int j1, int j2) {
    for (int i = i1; i <= i2; i++) {
        for (int j = j1; j <= j2; j++)
            cout << a[i][j] << " ";
        cout << '\n';
    }
    cout << '\n';
}

int main() {
    int a[DIM][DIM], n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> a[i][j];

    bordeaza(a, n, m, 0);
    afisare(a, 0, n + 1, 0, m + 1);

    bordeazaSume(a, n, m);
    afisare(a, 1, n + 1, 1, m + 1);

    bordeazaMaxMin(a, n, m);
    afisare(a, 0, n, 0, m);

    cout << varfuri(a, n, m) << " varfuri";
    return 0;
}

Subprogramul afisare primește limitele liniilor și ale coloanelor, deci aceeași matrice se poate afișa cu bordura întreagă sau numai cu laturile completate.

În Python

O listă de liste nu are linii și coloane rezervate dinainte. Matricea bordată se construiește ca o listă nouă: liniile vechi primesc câte un element la capete, iar deasupra și dedesubt se adaugă câte o linie întreagă.

def bordeaza(a, v):
    m = len(a[0])
    return ([[v] * (m + 2)] +
            [[v] + linie + [v] for linie in a] +
            [[v] * (m + 2)])

def bordeaza_sume(a):
    b = [linie + [sum(linie)] for linie in a]
    b.append([sum(coloana) for coloana in zip(*b)])
    return b

def bordeaza_max_min(a):
    sus = [0] + [min(coloana) for coloana in zip(*a)]
    return [sus] + [[max(linie)] + linie for linie in a]

def varfuri(a):
    b = bordeaza(a, -1)
    nr = 0
    for i in range(1, len(a) + 1):
        for j in range(1, len(a[0]) + 1):
            if b[i][j] > max(b[i - 1][j], b[i + 1][j],
                             b[i][j - 1], b[i][j + 1]):
                nr += 1
    return nr

def afisare(a):
    for linie in a:
        print(*linie)
    print()

n, m = map(int, input().split())
a = [list(map(int, input().split())) for i in range(n)]
afisare(bordeaza(a, 0))
afisare(bordeaza_sume(a))
afisare(bordeaza_max_min(a))
print(varfuri(a), "varfuri")

zip(*a) grupează elementele matricei pe coloane: primul grup conține primul element al fiecărei linii, al doilea grup al doilea element și așa mai departe.

Pentru matricea din desene, programul scrie cele trei matrice bordate, apoi 3 varfuri: 4, 5 și 6 sunt mai mari decât toți vecinii lor.

Șirurile de caractere în memorie

În C++

Un șir de caractere este o succesiune de caractere încheiată cu caracterul NULL, adică cel cu codul ASCII 0, scris '\0'.

Limbajul nu are un tip aparte pentru șiruri: ele se păstrează în vectori de caractere. De aici decurge tot ce urmează: un șir este un vector, iar sfârșitul lui nu e dat de o lungime memorată undeva, ci de primul caracter NULL întâlnit.

Cum se păstrează în memorie:

  • fiecare caracter ocupă un octet;
  • în octet se păstrează codul ASCII al caracterului;
  • caracterele sunt memorate unul după altul, în ordinea din șir: primul pe indicele 0, al doilea pe indicele 1 și așa mai departe;
  • după ultimul caracter urmează caracterul NULL.

Cuvântul informatica are 11 litere, deci ocupă 12 octeți:

Vectorul a, cu 12 octeți: pe indicii 0–10 literele cuvântului informatica, cu codurile lor ASCII, de la 105 la 97; pe indicele 11, caracterul NULL, cu codul 0 indice a cod ASCII 0 105 1 110 2 102 3 111 4 114 5 109 6 97 7 116 8 105 9 99 10 97 11 0 i n f o r m a t i c a \0
Fiecare octet păstrează codul ASCII al unui caracter. Pe indicele 11 se află caracterul NULL, cu codul 0: el arată unde se termină șirul.
char a[] = "informatica";
cout << sizeof(a);     // 12: octetii ocupati, cu tot cu caracterul NULL
cout << strlen(a);     // 11: caracterele sirului, fara caracterul NULL
cout << (int) a[0];    // 105: codul ASCII al literei i

Vectorul poate avea mai multe poziții decât îi trebuie șirului. Octeții de după caracterul NULL sunt rezervați, dar nu fac parte din șir, iar conținutul lor nu contează:

Vectorul s, cu 10 octeți: pe indicii 0–3 literele m, e, r, e, pe indicele 4 caracterul NULL, iar indicii 5–9 sunt rezervați, dar nefolosiți indice s cod ASCII 0 109 1 101 2 114 3 101 4 0 5 6 7 8 9 m e r e \0
Declarația char s[10] = "mere"; rezervă 10 octeți. Șirul ocupă 5 dintre ei, cu tot cu caracterul NULL; ceilalți cinci, cu contur punctat, nu fac parte din șir.

Numele vectorului este o adresă

Numele unui vector de caractere este adresa primului lui octet. De aici urmează:

  • a + k este adresa octetului de pe indicele k; folosit ca șir, a + k este partea din a care începe pe poziția k și se termină la caracterul NULL;
  • *(a + k) și a[k] sunt același caracter;
  • o adresă se poate păstra într-o variabilă de tip char *: atribuirea p = a este corectă, iar a = p nu este, fiindcă numele vectorului este o adresă constantă;
  • diferența dintre două adrese din același vector este un număr întreg: p - a este indicele octetului spre care arată p.
Vectorul a cu cuvântul informatica; trei săgeți arată spre octeți: a spre indicele 0, a + 2 spre indicele 2, p spre indicele 5, unde se află litera m indice a 0 1 2 3 4 5 6 7 8 9 10 11 a a + 2 p i n f o r m a t i c a \0
Numele vectorului este adresa primului octet. a + 2 este adresa octetului de pe indicele 2, iar p = strchr(a, 'm') este adresa literei m, de pe indicele 5. Diferența p - a este 5, adică indicele.
char a[] = "informatica";
cout << a + 2;              // formatica
char *p = strchr(a, 'm');   // adresa literei m
cout << p;                  // matica
cout << p - a;              // 5, indicele literei m

Pe aceeași regulă lucrează funcțiile din <cstring> care primesc sau întorc o adresă: strcpy(t, a + 2) copiază în t șirul formatica, iar poziția găsită de strchr se află cu strchr(a, c) - a.

Pointerul la caracter

Variabila care păstrează adresa unui caracter se numește pointer la caracter. Se declară cu steluță în fața numelui:

char *p;

Declarația se citește: p păstrează adresa unei variabile de tip char. Steluța se aplică variabilei, nu tipului: în char *x, y; numai x este pointer, iar y este un caracter.

Ce se face cu un pointer la caracter:

  • primește o adresă: p = a; îi dă adresa lui a[0], iar p = &a[2]; adresa lui a[2]; operatorul & dă adresa unei variabile;
  • *p este caracterul de la adresa păstrată în p; operatorul * scris în fața pointerului dă valoarea de la adresă, care se poate și schimba: *p = 'l' schimbă caracterul spre care arată p;
  • cout << p afișează șirul care începe la acea adresă, până la caracterul NULL, iar cout << *p afișează un singur caracter;
  • p++ trece la caracterul următor, iar p + k este adresa caracterului aflat cu k poziții mai departe;
  • funcțiile care caută, ca strchr, strstr și strtok, întorc un pointer la caracter, iar când nu găsesc nimic întorc NULL; de aceea rezultatul lor se verifică înainte de folosire.
Vectorul a cu mere și caracterul NULL; dedesubt variabila p, care conține o adresă; săgeata arată spre a[2], litera r indice a 0 1 2 3 4 p *p este 'r' m e r e \0 adresă
După p = &a[2]; variabila p păstrează adresa lui a[2]. *p este caracterul de la acea adresă, r; cout << p afișează șirul care începe acolo, re.
char a[] = "mere";
char *p;
p = a;
cout << *p;     // m
cout << p;      // mere
p = &a[2];
cout << *p;     // r
cout << p;      // re
*p = 'l';
cout << a;      // mele

Un șir se poate parcurge și cu un pointer, în locul indicelui:

int n = 0;
for (p = a; *p != '\0'; p++)
    n++;        // la sfarsit, n este lungimea sirului

Un caracter NULL pus în mijlocul șirului

Afișarea, strlen și celelalte funcții se opresc la primul caracter NULL întâlnit. Dacă unei poziții din mijloc i se atribuie '\0', șirul se scurtează, deși caracterele de după ea rămân în memorie:

char a[] = "informatica";
a[6] = '\0';
cout << a;            // inform
cout << strlen(a);    // 6
cout << a + 7;        // tica
Vectorul a după atribuirea a[6] = caracterul NULL: pe indicele 6 se află acum NULL, deci șirul este inform; literele t, i, c, a și al doilea NULL au rămas în memorie indice a cod ASCII 0 105 1 110 2 102 3 111 4 114 5 109 6 0 7 116 8 105 9 99 10 97 11 0 i n f o r m \0 t i c a \0
După a[6] = '\0'; șirul se termină la primul caracter NULL: cout << a afișează inform, iar strlen(a) este 6. Literele de după rămân în memorie; cout << a + 7 afișează tica.

Tot așa lucrează strtok: scrie caracterul NULL în locul separatorului de după fiecare cuvânt.

Vector de caractere și adresă de șir

char t[] = "Salut C!";         // vector: o copie a textului, 9 octeti
const char *b = "Salut C!";    // adresa unei constante sir
Sus, vectorul t, cu 9 octeți care conțin Salut C! și caracterul NULL. Jos, variabila b, care conține numai o adresă; săgeata arată spre constanta Salut C!, păstrată în altă zonă de memorie indice t 0 1 2 3 4 5 6 7 8 b constanta șir, în altă zonă de memorie S a l u t C ! \0 adresă S a l u t C ! \0
t este un vector de 9 octeți, cu propria copie a textului: sizeof(t) este 9. b păstrează numai adresa constantei: sizeof(b) este mărimea unei adrese, 4 sau 8 octeți, după compilator.

Deosebirile:

  • t are octeții lui, iar caracterele se pot schimba: t[0] = 's' este corectă;
  • b păstrează numai o adresă; constanta spre care arată nu se poate modifica, de aceea se declară const char *;
  • sizeof(t) este 9, iar sizeof(b) este mărimea unei adrese, 4 sau 8 octeți, după compilator.

Vectorul de cuvinte

Mai multe cuvinte se păstrează într-o matrice de caractere, câte un cuvânt pe linie:

char c[3][8];
strcpy(c[0], "ana");
strcpy(c[1], "are");
strcpy(c[2], "mere");
cout << c[2];        // mere
cout << c[2][1];     // e
Matricea c, cu 3 linii și 8 coloane: linia 0 conține ana, linia 1 are, linia 2 mere, fiecare urmat de caracterul NULL; restul celulelor sunt nefolosite 0 1 2 3 4 5 6 7 c[0] c[1] c[2] a n a \0 a r e \0 m e r e \0
Fiecare linie a matricei păstrează un cuvânt de cel mult 7 caractere, plus caracterul NULL. c[2] este cuvântul mere, iar c[2][1] este litera e.

c[i] este adresa liniei i, deci se folosește oriunde se cere un șir: în cout, în strcpy, în strcmp. O linie cu 8 coloane păstrează un cuvânt de cel mult 7 caractere, fiindcă o poziție rămâne caracterului NULL.

Materialul acesta se citește pe educamp.ro și nu se tipărește.

Ce cuprinde subcapitolul

  1. Tablouri unidimensionale
  2. Parcurgerea unui vector
  3. Operații cu vectori
  4. Vectorul de frecvență
  5. Tablouri bidimensionale
  6. Șirurile de caractere în memorie
  7. Declararea și inițializarea șirurilor
  8. Citirea șirurilor de caractere
  9. Afișarea șirurilor de caractere
  10. Prelucrarea caracter cu caracter
  11. Funcții pentru șiruri de caractere
  12. Înregistrarea
  13. Apariții la examen

Continuă lectura ca și cursant

Cel puțin un subcapitol din fiecare capitol este disponibil gratuit și integral. Pentru a citi toate celelalte subcapitole ale disciplinei, te înscrii la cursul de pregătire.

Te înscrii, alegi în cont când începe cursul și plătești prima lună prin transfer bancar. Pentru a vedea mai întâi cum este prezentată materia, poți reveni la primul subcapitol al capitolului.

Acces la suportul de curs

25 RON / lună, fără ședințe

Ce cuprinde:

  • Acces la toate lecțiile disponibile
  • Suport de curs pentru tematica științifică și metodică
  • Exemple, explicații și exerciții
  • Acces la materialele noi adăugate pe platformă
  • Posibilitatea de a adresa întrebări

Poți alege și variante cu ședințe online, în grup sau individuale. Le vezi pe pagina cursului.

Ai întrebări sau o problemă? Scrie-mi pe WhatsApp, la 0745 874 576.

Înscrie-te Alte varianteCum plătești Login

Surse (3)
  • Cerchez, E., Șerban, M., „Programarea în limbajul C/C++ pentru liceu”, Editura Polirom, Iași, 2005
  • „Informatică. Manual pentru clasa a XI-a”, Editura Didactică și Pedagogică, București
  • „Tutorialul de Python”, documentația oficială a limbajului Python, https://docs.python.org/ro/3/tutorial/index.html
Actualizat: 20 octombrie 2026