Matrice dinamice
Pe lângă obiectele dinamice individuale, în limbajul C++ putem folosi și matrici dinamice. Pentru alocarea memoriei unei matrici dinamice se folosește tot operatorul new, urmat de numărul de elemente ale matricii între paranteze drepte:
int *numbers {new int[4]}; // matrice dinamică cu 4 numere
// sau așa
// int *numbers = new int[4];
În acest caz, operatorul new returnează tot un pointer către un obiect de tip int – primul element din matricea creată.
Aici este definită o matrice cu patru elemente de tip int, dar fiecare dintre ele are o valoare nedefinită. Totuși, putem inițializa și matricea cu anumite valori:
int *numbers1 {new int[4]{}}; // matrice formată din numerele 0, 0, 0, 0
int *numbers2 {new int[4]{ 1, 2, 3, 4 }}; // matrice formată din numerele 1, 2, 3, 4
int *numbers3 {new int[4]{ 1, 2 }}; // matrice formată din numerele 1, 2, 0, 0
// definiții echivalente de matrici
// int *numbers1 = new int[4]{}; // matrice formată din numerele 0, 0, 0, 0
// int *numbers1 = new int[4](); // matrice formată din numerele 0, 0, 0, 0
// int *numbers2 = new int[4]{ 1, 2, 3, 4 }; // matrice formată din numerele 1, 2, 3, 4
// int *numbers3 = new int[4]{ 1, 2 }; // matrice formată din numerele 1, 2, 0, 0
Când se inițializează o matrice cu anumite valori, trebuie reținut că dacă valorile din acolade sunt mai multe decât lungimea matricii, operatorul new va eșua și nu va putea crea matricea. Dacă valorile transmise sunt mai puține, elementele fără valori vor fi inițializate cu valoarea implicită.
Merită menționat că în standardul C++20 a fost adăugată posibilitatea deducerii dimensiunii matricii, așa că dacă se folosește C++20, lungimea matricii poate fi omisă:
int *numbers {new int[]{ 1, 2, 3, 4 }}; // matrice formată din numerele 1, 2, 3, 4
După crearea unei matrici dinamice putem lucra cu ea prin pointerul obținut, accesând și modificând elementele sale:
int *numbers {new int[4]{ 1, 2, 3, 4 }};
// accesarea elementelor prin sintaxa de matrice
std::cout << numbers[0] << std::endl; // 1
std::cout << numbers[1] << std::endl; // 2
// accesarea elementelor prin dereferențiere
std::cout << *numbers << std::endl; // 1
std::cout << *(numbers+1) << std::endl; // 2
Pentru accesarea elementelor unei matrici dinamice putem folosi atât sintaxa de matrice (numbers[0]), cât și operația de dereferențiere (*numbers)
Pentru parcurgerea unei astfel de matrici se pot folosi mai multe metode:
unsigned n{ 5 }; // dimensiunea matricii
int* p{ new int[n] { 1, 2, 3, 4, 5 } };
// folosim indici
for (unsigned i{}; i < n; i++)
{
std::cout << p[i] << "\t";
}
std::cout << std::endl;
// adăugăm un offset la adresa din pointer
for (unsigned i{}; i < n; i++)
{
std::cout << *(p+i)<< "\t";
}
std::cout << std::endl;
// parcurgem matricea folosind un pointer auxiliar
for (int* q{ p }; q != p + n; q++)
{
std::cout << *q << "\t";
}
std::cout << std::endl;
Observați că pentru a specifica dimensiunea unei matrici dinamice putem folosi o variabilă obișnuită, nu o constantă, așa cum este necesar pentru matricile statice.
Pentru ștergerea unei matrici dinamice și eliberarea memoriei se folosește o formă specială a operatorului delete:
delete [] pointer_la_matricea_dinamică;
Exemplu:
#include <iostream>
int main()
{
unsigned n{ 5 }; // dimensiunea matricii
int* p{ new int[n] { 1, 2, 3, 4, 5 } };
// folosim indici
for (unsigned i{}; i < n; i++)
{
std::cout << p[i] << "\t";
}
std::cout << std::endl;
delete [] p;
}
Pentru ca după eliberarea memoriei pointerul să nu mai rețină adresa veche, se recomandă și resetarea lui:
delete [] p;
p = nullptr; // resetăm pointerul
Matrici multidimensionale
Putem crea și matrici dinamice multidimensionale. Să luăm ca exemplu matricile bidimensionale. Ce este o matrice bidimensională? Este un set de matrici. Așadar, pentru a crea o matrice bidimensională dinamică, trebuie să creăm mai întâi o matrice dinamică de pointeri, apoi elementele acesteia – matricile dinamice interne. În general, arată astfel:
#include <iostream>
int main()
{
unsigned rows = 3; // numărul de rânduri
unsigned columns = 2; // numărul de coloane
int** numbers{new int*[rows]{}}; // alocăm memorie pentru matricea bidimensională
// alocăm memorie pentru matricile interne
for (unsigned i{}; i < rows; i++)
{
numbers[i] = new int[columns]{};
}
// ștergerea matricilor
for (unsigned i{}; i < rows; i++)
{
delete[] numbers[i];
}
delete[] numbers;
}
Mai întâi alocăm memorie pentru matricea de pointeri (ca un tabel):
int** numbers{new int*[rows]{}};
Apoi, în buclă, alocăm memorie pentru fiecare matrice individuală (ca rândurile tabelului):
numbers[i] = new int[columns]{};
Eliberarea memoriei se face în ordine inversă – mai întâi pentru fiecare matrice internă, apoi pentru întreaga matrice de pointeri.
Exemplu cu introducerea și afișarea datelor unei matrici dinamice bidimensionale:
#include <iostream>
int main()
{
unsigned rows = 3; // numărul de rânduri
unsigned columns = 2; // numărul de coloane
int** numbers{new int*[rows]{}}; // alocăm memorie pentru matricea bidimensională
for (unsigned i{}; i < rows; i++)
{
numbers[i] = new int[columns]{};
}
// introducem datele pentru matricea rows x columns
for (unsigned i{}; i < rows; i++)
{
std::cout << "Enter data for " << (i + 1) << " row" << std::endl;
// introducem datele pentru coloanele rândului i
for (unsigned j{}; j < columns; j++)
{
std::cout << (j + 1) << " column: ";
std::cin >> numbers[i][j];
}
}
// afișarea datelor
for (unsigned i{}; i < rows; i++)
{
// afișăm datele coloanelor rândului i
for (unsigned j{}; j < columns; j++)
{
std::cout << numbers[i][j] << "\t";
}
std::cout << std::endl;
}
for (unsigned i{}; i < rows; i++)
{
delete[] numbers[i];
}
delete[] numbers;
}
Exemplu de rulare a programului:
Enter data for 1 row
1 column: 2
2 column: 3
Enter data for 2 row
1 column: 4
2 column: 5
Enter data for 3 row
1 column: 6
2 column: 7
2 3
4 5
6 7
Pointer către matrice
Tipul int**, care reprezintă un pointer la pointer (pointer către pointer), trebuie deosebit de situația de „pointer către o matrice” (pointer to array). De exemplu:
#include <iostream>
int main()
{
unsigned n{3}; // numărul de rânduri
int (*a)[2] = new int[n][2];
int k{};
// setăm valorile
for (unsigned i{}; i < n; i++)
{
// setăm valorile coloanelor rândului i
for (unsigned j{}; j < 2; j++)
{
a[i][j] = ++k;
}
}
// afișarea datelor
for (unsigned i{}; i < n; i++)
{
// afișăm datele coloanelor rândului i
for (unsigned j{}; j < 2; j++)
{
std::cout << a[i][j] << "\t";
}
std::cout << std::endl;
}
// ștergem datele
delete[] a;
a = nullptr;
}
Aici expresia int (*a)[2] reprezintă un pointer către o matrice de doi întregi. De fapt, putem lucra cu acest obiect ca și cu o matrice bidimensională (tabel), doar că numărul de coloane este fix – 2. Iar memoria pentru această matrice se alocă o singură dată:
int (*a)[2] = new int[n][2];
Adică avem un tabel cu n rânduri și 2 coloane. Folosind doi indici (pentru rând și coloană), putem accesa un anumit element, să-l setăm sau să-l citim. Afișarea în consolă a acestui program:
1 2
3 4
5 6