Sie sind auf Seite 1von 29

Rozdzia 9.

Referencje
W poprzednim rozdziale poznae wskaniki i dowiedziae si, jak za ich pomoc mona
operowa obiektami na stercie oraz jak odwoywa si do obiektw porednio. Referencje maj
prawie te same moliwoci, co wskaniki, ale posiadaj przy tym duo prostsz skadni.
Z tego rozdziau dowiesz si:

czym s referencje,

czym rni si od wskanikw,

jak si je tworzy i wykorzystuje,

jakie s ich ograniczenia,

w jaki sposb przekazywa obiekty i wartoci do i z funkcji za pomoc referencji.

Czym jest referencja?


Referencja jest aliasem (inn nazw); gdy tworzysz referencj, inicjalizujesz j nazw innego
obiektu, bdcego celemu referencji. Od tego momentu referencja dziaa jak alternatywna nazwa
celu. Wszystko, co robisz z referencj, w rzeczywistoci jest robione dotyczyz jej obiektuem
docelowego.
Referencj tworzy si, zapisujc typ obiektu docelowego, operator referencji (&) oraz nazw
referencji.
Nazwy referencji mog by dowolne, ale wielu programistw woli poprzedza jej nazw liter r.
Jeli masz zmienn cakowit o nazwie someInt, moesz stworzy referencj do niej piszc:
int &rSomeRef = someInt;

Odczytuje si to jako: rSomeRef jest referencj do wartoci zmiennej typu int. Ta referencja
zostaa zainicjalizowana tak, aby odnosia si do zmiennej someInt. Sposb tworzenia referencji
i korzystania z niej przedstawia listing 9.1.
UWAGA Operator referencji (&) ma taki sam symbol, jak operator adresu. Nie s to jednak te
same operatory (cho oczywicie s ze sob powizane).

Zastosowanie spacji przed operatorem referencji jest obowizkowe, uycie spacji pomidzy
operatorem referencji a nazw zmiennej referencyjnej jest opcjonalne. Tak wic:
int &rSomeRef = someInt; // ok
int & rSomeRef = someInt; // ok

Listing 9.1. Tworzenie referencji i jej uycie


0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:

//Listing 9.1
// Demonstruje uycie referencji
#include <iostream>
int main()
{
using namespace std;
int intOne;
int &rSomeRef = intOne;
intOne = 5;
cout << "intOne: " << intOne << endl;
cout << "rSomeRef: " << rSomeRef << endl;
rSomeRef = 7;
cout << "intOne: " << intOne << endl;
cout << "rSomeRef: " << rSomeRef << endl;
return 0;
}

Wynik
intOne: 5
rSomeRef: 5
intOne: 7
rSomeRef: 7

Analiza
W linii 8. jest deklarowana lokalna zmienna intOne. W linii 9. referencja rSomeRef (jaka
referencja) jest deklarowana i inicjalizowana tak, by odnosia si do zmiennej intOne. Jeli
zadeklarujesz referencj, lecz jej nie zainicjalizujesz, kompilator zgosi bd powstay podczas
kompilacji. Referencje musz by zainicjalizowane.
W linii 11. zmiennej intOne jest przypisywana warto 5. W liniach 12. i 13. s wypisywane
wartoci zmiennej intOne i referencji rSomeRef; s one oczywicie takie same.

W linii 17. referencji rSomeRef jest przypisywana warto 7. Poniewa jest to referencja, czyli
inna nazwa zmiennej intOne, w rzeczywistoci warto ta jest przypisywana tej zmiennej (co
potwierdzaj komunikaty wypisywane w liniach 16. i 17.).

Uycie operatora adresu z referencj


Gdy pobierzesz adres referencji, uzyskasz adres jej celu. Wynika to z natury referencji (s one
aliasami dla obiektw docelowych). Pokazuje to listing 9.2.
Listing 9.2. Odczytywanie adresu referencji
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:

//Listing 9.2
// Demonstruje uycie referencji
#include <iostream>
int main()
{
using namespace std;
int intOne;
int &rSomeRef = intOne;
intOne = 5;
cout << "intOne: " << intOne << endl;
cout << "rSomeRef: " << rSomeRef << endl;
cout << "&intOne: " << &intOne << endl;
cout << "&rSomeRef: " << &rSomeRef << endl;
return 0;
}

Wynik
intOne: 5
rSomeRef: 5
&intOne: 0012FF7C
&rSomeRef: 0012FF7C

UWAGA W twoim komputerze dwie ostatnie linie mog wyglda inaczej.

Analiza
W tym przykadzie referencja rSomeRef ponownie odnosi si do zmiennej intOne. Tym razem
jednak wypisywane s adresy obu zmiennych; s one identyczne. C++ nie umoliwia dostpu do
adresu samej referencji, gdy jego uycie, w odrnieniu od uycia adresu zmiennej, nie miaoby
sensu. Referencje s inicjalizowane podczas tworzenia i zawsze stanowi synonim dla swojego
obiektu docelowego (nawet gdy zostanie zastosowany operator adresu).
Na przykad, jeli masz klas o nazwie President, jej egzemplarz moesz zadeklarowa
nastpujco:

President George_Washington;

Moesz wtedy zadeklarowa referencj do klasy President i zainicjalizowa j tym obiektem:


President &FatherOfOurCountry = George_Washington;

Istnieje tylko jeden obiekt klasy President; oba identyfikatory odnosz si do tego samego
egzemplarza obiektu tej samej klasy. Wszelkie operacje, jakie wykonasz na zmiennej
FatherOfOurCountry (ojciec naszego kraju), bd odnosi si do obiektu
George_Washington.
Naley odrni symbol & w linii 9. listingu 9.2 (deklarujcy referencj o nazwie rSomeRef) od
symboli & w liniach 15. i 16., ktre zwracaj adresy zmiennej cakowitej intOne i referencji
rSomeRef.
Zwykle w trakcie uywania referencji nie uywa si operatora adresu. Referencji uywa si tak,
jak jej zmiennej docelowej. Pokazuje to linia 13.

Nie mona zmienia przypisania referencji


Nawet dowiadczonym programistom C++, ktrzy wiedz, e nie mona zmienia przypisania
referencji, gdy jest ona aliasem swojego obiektu docelowego, zdarza si prba zmiany jej
przypisania. To, co wyglda w takiej sytuacji na ponowne przypisanie referencji, w rzeczywistoci
jest przypisaniem nowej wartoci obiektowi docelowemu. Przedstawia to listing 9.3.
Listing 9.3. Przypisanie do referencji
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:

//Listing 9.3
//Ponowne przypisanie referencji
#include <iostream>
int main()
{
using namespace std;
int intOne;
int &rSomeRef = intOne;
intOne = 5;
cout << "intOne:\t" << intOne << endl;
cout << "rSomeRef:\t" << rSomeRef << endl;
cout << "&intOne:\t" << &intOne << endl;
cout << "&rSomeRef:\t" << &rSomeRef << endl;
int intTwo = 8;
rSomeRef = intTwo; // to nie to o czym mylisz!
cout << "\nintOne:\t" << intOne << endl;
cout << "intTwo:\t" << intTwo << endl;
cout << "rSomeRef:\t" << rSomeRef << endl;
cout << "&intOne:\t" << &intOne << endl;
cout << "&intTwo:\t" << &intTwo << endl;
cout << "&rSomeRef:\t" << &rSomeRef << endl;

25:
26:

return 0;
}

Wynik
intOne: 5
rSomeRef:
&intOne:
&rSomeRef:

5
0012FF7C
0012FF7C

intOne: 8
intTwo: 8
rSomeRef:
&intOne:
&intTwo:
&rSomeRef:

8
0012FF7C
0012FF74
0012FF7C

Analiza
Take w tym programie zostay zadeklarowane (w liniach 8. i 9.) zmienna cakowita i referencja
do niej. W linii 11. zmiennej jest przypisywana warto 5, po czym w liniach od 12. do 15.
wypisywane s wartoci i ich adresy.
W linii 17. tworzona jest nowa zmienna, intTwo, inicjalizowana wartoci 8. W linii 18.
programista prbuje zmieni przypisanie referencji rSomeRef tak, aby odnosia si do zmiennej
intTwo, lecz mu si to nie udaje. W rzeczywistoci referencja rSomeRef w dalszym cigu jest
aliasem dla zmiennej intOne, wic to przypisanie stanowi ekwiwalent dla:
intOne = intTwo;

Potwierdzaj to wypisywane w liniach 19. do 21. komunikaty, pokazujce wartoci zmiennej


intOne i referencji rSomeRef. Ich wartoci s takie same, jak warto zmiennej intTwo. W
rzeczywistoci, gdy w liniach od 22. do 24. s wypisywane adresy, okazuje si, e rSomeRef w
dalszym cigu odnosi si do zmiennej intOne, a nie do zmiennej intTwo.
TAK

NIE

W celu stworzenia aliasu do obiektu uywaj


referencji.

Nie zmieniaj przypisania referencji.

Inicjalizuj wszystkie referencje.

Nie myl operatora adresu z operatorem


referencji.

Do czego mog odnosi si referencje?


Referencje mog odnosi si do kadego z obiektw, take do obiektw zdefiniowanych przez
uytkownika. Zwr uwag, e referencja odnosi si do obiektu, a nie do klasy, do ktrej ten
obiekt naley. Nie moesz napisa:
int & rIntRef = int; // le

Musisz zainicjalizowa referencj rIntRef tak, aby odnosia si do konkretnej zmiennej


cakowitej, na przykad:
int howBig = 200;
int & rIntRef = howBig;

W ten sam sposbNie moesz zainicjalizowa te referencji do klasy CAT:


CAT & rCatRef = CAT; // le

Musisz zainicjalizowa referencj rIntCatRef tak, aby odnosia si do konkretnego egzemplarza


tej klasy:
CAT mruczek;
CAT & rCatRef = mruczek;

Referencje do obiektw s uywane w taki sam sposb, jak obiekty. Dane i funkcje skadowe s
dostpne poprzez ten sam operator dostpu do skadowych (.) i, podobnie jak w typach
wbudowanych, referencja dziaa jak inna nazwa obiektu. Ilustruje to listing 9.4.
Listing 9.4. Referencje do obiektw
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:

// Listing 9.4
// Referencje do obiektw klas
#include <iostream>
class SimpleCat
{
public:
SimpleCat (int age, int weight);
~SimpleCat() {}
int GetAge() { return itsAge; }
int GetWeight() { return itsWeight; }
private:
int itsAge;
int itsWeight;
};
SimpleCat::SimpleCat(int age, int weight)

18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:

{
itsAge = age;
itsWeight = weight;
}
int main()
{
SimpleCat Mruczek(5,8);
SimpleCat & rCat = Mruczek;
std::cout
std::cout
std::cout
std::cout
return 0;

<<
<<
<<
<<

"Mruczek ma: ";


Mruczek.GetAge() << " lat. \n";
"i wazy: ";
rCat.GetWeight() << " funtow. \n";

Wynik
Mruczek ma: 5 lat.
i wazy: 8 funtow.

Analiza
W linii 25. zmienna Mruczek jest deklarowana jako obiekt klasy SimpleCat (prostyzwyky kot).
W linii 26. jest deklarowana referencja rCat do obiektu klasy SimpleCat, ktra odnosi si do
obiektu Mruczek. W liniach 29. i 31. s wykorzystywane akcesory klasy SimpleCat, najpierw
poprzez obiekt klasy, a potem poprzez referencj. Zwr uwag, e dostp do nich jest identyczny.
Take w tym przypadku referencja jest inn nazw (aliasem) rzeczywistego obiektu.

Referencje

Referencj deklaruje si, zapisujc typ, operator referencji (&) oraz nazw referencji. Referencje
musz by inicjalizowane w trakcie ich tworzenia.

Przykad 1
int hisAge;
int &rAge = hisAge;

Przykad 2
CAT Filemon;
CAT &rCatRef = Filemon;

PusteZerowe wskaniki i pustezerowe


referencje
Gdy wskaniki nie s inicjalizowane lub zostan zwolnione, powinno si im przypisa warto
zerow null (0). W przypadku referencji sytuacja wyglda inaczej. Referencja nie moe by
pustazerowa, a program zawierajcy referencj do nie istniejcego (czyli pustego) obiektu, jest
uwaany za niewaciwy. Gdy program jest niewaciwy, moe zdarzy si prawie wszystko. Moe
si zdarzy, e taki program dziaa, ale rwnie dobrze moe te usun wszystkie pliki z dysku.
Wikszo kompilatorw obsuguje puste obiekty, powodujc zaamanie programu tylko wtedy,
gdy sprbujesz uy takiego obiektu. Obsuga pustych obiektw nie jest dobrym pomysem. Gdy
przeniesiesz program do innego komputera lub kompilatora, puste obiekty mog spowodowa
tajemnicze bdy w dziaaniu programu.

Przekazywanie argumentw funkcji przez


referencj
Z rozdziau 5., Funkcje, dowiedziae si, e funkcje maj dwa ograniczenia: argumenty s
przekazywane przez warto, a funkcja moe zwrci tylko jedn warto.
Przekazywanie argumentw funkcji poprzez referencj moe zlikwidowa oba te ograniczenia. W
C++, przekazywanie przez referencj odbywa si na dwa sposoby: z wykorzystaniem wskanikw
i z wykorzystaniem referencji. Zauwa rnic: przekazujesz poprzez referencj, uywajc
wskanika lub przekazujesz poprzez referencj, uywajc referencji.
Skadnia uycia wskanika jest inna ni uycia referencji, ale oglny efekt jest taki sam. W duym
uproszczeniu mona powiedzie, e zamiast tworzy w funkcji kopi przekazywanego obiektu,
program przekazuje jej obiekt oryginalny.
Z rozdziau 5. dowiedziae si, e argumenty funkcji s im przekazywane poprzez stos. Gdy
funkcja otrzymuje warto poprzez referencj (z uyciem wskanika lub referencji), na stosie
umieszczany jest adres obiektu, a nie cay obiekt.
W niektrych komputerach adres jest przechowywany w rejestrze i nie jest umieszczany na stosie.
Kompilator wie, jak odwoa si do oryginalnego obiektu, wic zmiany s dokonywane w tym
obiekcie, a nie w jego kopii.
Przekazanie obiektu przez referencj umoliwia funkcji dokonywanie zmian w tym obiekcie.
Przypomnij sobie, e listing 5.5 z rozdziau pitego pokazywa, e wywoanie funkcji swap() nie
miao wpywu na wartoci w funkcji wywoujcej. Listing 5.5 zosta tu dla wygody
odtworzonypowtrzony jako listing 9.5.
Listing 9.5. Przykad przekazywania przez warto
0:
1:
2:
3:

//Listing 9.5 Demonstruje przekazywanie przez warto


#include <iostream>

4:
5:
6:
7:
8:
9:
10:
11:
" y:
12:
13:
y: "
14:
15:
16:
17:
18:
19:
20:
21:
<< y
22:
23:
24:
25:
26:
27:
y <<
28:
29:

using namespace std;


void swap(int x, int y);
int main()
{
int x = 5, y = 10;
cout << "Funkcja main(). Przed funkcja swap(), x: " << x <<
" << y << "\n";
swap(x,y);
cout << "Funkcja main(). Po funkcji swap(), x: " << x << "
<< y << "\n";
return 0;
}
void swap (int x, int y)
{
int temp;
cout << "Funkcja swap(). Przed zamiana, x: " << x << " y: "
<< "\n";
temp = x;
x = y;
y = temp;
cout << "Funkcja swap(). Po zamianie, x: " << x << " y: " <<
"\n";
}

Wynik
Funkcja
Funkcja
Funkcja
Funkcja

main().
swap().
swap().
main().

Przed funkcja swap(), x: 5 y: 10


Przed zamiana, x: 5 y: 10
Po zamianie, x: 10 y: 5
Po funkcji swap(), x: 5 y: 10

Analiza
Wewntrz funkcji main() program inicjalizuje dwie zmienne i przekazuje je funkcji swap()
(zamie), ktra wydaje si je zamienia. Jednak gdy ponownie sprawdzimy ich wartoci w funkcji
main(), okae si, e nie ulegy one zmianie!
Problem polega na tym, e zmienne x i y s przekazywane funkcji swap() poprzez warto.
Oznacza to, e wewntrz tej funkcji s tworzone ich lokalne kopie. Nam potrzebne jest
przekazanie zmiennych x i y przez referencj.
W C++ istniej dwie moliwoci rozwizania tego problemu: parametry funkcji swap() moesz
zamieni na wskaniki do oryginalnych wartoci, lub przekaza referencje do pierwotnych
wartoci.

Tworzenie funkcji swap() otrzymujcej wskaniki


Przekazujc wskanik, przekazujesz adres obiektu, dlatego funkcja moe manipulowa wartoci
znajdujc si pod tym adresem. Aby za pomoc wskanikw umoliwi funkcji swap() zamian

wartoci swoich argumentw, powiniene zadeklarowa j jako przyjmujc dwa wskaniki do


zmiennych cakowitych. Nastpnie, poprzez wyuskanie wskanikw (czyli dereferencj), moesz
zamieni wartoci zmiennych miejscami miejscami. Demonstruje to listing 9.6.
Listing 9.6. Przekazywanie przez referencj za pomoc wskanikw
0: //Listing 9.6 Demonstruje przekazywanie przez referencj
1:
2: #include <iostream>
3:
4: using namespace std;
5: void swap(int *x, int *y);
6:
7: int main()
8: {
9:
int x = 5, y = 10;
10:
11:
cout << "Funkcja main(). Przed funkcja swap(), x: " << x <<
" y: " << y << "\n";
12:
swap(&x,&y);
13:
cout << "Funkcja main(). Po funkcji swap(), x: " << x << "
y: " << y << "\n";
14:
return 0;
15: }
16:
17: void swap (int *px, int *py)
18: {
19:
int temp;
20:
21:
cout << "Funkcja swap(). Przed zamiana, *px: " << *px <<
22:
" *py: " << *py << "\n";
23:
24:
temp = *px;
25:
*px = *py;
26:
*py = temp;
27:
28:
cout << "Funkcja swap(). Po zamianie, *px: " << *px <<
29:
" *py: " << *py << "\n";
30:
31: }

Wynik
Funkcja
Funkcja
Funkcja
Funkcja

main().
swap().
swap().
main().

Przed funkcja swap(), x: 5 y: 10


Przed zamiana, *px: 5 *py: 10
Po zamianie, *px: 10 *py: 5
Po funkcji swap(), x: 10 y: 5

Analiza
Udao si! W linii 5. zosta zmieniony prototyp funkcji swap(), w ktrym zadeklarowano e oba
parametry funkcji s wskanikami do zmiennych typu int, a nie zmiennymi tego typu. Gdy w
linii 12. nastpuje wywoanie funkcji swap(), jako argumenty s jej przekazywane adresy
zmiennych x i y.

W linii 19., w funkcji swap(),deklarowana jest lokalna zmienna temp1. Ta zmienna nie musi by
wskanikiem; w czasie ycia funkcji swap() przechowuje ona warto *px (tj. warto zmiennej
x zadeklarowanej w funkcji wywoujcej). Gdy funkcja swap() zakoczy dziaanie, zmienna
temp nie bdzie ju potrzebna.
W linii 24. zmiennej temp przypisywana jest warto wskazywana przez px. W linii 25. zmiennej
wskazywanej przez px przypisywana jest warto wskazywana przez py. W linii 26.
zmiennejwarto umieszczona przechowywana w zmiennej temp (tj. oryginalna warto
wskazywana przez px) jest umieszczana w zmiennej wskazywanej przez py.
Efektem przeprowadzonych przez nas dziaa jest zamiana wartoci tych zmiennych, ktrych
adresy zostay przekazane do funkcji swap().

Implementacja funkcji swap() za pomoc referencji


Przedstawiony wczeniej program dziaa, ale skadnia pokazanej w nim funkcji swap() ma dwie
wady. Po pierwsze, konieczno wyuskiwania wskanikw wewntrz funkcji swap() uatwia
popenieni bdw i zmniejsza czytelno programu. Po drugie, konieczno przekazania adresw
zmiennych przez funkcj wywoujc zdradza uytkownikom sposb dziaania funkcji swap().
W jzyku C++ uytkownik funkcji nie ma moliwoci poznania sposobu jej dziaania.
Przekazywanie wskanikw do argumentwparametrw oznacza konieczno odpowiednich
przygotowa w funkcji wywoujcej, a przecie przygotowania te powinny nalee do
obowizkw funkcji wywoywanej. W listingu 9.7 funkcja swap() zostaa ponownie przepisana,
tym razem z zastosowaniem referencji, a nie wskanikw.
Listing 9.7. Funkcja swap() przepisana z zastosowaniem referencji
0: //Listing 9.7 Demonstruje przekazywanie przez referencj
1: // z zastosowaniem referencji!
2:
3: #include <iostream>
4:
5: using namespace std;
6: void swap(int &x, int &y);
7:
8: int main()
9: {
10:
int x = 5, y = 10;
11:
12:
cout << "Funkcja main(). Przed funkcja swap(), x: " << x <<
" y: "
13:
<< y << "\n";
14:
15:
swap(x,y);
16:
17:
cout << "Funkcja main(). Po funkcji swap(), x: " << x << "
y: "
18:
<< y << "\n";
19:
20:
return 0;
21: }
1

Ta nazwa jest skrtem od sowa temporary (tymczasowa) i bardzo czsto wystpuje w programach.
przyp.tum.

22:
23:
24:
25:
26:
27:

void swap (int &rx, int &ry)


{
int temp;
cout << "Funkcja swap(). Przed zamiana, rx: " << rx << " ry:

"
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:

<< ry << "\n";


temp = rx;
rx = ry;
ry = temp;
cout << "Funkcja swap(). Po zamianie, rx: " << rx << " ry: "
<< ry << "\n";
}

Wynik
Funkcja
Funkcja
Funkcja
Funkcja

main().
swap().
swap().
main().

Przed funkcja swap(), x: 5 y: 10


Przed zamiana, rx: 5 ry: 10
Po zamianie, rx: 10 ry: 5
Po funkcji swap(), x: 10 y: 5

Analiza
Podobnie, jak w przykadzie ze wskanikami, take i tu (w linii 10.) deklarowane s dwie
zmienne, ktrych wartoci wypisywane s w linii 12. W linii 15. nastpuje wywoanie funkcji
swap(), ale zwr uwag, e tym razem nie s przekazywane adresy zmiennych x i y, lecz same
zmienne. Funkcja wywoujca po prostu przekazuje zmienne.
Gdy wywoywana jest funkcja swap(), dziaanie programu przechodzi do linii 23., w ktrej
zmienne zostaj zidentyfikowane jako referencje. Ich wartoci s wypisywane w linii 27., zwr
uwag, e nie wymagaj one przeprowadzania adnych dodatkowych operacji. S to aliasy
oryginalnych wartoci, ktre mog zosta uyte jako te wartoci.
W liniach od 30. do 32. wartoci s zamieniane, a nastpnie ponownie wypisywane w linii 35.
Wykonanie programu wraca do funkcji wywoujcej, zatem funkcja main() (w linii 17.)
ponownie wypisuje wartoci zmiennych. Poniewa parametry funkcji swap() zostay
zadeklarowane jako referencje, wartoci w funkcji main() zostay przekazane przez referencj,
dlatego s zamienione rwnie w tej funkcji.
Referencje uatwiaj korzystanie z normalnych zmiennych, zachowujc przy tym moliwo
przekazywania argumentw poprzez referencj.

Nagwki i prototypy funkcji


Listing 9.6 zawiera funkcj swap(), uywajc wskanikw, za listing 9.7 zawiera t sam
funkcj uywajc referencji. Stosowanie funkcji korzystajcej z referencji jest atwiejsze;
atwiejsze jest take zrozumienie kodu, ale skd funkcja wywoujca wie, czy wartoci s

przekazywane poprzez warto, czy poprzez referencj? Jako klient (czyli uytkownik) funkcji
swap(), programista musi mie pewno, e funkcja ta faktycznie zamieni swoje parametry.
Oto kolejne zastosowanie prototypw funkcji. Sprawdzajc parametry zadeklarowane w
prototypie, ktry zwykle znajduje si w pliku nagwkowym wraz z innymi prototypami,
programista wie, e wartoci przekazywane do funkcji swap() s przekazywane poprzez
referencj i wie, jak powinien ich uy.
Gdyby funkcja swap() bya czci klasy, informacji tych dostarczyaby deklaracja klasy, take
umieszczana zwykle w pliku nagwkowym.
W jzyku C++ wszystkich informacji potrzebnych klientom klas i funkcji mog dostarczy pliki
nagwkowe; peni one rol interfejsu dla klasy lub funkcji. Implementacja jest natomiast
ukrywana przed klientem. Dziki temu programista moe skupi si na analizowanym aktualnie
problemie i korzysta z klasy lub funkcji bez zastanawiania si, w jaki sposb ona dziaa.
Gdy John Roebling projektowa Most Brookliski, zajmowa si takimi szczegami, jak sposb
wylewania betonu czy metoda produkcji drutu do kabli nonych. Zna kady fizyczny i chemiczny
proces zwizany z tworzeniem materiaw przeznaczonych do budowy mostu. Obecnie
inynierowie oszczdzaj czas, uywajc dobrze znanych materiaw budowlanych, nie
zastanawiajc si, w jaki sposb s one tworzone przez producenta.
Jzyka C++ umoliwia programistom korzystanie z dobrze znanych klas i funkcji, bez
koniecznoci zajmowania si szczegami ich dziaania. Te czci skadowe zostay
zoonemog zosta poczone w celu stworzenia programu (podobnie jak czone s kable, rury,
klamry i inne czci w celu stworzenia mostu czy budynku).
Inynier przegldajcy specyfikacj betonu w celu poznania jego wytrzymaoci, ciaru
wasnego, czasu krzepnicia, itd., a programista przeglda interfejs funkcji lub klasy w celu
poznania usug, jakich ona dostarcza, parametrw, ktrych potrzebuje i wartoci, jakie zwraca.

Zwracanie kilku wartoci


Jak wspominalimy wczeniej, funkcja moe zwraca (bezporednio) tylko jedn warto. Co
zrobi, gdy chcesz otrzyma od funkcji dwie wartoci? Jednym ze sposobw rozwizania tego
problemu jest przekazanie funkcji dwch obiektw poprzez referencje. Funkcja moe wtedy
wypeni te obiekty waciwymi wartociami. Poniewa przekazywanie przez referencj
umoliwia funkcji zmian pierwotnego obiektu, moe ona zwrci dwie oddzielne informacje.
Dziki temu warto zwracana przez funkcj bezporednio moe zosta wykorzystana w inny
sposb, na przykad do zgoszenia informacji o bdach.
Take w tym przypadku do zwracania wartoci w tenten sposb mona uy wskanikw lub
referencji. Listing 9.8 przedstawia funkcj zwracajc trzy wartoci: dwie zwracane jako
parametry majce posta przekazywane przez wskanikw i jedn jako warto zwracan otn
funkcji.
Listing 9.8. Zwracanie wartoci poprzez wskaniki
0:
1:
2:
3:

//Listing 9.8
// Zwracanie kilku wartoci z funkcji
#include <iostream>

4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:

using namespace std;


short Factor(int n, int* pSquared, int* pCubed);
int main()
{
int number, squared, cubed;
short error;
cout << "Wpisz liczbe (0 - 20): ";
cin >> number;
error = Factor(number, &squared, &cubed);
if (!error)
{
cout <<
cout <<
cout <<
}
else
cout <<
return 0;

"liczba: " << number << "\n";


"do kwadratu: " << squared << "\n";
"do trzeciej potegi: " << cubed
<< "\n";
"Napotkano blad!!\n";

}
short Factor(int n, int *pSquared, int *pCubed)
{
short Value = 0;
if (n > 20)
Value = 1;
else
{
*pSquared = n*n;
*pCubed = n*n*n;
Value = 0;
}
return Value;
}

Wynik
Wpisz liczbe (0 - 20): 3
liczba: 3
do kwadratu: 9
do trzeciej potegi: 27

Analiza
W linii 10. zostay zadeklarowane trzy krtkie zmienne cakowite: number (liczba), squared (do
kwadratu) oraz cubed (do trzeciej potgi). Warto zmiennej number jest wpisywana przez
uytkownika. Ta liczba oraz adresy zmiennych squared i cubed s przekazywane do funkcji
Factor() (czynnik).
Funkcja Factor() sprawdza pierwszy parametr, ktry jest przekazywany przez warto. Jeli jest
wikszy od 20 (maksymalnej wartoci, jak moe obsuy funkcja), zwracanej wartoci Value
(warto) przypisywany jest prosty kod bdu. Zwr uwag, e warto zwracana funkcjia
Factor() jest zarezerwowana dla moe zwrotuci t albo tej wartoci bdu lubalbo wartoci
0, oznaczajcej, e wszystko poszo dobrze; warto t funkcja zwraca w linii 40.

Obliczane w funkcji wartoci, czyli podniesiona do potgi drugiej i do trzeciej potgi liczba, s
zwracane nie poprzez instrukcj return, ale bezporednio poprzez zmian wartoci zmiennych
wskazywanych przez wskaniki przekazane do funkcji.
W liniach 36. i 37. zmiennym wskazywanym poprzez wartociomwskaniki przypisywane s
wyobliczone wartoci wczeniej. W linii 38. zmiennej Value jest przypisywany kod sukcesu,
ktry jest zwracany w linii 40.
Jednym z ulepsze wprowadzonych do tej funkcji mogaoby by napisaniedeklaracja:
enum ERROR_VALUE { SUCCESS, FAILURE};

Dziki temu, zamiast zwraca wartoci 0 lub 1, program mgby zwraca odpowiedni warto
sta typu wyliczeniowegoa ERROR_VALUE (warto bdu), czyli albo SUCCESS (sukces) lubalbo
FAILURE (poraka).

Zwracanie wartoci przez referencj


Cho program z listingu 9.8 dziaa poprawnie, byby atwiejszy w uyciu i modyfikacji, gdyby
zamiast wskanikw zastosowano w nim referencje. Listing 9.9 przedstawia ten sam program
przepisany tak, aby wykorzystywa referencje i typ wyliczeniowye ERR_CODE (kod bdu).
Listing 9.9. Listing 9.8 przepisany z zastosowaniem referencji
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:

//Listing 9.9
// Zwracanie kilku wartoci z funkcji
// z zastosowaniem referencji
#include <iostream>
using namespace std;
typedef unsigned short USHORT;
enum ERR_CODE { SUCCESS, ERROR };
ERR_CODE Factor(USHORT, USHORT&, USHORT&);
int main()
{
USHORT number, squared, cubed;
ERR_CODE result;
cout << "Wpisz liczbe (0 - 20): ";
cin >> number;
result = Factor(number, squared, cubed);
if (result == SUCCESS)
{
cout << "liczba: " << number << "\n";
cout << "do kwadratu: " << squared << "\n";
cout << "do trzeciej potegi: " << cubed
<< "\n";
}
else
cout << "Napotkano blad!!\n";
return 0;

31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:

}
ERR_CODE Factor(USHORT n, USHORT &rSquared, USHORT &rCubed)
{
if (n > 20)
return ERROR;
// prosty kod bdu
else
{
rSquared = n*n;
rCubed = n*n*n;
return SUCCESS;
}
}

Wynik
Wpisz liczbe (0 - 20): 3
liczba: 3
do kwadratu: 9
do trzeciej potegi: 27

Analiza
Listing 9.9 jest prawie identyczny z listingiem 9.8, z dwiema rnicami. Dziki zastosowaniu
wyliczenia ERR_CODE zgaszanie bdw w liniach 36. i 41., a take ich obsuga w linii 22., s
bardziej przejrzyste.
Istotn zmian jest to, e tym razem funkcja Factor() zostaa zadeklarowana jako przyjmujca
referencje, a nie wskaniki, do zmiennych squared i cubed. Dziki temu operowanie tymi
parametrami jest prostsze i bardziej zrozumiae.

Przekazywanie przez referencj zwiksza


efektywno dziaania programu
Za kadym razem, gdy przekazujesz obiekt do funkcji poprzez warto, tworzona jest kopia tego
obiektu. Za kadym razem, gdy zwracasz z funkcji obiekt poprzez warto, tworzona jest kolejna
kopia.
Z rozdziau 5. dowiedziae si, e obiekty te s kopiowane na stos. Wymaga to sporej iloci czasu
i pamici. W przypadku niewielkich obiektw, takich jak wbudowane typy cakowite, koszt ten
jest niewielki.
Jednak w przypadku wikszych, zdefiniowanych przez uytkownika obiektw, ten koszt staje si
duo wikszy. Rozmiar zdefiniowanego przez uytkownika obiektu umieszczonego na stosie jest
sum rozmiarw wszystkich jego zmiennych skadowych. Kada z tych zmiennych take moe
by obiektem zdefiniowanym przez uytkownika, a przekazywanie takich rozbudowanych struktur
przez kopiowanie ich na stos moe by mao wydajne i zuywa duoe pamici.
Pojawiaj si take dodatkowe koszty. W przypadku tworzonych przez ciebie klas, za kadym
razem gdy kompilator tworzy kopi tymczasow, wywoywany jest specjalny konstruktor:
konstruktor kopiujcyi. Dziaanie konstruktorw kopiujcychi i metody ich tworzenia zostan

omwione w nastpnym rozdziale, na razie wystarczy, e bdziesz wiedzia, e konstruktor taki


jest wywoywany za kadym razem, gdy na stosie jest umieszczana tymczasowa kopia obiektu.
Gdy niszczony jest obiekt tymczasowy (na zakoczenie dziaania funkcji), wywoywany jest
destruktor obiektu. Jeli obiekt jest zwracany z funkcji poprzez warto, konieczne jest stworzenie
i zniszczenie kopii take i tego obiektu.
W przypadku duych obiektw, takie wywoania konstruktorw i destruktorw mog by
kosztowne ze wzgldu na szybko i zuycie pamici. Aby to zilustrowa, listing 9.9 tworzy
okrojony, zdefiniowany przez uytkownika obiekt klasy: SimpleCat. Prawdziwy obiekt byby
wikszy i droszy, ale nasz obiekt wystarczy do pokazania, jak czsto wywoywany jest
konstruktor kopiujcyi oraz destruktor.
Listing 9.10 tworzy obiekt typu SimpleCat, po czym wywouje dwie funkcje. Pierwsza z nich
otrzymuje obiekt poprzez warto i zwraca go rwnie poprzez warto. Druga funkcja otrzymuje
wskanik do obiektu i zwraca take wskanik, bez przekazywania samego obiektu.
Listing 9.10. Przekazywanie obiektw poprzez referencj, za pomoc wskanikw
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:

//Listing 9.10
// Przekazywanie wskanikw do obiektw
#include <iostream>
using namespace std;
class SimpleCat
{
public:
SimpleCat ();
SimpleCat(SimpleCat&);
~SimpleCat();
};

// konstruktor
// konstruktor kopiujcyi
// destruktor

SimpleCat::SimpleCat()
{
cout << "Konstruktor klasy SimpleCat...\n";
}
SimpleCat::SimpleCat(SimpleCat&)
{
cout << "Konstruktor kopiujcyi klasy SimpleCat...\n";
}
SimpleCat::~SimpleCat()
{
cout << "Destruktor klasy SimpleCat...\n";
}
SimpleCat FunctionOne (SimpleCat theCat);
SimpleCat* FunctionTwo (SimpleCat *theCat);
int main()
{
cout << "Tworze obiekt...\n";
SimpleCat Mruczek;
cout << "Wywoluje funkcje FunctionOne...\n";
FunctionOne(Mruczek);
cout << "Wywoluje funkcje FunctionTwo...\n";
FunctionTwo(&Mruczek);
return 0;

41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:

}
// FunctionOne, parametr przekazywany poprzez warto
SimpleCat FunctionOne(SimpleCat theCat)
{
cout << "FunctionOne. Wracam...\n";
return theCat;
}
// FunctionTwo, parametr przekazywany poprzez wskanik
SimpleCat* FunctionTwo (SimpleCat *theCat)
{
cout << "FunctionTwo. Wracam...\n";
return theCat;
}

Wynik
Tworze obiekt...
Konstruktor klasy SimpleCat...
Wywoluje funkcje FunctionOne...
Konstruktor kopiujcyi klasy SimpleCat...
FunctionOne. Wracam...
Konstruktor kopiujcyi klasy SimpleCat...
Destruktor klasy SimpleCat...
Destruktor klasy SimpleCat...
Wywoluje funkcje FunctionTwo...
FunctionTwo. Wracam...
Destruktor klasy SimpleCat...

Analiza
W liniach od 6. do 12. zostaa zadeklarowana bardzo uproszczona klasa SimpleCat. Zarwno
konstruktor, jak i konstruktor kopiujcyi oraz destruktor wypisuj odpowiednie dla siebie
komunikaty, dziki ktrym wiadomo, w ktrym momencie zostay wywoane.
W linii 34. funkcja main() wypisuje komunikat widoczny w pierwszej linii wyniku. W linii 35.
tworzony jest egzemplarz obiektu klasy SimpleCat. Powoduje to wywoanie konstruktora tej
klasy, co potwierdza druga linia wyniku.
W linii 36. funkcja main() zgasza (poprzez wypisanie komunikatu w trzeciej linii wydruku), e
wywouje funkcj FunctionOne., ktra wypisuje komunikat w trzeciej linii wyniku. Poniewa ta
funkcja otrzymuje obiekt typu SimpleCat przekazywany poprzez warto, na stosie tworzona jest
lokalna dla tej funkcji kopia obiektu klasy SimpleCat. To powoduje wywoanie konstruktora
kopiujcegoi, ktry wypisuje czwart lini wyniku.
Wykonanie programu przechodzi do wywoywanej funkcji, do linii 46., w ktrej wypisywany jest
komunikat informacyjny, stanowicy pit lini wyniku. Nastpnie funkcja wraca i zwraca obiekt
typu SimpleCat poprzez warto. To powoduje utworzenie kolejnej kopii obiektu (cznie
zpoprzez wywoaniem konstruktora kopiujcegoi, wypisujcego te szst lini wyniku).
Warto zwracana przez funkcj FunctionOne() nie jest niczemu przypisywana, wic
tymczasowy obiekt utworzony na stosie jest odrzucany, co powoduje wywoanie destruktora, ktry
wypisuje sidm lini wyniku. Poniewa dziaanie funkcji FunctionOne() si zakoczyo, jej

lokalna kopia obiektu wychodzi z zakresu i jest niszczona; powoduje to wywoanie destruktora i
wypisanie smej linii wyniku.
Program wraca do funkcji main(), w ktrej zostaje teraz wywoana funkcja FunctionTwo(),
lecz tym razem jej parametr jest przekazywany przez referencj. Nie jest tworzona adna kopia,
dlatego nie jest wypisywany aden komunikat konstruktora. Funkcja FunctionTwo() wypisuje
jedynie wasny komunikat w dziesitej linii wyniku, po czym zwraca obiekt typu SimpleCat,
take poprzez wskanik, zatem take tym razem nie jest wywoywany konstruktor ani destruktor.
Program koczy swoje dziaanie i obiekt Mruczek wychodzi z zakresu, powodujc jeszcze jedno
wywoanie destruktora, wypisujcego komunikat w jedenastej linii wyniku.
Poniewa parametr funkcji FunctionOne() jest przekazywany i zwracany przez warto, jej
wywoanie wie si z dwoma wywoaniami konstruktora kopiujcegoi i dwoma wywoaniami
destruktora; natomiast wywoanie funkcji FunctionTwo() nie wymagao wywoania ani
konstruktora, ani destruktora.

Przekazywanie wskanika const


Cho przekazywanie wskanika jest duo bardziej efektywne w funkcji FunctionTwo(), jednak
jest take bardziej niebezpieczne. Funkcja FunctionTwo() nie powinna mie moliwoci zmiany
otrzymanego obiektu SimpleCat, mimo, eale otrzymuje wskanik do tego obiektu. ToTen
wskanik daje jej jednak moliwo zmiany wartoci tego obiektu, co nie jest moliwe w
przypadku przekazywania obiektu przez warto.
Przekazywanie poprzez warto przypomina przekazanie do muzeum reprodukcji arcydziea,
zamiast prawdziwego obrazu. Nawet, gdy do muzeum zakradnie si wandal, orygina nie poniesie
uszczerbku. Przekazywanie poprzez referencj przypomina przesanie do muzeum swojego adresu
domowego i zaproszenie goci do ogldania oryginaw.
Rozwizaniem tego problemu jest przekazanie wskanika do staego (const) obiektu typu
SimpleCat. W ten sposb zabezpieczamy ten obiekt przed wywoywaniem metod tej klasy
innych ni metody typu ni const , chronic go tym samym metod tej klasy, czyli chronimy go
przed zmianami.
Przekazanie referencji typu const umoliwia gociom ogldanie oryginau, ale nie umoliwia
jego modyfikacji. Demonstruje to listing 9.11.
Listing 9.11. Przekazywanie wskanika do obiektu const
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:

//Listing 9.11
// Przekazywanie wskanikw do obiektw
#include <iostream>
using namespace std;
class SimpleCat
{
public:
SimpleCat();
SimpleCat(SimpleCat&);
~SimpleCat();
int GetAge() const { return itsAge; }

14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:

void SetAge(int age) { itsAge = age; }


private:
int itsAge;
};
SimpleCat::SimpleCat()
{
cout << "Konstruktor klasy SimpleCat...\n";
itsAge = 1;
}
SimpleCat::SimpleCat(SimpleCat&)
{
cout << "Konstruktor kopiujcyi klasy SimpleCat...\n";
}
SimpleCat::~SimpleCat()
{
cout << "Destruktor klasy SimpleCat...\n";
}
const SimpleCat * const FunctionTwo
(const SimpleCat * const theCat);
int main()
{
cout << "Tworze obiekt...\n";
SimpleCat Mruczek;
cout << "Mruczek ma " ;
cout << Mruczek.GetAge();
cout << " lat\n";
int age = 5;
Mruczek.SetAge(age);
cout << "Mruczek ma " ;
cout << Mruczek.GetAge();
cout << " lat\n";
cout << "Wywoluje funkcje FunctionTwo...\n";
FunctionTwo(&Mruczek);
cout << "Mruczek ma " ;
cout << Mruczek.GetAge();
cout << " lat\n";
return 0;
}
// functionTwo, otrzymuje wskanik const
const SimpleCat * const FunctionTwo
(const SimpleCat * const theCat)
{
cout << "FunctionTwo. Wracam...\n";
cout << "Mruczek ma teraz " << theCat->GetAge();
cout << " lat \n";
// theCat->SetAge(8);
const!
return theCat;
}

Wynik
Tworze obiekt...
Konstruktor klasy SimpleCat...

Mruczek ma 1 lat
Mruczek ma 5 lat
Wywoluje funkcje FunctionTwo...
FunctionTwo. Wracam...
Mruczek ma teraz 5 lat
Mruczek ma 5 lat
Destruktor klasy SimpleCat...

Analiza
Klasa SimpleCat zawiera dwa akcesory: GetAge() w linii 13., bdcy funkcj const oraz
SetAge() w linii 14., nie bdcy funkcj const. Oprcz tego posiada zmienn skadow
itsAge, deklarowan w linii 17.
Konstruktor, konstruktor kopiujcyi oraz destruktor wypisuj odpowiednie komunikaty. Jednak
konstruktor kopiujcyi nie jest wywoywany, gdy obiekt przekazywany jest poprzez referencj i
nie jest tworzona adna kopia. Na pocztku programu, w linii 42., tworzony jest obiekt, a w
liniachi od 43. do 45. jest wypisywany wiek pocztkowy.
W linii 47. zmienna skadowa itsAge jest ustawiana za pomoc akcesora SetAge(), za wynik
jest wypisywany w liniachi od 48. do 50. W tym programie nie jest uywana funkcja
FunctionOne(). i Pposugujemy si tylko funkcj FunctionTwo(). Ulega ona jednak
niewielkiej zmianie; jej nagwek zosta zmodyfikowany tak, e funkcja przyjmuje teraz stay
wskanik do staego obiektu i zwraca stay wskanik do staego obiektu.
Poniewa parametr i warto zwracana otna wci s przekazywane poprzez referencje, nie s
tworzone adne kopie, nie jest zatem wywoywany konstruktor kopiujcyi. Jednak obecnie obiekt
wskazywany w funkcji FunctionTwo() jest obiektem const, wic nie mona wywoywa jego
metod, nie bdcych metodami const, czyli nie mona wywoa jego metody SetAge(). Gdyby
wywoanie tej metody w linii 66. nie zostao umieszczone w komentarzu, program nie
skompilowaby si.
Zwr uwag, e obiekt tworzony w funkcji main() nie jest const, wic moemy dla niego
wywoa funkcj SetAge(). Do funkcji FunctionTwo()przekazywany jest adres tego zwykego
obiektu, ale poniewa deklaracja tej funkcji okrela, e ten parametr jest wskanikiem const do
obiektu const, obiekt ten jest traktowany, jakby by stay!

Referencje jako metoda alternatywna


Listing 9.11 rozwizuje problem tworzenia dodatkowych kopii i w ten sposb zmniejsza ilo
wywoa konstruktora kopiujcegoi i destruktora. Uywa staych wskanikw do staych
obiektw, rozwizujc w ten sposb problem zmiany obiektu przez funkcj. Jednak w dalszym
cigu jest do nieczytelny, gdy obiekty przekazywane do funkcji s wskanikami.
Poniewa wiemy, e ten obiekt nie jest pusty, moemy uatwi sobie prac w funkcji, stosujc
przekazanie przez referencj, a nie przez wskanik. Pokazuje to listing 9.12.
Listing 9.12. Przekazywanie referencji do obiektw
0:
1:
2:
3:
4:

//Listing 9.12
// Przekazywanie wskanikw do obiektw
#include <iostream>

5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:

using namespace std;


class SimpleCat
{
public:
SimpleCat();
SimpleCat(SimpleCat&);
~SimpleCat();
int GetAge() const { return itsAge; }
void SetAge(int age) { itsAge = age; }
private:
int itsAge;
};
SimpleCat::SimpleCat()
{
cout << "Konstruktor klasy SimpleCat...\n";
itsAge = 1;
}
SimpleCat::SimpleCat(SimpleCat&)
{
cout << "Konstruktor kopiujcyi klasy SimpleCat...\n";
}
SimpleCat::~SimpleCat()
{
cout << "Destruktor klasy SimpleCat...\n";
}
const SimpleCat & FunctionTwo (const SimpleCat & theCat);
int main()
{
cout << "Tworze obiekt...\n";
SimpleCat Mruczek;
cout << "Mruczek ma " ;
cout << Mruczek.GetAge();
cout << " lat\n";
int age = 5;
Mruczek.SetAge(age);
cout << "Mruczek ma " ;
cout << Mruczek.GetAge();
cout << " lat\n";
cout << "Wywoluje funkcje FunctionTwo...\n";
FunctionTwo(Mruczek);
cout << "Mruczek ma " ;
cout << Mruczek.GetAge();
cout << " lat\n";
return 0;
}
// functionTwo, otrzymuje referencj do obiektu const
const SimpleCat & FunctionTwo (const SimpleCat & theCat)
{
cout << "FunctionTwo. Wracam...\n";
cout << "Mruczek ma teraz " << theCat.GetAge();
cout << " lat \n";
// theCat.SetAge(8);
const!
return theCat;

66:

Wynik
Tworze obiekt...
Konstruktor klasy SimpleCat...
Mruczek ma 1 lat
Mruczek ma 5 lat
Wywoluje funkcje FunctionTwo...
FunctionTwo. Wracam...
Mruczek ma teraz 5 lat
Mruczek ma 5 lat
Destruktor klasy SimpleCat...

Analiza
Wynik jest identyczny z wynikiem z listingu 9.11. Jedyn istotn rnic w programie jest to, e
obecnie funkcja FunctionTwo() otrzymuje i zwraca referencj do staego obiektu. Take tym
razem praca z referencjami jest nieco prostsza od pracy ze wskanikami, a na dodatek zapewnia t
sam efektywno oraz bezpieczestwo obiektu const.
Referencje const

Programici C++ zwykle nie uznaj rnicy pomidzy sta referencj do obiektu typu
SimpleCat a referencj do staego obiektu typu SimpleCat. Referencje nigdy nie mog
otrzyma ponownego przypisania i odnosi si do innego obiektu, wic s zawsze stae. Jeli
sowo kluczowe const zostanie zastosowane w odniesieniu do referencji, sprawi, e to obiekt
zwizany z referencj staje si stay.

Kiedy uywa wskanikw, a kiedy referencji


Programici C++ zdecydowanie przedkadaj referencje nad wskaniki. Referencje s bardziej
przejrzyste i atwiejsze w uyciu, ponadto lepiej ukrywaj szczegy implementacji, co moglimy
zobaczy w poprzednim przykadzie.
Nie mona zmienia obiektu docelowego referencji. Jeli chcesz najpierw wskaza na jeden
obiekt, a potem na inny, musisz uy wskanika. Referencje nie mog by zerowe puste, wic jeli
istnieje jakakolwiek moliwo, e dany obiekt bdzie pusty (tzn., e moe przesta istnie), nie
moesz uy referencji. Musisz uy wskanika.
To ostatnie zagadnienie dotyczy operatora new. Gdy new nie moe zaalokowa pamici na stercie,
zwraca wskanik null (wskanik zerowy, czyli pusty). Poniewa referencje nie mog by puste,
nie wolno ci przypisa referencji do tej pamici, dopki nie upewnisz si, e nie jest pusta.
Waciwy sposb przypisania pokazuje poniszy przykad:
int *pInt = new int;
if (pInt != NULL)
int &rInt = *pInt;

W tym przykadzie deklarowany jest wskanik pInt do typu int; jest on inicjalizowany adresem
pamici zwracanym przez operator new. Nastpnie jest sprawdzany adres w pInt i jeli nie jest on
pusty, wyuskiwany jest wskanik. Rezultatem wyuskania wskanika do typu int jest obiekt int,
wic referencja rInt jest inicjalizowana jako odnoszca si do tego obiektu. W efekcie, referencja
rInt staje si aliasem do wartoci int o adresie zwrconym przez operator new.
TAK

NIE

Jeli jest to moliwe, przekazuje parametry


przez wartoreferencj.

Nie uywaj wskanikw tam, gdzie mona uy


referencji.

Jeli jest to moliwe, przekazuj przez referencj


warto zwracan otnprzez funkcj.
Jeli jest to moliwe, uywaj const do ochrony
referencji i wskanikw.

czenie referencji i wskanikw


Dozwolone jest jednoczesne deklarowanie wskanikw oraz referencji na tej samej licie
parametrw funkcji, a take obiektw przekazywanych przez warto. Na przykad:
CAT * SomeFunction (Person &theOwner, House *theHouse, int age);

Ta deklaracja informuje, e funkcja SomeFunction ma trzy parametry. Pierwszy z nich jest


referencja do obiektu klasy Person (osoba), drugim jest wskanik do obiektu klasy House
(dom), za trzecim jest warto typu int. Funkcja zwraca wskanik do obiektu klasy CAT.
Pytanie, gdzie powinien zosta umieszczony operator referencji (&) lub wskanika (*), jest bardzo
kontrowersyjne. Moesz zastosowa ktry z poniszych zapisw:
1: CAT& rMruczek;
2: CAT & rMruczek;
3: CAT &rMruczek;

UWAGA Biae spacje s cakowicie ignorowane, dlatego wszdzie tam, gdzie mona umieci
spacj, mona take umieci dowoln ilo innych spacji, tabulatorw czy nowych linii.

Jeli powysze zapisy Pozostawiajc zagadnienia wyraes rwnowane, ktry z zapisw


nich jest najlepszy? Oto argumenty przemawiajce za wszystkimi trzema:

Argumentem przemawiajcym za przypadkiem 1. jest to, e rMruczek jest zmienn, ktrej


nazw jest rMruczek, za typ moe by traktowany jako referencja do obiektu klasy CAT.
Zgodnie z t argumentacj, & powinno znale si przy typie.

Argumentem przeciwko przypadkowi 1. jest to, e typem jest klasa CAT. Symbol & jest czci
deklaratora zawierajcego nazw klasy i znak ampersand (&). Jednak umieszczenie & przy
CAT moe spowodowa wystpienie poniszego bdu:
CAT& rMruczek, rFilemon;

Szybkie sprawdzenie tej linii moe doprowadzi ci do odkrycia, e zarwno rMruczek, jak i
rFilemon s referencjami do obiektw klasy CAT, ale w rzeczywistoci tak nie jest. Ta
deklaracja informuje, e rMruczek jest referencj do klasy CAT, za rFilemon (mimo
zastosowanego przedrostka) nie jest referencj, lecz zwykym obiektem klasy CAT. T
deklaracj naley przepisa nastpujco:
CAT

&rMruczek, rFilemon;

Wniosek pyncy z powyszych rozwaa brzmi nastpujco: deklaracje referencji i zmiennych


nigdy nie powinny wystpowa w tej samej linii. Oto poprawny zapis:
CAT& rMruczek;
CAT
Filemon;

Wielu programistw optuje za zastosowaniem operatora porodku, tak jak pokazuje przypadek
2.

Oczywicie, wszystko, co powiedziano dotd na temat operatora referencji (&), odnosi si take
do operatora wskanika (*). Naley zdawa sobie spraw, e styl zapisu zaley od programisty.
Wybierz wic styl, ktry ci odpowiada i konsekwentnie stosuj go w programach; przejrzysto
kodu jest w kocu jednym z twoich gwnych celw.

Deklarujc referencje i wskaniki, wielu programistw przestrzega nastpujcych konwencji:

1.

Umieszczaj znak ampersand lub gwiazdk porodku, ze spacj po obu stronach.

2.

Nigdy nie deklaruj w tej samej linii referencji, wskanikw i zmiennych.

Nie pozwl funkcji zwraca referencji do


obiektu, ktrego nie ma w zakresie!
Gdy programici C++ naucz si korzysta z referencji, przejawiaj tendencj do uywania ich
bez zastanowienia, wszdzie, gdzie tylko si da. Mona z tym przesadzi. Pamitaj, e referencja
jest zawsze aliasem do innego obiektu. Gdy przekazujesz referencje do lub z funkcji, pamitaj, by
zada sobie pytanie: Czym jest obiekt, do ktrego odnosi si referencja, i czy bdzie istnia przez
cay czas, gdy bd z niego korzysta?
Listing 9.13 pokazuje niebezpieczestwo zwrcenia referencji do obiektu, ktry ju nie istnieje.
Listing 9.13. Zwracanie referencji do nieistniejcego obiektu
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:

Wynik

// Listing 9.13
// Zwracanie referencji do obiektu
// ktry ju nie istnieje
#include <iostream>
class SimpleCat
{
public:
SimpleCat (int age, int weight);
~SimpleCat() {}
int GetAge() { return itsAge; }
int GetWeight() { return itsWeight; }
private:
int itsAge;
int itsWeight;
};
SimpleCat::SimpleCat(int age, int weight)
{
itsAge = age;
itsWeight = weight;
}
SimpleCat &TheFunction();
int main()
{
SimpleCat &rCat = TheFunction();
int age = rCat.GetAge();
std::cout << "rCat ma " << age << " lat!\n";
return 0;
}
SimpleCat &TheFunction()
{
SimpleCat Mruczek(5,9);
return Mruczek;
}

Bd kompilacji: prba zwrcenia referencji do lokalnego


obiektu!
OSTRZEENIE Ten program nie skompiluje si z kompilatorem firmy Borland. Skompiluje si
jednak z kompilatorem firmy Microsoft, jednakco mimo wszystko powinno to by uwaane za
bd.

Analiza
W liniach od 7. do 17. deklarowana jest klasa SimpleCat. W linii 29. referencja do klasy
SimpleCat jest inicjalizowana rezultatem wywoania funkcji TheFunction(), zadeklarowanej
w linii 25. jako zwracajca referencj do obiektw klasy SimpleCat.
W ciele funkcji TheFunction() jest deklarowany lokalny obiekt typu SimpleCat; konstruktor
inicjalizuje jego wiek i wag. Nastpnie ten obiekt lokalny jest zwracany poprzez referencj.
Niektre kompilatory s na tyle inteligentne, by wychwyci ten bd i nie pozwoli na
uruchomienie programu. Inne pozwol na jego skompilowanie i uruchomienie, co moe
spowodowa nieprzewidywalne zachowanie komputera.
Gdy funkcja TheFunction() koczy dziaanie, jej obiekt lokalny, Mruczek, jest niszczony
(zapewniam, e bezbolenie). Referencja zwracana przez t funkcj staje si aliasem do
nieistniejcego obiektu, a to powany bd.

Zwracanie referencji do obiektu na stercie


By moe kusi ci rozwizanie problemu z listingu 9.13 modyfikacja funkcji TheFunction()
tak, by tworzya Mruczka na stercie. Dziki temu, gdy funkcja zakoczy dziaanie, Mruczek
bdzie nadal istnia.
W tym miejscu pojawia si nastpujcy problem: co zrobisz z pamici zaalokowan dla obiektu
Mruczek, gdy nie bdzie ju potrzebny? To zagadnienie ilustruje listing 9.14.
Listing 9.14. Wycieki pamici
0:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:

// Listing 9.14
// Unikanie wyciekw pamici
#include <iostream>
class SimpleCat
{
public:
SimpleCat (int age, int weight);
~SimpleCat() {}
int GetAge() { return itsAge; }
int GetWeight() { return itsWeight; }
private:
int itsAge;
int itsWeight;
};
SimpleCat::SimpleCat(int age, int weight)

19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:

{
itsAge = age;
itsWeight = weight;
}
SimpleCat & TheFunction();
int main()
{
SimpleCat & rCat = TheFunction();
int age = rCat.GetAge();
std::cout << "rCat ma " << age << " lat!\n";
std::cout << "&rCat: " << &rCat << std::endl;
// jak si go pozbdziesz z pamici?
SimpleCat * pCat = &rCat;
delete pCat;
// a do czego teraz odnosi si rCat??
return 0;
}
SimpleCat &TheFunction()
{
SimpleCat * pMruczek = new SimpleCat(5,9);
std::cout << "pMruczek: " << pMruczek << std::endl;
return *pMruczek;
}

Wynik
pMruczek: 004800F0
rCat ma 5 lat!
&rCat: 004800F0
OSTRZEENIE Ten program kompiluje si, uruchamia i sprawia wraenie, e dziaa
poprawnie. Jest jednak swego rodzaju bomb zegarow, ktra moe w kadej chwili
wybuchn.

Funkcja TheFunction() zostaa zmieniona tak, e ju nie zwraca referencji do lokalnej


zmiennej. W linii 41. funkcja alokuje pami na stercie i przypisuje jej adres do wskanika. Adres
zawarty w tym wskaniku jest wypisywany w nastpnej linii, po czym wskanik jest wyuskiwany,
a wskazywany przez niego obiekt typu SimpleCat jest zwracany przez referencj.
W linii 28. wynik funkcji TheFunction() jest przypisywany referencji do obiektu klasy
SimpleCat, po czym ta referencja jest uywana w celu uzyskania wieku kota, wypisywanego w
linii 30.
Aby udowodni, e referencja zadeklarowana w funkcji main() odnosi si do obiektu
umieszczonego na stercie przez funkcj TheFunction(), do referencji rCat zosta zastosowany
operator adresu. Oczywicie, wywietla on adres obiektu, do ktrego odnosi si referencja, zgodny
z adresem pamici na stercie.
Jak dotd wszystko jest w porzdku. Ale w jaki sposb moemy zwolni t pami? Nie mona
wywoa operatora delete nadla referencji. Sprytnym rozwizaniem jest utworzenie kolejnego
wskanika i zainicjalizowanie go adresem uzyskanym od referencji rCat. Dziki temu mona
zwolni pami i powstrzyma jej wyciek. Powstaje jednak pewien problem: do czego odnosi

si referencja rCat po wykonaniu linii 34.? Jak ju wspomnielimy, referencja zawsze musi
stanowi alias rzeczywistego obiektu; jeli odnosi si do obiektu pustego (tak, jak w tym
przypadku), program jest bdny.
UWAGA Jeszcze raz naley przypomnie, e program z referencj do pustego obiektu moe si
skompilowa, ale jest bdny i jego dziaanie jest nieprzewidywalne.

Istniej trzy rozwizania tego problemu. Pierwszym jest zadeklarowanie obiektu typu SimpleCat
w linii 28. i zwrot tego obiektu z funkcji TheFunction() poprzez warto. Drugim jest
zadeklarowanie w funkcji TheFunction() obiektu SimpleCat na stercie, lecz ze zwrceniem
wskanika. Wtedy funkcja wywoujca moe sama usun ten wskanik gdy, nie bdzie ju
potrzebowa obiektu.
Trzecim rozwizaniem, tym waciwym, jest zadeklarowanie obiektu w funkcji wywoujcej i
przekazanie go funkcji TheFunction() przez referencj.

Wskanik, wskanik, kto ma wskanik?


Gdy program alokuje pami na stercie, otrzymuje wskanik. Przechowywanie tego wskanika
jest koniecznoci, gdy zostanie on utracony, pami nie bdzie moga zosta zwolniona i
powikszy stanie si tzw. wyciek iem pamici.
W czasie przekazywania bloku pamici pomidzy funkcjami, kto przez cay czas posiada ten
wskanik. Zwykle wartoci w bloku s przekazywane poprzez referencje, za funkcja, ktra
stworzya pami, zajmuje si jej zwolnieniem. Jest to jednak regua poparta dowiadczeniem, a
nie zasada wyryta w kamieniu.
Tworzenie pamici w jednej funkcji i zwalnianie jej w innej moe by niebezpieczne.
Nieporozumienia co do tego, kto posiada wskanik, mog spowodowa dwa nastpujce
problemy: zapomnienie o zwolnieniu wskanika lub dwukrotnie zwolnienie go. W obu
przypadkach jest to powany bd programu. Bezpieczniej jest budowa funkcje tak, by usuway
pami, ktr stworzyy.
Jeli piszesz funkcj, ktra musi stworzy pami, po czym przekaza j funkcji wywoujcej,
zastanw si nad zmian jej interfejsu. Niech funkcja wywoujca sama alokuje pami i
przekazuje j innej funkcji przez referencj. Dziki temu zarzdzanie pamici pozostaje w tej
funkcji, ktra jest przygotowana do jej usunicia.
TAK

NIE

Gdy jeste do tego zmuszony, przekazuj


parametry przez warto.

Nie przekazuj referencji, jeli obiekt, do


ktrego si ona odnosi, moe znale si poza
zakresem.

Gdy jeste do tego zmuszony, zwracaj wynik


funkcji przez warto.

Nie uywaj referencji do pustych obiektw.

Das könnte Ihnen auch gefallen