Referencja (typ referencyjny) w języku C++. Znaczenie referencji w programowaniu w C++.

Typy podstawowe a typy pochodne

Do tej pory przedstawiłem Ci już wiele typów danych. Były to m.in. int, float, char. Wszystkie te typy danych to typy podstawowe. Dodatkowo znasz już typ struct, który jest typem złożonym, bowiem zawiera w ogólnym przypadku kilka pól typu podstawowego.

Musisz jednak wiedzieć, że oprócz typów podstawowych i złożonych, w języku C++ istnieją również typy pochodne. Czym są typy pochodne? Otóż typy pochodne są to takie typy, które pochodzą od typów podstawowych. Wiem, że pewnie niewiele Ci to na razie mówi, ale w najbliższych lekcjach mam nadzieję, że uda Ci się z tym pojęciem zaznajomić.

Ważne, żeby udało Ci się zapamiętać, że przedstawiony w tej lekcji typ, będzie właśnie przykładem typu pochodnego. A w tej lekcji zaprezentuję Ci typ referencyjny, nazywany inaczej referencją lub przezwiskiem.

Referencja - wprowadzenie

Zastanawiasz się pewnie w tym momencie, czym jest typ referencyjny. W pewnym sensie referencja jest to dodatkowa nazwa dla danej zmiennej. Dzięki referencji jedna zmienna może mieć 2 nazwy - swoją własną i nazwę utworzoną przez referencję. Ponieważ tak naprawdę do jednej zmiennej możemy utworzyć kilka referencji, oznacza to, że jedna zmienna uzyskuje w ten sposób wiele nazw.

Pora na wyjaśnienie, czemu typ referencyjny jest typem pochodnym. Ponieważ referencja, jak przed chwilą wspomniałem, jest nową nazwą dla zmiennej, znaczy to, że ta zmienna musiała zostać wcześniej utworzona. A skoro zmienna została utworzona, to znaczy, że musi być określonego typu (bo jak pamiętasz doskonale w C++ zmienne muszą mieć zawsze określony typ). Intuicyjnie, referencja powinna wiedzieć jakiego typu jest zmienna, z którą jest powiązana, bo tak jak w rzeczywistości Ty rozmawiasz inaczej ze swoim bratem, a inaczej z rodzicami, tak samo zmienna typu referencyjnego będzie inaczej "rozmawiała" ze zmienną typu int, a inaczej ze zmienną typu float.

Schematycznie utworzenie zmiennej typu referencyjnego wygląda następująco:

typ & nazwaZmiennejTypuReferencyjnego = nazwaZmiennej;

Pierwszą rzeczą jaka powinna Ci się rzucić w oczy, jest znak & pojawiający się przed nazwą zmiennej typu referencyjnego. Jako, że schematyczny typ w praktyce może być na przykład typem int lub float, to musiało zostać wprowadzone jakieś oznaczenie na referencję. Jako takie oznaczenie przyjęto dodatkowy &. Gdyby znaku & nie było, utworzylibyśmy po prostu zwykłą zmienną.

Drugą sprawą jest to, że jak widzisz zmienna typu referencyjnego jest od razu inicjalizowana. Czy to przypadek, że tak napisałem? Otóż nie - w przypadku referencji inicjalizacja jest konieczna i jeśli nie dokonasz inicjalizacji, wówczas kompilator zaprotestuje i Twój program nie zostanie skompilowany.

Tak naprawdę program był ilustracją tego, co wyjaśniłem Ci już wcześniej. Utworzyliśmy zmienną o nazwie liczba typu int. Następnie została utworzona referencja do typu int o nazwie numerek, która odnosi się do zmiennej liczba - jak widzisz, zmienna typu referencyjnego została zainicjalizowana. Następnie wypisaliśmy na ekran wartość zmiennej i wartość referencji i jak widać, obie wartości są identyczne.

Referencja - ważne zasady i cechy

Poniżej przedstawię Ci kilka zasad i cech dotyczących referencji. Na Twoim miejscu wydrukowałbym je sobie i powiesił koło komputera, aby szybko udało Ci się te reguły zapamiętać. Jest to tym bardziej ważne, że niebawem, referencja będzie przydatna do innych zagadnień, a być może już niedługo będzie używana przez Ciebie na co dzień, więc jeśli już teraz uda Ci się ją zrozumieć, punkt dla Ciebie.

Oto 5 ważnych zasad dotyczących referencji:

Przykład "z życia wzięty"

Jeśli nie udało Ci się dobrze zrozumieć, czym jest referencja, to wierzę, że poniższy przykład Ci w tym pomoże.

Wyobraź sobie pana Tomasza. Pan Tomasz jest pracownikiem pewnej firmy. Oczywiście wszyscy w firmie nazywają go Tomaszem. Jednak gdy pan Tomasz spotyka się z kolegami po pracy, koledzy nie nazywają go Tomaszem tylko Tomkiem. Czy to ta sama osoba? Oczywiście! Ale jak widać jest nazywana odmiennie w zależności od sytuacji.

Idźmy dalej. Gdy pan Tomasz wraca do domu, dzieci nie mówią do niego ani Tomaszu, ani Tomku, tylko po prostu tato. A żona nazywa go mężem. Jak więc widać jedna osoba - a jak w różny sposób można ją nazywać: Tomaszem, Tomkiem, tatą, mężem. Fizycznie jest to nadal ta sama osoba - ma tyle samo wzrostu, tyle samo waży, ma taki sam kolor włosów, a jednak jest nazywana różnie.

Oczywiście wszyscy mogliby go nazywać Tomaszem, ale jednak dla swojej wygody, komfortu nazywają Tomasza w zależności od swoich upodobań.

Co ważne pan Tomasz doskonale wie, że jak dzieci mówią na niego tato, to chodzi o niego, jak żona mówi do niego mężu, to też chodzi o niego. Zatem wszystkie polecenie pan Tomasz może wykonywać bez względu na to, czy ktoś go nazwie Tomaszem, Tomkiem, tatą czy mężem.

Pomyślisz sobie - no dobrze, rozumiem o co chodzi z panem Tomaszem, ale w jaki sposób ma się to do referencji? Okazuje się, że obie te sytuacje są bardzo podobne.

W programie jest tak jak w życiu. Dla danej zmiennej możemy utworzyć kilka referencji w zależności kto będzie się do niej odwoływał. Tak samo, pan Tomasz mógł być nazywany różnie, w zależności z kim rozmawiał.

Podobnie, każda referencja do zmiennej ma taką samą wartość jak ta zmienna, bowiem jest to tylko dodatkowa nazwa. Tak samo było z panem Tomaszem - nieważne, kto go jak nazywał, to zawsze rozmawiał z tym samym panem Tomaszem, którego cechy jak wygląd, zachowanie były tak naprawdę takie same.

Musisz jednak wiedzieć, że mimo że całe to powyższe tłumaczenie jest jak najbardziej poprawne, to tak naprawdę referencji w takiej postaci, jak to tu przedstawiam, nie wykorzystuje się prawie nigdy.

Tak naprawdę bowiem, powiedz szczerze, czy chciało by Ci się w programie nazywać jedną zmienną na kilka różnych sposobów? Mnie na pewno nie chciało by się tego robić i nie chce się też innym, dlatego takie wykorzystanie referencji nie jest raczej w użyciu.

Ważne jest jednak, żeby udało Ci się dobrze zrozumieć, czym jest referencja, bowiem jest ona bardzo często wykorzystywana podczas używania funkcji (o tym za kilka lekcji). Zatem jeśli jeszcze nie do końca udało Ci się zrozumieć ideę referencji, postaraj się przeczytać powyższe tłumaczenie raz jeszcze.

Referencja a zmienna

Jak już dobrze wiesz, wartość zmiennej i wartość referencji do tej zmiennej jest taka sama. Co jednak jeśli w trakcie działania programu zmienimy wartość zmiennej? Zasada nadal działa - wówczas wartość referencji będzie taka jak nowa wartość zmiennej.

Co więcej, jeśli zmienimy wartość referencji, wówczas zostanie zmieniona wartość zmiennej, do której jest ta referencja i oczywiście także wartość wszystkich pozostałych referencji (o ile takie istnieją).

Dzięki takiemu mechanizmowi, nie musimy się obawiać, że w pewnym momencie będziemy mieli zmienną i kilka referencji i każda z tych wartości będzie inna. Coś takiego po prostu w C++ stać się nie może.

Poniżej przedstawiam program, który obrazuje, że niezależnie, czy zmieniamy wartość zmiennej, czy wartość zmiennej przez referencję, to obie wartości pozostają identyczne.

#include <iostream>

using namespace std;

int main()
{  
  float waga=68.5;
  // double &referencja = waga; // (1) Blad - brak scislej zgodnosci typow
  float &wartosc = waga;
  cout <<"Wartosc zmiennej waga wynosi "<<waga<<endl;
  cout <<"Wartosc referencji wynosi "<<wartosc<<endl;
  waga=90;
  cout <<"Wartosc zmiennej waga wynosi "<<waga<<endl;
  cout <<"Wartosc referencji wynosi "<<wartosc<<endl;
  wartosc=23;
  cout <<"Wartosc zmiennej waga wynosi "<<waga<<endl;
  cout <<"Wartosc referencji wynosi "<<wartosc<<endl;      
 
  cout <<endl<<"Nacisnij ENTER aby zakonczyc..."<<endl;
  getchar();  
  return 0;
}
program nr 26.2

Jak widzisz, niezależnie od dokonywanych zmian, wartości zarówno referencji jak i zmiennej są identyczne. Zwróć również uwagę na linijkę opatrzoną komentarzem. Ponieważ jak już wcześniej wspominałem, typ referencji jak i zmiennej na którą ona wskazuje muszą być identyczne, to gdybyśmy usunęli komentarz, programu nie udałoby się skompilować.

Referencje a stałe

Jak już wspomniałem na wcześniejszym etapie tej lekcji, referencja musi zostać zainicjalizowana i zostaje na zawsze związana z jedną zmienną i nie można połączyć tej samej referencji z inną zmienną.

Przyjrzyjmy się teraz poniższej deklaracji i zastanówmy się co ona oznacza, nie zwracając na razie uwagi na komentarz:

int & const referencjaDoInt = zmiennaInt; // UWAGA - BLAD !!!

Ponieważ deklaracje zmiennych w C++, o ile nie ma dodatkowych nawiasów, czytamy od strony prawej do lewej, to usiłujemy rozszyfrować powyższy zapis, pamiętając, że właściwą deklaracją jest jedynie ta część: int & const referencjaDoInt - pozostała część to inicjalizacja.

Startujemy zatem - zmienna o nazwie referencjaDoInt jest stałą (const) referencją (&) do typu całkowitego (int).

Tu jednak napotykamy powtórzenie definicji referencji, bowiem referencja zawsze jest stała, bo nie może nagle zostać przypisana do innej zmiennej. Dlatego też, nie ma sensu dodawać w tym przypadku modyfikatora const, a ponieważ nie ma to sensu, zostało to w języku C++ przyjęte za niepoprawne, stąd błąd w przykładowym fragmencie kodu - słowa const tam być nie powinno.

Mimo, że referencja sama w sobie jest stała, to jednak jak udało Ci się przekonać w jednym z poprzednich przykładów, gdy do referencji przypiszemy nową wartość, to wartość zmiennej, z którą referencja została powiązana, zostaje zmieniona.

Mimo że często właśnie o takie zachowanie nam chodzi, czasami chcielibyśmy, aby zmiany mogły następować tylko poprzez użycie zmiennej, a referencja mogła tylko odczytywać wartość, jaką zmienna posiada i nie mogła z nią nic zrobić. W ten sposób chronimy się przed tym, że zmiany zmiennej może dokonać ktoś niepowołany.

Taki rezultat osiągniemy, stosując poniższy zapis:

const int & referencjaDoInt = zmiennaInt;

Analizując po raz kolejny deklarację, od prawej strony do lewej, stwierdzamy, że zmienna referencjaDoInt jest referencją (&) do typu całkowitego (int), który jest stały (const).

W ten oto sposób, osiągniemy to co chcieliśmy. Referencja będzie mogła tylko odczytywać wartość zmiennej, ale zmieniać jej nie będzie mogła. Dowodem jest poniższy kod:

#include <iostream>

using namespace std;

int main()
{  
  float waga=68.5;
  const float &wartosc = waga;
  cout <<"Wartosc zmiennej waga wynosi "<<waga<<endl;
  cout <<"Wartosc referencji wynosi "<<wartosc<<endl;
  waga=90;
  cout <<"Wartosc zmiennej waga wynosi "<<waga<<endl;
  cout <<"Wartosc referencji wynosi "<<wartosc<<endl;  
  // wartosc=23; // BLAD - referencja do stalej !!!
 
  cout <<endl<<"Nacisnij ENTER aby zakonczyc..."<<endl;
  getchar();  
  return 0;
}
program nr 26.3

Jeśli w powyższym kodzie, spróbowalibyśmy usunąć komentarz, program by się nie skompilował, bowiem, referencja do stałego typu próbowałaby zmienić wartość, a jest to niemożliwe.

Jeśli teraz jeszcze raz dobrze się przyjrzysz powyższemu programowi, możesz napotkać coś, co Cię powinno choć trochę zaskoczyć. Jak pamiętasz, wspominałem, że przy inicjalizacji referencji typ referencji i typ zmiennej, którą łączymy z referencją muszą być identyczne.

Tutaj oczywiście tak nie jest - po stronie referencji mamy typ const int, a po stronie zmiennej typ int. W takim przypadku możliwe jest jednak odstępstwo - referencja jest bowiem nadal typu int z dodatkowym warunkiem nałożonym za pomocą modyfikatora const. Można zatem stwierdzić, że referencja spełnia warunek zgodności typu i dodatkowo nakłada na siebie ograniczenie, że nie będzie zmieniać tej wartości.

O ile takie rozwiązanie mimo, że mogłoby się wydawać, że typy nie są zgodne, działa zgodnie z zamierzeniami, to już w poniższym przykładzie z oczywistych powodów, kompilator zaprotestuje:

const float waga=68.5;
float &wartosc = waga; // UWAGA - BLAD !!!

Zapytasz dlaczego? Ponieważ określiliśmy, że wartość zmiennej waga jest stała, to nie możemy dopuścić, aby referencja mogła dokonać jakichkolwiek zmian. Jeśli chcemy mieć zmienną, której wartość pozostaje stała przez cały czas wykonania programu, musimy również zadeklarować referencję do stałej zmiennej. Zatem należałoby to zrobić tak:

const float waga=68.5;
const float &wartosc = waga;

Referencja a stała wartość

Jak się inicjalizuje wartość zwykłej zmiennej, wiesz już doskonale. Spróbuj jednak zastosować poniższą regułę do referencji, a okaże się, że to co było dozwolone dla zmiennej, nie jest dozwolone dla referencji:

float &wartosc = 68.5;

Pomyślisz sobie - no dobrze, wiem dlaczego tak jest. Po lewej stronie mamy referencję do typu float, a po prawej liczbę i kompilator nie wie, że typy są zgodne. Muszę Cię zmartwić - to nie to. Kompilator w tym przypadku poradzi sobie z rozpoznaniem typów.

W czym zatem tkwi problem? Otóż referencja mimo, że jest typem, wskazuje zawsze na pewne miejsce, w którym znajduje się inna zmienna. Tutaj natomiast nie mamy żadnej zmiennej, a referencja sama w sobie nie jest w stanie przechować dodatkowej wartości, bo używa zawsze do tego zmiennej, z którą jest powiązana.

Mimo to, również w tym przypadku istnieje rozwiązanie problemu. Wystarczy powiedzieć kompilatorowi, że referencja będzie referencją do stałej i w ten sposób obiecujemy nigdy nie zmieniać wartości referencji:

const float &wartosc = 68.5;

Zatem w ten oto sposób stworzyliśmy referencję, która nie jest jawnie powiązana z żadną zmienną, a jedynie z pewną wartością liczbową. Oczywiście w takiej postaci byśmy takiego zapisu nie użyli (zamiast referencji użylibyśmy zwykłą zmienną), ale jak się przekonasz, w przyszłości wiedza, że można taką referencję utworzyć, może Ci się niekiedy przydać.

Podsumowanie

Przedstawiłem Ci w tej lekcji wszystkie najważniejsze zagadnienia dotyczące referencji, jakie są niezbędne na tym etapie tego kursu. Są to zagadnienia bardzo ważne, a nawet kluczowe, aby opanować bardziej skomplikowane mechanizmy.

Referencja jest bardzo często spotykaną konstrukcją w języku C++, dlatego też radzę Ci dobrze - upewnij się, że wszystko udało Ci się zrozumieć. Jeśli nie - przeczytaj tę lekcję jeszcze raz i postaraj się poćwiczyć poznane zagadnienia na własną rękę.

powrót