Wskaźniki a referencje

Zmienne wskaźnikowe deklaruje się w ten sposób, że pomiędzy nazwą typu wskazywanego obiektu i nazwą deklarowanej zmiennej wskaźnikowej umieszcza się gwiazdkę (która łączy się z nazwą zmiennej, a nie nazwą typu). Instrukcja
int *pValue;

definiuje więc zmienną pValue jako wskaźnik do liczby całkowitej (int). Nadając wskaźnikowi wartość początkową (lub zmieniając jego dotychczasową wartość operatorem przypisania, "=") należy posługiwać się adresem innej zmiennej lub obiektu. W języku C++ operator pobrania adresu oznaczany jest symbolem "&" (nie powoduje to konfliktu z używaniem tego symbolu do deklarowania referencji, gdyż te dwa różne sposoby wykorzystania symbolu & występują w różnych kontekstach). Dlatego instrukcje
int TheValue = 10;
pValue = &TheValue;
powodują przypisanie wskaźnikowi pValue adresu zmiennej TheValue.

Rysunek 1. Zmienna wskaźnikowa (czyli "wskaźnik") pValue przechowuje adres zmiennej ("wskazuje na zmienną") TheValue.

Dostęp do wartości zmiennej lub obiektu, którego adres przechowywany jest w zmiennej wskaźnikowej, uzyskuje się poprzez operator wyłuskania, zapisywany jako gwiazdka (czyli dokładnie tak, jak operator mnożenia, jednak ponownie nie prowadzi to do niejednoznaczności). Dlatego instrukcja
int i = *pValue;

powoduje nadanie zmiennej całkowitej i wartości tej liczby całkowitej, której adres zapisano wcześniej w zmiennej wskaźnikowej pValue. W naszym konkretnym przykładzie zmiennej i zostanie więc przypisana wartość zmiennej TheValue, czyli 10. Z kolei instrukcja
*pValue = 20;

powoduje zmianę wartości obiektu wskazywanego przez zmienną wskaźnikową pValue. W naszym przykładzie modyfikacji ulegnie więc wartość zmiennej TheValue, która po wykonaniu powyższej instrukcji będzie miała wartość 20. Jak widzimy, posługiwanie się wskaźnikami jest nieco bardziej skomplikowane, niż używanie referencji. Posługując się wyłącznie referencjami powyższe instrukcje moglibyśmy bowiem zapisać w następujący sposób:
int TheValue = 10;
int& aliasValue = TheValue; // aliasValue jest referencją do TheValue
int i = aliasValue;         // odczytujemy wartość TheValue poprzez referencję 
aliasValue = 20;            // modyfikujemy TheValue poprzez referencję

Nie ma żadnych wątpliwości - referencje są dużo prostsze i wygodniejsze od wskaźników.

Kolejny przykład ilustruje dość popularny, formalnie poprawny, jednak niezalecany w tej książce sposób posługiwania się wskaźnikami. Kod ten przedstawia specjalny sposób uzyskiwania dostępu do składowej obiektu, wskazywanego przez wskaźnik pStack. W tym celu wykorzystuje się operator wyłuskania składowej (w rzeczywistości zapisuje się go jako ciąg dwóch znaków, minusa i znaku większości, "->"). Operator ten łączy w sobie dwie funkcje, wyłuskiwanie wartości wskazywanej przez wskaźnik i uzyskiwanie dostępu do składowej tego obiektu (w naszym przykładzie jest nią funkcja składowa Push klasy IStack).
IStack TheStack;
IStack* pStack = &TheStack;
pStack->Push (7);  // umieść na stosie liczbę 7 poprzez wskaźnik pStack
                   // równoważne instrukcji (*pStack).Push (7)

Ten sam efekt można jednak uzyskać za pośrednictwem zmiennych referencyjnych:
IStack TheStack;
IStack& aliasStack = TheStack;
aliasStack.Push (7);  // umieść na stosie liczbę 7 poprzez zmienną referencyjną

Ponownie wersja z referencjami jest lepsza.