3. Definicje zmiennych i ich zasięg

Zanim przejdziemy do opisywania typów pochodnych do podstawowych chciałbym powiedzieć coś więcej o deklaracjach i definicjach zmiennych. Same pojęcia wprowadziłem w poprzednim rozdziale – teraz pora na rozszerzenie podanych informacji w kontekście zmiennych.

Każda zmienna wymaga przydzielenia obszaru pamięci odpowiedniego dla niej. W C++ cała dostępna pamięć jest dzielona na kilka niezależnych obszarów (klas pamięci, ang. storage class). Obszar przeznaczony na zmienne lokalne zwyczajowo nazywa się stosem. Sam stos jest podzielony na dwie klasy pamięci: na zmienne globalne i lokalne. Każdy obiekt lokalny jest dostępny wyłącznie w kontekście, w którym został zadeklarowany, zaś jego czas życia jest od wejścia do kontekstu do wyjścia z niego.

 Globalny zaś, deklarowany poza wszystkimi funkcjami, jest dostępny dla wszystkich funkcji. Jeszcze jedna różnica dotyczy inicjacji: zmienne typu prostego z kontekstu lokalnego nie są inicjowane wcale, natomiast pamięć przeznaczona na zmienne globalne powinna być inicjowana - czyli zmienne te są inicjowane abo przy wykorzystaniu domyślnego konstruktora (typy własne), albo zerując cały obszar pamięci przyznany zmiennej globalnej (typy proste).

Nie zainicjalizowana zmienna posiada wartość taką, jaka się jej trafiła w przeznaczonym dla niej kawałku pamięci. Ważną informacją jest fakt, że wyniki wszystkich operacji na takich wartościach (z wyjątkiem przypisania) są niezdefiniowane. Wartość taką nazywamy wartością osobliwą (ang. singular). Wartość osobliwa to po prostu taka wartość o której nie tylko nic nie wiemy, ale też nad którą program nie ma żadnej kontroli; innymi słowy, jest to wartość, której nikt nie nadał. 

Wykonywanie jakichkolwiek operacji poza przypisaniem na niezainicjowanych zmiennych może spowodować rzucenie wyjątku, i związane z nim awaryjne zakończenie programu. Tak więc, poniższy kod jest nie tylko mało sensowny, ale też potencjalnie niebezpieczny: 
{
  int x, y; 
  x = y;
}

Inicjacja zmiennych

W języku C++ mamy kilka wyrażeń umożliwiających inicjację zmiennych. Zasadniczo - działają one podobnie, a wielość form wynika raczej z uwarunkować historycznych.


double x=2.2; 
double y(3.3); 
double z{4.4}

Pierwsza forma ze znakiem przypisania = jest nieco myląca - w tym wypadku następuje inicjalizacja, a nie przypisanie zmiennej. Ta forma wywodzi się jeszcze z C.

Druga forma - z nawiasami okrągłymi - została wprowadzona w C++ - ze względu na składniowe podobieństwo do wykorzystania konstruktora. Niemniej znaczenie pozostało to samo co wykorzystanie =, ogólnie też zapis "kostruktorowy" nie przyjął się szeroko w społeczności.

Trzecia forma wprowadzona została w standardzie C++11. Znaczeniowo różni się ona od dwóch poprzednich - mianowicie, zabrania konwersji zawężających - czyli powodujących utratę informacji. Popatrzcie na poniższy kod:


int c1=1.7; // nie ma błędu, w c1 jest 1
int c2(2.7); // nie ma błędu, w c2 jest 2

int c3{3.7}; // to jest błąd
unsigned short c4{-1}; // to też
unsigned short c5{1e7}; // to też

Ogólnie - zaleca się jawne inicjowanie zmiennych - o ile to tylko jest możliwe. Kompilatory powinny generować ostrzeżenia o zmiennych które nie są zainicjowane - warto je czytać, i eliminować z własnego kodu.