Referencje (odnośniki) są często nazywane „przezwiskami” (aliasami) zmiennych — zarówno zmiennych typów prostych, jak i obiektowych. Zostały one wprowadzone w C++; w czystym C takie pojęcie nie występuje. Trzeba pamiętać, że w zasadzie nie ma czegoś takiego jak zmienna tego typu. Odnośnik (referencja), jak nazwa sugeruje, zawsze odnosi się do niezależnie istniejącej zmiennej o dobrze określonym typie (int, double, string,...). Tym niemniej referencje nazywa się często zmiennymi odnośnikowymi (referencyjnymi). Jest to dopuszczalne, pod warunkiem, że pamięta się prawdziwe znaczenie referencji. Zatem pamiętajmy, że
Zanim wyjaśnimy, co to właściwie znaczy i jaki sens ma nadawanie innych nazw zmiennym, powiemy, jak takie referencje definiować i używać.
Przede wszystkim, skoro referencja to inna nazwa istniejącej zmiennej, to nie da się utworzyć referencji bez związania jej ze zmienną, której ma być inną nazwą. Takie związanie referencji ze zmienną jest trwałe: po utworzeniu nie można referencji związać z jakąś inną zmienną.
Tak więc referencja zadeklarowana musi zostać od razu zainicjowana odniesieniem do istniejącej zmiennej: od tej pory nazwa referencji i nazwa tej zmiennej są traktowane równoważnie jako dwie nazwy tej samej zmiennej, a wszelkie operacje na referencjach są równoważne operacjom na pierwotnej zmiennej (ponieważ są operacjami na tej zmiennej).
Deklaracja i inicjacja referencji (tu o nazwie
refk)
może na przykład wyglądać tak:
1. #include <iostream> 2. using namespace std; 3. 4. int main() { 5. int k = 5; 6. int &refk = k; 7. 8. cout << "refk = " << refk << endl; 9. cout << " k = " << k << endl; 10. 11. k = 7; 12. 13. cout << "refk = " << refk << endl; 14. cout << " k = " << k << endl; 15. 16. refk = 9; 17. 18. cout << "refk = " << refk << endl; 19. cout << " k = " << k << endl; 20. }
Na początku deklarujemy/definiujemy tu k, zwykłą zmienną typu int. W następnej linii deklarujemy referencję refk do zmiennej typu int i związujemy ją z konkretną, istniejącą zmienną tego właśnie typu, zmienną k. Ogólnie zatem
gdzie Typ jest nazwą pewnego typu, ref dowolną nazwą, jaką nadajemy odnośnikowi, a zmienna nazwą pewnej już istniejącej zmiennej typu Typ. Po takiej deklaracji
W naszym przykładzie refk odnosi się do k i jak widzimy po wynikach
refk = 5 k = 5 refk = 7 k = 7 refk = 9 k = 9nazwy k i refk odnoszą się do tej samej zmiennej: możemy modyfikować tę zmienną poprzez którąkolwiek z tych nazw z tym samym efektem. Również drukowanie wartości zmiennej nie zależy od tego, której nazwy użyliśmy.
Pamiętamy (rozdz. o wskaźnikach ), że symbol ' &' oznacza operator wyłuskania adresu. Tu poznaliśmy jego drugie znaczenie. Podobnie jak to ma miejsce w wypadku symbolu ' *' (używanego przy deklarowaniu wskaźników i jako operatora wyłuskania wartości — dereferencji), właściwe znaczenie wynika zawsze z kontekstu: jeżeli chodzi nam o deklarację referencji, to znak ' &' jest zawsze poprzedzony nazwą typu, a to z kolei nie jest składniowo możliwe, jeśli ' &' ma pełnić rolę jednoargumentowego operatora wyłuskania adresu.
Spójrzmy na prosty, ale pouczający przykład:
1. #include <iostream> 2. using namespace std; 3. 4. int main () { 5. int k = 7, *p = &k, &refk = *p, m = 9; ➊ 6. 7. p = &m; ➋ 8. k = 11; 9. 10. cout << " *p = " << *p << endl; ➌ 11. cout << "refk = " << refk << endl; ➍ 12. }
którego uruchomienie daje:
*p = 9 refk = 11Przyjrzyjmy się deklaracjom/definicjom w linii ➊:
Podobnie jak gwiazdki dla wskaźników, znaki ' &' trzeba stawiać przed nazwą każdej referencji na liście deklaracyjnej. Poniższa deklaracja na przykład
double x = 3, &y = x, *z = &x, &u = *z;deklaruje dokładnie dwie zmienne: zmienną x typu double zainicjowaną wartością 3 oraz zmienną wskaźnikową z typu double* zainicjowaną adresem zmiennej x. Zauważmy, że y nie jest zmienną, tylko referencją do zmiennej — mianowicie do zmiennej x. Podobnie u jest referencją do zmiennej aktualnie (w momencie gdy strumień sterowania przechodzi przez tę deklarację) wskazywanej przez wskaźnik z: jest to więc również zmienna x. Po takiej deklaracji referencje y i u będą się odnosić do zmiennej x i tego się już zmienić nie da — zmienna x będzie miała w tym fragmencie programu trzy nazwy!
Wskaźnik z natomiast aktualnie wskazuje na x, ale w dalszej części programu to wskazanie można będzie zmienić.
Wszystko to wygląda nawet zabawnie, ale nie odpowiada na pytanie, po co nam zmienne o kilku różnych nazwach. Rzeczywiście, zwykle referencje nie służą do tego, aby mnożyć nazwy dla tych samych zmiennych. Ich prawdziwa siła tkwi w roli, jaką pełnią przy przesyłaniu danych do funkcji i, odwrotnie, przesyłaniu wyników działania z wywoływanej funkcji do funkcji wywołującej. Dyskusję tego aspektu referencji odłożymy do rozdziałów o argumentach referencyjnych i o funkcjach zwracających referencje .
Zauważmy jeszcze, że nie ma czegoś takiego jak tablica referencji. Natomiast mogą istnieć, i są czasem przydatne, referencje do tablic.
T.R. Werner, 21 lutego 2016; 20:17