11.7 Referencje jako wartości zwracane funkcji

Odniesienie (referencja) może nie tylko być parametrem, ale i wartością zwracaną przez funkcję. Następująca deklaracja

       int& fun(int);
oznacza, że funkcja zwraca pewną zmienną typu int przez referencję, czyli na przykład wyrażenie fun(3) jest inną nazwą pewnej istniejącej zmiennej, która została podana w funkcji w instrukcji return.

Funkcja nie może zwracać przez referencję (ani przez wskaźnik) swoich zmiennych lokalnych, gdyż po powrocie do funkcji wywołującej zmienne lokalne funkcji już nie istnieją. Może natomiast zwrócić zmienną lokalną przez wartość: po powrocie z funkcji zmienna lokalna nie istnieje, ale kopia jej wartości, po ewentualnej konwersji do typu zadeklarowanego jako typ zwracany, przekazana zostaje do funkcji wywołującej (poprzez stos, rejestr procesora lub jeszcze inaczej; to już jest szczegół implementacyjny, którym nie musimy się zajmować).

To, że wyrażenie z wywołaniem funkcji jest traktowane jako nazwa istniejącej zmiennej, a więc jako l-wartość, oznacza, że wywołanie funkcji zwracającej referencję może pojawić się po lewej stronie przypisania, co wygląda trochę szokująco i nie stosuje się zbyt często; tym niemniej nie ma tu błędu. Przykładem może być funkcja funmax z poniższego programu:


P72: varepo.cpp     Różne typy argumentów i wartości zwracanych

      1.  #include <iostream>
      2.  #include <cmath>
      3.  using namespace std;
      4.  
      5.  double potegi(double&,double*);
      6.  int*   kwadrat(int*);
      7.  int&   funmax(int[],int);
      8.  
      9.  int main() {
     10.  
     11.        // argumenty wskaźnikowe i referencyjne
     12.      double u = 4, v;
     13.      double szesc = potegi(u, &v);                     
     14.      cout << "Szescian: " << szesc  << "; kwadrat: "
     15.           << u << "; pierwiastek: " << v  << endl;
     16.  
     17.        // to też ma sens
     18.      int i = 4;
     19.      cout << "20? : " << ++*kwadrat(&i)+3 << endl;     
     20.  
     21.      // funkcja zwracająca referencję
     22.      int tab[] = {1,4,6,2};
     23.      cout << "Tablica przed: ";
     24.      for ( i = 0; i < 4; i++ ) cout << tab[i] << " ";
     25.      cout << endl;
     26.  
     27.      funmax(tab,4) = 0;                                
     28.  
     29.      cout << "Tablica po   : ";
     30.      for ( i = 0; i < 4; i++ ) cout << tab[i] << " ";
     31.      cout << endl;
     32.  }
     33.  
     34.  double potegi(double& u, double* v) {                 
     35.      double x = u;
     36.      u *= u;
     37.      *v = sqrt(x);
     38.      return u*x;
     39.  }
     40.  
     41.  int* kwadrat(int* p) {                                
     42.      *p *= *p;
     43.      return p;
     44.  }
     45.  
     46.  int& funmax(int* tab, int ile) {                      
     47.      int i, ind = 0;
     48.      for ( i = 1; i < ile; i++ )
     49.          if ( tab[i] > tab[ind] ) ind = i;
     50.      return tab[ind];
     51.  }

Funkcja potegi ma jeden argument referencyjny i jeden wskaźnikowy. Przez referencję, jako pierwszy argument, przesyłamy liczbę dodatnią: funkcja oblicza jej kwadrat, sześcian i pierwiatek kwadratowy (). Kwadrat argumentu jest w funkcji przypisywany do tej samej zmiennej u, która była pierwszym argumentem tej funkcji. Ponieważ argument jest referencyjny, w funkcji main po wywołaniu funkcji potegi wartość ta będzie nową wartością zmiennej u — stara wartość, wynosząca 4, zostanie zamazana.

Jako drugi argument wysyłamy do funkcji kopię adresu zmiennej v (). W funkcji do zmiennej o tym adresie wpisywana jest wartość pierwiastka kwadratowego pierwszego argumentu. Tak więc po powrocie z funkcji zmienna v w programie głównym będzie miała wartość 2. Funkcja potegi zwraca, przez wartość, sześcian argumentu.

Po wywołaniu funkcji potegi sześcian pierwotnej wartości zmiennej u mamy w zmiennej szesc, kwadrat jest nową wartością zmiennej u, a pierwiastek jest wartością zmiennej v: potwierdza to wydruk z programu

    Szescian: 64; kwadrat: 16; pierwiastek: 2
    20? : 20
    Tablica przed: 1 4 6 2
    Tablica po   : 1 4 0 2
Dość karkołomna konstrukcja użyta jest w linii . Funkcja kwadrat () oblicza kwadrat argumentu przekazanego przez wskaźnik (do funkcji przekazujemy kopię adresu zmiennej i). Obliczona wartość jest wpisywana do zmiennej wskazywanej przez wskaźnik, a więc do zmiennej i z programu głównego. Jednocześnie wskaźnik do tej samej zmiennej jest zwracany w instrukcji return — zauważmy, że nie jest to wskaźnik do zmiennej lokalnej, ale do istniejącej w programie głównym zmiennej i. Po wywołaniu funkcji wartością wyrażenia kwadrat(&i) jest zatem wskaźnik do zmiennej i, której wartość uległa zmianie i wynosi teraz 16 (= 4×4). Za pomocą operatora gwiazdki dokonujemy dereferencji, a więc wyrażenie *kwadrat(&i) jest równoważne zmiennej i, o wartości 16. Tę wartość zwiększamy o jeden za pomocą operatora zwiększenia i do wyniku dodajemy 3; zatem wartością całego wyrażenia ++*kwadrat(&i)+3 jest 20. Wbrew pozorom takie zawiłe konstrukcje dość często zdarzają się w realnych programach (choć może nie powinny).

Przyjrzyjmy się teraz funkcji funmax (). Do funkcji w tradycyjny sposób wysyłamy tablicę i jej wymiar (). Funkcja szuka największego elementu i zwraca tenże element przez referencję, czego z linii zawierającej instrukcję return nie widać, ale widać z nagłówka w deklaracji/definicji. Zatem wyrażenie funmax(tab,4) jest nazwą tej zmiennej, która jest największym elementem tablicy, czyli w naszym przykładzie tab[2]. Wyrażenie to stoi po lewej stronie przypisania, zatem zmienna ta jest zerowana, o czym przekonuje nas wydruk.

T.R. Werner, 23 lutego 2019; 23:59