Wstęp
W czasie lektury poprzedniej lekcji, być może zastanawiałeś się, po co w ogóle te wskaźniki? Nie wydają się wprowadzać niczego nowego, bo można równie dobrze używać zmiennych po ich nazwach, a nie adresach. Arytmetyka wskaźników to fajna opcja, ale ma sens tylko na tablicach, a te można indeksować i na jedno wychodzi. W tej lekcji omówiony będzie sposób na uzyskanie pamięci, do której nie można się odnieść po nazwie.
new i delete
new jest operatorem
dynamicznej alokacji pamięci. Zwraca adres lokalizacji pamięci, której możemy od teraz używać do przechowywania wartości jakiegoś typu.
int * wsk = new int;
* wsk = 123;
Zrób "nowy
int" i zapisz adres. Tak utworzona zmienna będzie istnieć tak długo, aż zostanie ręcznie zwolniona, lub program się zakończy. Do zwolnienia pamięci służy operator
deletePo wykonaniu tej instrukcji, wartość wskaźnika
wsk pozostaje bez zmiany, ale nie można już robić niczego z pamięcią pod tym adresem - nie jest już nasza. Jak pewnie pamiętasz z lekcji o wskaźnikach, błędna niezerowa wartość wskaźnika jest nieodróżnialna od poprawnej, więc dobrą praktyką jest wyzerowanie wskaźnika po usunięciu pamięci:
delete wsk;
wsk = nullptr;
Każda zaalokowana pamięć powinna zostać zwolniona. Jeśli program w jakimś przypadku nie zwalnia pamięci, mamy wówczas do czynienia z wyciekiem pamięci (memory leak). Niezwalnianie pamięci może doprowadzić do wyczerpania zasobów komputera, a wtedy będzie problem. Nie da się stwierdzić, czy jakaś pamięć jest nieużywana i potrzebna, czy nieużywana i niepotrzebna, bo jakiś niezdarny programista zgubił jej adres. |
new[] i delete[]
Wszystko super z tym
new, ale tyle zachodu dla utworzenia jednego
inta to słaby deal. Jak trzeba utworzyć zmienną na wskaźnik, to czemu po prostu nie utworzyć zmiennej? Alokacja jednej zmiennej ma swoje istotne zastosowania, które wkrótce poznasz, ale na razie bardziej przydatna byłaby możliwość utworzenia całej tablicy zmiennych. Od tego jest operator
new[].
int * tab = new int[ 100 ];
tab[ 42 ] = 123;
To ma kilka dużych zalet względem zwykłej tablicy
int tab[100]:
Do usunięcia tablicy należy użyć
delete[]:
Do delete/delete[] można bezpiecznie podać pusty wskaźnik i nie powoduje to zwolnienia żadnej pamięci |
new int i new int[1] nie są równoważne. Alokacja jednej zmiennej nie jest alokacją jednoelementowej tablicy, więc delete[] nie jest "uniwersalniejszą" wersją delete. delete i delete[] nie mogą być stosowane zamiennie. |
Przykład - powiększająca się tablica
#include <iostream>
int main()
{
int * tablica = nullptr, rozmiar = 0;
std::cout << "Podawaj liczby, 0 konczy wczytywanie.\n";
while( true )
{
int liczba;
std::cin >> liczba;
if( liczba == 0 )
break;
int * nowa = new int[ rozmiar + 1 ];
for( int i = 0; i < rozmiar; ++i )
nowa[ i ] = tablica[ i ];
nowa[ rozmiar ] = liczba;
delete[] tablica;
tablica = nowa;
rozmiar++;
}
std::cout << "Te same liczby, ale odwrotnie!\n";
for( int i = rozmiar - 1; i >= 0; --i )
std::cout << tablica[ i ] << ' ';
delete[] tablica;
}
Zwróć uwagę, że
rozmiar równe zero na początku programu sprawia, że program nigdy nie odwołuje się do nieistniejącej pamięci.
Zadanie domowe
Zmodyfikuj przykładowy kod tak, aby nowa tablica nie była tworzona za każdym razem, gdy dodawany jest nowy element.