r09-06.doc

(194 KB) Pobierz
Szablon dla tlumaczy

Rozdział 9.
Referencje

W poprzednim rozdziale poznałeś wskaźniki i dowiedziałeś się, jak za ich pomocą można operować obiektami na stercie oraz jak odwoływać się do obiektów pośrednio. Referencje mają prawie te same możliwości, co wskaźniki, ale posiadają przy tym dużo prostszą składnię.

Z tego rozdziału dowiesz się:

·         czym są referencje,

·         czym różnią się od wskaźników,

·         jak się je tworzy i wykorzystuje,

·         jakie są ich ograniczenia,

·         w jaki sposób przekazywać obiekty i wartości do i z funkcji za pomocą referencji.

Czym jest referencja?

Referencja jest aliasem (inną nazwą); gdy tworzysz referencję, inicjalizujesz ją nazwą innego obiektu, będącego celemu referencji. Od tego momentu referencja działa jak alternatywna nazwa celu. Wszystko, co robisz z referencją, w rzeczywistości jest robione dotyczyz jej obiektuem docelowego.

Referencję tworzy się, zapisując typ obiektu docelowego, operator referencji (&) oraz nazwę referencji.

Nazwy referencji mogą być dowolne, ale wielu programistów woli poprzedzać jej nazwę literą „r”. Jeśli masz zmienną całkowitą o nazwie someInt, możesz stworzyć referencję do niej pisząc:

 

int &rSomeRef = someInt;

 

Odczytuje się to jako: „rSomeRef jest referencją do wartości zmiennej typu int. Ta referencja została zainicjalizowana tak, aby odnosiła się do zmiennej someInt.” Sposób 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ć oczywiście są ze sobą powiązane).

Zastosowanie spacji przed operatorem referencji jest obowiązkowe, użycie spacji pomiędzy operatorem referencji a nazwą zmiennej referencyjnej jest opcjonalne. Tak więc:

int &rSomeRef = someInt;  // ok

int & rSomeRef = someInt; // ok

 

Listing 9.1. Tworzenie referencji i jej użycie

  0:  //Listing 9.1

  1:  // Demonstruje użycie referencji

  2: 

  3:  #include <iostream>

  4: 

  5:  int main()

  6:  {

  7:      using namespace std;

  8:      int  intOne;

  9:      int &rSomeRef = intOne;

10: 

11:      intOne = 5;

12:      cout << "intOne: " << intOne << endl;

13:      cout << "rSomeRef: " << rSomeRef << endl;

14: 

15:      rSomeRef = 7;

16:      cout << "intOne: " << intOne << endl;

17:      cout << "rSomeRef: " << rSomeRef << endl;

18:     

19:      return 0;

20:  }

 

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 odnosiła się do zmiennej intOne. Jeśli zadeklarujesz referencję, lecz jej nie zainicjalizujesz, kompilator zgłosi błąd powstały podczas kompilacji. Referencje muszą być zainicjalizowane.

W linii 11. zmiennej intOne jest przypisywana wartość 5. W liniach 12. i 13. są wypisywane wartości zmiennej intOne i referencji rSomeRef; są one oczywiście takie same.

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

Użycie operatora adresu z referencją

Gdy pobierzesz adres referencji, uzyskasz adres jej celu. Wynika to z natury referencji (są one aliasami dla obiektów docelowych). Pokazuje to listing 9.2.

Listing 9.2. Odczytywanie adresu referencji

  0:  //Listing 9.2

  1:  // Demonstruje użycie referencji

  2: 

  3:  #include <iostream>

  4: 

  5:  int main()

  6:  {

  7:      using namespace std;

  8:      int  intOne;

  9:      int &rSomeRef = intOne;

10: 

11:      intOne = 5;

12:      cout << "intOne: " << intOne << endl;

13:      cout << "rSomeRef: " << rSomeRef << endl;

14: 

15:      cout << "&intOne: "  << &intOne << endl;

16:      cout << "&rSomeRef: " << &rSomeRef << endl;

17: 

18:      return 0;

19:  }

 

Wynik

intOne: 5

rSomeRef: 5

&intOne: 0012FF7C

&rSomeRef: 0012FF7C

 

UWAGA              W twoim komputerze dwie ostatnie linie mogą wyglądać inaczej.

Analiza

W tym przykładzie referencja rSomeRef ponownie odnosi się do zmiennej intOne. Tym razem jednak wypisywane są adresy obu zmiennych; są one identyczne. C++ nie umożliwia dostępu do adresu samej referencji, gdyż jego użycie, w odróżnieniu od użycia adresu zmiennej, nie miałoby sensu. Referencje są inicjalizowane podczas tworzenia i zawsze stanowią synonim dla swojego obiektu docelowego (nawet gdy zostanie zastosowany operator adresu).

Na przykład, jeśli masz klasę o nazwie President, jej egzemplarz możesz zadeklarować następująco:

 

President George_Washington;

 

Możesz 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), będą odnosić się do obiektu George_Washington.

Należy odróżnić symbol & w linii 9. listingu 9.2 (deklarujący referencję o nazwie rSomeRef) od symboli & w liniach 15. i 16., które zwracają adresy zmiennej całkowitej intOne i referencji rSomeRef.

Zwykle w trakcie używania referencji nie używa się operatora adresu. Referencji używa się tak, jak jej zmiennej docelowej. Pokazuje to linia 13.

Nie można zmieniać przypisania referencji

Nawet doświadczonym programistom C++, którzy wiedzą, że nie można zmieniać przypisania referencji, gdyż jest ona aliasem swojego obiektu docelowego, zdarza się próba zmiany jej przypisania. To, co wygląda w takiej sytuacji na ponowne przypisanie referencji, w rzeczywistości jest przypisaniem nowej wartości obiektowi docelowemu. Przedstawia to listing 9.3.

Listing 9.3. Przypisanie do referencji

  0:  //Listing 9.3

  1:  //Ponowne przypisanie referencji

  2: 

  3:  #include <iostream>

  4: 

  5:  int main()

  6:  {

  7:      using namespace std;

  8:      int  intOne;

  9:      int &rSomeRef = intOne;

10: 

11:      intOne = 5;

12:      cout << "intOne:\t" << intOne << endl;

13:      cout << "rSomeRef:\t" << rSomeRef << endl;

14:      cout << "&intOne:\t"  << &intOne << endl;

15:      cout << "&rSomeRef:\t" << &rSomeRef << endl;

16: 

17:      int intTwo = 8;

18:      rSomeRef = intTwo;  // to nie to o czym myślisz!

19:      cout << "\nintOne:\t" << intOne << endl;

20:      cout << "intTwo:\t" << intTwo << endl;

21:      cout << "rSomeRef:\t" << rSomeRef << endl;

22:      cout << "&intOne:\t"  << &intOne << endl;

23:      cout << "&intTwo:\t"  << &intTwo << endl;

24:      cout << "&rSomeRef:\t" << &rSomeRef << endl;

25:      return 0;

26:  }

 

Wynik

intOne: 5

rSomeRef:       5

&intOne:        0012FF7C

&rSomeRef:      0012FF7C

 

intOne: 8

intTwo: 8

rSomeRef:       8

&intOne:        0012FF7C

&intTwo:        0012FF74

&rSomeRef:      0012FF7C

Analiza

Także w tym programie zostały zadeklarowane (w liniach 8. i 9.) zmienna całkowita i referencja do niej. W linii 11. zmiennej jest przypisywana wartość 5, po czym w liniach od 12. do 15. wypisywane są wartości i ich adresy.

W linii 17. tworzona jest nowa zmienna, intTwo, inicjalizowana wartością 8. W linii 18. programista próbuje zmienić przypisanie referencji rSomeRef tak, aby odnosiła się do zmiennej intTwo, lecz mu się to nie udaje. W rzeczywistości referencja rSomeRef w dalszym ciągu jest aliasem dla zmiennej intOne, więc to przypisanie stanowi ekwiwalent dla:

 

intOne = intTwo;

 

Potwierdzają to wypisywane w liniach 19. do 21. komunikaty, pokazujące wartości zmiennej intOne i referencji rSomeRef. Ich wartości są takie same, jak wartość zmiennej intTwo. W rzeczywistości, gdy w liniach od 22. do 24. są wypisywane adresy, okazuje się, że rSomeRef w dalszym ciągu odnosi się do zmiennej intOne, a nie do zmiennej intTwo.

 

TAK

NIE

W celu stworzenia aliasu do obiektu używaj referencji.

Inicjalizuj wszystkie referencje.

Nie zmieniaj przypisania referencji.

Nie myl operatora adresu z operatorem referencji.

 

Do czego mogą odnosić się referencje?

Referencje mogą odnosić się do każdego z obiektów, także do obiektów zdefiniowanych przez użytkownika. Zwróć uwagę, że referencja odnosi się do obiektu, a nie do klasy, do której ten obiekt należy. Nie możesz napisać:

 

int & rIntRef = int; // źle

 

Musisz zainicjalizować referencję rIntRef tak, aby odnosiła się do konkretnej zmiennej całkowitej, na przykład:

 

int howBig = 200;

int & rIntRef = howBig;

 

W ten sam sposóbNie możesz zainicjalizować też referencji do klasy CAT:

 

CAT & rCatRef = CAT; // źle

 

Musisz zainicjalizować referencję rIntCatRef tak, aby odnosiła się do konkretnego egzemplarza tej klasy:

 

CAT mruczek;

CAT & rCatRef = mruczek;

 

Referencje do obiektów są używane w taki sam sposób, jak obiekty. Dane i funkcje składowe są dostępne poprzez ten sam operator dostępu do składowych (.) i, podobnie jak w typach wbudowanych, referencja działa jak inna nazwa obiektu. Ilustruje to listing 9.4.

Listing 9.4. Referencje do obiektów

  0:  // Listing 9.4

  1:  // Referencje do obiektów klas

  2: 

  3:  #include <iostream>

  4: 

  5:  class SimpleCat

  6:  {

  7:  public:

  8:      SimpleCat (int age, int weight);

  9:      ~SimpleCat() {}

10:      int GetAge() { return itsAge; }

11:      int GetWeight() { return itsWeight; }

12:  private:

13:      int itsAge;

14:      int itsWeight;

15:  };

16: 

17:  SimpleCat::SimpleCat(int age, int weight)

18:  {

19:      itsAge = age;

20:      itsWeight = weight;

21:  }

22: 

23:  int main()

24:  {

25:      SimpleCat Mruczek(5,8);

26:      SimpleCat & rCat = Mruczek;

27: 

28:      std::cout << "Mruczek ma: ";

29:      std::cout << Mruczek.GetAge() << " lat. \n";

30:      std::cout << "i wazy: ";

31:      std::cout << rCat.GetWeight() << " funtow. \n";

32:      return 0;

33:  }

 

Wynik

Mruczek ma: 5 lat.

i wazy: 8 funtow.

Analiza

W linii 25. zmienna Mruczek jest deklarowana jako obiekt klasy SimpleCat (prostyzwykły kot). W linii 26. jest deklarowana referencja rCat do obiektu klasy SimpleCat, która 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 dostęp do nich jest identyczny. Także w tym przypadku referencja jest inną nazwą (aliasem) rzeczywistego obiektu.

 

Referencje

Referencję deklaruje się, zapisując typ, operator referencji (&) oraz nazwę referencji. Referencje muszą być inicjalizowane w trakcie ich tworzenia.

Przykład 1

...

Zgłoś jeśli naruszono regulamin