Programowanie (język C++) referencje Wykład 2. Referencje (1) Referencja (odnośnik) jest zmienną identyfikującą inną zmienną. Wykonanie operacji na referencji ma taki sam skutek, jak wykonanie tejŝe operacji na zmiennej identyfikowanej przez tę referencję. int Num = 50; zdefiniowano zmienną Num (typu int) nadając jej wartość początkową 50. int& Ref = Num; zdefiniowano zmienną Ref (typu referencja do int) i skojarzono ją ze zmienną Num. Np. int Num = 50; int& Ref = Num; Ref++; cout << Num << Ref; // 51 51 Ref++; inkrementacja zmiennej Ref, toŝsama z inkrementacją zmiennej Num. cout << Ref << Num; zmienne Ref i Num, to faktycznie ta sama zmienna. -1- -2- -3- -4-
UWAGA: Zabrania się konstruowania deklaracji: referencja-do-referencji // int && ref wskazanie-referencji // int &* ptr tablica-referencji // int & arr[5] int n1, n2; int &ref = n1; Jeszcze jeden przykład n1 = 10; n2 = 20; cout << n1 << n2 << ref; // 10 20 10 ref = n2; n2 = 30; cout << n1 << n2 << ref; // 20 30 20 ref = ref + 3; cout << n1 << n2 << ref; // 23 30 23 Referencje (2) Referencja moŝe być inicjowana stałą. Wymaga się wtedy, aby definiowana zmienna była określona jako referencja do zmiennej ustalonej. W takim przypadku tworzona jest zmienna tymczasowa inicjowana wartością stałej i wszystkie odwołania do zmiennej referencyjnej kierowane są do tej zmiennej tymczasowej. Np. const int &Ref = 50; cout << Ref; // 50 Ref = 33; // BŁĄD: próba modyfikacji zmiennej ustalonej Referencje (3) Parametr funkcji moŝe być określony jako referencja. W takim przypadku odpowiedni argument wywołania funkcji musi być wyraŝeniem określającym zmienną odpowiedniego typu nie moŝe być n.p. sumą zmiennych. N.p. void fun( double& x ); double a, b;... fun( a ); // O.K. fun( a + b ); // Źle!!! -5- -6- -7- -8-
Referencje (4) Rezultat funkcji moŝe być referencją wtedy identyfikuje zmienną określoną w instrukcji powrotu (return). Wywołanie funkcji zwracającej referencję moŝe wystąpić wszędzie tam, gdzie moŝe wystąpić nazwa zmiennej, a więc np. jako: - lewy argument operacji przypisania (=), - prawy argument op. wprowadzania (>>), - argument operacji utworzenia wskazania (&) parametry definicji funkcji argumenty wywołania funkcji int a, b; a = 2; b = 5; Sekwencja wymiany wartości 2 zmiennych // teraz chcemy zamienić wartości // pomiędzy zmiennymi a i b tmp = a; a = b; b = tmp; cout << a << b; // 5 2... UWAGA: W języku C++, w obrębie bloku deklaracje mogą się przeplatać z instrukcjami. Ale... (1) deklaracja zmiennej musi poprzedzać jej uŝycie ( tak jak w języku C ), (2) deklaracja nie moŝe być "przeskakiwana". -9- -10- -11- -12-
Funkcja C/C++ (zła) zamieniająca wartości zmiennych Funkcja C/C++ (zła) zamieniająca wartości zmiennych void exchg1 ( int i, int j ) tmp = i; i = j; j = tmp;.. exchg1 ( a, b ); cout << a << b; // 2 5 exchg1 ( a, b ); // jest równowaŝne realizacji bloku: int i = a, j = b; tmp = i; i = j; j = tmp; cout << a << b; // 2 5 Funkcja C/C++ (dobra) zamieniająca wartości zmiennych Funkcja C/C++ (dobra) zamieniająca wartości zmiennych void exchg2 ( int *i, int *j ) tmp = *i; *i = *j; *j = tmp;.. exchg2 ( &a, &b ); cout << a << b; // 5 2 exchg2 ( &a, &b ); // jest równowaŝne realizacji bloku: int *i = &a, *j = &b; tmp = *i; *i = *j; *j = tmp; cout << a << b; // 5 2-13- -14- -15- -16-
Funkcja C++ (dobra) zamieniająca wartości zmiennych Funkcja C++ (dobra) zamieniająca wartości zmiennych void exchg3 ( int &i, int &j ) tmp = i; i = j; j = tmp;.. exchg3 ( a, b ); cout << a << b; // 5 2 exchg3 ( a, b ); // jest równowaŝne realizacji bloku: int &i = a, &j = b; tmp = i; i = j; j = tmp; cout << a << b; // 5 2 Do zapamiętania Parametr funkcji jest jej zmienną lokalną zainicjowaną argumentem wywołania funkcji. przeciąŝanie nazw funkcji JeŜeli parametr jest referencyjny, to operacje dokonywane na parametrze dotyczą skojarzonego z nim argumentu. -17- -18- -19- -20-
PrzeciąŜanie nazw funkcji (1) W języku C jest niedopuszczalne! Nazwy funkcji muszą być unikatowe. PrzeciąŜanie nazw funkcji (2) W języku C++ jest dopuszczalne i bardzo często wykorzystywane. int Sum ( int a, int b ) return a+b; int Sum3 ( int a, int b, int c ) return a+b+c; double SumD ( double a, double b ) return a+b; int i, j, k, m, n; double x, y, z; n = Sum ( i, j ); m = Sum3 (i, j, k ); z = SumD ( x, y ); int Sum ( int a, int b ) return a+b; int Sum ( int a, int b, int c ) return a+b+c; double Sum ( double a, double b ) return a+b; int i, j, k, m, n; n = Sum ( i, j ); m = Sum (i, j, k ); double x, y, z; z = Sum ( x, y ); PrzeciąŜanie nazw funkcji (3) Ale uwaga! JeŜeli kompilator "widzi" 2 funkcje int Sum ( int a, int b ) return a+b; double Sum ( double a, double b ) return a+b; int i, j, k, m, n; n = Sum ( i, j ); // O.K. double x, y, z; // def. zmiennych po instrukcji! z = Sum ( x, y ); // O.K. z = Sum ( x, i ); // BŁĄD z = Sum ( 123, 55.5 ); // BŁĄD PrzeciąŜanie nazw funkcji (4) Liczba i typy argumentów wywołania funkcji muszą się dać dokładnie dopasować do liczby i typów parametrów którejś z zadeklarowanych funkcji. Trzeba tu jeszcze wiedzieć, Ŝe referencja do TYP jest nieodróŝnialna od TYP. Dlatego n.p. nie moŝna definiować przeciąŝenia int Sum ( int a, int b ) return a+b; int Sum ( int &a, int &b ) return a+b; -21- -22- -23- -24-
Domyślne wartości argumentów (1) domyślne wartości argumentów W języku C++ zamiast uŝywać przeciąŝenia Sum dla 2 i 3 parametrów typu int, wystarczy zdefiniować jedną funkcję int Sum ( int a, int b, int c ) return a+b+c; a następnie posłuŝyć się deklaracją int Sum ( int, int, int=0 ); int i, j, k, m, n; m = Sum (i, j, k ); n = Sum ( i, j ); // równowaŝne n = Sum ( i, j, 0 ); Domyślne wartości argumentów (2) W skrajnym przypadku moŝna uŝyć deklaracji int Sum ( int=0, int=0, int=0 ); int i, j, k, m, n; m = Sum ( i, j, k ); // tyle argumentów ile parametrów m = Sum ( i, j ); // równowaŝne n = Sum ( i, j, 0 ); m = Sum ( i ); // równowaŝne n = Sum ( i, 0, 0 ); m = Sum ( ); // równowaŝne n = Sum ( 0, 0, 0 ); DEFINIOWANIE KLAS -25- -26- -27- -28-
Definiowanie klas (1) Definiowanie klas (2) Klasę moŝna definiować uŝywając słowa kluczowego struct Klasę moŝna definiować uŝywając słowa kluczowego class struct NazwaKlasy.. składowe domyślnie publiczne.. ; class NazwaKlasy.. składowe domyślnie prywatne... ; Definiowanie klas (3) public private protected Określenia publiczne, prywatne związane są z pojęciem dostępności nazw składowych. Domyślnie, dla składowych klasy definiowanej z uŝyciem słowa kluczowego struct przyjmuje się dostęp publiczny (ma to na celu zapewnienie zgodności z językiem C). Domyślnie, dla składowych klasy definiowanej z uŝyciem słowa kluczowego class przyjmuje się dostęp prywatny. Do jawnego określania dostępności składowych słuŝą słowa kluczowe: public, private, protected (chronione). Są to tzw. specyfikatory dostępności. -29- -30- -31- -32-
Definicja klasy CMPLX struct CMPLX double Re, Im; ; jest równowaŝna definicji public: double Re, Im; ; Definiowanie klas (4) Specyfikatory dostępności składowych klasy: public nazw składowych publicznych mogą uŝywać wszystkie funkcje "widzące" definicję klasy, private nazw składowych prywatnych mogą uŝywać jedynie metody danej klasy i funkcje zaprzyjaźnione z tą klasą, protected - nazw składowych chronionych mogą uŝywać jedynie metody danej klasy i funkcje zaprzyjaźnione z tą klasą, oraz metody i funkcje zaprzyjaźnione klas pochodnych tej klasy. Napiszmy definicję klasy CMPLX w nowej wersji double Re, Im; // Re, Im są teraz prywatne public: double Abs ( ) const; void Read ( ); ; W konsekwencji definicja funkcji CMPLX Add ( CMPLX a, CMPLX b ) a.re += b.re; a.im += b.im; return a; przestała być poprawna, bo zawiera zabronione odwołania do prywatnych pól klasy CMPLX. -33- -34- -35- -36-
MoŜemy ten problem rozwiązać na kilka sposobów. (1) uzupełnić definicję klasy CMPLX o prymitywne metody dostępu do pól:. public: double getre ( ) const return Re; double getim ( ) const return Im; void setre ( double re ) Re = re; void setim ( double im ) Im = im; ; (2) uzupełnić definicję klasy CMPLX o 2 metody dostępu do pól wykorzystujące referencje:. public: double& refre ( ) return Re; double& refim ( ) return Im; ; i napisać treść definicji funkcji Add w postaci jak niŝej i napisać treść definicji funkcji Add w postaci jak niŝej CMPLX Add ( CMPLX a, CMPLX b ) a.setre ( a.getre( ) + b.getre( ) ); a.setim ( a.getim( ) + b.getim( ) ); return a; CMPLX Add ( CMPLX a, CMPLX b ) a.refre( ) += b.refre( ) ; a.refim( ) += b.refim( ) ; return a; _ friend (3) "Zaprzyjaźnić" funkcję Add z klasą CMPLX:.. ; friend CMPLX Add ( CMPLX, CMPLX ); // deklaracja zaprzyjaźnienia i pozostawić treść definicji funkcji Add w oryginalnej postaci. Są jeszcze inne moŝliwości, ale o nich w tej chwili nie będziemy mówili. UWAGA: PołoŜenie deklaracji zaprzyjaźnienia względem specyfikatorów dostępności (public, private, protected) w obrębie definicji klasy nie ma Ŝadnego znaczenia. _ -37- -38- -39- -40-
Jeszcze jedną konsekwencją "uprywatnienia" pól Re i Im jest utrata moŝliwości bezpośredniego określania ich wartości poza definicją klasy. N.p. w funkcji main() mogliśmy zastąpić odczyt obiektów x, y instrukcjami realizującymi przypisanie. Zamiast x.read ( ); y.read ( ); mogliśmy napisać (n.p. dla celów testowych): x.re=1.0; x.im=2.5; y.re = y.im = -2.2; // cmplx.h Nasza definicja klasy CMPLX (nowa) double Re, Im; public: void Set (double=0, double=0 ); void Read ( ); void Write ( ) const; double Abs ( ) const; friend CMPLX operator+ ( CMPLX, CMPLX ); ; Teraz juŝ takiej moŝliwości nie mamy! Główny plik aplikacji (nowy) // myprog.cpp #include < iostream > using namespace std; #include cmplx.h int main ( ) CMPLX x, y, z; Koniec wykładu 2. x.read ( ); // cin 1.5 2.2 y.set ( 2.0, 3.0 ); z = x + y; z.write( ); // cout [ 3.5, 5.2 ] -41- -42- -43- -44-