Programowanie C++ Qt4 w systemie Gnu-Linux/Referencje w C++

Referencje Czym są ??

edytuj

Chciałbym przybliżyć temat referencji w języku C++. Referencję traktuję jako "namiar na zmienną" lub "namiar na obiekt". Jeśli funkcję zadeklarujemy tak

void fun (int i) //1

To funkcja oczekuje na liczbę typu INTEGER. Do wnętrza funkcji dostanie się wartość, jaką w chwili wywołania funkcji miała zmienna. Wartość ta zostanie skopiowana do zmiennej "i". Zaś oryginał zmiennej nie zostanie naruszony. Natomiast jeśli użyjemy referencji

void fun (int& i)//2

To funkcja oczekuje namiaru na zmienną typu INTEGER. Do wnętrza funkcji dostanie się więc zmienna a nie jej wartość.

Referencję można również traktować jako "inna nazwa zmiennej", "przezwisko" itp.

void fun (int& i)
     {
      i=i*2;
     }
int main()
{
  k=10;
  fun(k); //"i" wewnątrz funkcji fun jest przezwiskiem (inną nazwą dla zmiennej k)
  //k wynosi teraz 20
}

Przykład - funkcja zwraca referencję

edytuj

Poniżej przykład klasy licznika. Klasa składa się ze zmiennej licznikowej "i"(To właśnie tą zmienną będziemy inkrementować). W skład klasy wchodzą również dodatkowe funkcje inkrementujące 1,2.

#include <iostream>

using namespace std;
struct counter
{
    int i;
    counter increment_1() //1 - pierwsza funkcja inkrementująca - zwraca przez wartość
        {
            ++i;
            return *this;

        }
    counter & increment_2() //2 - druga funkcja inrementująca - zwraca referencje
        {
            ++i;
            return *this;

        }
};
int main()
{
    counter licznik;
    licznik.i=0;
    cout<<licznik.i<< endl;
    cout<<"dokokonamy zwiększenia wartości przez potrójną inkrementacje = "<<(licznik.increment_1().increment_1().increment_1()).i<<" -wynik ok"<<endl; //3
    cout<<"ale na wszelki wypadek wyświetlimy zawartość licznika "<<licznik.i<< endl<<"ups coś poszło nie tak :P"<< endl;

    licznik.i=0;
    cout<<licznik.i<< endl;
    cout<<"dokonamy zwiększenia wartości przez potrójną inkrementacje = "<<(licznik.increment_2().increment_2().increment_2()).i<< endl;//4
    cout<<"wyświetlimy zawartość licznika "<<licznik.i<< endl;
}

Chciał bym wyjaśnić linijki oznaczone w komentarzu numerami 3,4. Ponieważ funkcja

licznik.increment_1(),

inkrementuje nam obiekt "licznik" i zwraca kopię obiektu (zwracanie przez wartość). Mniej formalnie możemy to napisać w ten sposób:

licznik2=licznik.increment;
licznik3=licznik2.increment;
//itd....

Tu docieramy do sedna spawy. To właśnie robi nam się w wypadku zwracania przez wartość. Zostaje wówczas utworzony nowy obiekt tymczasowy, do którego zostaje wpisana wartość licznika po inkrementacji. Dlatego obiekt oryginalny został zwiększony tylko raz, a po mimo tego wynik potrójnej inkrementacji jest jak najbardziej poprawny heh :).

Inaczej ma się sprawa w przypadku zwracania przez funkcję referencji. Wówczas wynikiem funkcji

licznik.increment_2();

będzie referencja do obiektu "licznik" namiar na obiekt "licznik". Funkcja zwróci namiary na oryginalny obiekt, który ponownie zostanie poddany inkrementacji (oszczędza się na tworzeniu nowego obiektu tymczasowego o wartościach zgodnych z oryginałem).

Mniej formalnie to co robi taka funkcja można by zapisać tak:

licznik.increment 
licznik.increment
//itd....

Jeszcze raz z innej beczki. Funkcja

counter & increment_2()

wywołana na rzecz obiektu "licznik" i zwróci nam namiary na ten obiekt (coś jakby adres). Dzięki temu zabiegowi możemy ponownie zastosować opcję inkrementacji i ponownie będzie to się odbywało na rzecz obiektu "licznik"

Wywołanie programu

edytuj

Wynik po uruchomieni programu prezentuje się następująco.

0
dokonamy zwiększenia wartości przez potrójną inkrementacje = 3 -wynik ok
ale na wszelki wypadek wyświetlimy zawartość licznika 1
ups coś poszło nie tak :P
0
dokonamy zwiększenia wartości przez potrójną inkrementacje = 3
wyświetlimy zawartość licznika 3


Referencja do wartości zwracanej przez funkcję

edytuj
#include <iostream>

using namespace std;
int suma(int a, int b)
    {return a+b;}


int main()
{
    cout << "Hello World!" << endl;
    //int & wynik=suma(10,2); // błąd: Na zmienną tymczasową zwracaną przez funkcję można ustawić tylko stałą referencję
    const int & wynik=suma(10,2);
    cout << wynik << endl;
    //wynik=wynik+2; //błąd kompilacji wynik nie może zostać zmieniony
    cout << suma(wynik,2) << endl;
    return 0;
}

W języku C++ dozwolone jest zrobienie tego co przedstawiłem w tym krótkim programie. Można znacjonalizować referencję obiektem tymczasowym zwracanym przez funkcję. Obiekt ten będzie istniał dopóty, dopóki będzie istniała referencja do niego. Jedyny wymóg w takich przypadkach to przydomek const. Obiekt ten nie może zostać zmieniony !!!

 const int & wynik=suma(10,2);

Czym w linijce powyżej jest zmienna "wynik" ??

  • wynik jest referencją
  • do zmiennej typu int
  • const oznacza, że do zmiennej int nie można pisać.
  • referencja jest zainicjalizowana zmienną tymczasową zwróconą przez funkcje suma()

Optymalizacje kompilatora

edytuj

Chciałbym napisać iż ustawianie referencji na zwróconą przez funkcję wartość jest tak samo wydajne jak klasyczny zwrot wartości pod warunkiem iż będzie on wyglądał w poniżej przedstawiony sposób


#include <iostream>

using namespace std;
int suma(int a, int b)
    {return a+b;}


int main()
{
    cout << "Hello World!" << endl;
    const int & wynik=suma(10,2);
    int wynik2 = suma(10,2); //tak samo wydajne jak rozwiązanie wyżej!!!
    int wynik3;
    wynik3 = suma(10,2); // tak jest gorzej !!! bo mamy operator przypisania - nie działa optymalnizacja
}

zobacz dowód na Youtube (HD)!!!!

https://www.youtube.com/embed/XTIbWEyYkhs

Referencja jako składnik klasy

edytuj

Tutaj opiszę jeszcze jeden przypadek. A mianowicie wykorzystanie referencji jako składnika klasy !!! To raczej taki sposób obejścia wskaźników, w sytuacji gdy chcemy przekazać oryginalny obiekt stworzony na zewnątrz klasy. Tak aby do wnętrza klasy dostał się jego oryginalna instancja. Oczywiście na pierwszy co się narzuca to wskaźniki. Ale można też do tego użyć do tego referencji??

#include <QString>
#include <QDebug>
class temp{
    
public:
    //klasa(struktura) zagnieżdżona wewnątrz klasy temp
    struct osoba{
        QString imie;
        //inne składniki
    };
    //zmienne definiowane w klasie mają zasięg klasy. Nic nie szkodzi że jeszcze nie zadeklarowaliśmy zmiennej pracownik będzie zadeklarowana później
    temp(osoba &_pracownik):pracownik(_pracownik)  //konstruktor tylko jemu wolno zainicjować referencję i to po dwukropku
    {
        // pracownik=_pracownik; //błąd nie można nic przypisać do referencji
    }
    
    void zmien(){pracownik.imie="piotrek";} //Jeśli konstruktor zainicjalizuje referencję jakimś obiektem. To funkcja zmień ten obiekt zmodyfikuje
    
private:

   osoba& pracownik; //składnik, który jest referencją do obiektu klasy osoba. Rzeczona zmienna którą inicjalizuje konstruktor
   //pracownik jeszcze nie istnieje. To jest referencja, która jeszcze do niczego w pamięci się nie odnosi. Dopiero konstruktor nada tej referencji sens
};


int main(int argc, char *argv[]){

  temp::osoba pracownik1; // tworzymy obiekt klasy osoba w funkcji main()
  pracownik1.imie="marek";
  
  temp tempObj(pracownik1);

  qDebug()<<pracownik1.imie;

  tempObj.zmien();

  qDebug()<<pracownik1.imie<<endl; //pracownik1 będzie piotrkiem
}