[C++] Referencja i wskaźniki


(Quentin) #1

Witam!

Mam takie pytania odnośnie referencji. Wiem, że referencja pozwala nadać nową nazwę istniejącej zmiennej:

:arrow: http://www.ithelpdesk.pl/kurs-c++/pozio ... cyjny.html

typ & nazwaZmiennejTypuReferencyjnego = nazwaZmiennej;

int & zmienna = obiekt;

Od tej pory możemy mówić zamiast "zmienna" - "obiekt" lub na odwrót. Tutaj też jest dobry kod:

#include 

using namespace std;

int main()

{


 int zmienna = 123;

 int &referencja = zmienna;


 cout << "Zawartosc zmiennej : " << zmienna << endl;

 cout << "Zawartosc referencji : " << referencja << endl;

 zmienna = 567;

 cout << "Teraz zawartosc referencji to : " << referencja << endl;

 referencja = 901; // na referencji operujemy tak samo jak na zmiennej

 cout << "Teraz zawartosc zmiennej to : " << zmienna << endl;


}

I teraz tak. Np. na Wikipedii jest napisane, że: :arrow: http://pl.wikibooks.org/wiki/C%2B%2B:Referencje

a trochę dalej:

A przecież w 1-szym przykładzie tak nie było - tzn. nie była zainicjalizowana na początku... Czyli mamy błąd tam, tak ? Drugie moje pytanie dotyczy tego jak dodać wiele referencji do 1 nazwy. Np. tak, zgadza się :?:

int zzz = 45;

int &bbb = zzz;

int &aaa = bbb; //to samo co int &aaa = zzz;


cout << aaa; //45

I teraz najdziwniejsza rzecz. W Symfonii C++ autor pisze:

Więc dlaczego kompilator nie wyświetla tutaj błędu :?:

int zzz = 45;

int &bbb = zzz;


int *wsk = &bbb;

cout << *wsk;

(Sawyer47) #2
  1. W którym przykładzie? W kodzie który podałeś wszystko jest ok.

  2. Tak

  3. bbb to referencja, ale wskazuje na zzz i to do niej tworzysz wskaźnik. Dodam też, że nie ma referencji do referencji. Na dowód, że nie ma wskaźników do referencji przedstawię taki kod:

    template

    struct add_ptr

    {

    typedef T* type;

    };

    int main()

    {

    add_ptr< int& >::type variable;
    
    return 0;

    }

add_ptr zmienia typ T na typ T*, ale jeśli użyjesz tego z referencją, kompilator zgłosi błąd, u mnie g++ 4.3:

main.cpp: In instantiation of ‘add_ptr’:

main.cpp:11: instantiated from here

main.cpp:5: error: forming pointer to reference type ‘int&’

(Quentin) #3

To jest źle przecież bo referencja musi być zainicjalizowana - czyli musimy przypisać ją jakby do innej zmiennej już przy deklaracji...

A można to pokazać na jakimś innym kodzie, prostszym ? Bo o klasach się nie uczyłem jeszcze, a z tego co widzę to tam 1 jest.


(Sawyer47) #4

OK, w sumie niepotrzebnie kombinowałem, o wiele prostszy kod:

int main()

{

	int&* var;

	return 0;

}

// main.cpp: In function ‘int main()’:

// main.cpp:4: error: cannot declare pointer to ‘int&’

([alex]) #5

Quentin , spróbuję wyjaśnić całe zagadnienie referencji w kilku zdaniach.

  1. [*:1lcxf02d] "Pod spodem" czyli od strony "kuchni" wskaźnik i referencja to jedno i to samo. Czyli kilka bajtów pamięci stanowiących słowo w danym komputerze w którym zapisany fizyczny adres innej komórki pamięci. Różni się jedyne sposobem zapisu.

    double zmienna,x;

    double *wsk1=zmienna; // inicjalizacja wskaźnika przy dekłaracji

    double ref1=zmienna; // dokładnie to samo co wyżej, tylko że inny PROSTSZY zapis

    *wsk1=0; wsk1[0]=0; // dwa sposoby na zmianę wskazywanej przez wskaźnik zmiennej

    ref1=0; // sposób na zmianę "wskazywanej" przez referencje zmiennej, tylko że inny PROSTSZY zapis

    wsk1=x; // sposób na zmianę wskazywanej zmiennej, nie ma odpowiednika dla referencji

    double *wsk2; // już gdzieś tam wskazuje, można nawet pod ten adres coś zapisać (ze złym skutkiem)

    //double ref2; // nie skompiluję się ponieważ musi zacząć "referować" tuż przy deklaracji

[*:1lcxf02d] Wskaźniki mogą być podwójne, potrójne i nawet więcej.

long ***** w;

[*:1lcxf02d] Można zadeklarować referencję na wskaźnik

short *w;

short *r=w; // r jest referencją na w

[*:1lcxf02d] Nie da się nawet zadeklarować wskaźnika na referencję jak również referencji na referencję. Wynika to właśnie z "kuchni", czyli z tego czym właściwie jest referencja. [*:1lcxf02d] Referencja przeważnie jest stosowana dla przekazywania parametrów do funkcji.

int a=3,b=4;

void swap(int *x,int *y) { int t=*x; *x=*y; *y=t; } // zamiana wartości przez wskaźniki

void swap(int x,int y) { int t=x; x=y; y=t; } // zamiana wartości przez referencje

swap(a,b); // wywołanie funkcji zamiany wartości przez wskaźniki

swap(a,b); // wywołanie funkcji zamiany wartości przez referencje

Chyba widać czemu z referencją lepiej. [*:1lcxf02d] Ponieważ od "kuchni" referencja jest tym samym co wskaźnik to twierdzenie "nie da się przestawić referencji na inny obiekt" nie jest do końca prawdą. Można zmienić ale nie wprost, przez "mazanie po pamięci" czasami używa się kontrolowanego mazania dla takiej zmiany, ale to jest bardzo nie elegantske. Przykłady zmiany referencji:

#include 
using std::cout;
using std::endl;
using std::cin;

struct z_ref { long r; };

long A=123456789,B=987654321,R1=A;
z_ref z={A};

int main()
  {
   long R2=A,Q=0;

   {
   cout"Przestawienie referencji bazujace na wiedzy:"endl;
   cout"struktura zr ma ten sam adres co jej pierwsza składowa."endlendl;
   cout"POCZATEK: z.r jest referencja na A, "endl;
   cout"adresy z.r i A sa identyczne "z.r'='A" A="z.rendlendl;
   long  **w=reinterpret_castlong ** (z); // zmienna w - właściwie jest wskaźnikiem na referencję
   *w=B; // zmieniamy referencje
   cout"A  TERAZ: z.r jest referencja na B, "endl;
   cout"adresy z.r i B sa identyczne "z.r'='B" B="z.rendlendl;
   long *r=*reinterpret_castlong **(z); // zmienna r - właściwie jest referencją na referencję
   r=A; // zmieniamy referencje
   cout"I  ZNOWU: z.r jest referencja na A, "endl;
   cout"adresy z.r i A sa identyczne "z.r'='A" A="z.rendlendlendl;
   }

   {
   cout"Przestawienie referencji bazujace na wiedzy:"endl;
   cout"zmienne globalne rozmieszczane w pamieci po kolei."endlendl;
   cout"POCZATEK: R1 jest referencja na A, "endl;
   cout"adresy R1 i A sa identyczne "R1'='A" A="R1endlendl;
   long  **w=reinterpret_castlong ** (B+1); // zmienna w - właściwie jest wskaźnikiem na referencję
   *w=B; // zmieniamy referencje
   cout"A  TERAZ: R1 jest referencja na B, "endl;
   cout"adresy R1 i B sa identyczne "R1'='B" B="R1endlendl;
   long *r=*reinterpret_castlong **(B+1); // zmienna r - właściwie jest referencją na referencję
   r=A; // zmieniamy referencje
   cout"I  ZNOWU: R1 jest referencja na A, "endl;
   cout"adresy R1 i A sa identyczne "R1'='A" A="R1endlendlendl;
   }

   {
   cout"Przestawienie referencji bazujace na wiedzy:"endl;
   cout"zmienne lokalne rozmieszczane w pamieci w odwrotnej kolejnosci."endlendl;
   cout"POCZATEK: R2 jest referencja na A, "endl;
   cout"adresy R2 i A sa identyczne "R2'='A" A="R2endlendl;
   long  **w=reinterpret_castlong ** (Q-1); // zmienna w - właściwie jest wskaźnikiem na referencję
   *w=B; // zmieniamy referencje
   cout"A  TERAZ: R2 jest referencja na B, "endl;
   cout"adresy R2 i B sa identyczne "R2'='B" B="R2endlendl;
   long *r=*reinterpret_castlong **(Q-1); // zmienna r - właściwie jest referencją na referencję
   r=A; // zmieniamy referencje
   cout"I  ZNOWU: R2 jest referencja na A, "endl;
   cout"adresy R2 i A sa identyczne "R2'='A" A="R2endlendlendl;
   }

   cin.get();
   return 0;
  } [/code]

Nie są to jedyne sposoby na zmianę referowanej zmiennej, ale najprostsze. Jeżeli w strukturze z_ref dodamy jakąś składową przed składową r to pierwszy przykład natychmiast się załamie. Drugi i trzeci przykład załamią się po zwykłej zmianie kolejności deklarowanych zmiennych, lub nawet po przenoszeniu zmiennych lokalnych do globalnych albo na odwrót.