Jawne wykorzystanie wskaźnika this. Samoodwołania. Konstruktor
Transkrypt
Jawne wykorzystanie wskaźnika this. Samoodwołania. Konstruktor
Jawne wykorzystanie wskaźnika this. Samoodwołania. class X { private: int pole; public: X ( ) : pole (0) { } void zmien (int); void wypisz ( ); void dodaj (int, int); }; int main ( ) { X objx; objx.zmien (10); objx.wypisz ( ); objx.dodaj (-2, 11); objx.wypisz ( ); return 0; } void X::zmien (int arg) { pole=arg; Na wykładzie zostaną omówione slajdy } void X::wypisz ( ) { cout<<pole; } od 4 do 15 z pliku wyklad5.pdf class X { private: int pole; public: X ( ) : pole (0) { } X& zmien (int); const X& wypisz ( ) const; X& dodaj (int, int); }; X& X::zmien (int arg) { pole=arg; return *this; } const X& X::wypisz ( ) const { cout<<pole<<endl; return *this; } X& X::dodaj (int arg1, int arg2) { pole+=arg1*arg2; return *this; } void X::dodaj (int arg1, int arg2) { pole+=arg1*arg2; } 1 int main ( ) { X objx; objx.zmien (10).wypisz ( ).dodaj (-2, 11).wypisz ( ); return 0; } 2 [C++ Warning] Non-const function X::dodaj(int,int) called for const object Konstruktor kopiujący Inicjowanie jednego obiektu klasy wartością innego obiektu nazywamy domyślnym inicjowaniem składowa po składowej, które polega na kolejnym kopiowaniu poszczególnych pól niestatycznych klasy do pól klasy inicjowanej w ten sposób. class X { private: int a, b; Projektant klasy może zastąpić ten domyślny sposób wykonania operacji kopiowania, definiując dla klasy specjalny konstruktor nazywany konstruktorem kopiującym. public: X ( ) : a(2), b(10) { } void wyswietl ( ) const { cout<<a<<" "<<b<<endl; } }; int main ( ) { X x1; cout<<"x1: "; x1.wyswietl( ); // X x2(x1); lub X x2=X(x1); X x2=x1; cout<<"x2: "; x1: 2 10 x2.wyswietl ( ); x2: 2 10 return 0; } Jeżeli klasa zawiera konstruktor kopiujący, to każde inicjowanie jednego obiektu tej klasy wartością innego jej obiektu spowoduje wywołanie tego konstruktora. Konstruktorem kopiującym danej klasy nazywamy konstruktor, który można wywołać z jednym argumentem, którym jest referencja do obiektu danej klasy. Konstruktor kopiujący może mieć więcej niż jeden argument, ale wtedy pozostałe argumenty muszą być domyślne. W praktyce konstruktor kopiujący powinien zrobić wierną kopię oryginału. 3 4 Deklaracja i definicja konstruktora kopiującego. class X { private: int a, b; public: X ( ) : a(2), b(10) { } void wyswietl ( ) const { cout<<a<<" "<<b<<endl; } X (X& wzor); //deklaracja konstruktora kopiującego }; X::X (X& wzor) //definicja konstruktora kopiującego { a=wzor.a*2; b=wzor.b+17; } Konstruktor kopiujący jest wywoływany: 1. gdy definiujemy obiekt klasy, który inicjujemy innym obiektem tej samej klasy, 2. podczas przesyłania argumentów do funkcji – jeśli argumentem funkcji jest int main ( ) { X x1; cout<<"x1: "; x1.wyswietl(); X x2=x1; cout<<"x2: "; x2.wyswietl ( ); return 0; } 3. obiekt klasy, który jest przesyłany przez wartość, podczas gdy funkcja jako swój rezultat zwraca przez wartość obiekt klasy. Ze względu na 2. argument konstruktora kopiującego nie może być przesyłany przez wartość. W przypadku klasy X, do tworzenia wiernych kopii wystarczy domyślny sposób inicjowania składowa po składowej. x1: 2 10 x2: 4 27 Kiedy jawny konstruktor kopiujący jest niezbędny? Konstruktor kopiujący definiujemy, w przypadku gdy: //definicja alternatywna X::X (X& wzor) : a(wzor.a*2), b(wzor.b+17) {} class Produkt { private: string nazwa; double *cena; pole klasy jest wskaźnikiem, pole musi być unikatowe, np. numer konta, numer karty bibliotecznej, numer 5 Przykład klasy w której konstruktor kopiujący powinien się pojawić 6 void Produkt::zmien (const string& _nazwa, double _cena) { nazwa=_nazwa; *cena=_cena; } void Produkt::wypisz ( ) const { cout<<"Produkt: "<<nazwa <<" Cena: "<< *cena<<endl; } public: Produkt (const string& _nazwa, double _cena=0.0); ~Produkt ( ); void zmien (const string& _nazwa, double _cena); void wypisz ( ) const; }; Produkt::Produkt (const string& _nazwa, double _cena) : nazwa( _nazwa), cena (new double(_cena)) {} Produkt::~Produkt ( ) { delete cena; } legitymacji. 7 int main ( ) { Produkt p1("kawa", 12); Produkt p2=p1; p1.wypisz ( ); p2.wypisz ( ); p2.zmien ("herbata", 5); p1.wypisz ( ); p2.wypisz ( ); return 0; } W przypadku składowych, które są wskaźnikami domyślne inicjowanie składowa po składowej lub niejawny konstruktor kopiujący, nie działają tak jakbyśmy chcieli. Produkt: kawa Cena: 12 Produkt: kawa Cena: 12 Produkt: kawa Cena: 5 Produkt: herbata Cena: 5 8 Po dodaniu do programu wyróżnionych fragmentów program będzie działał poprawnie. class Produkt { private: string nazwa; double *cena; public: Produkt (const string& _nazwa, double _cena=0.0); Produkt (Produkt& wzor); ~Produkt ( ); void zmien (const string& _nazwa, double _cena); void wypisz ( ) const; }; Konstruktor kopiujący gwarantujący nietykalność. Jeśli const Produkt p1 ("kawa", 12); Produkt p2=p1; int main ( ) { Produkt p1("kawa", 12); Produkt p2=p1; p1.wypisz ( ); p2.wypisz ( ); p2.zmien ("herbata", 5); p1.wypisz ( ); p2.wypisz ( ); return 0; } [C++ Error] Could not find a match for 'Produkt::Produkt(const Produkt)' Zdefiniowany dla klasy Produkt konstruktor kopiujący nie gwarantuje, że nie uszkodzi wzorca, który w tym przypadku jest obiektem stałym. Nietykalność wzorca zostanie zapewniona, jeśli argumentem konstruktora kopiującego będzie referencja do obiektu stałego. Linia skompiluje się jeśli deklaracja konstruktora kopiującego będzie następująca Produkt (const Produkt&); natomiast definicja Produkt::Produkt (Produkt& wzor) Produkt: kawa Cena: 12 :nazwa(wzor.nazwa), cena (new double(*wzor.cena)) Produkt: kawa Cena: 12 Produkt: kawa Cena: 12 {} Produkt: herbata Cena: 5 Produkt::Produkt (const Produkt& wzor) : nazwa(wzor.nazwa), cena (new double(*wzor.cena)) {} 9 Jednak nie zawsze jest tak prosto, jeśli klasa Produkt miałaby pola będące obiektami innych klas, to również w konstruktorach kopiujących tych klas argumentem formalnym musiałaby być referencja do obiektu stałego. 10 Operator przypisania Klasa Produkt zdefiniowana w poprzednim rozdziale nie jest kompletna. Dlatego też, jeśli piszemy program, to od razu należy stawiać const w takich miejscach jak konstruktory kopiujące. Prywatny konstruktor kopiujący. Zamiast definiować konstruktor kopiujący, można w ogóle zabronić inicjowania obiektów składowa po składowej. To rozwiązanie składa się z dwóch części: (a) należy zadeklarować konstruktor kopiujący jako metodę prywatną klasy co zapobiegnie inicjowaniu składowa po składowej we wszystkich miejscach programu, oprócz treści metod i funkcji zaprzyjaźnionych, (b) należy zrezygnować z definiowania konstruktora kopiującego (zachowując jednak jego deklarację zgodnie z punktem (a)), aby uniemożliwić inicjowanie składowa po składowej w treści metod klasy i funkcjach zaprzyjaźnionych z klasą. Wtedy program można skompilować, ale program łączący nie będzie mógł rozstrzygnąć wywołania konstruktora kopiującego, bo nie został on zdefiniowany. 11 class Produkt { private: string nazwa; double *cena; Produkt: kawa Cena: 30 Produkt: kawa Cena: 30 Produkt: kawa Cena: 4 Produkt: cukier Cena: 4 public: Produkt (const string& _nazwa, double _cena=0.0); Produkt (const Produkt& wzor); ~Produkt ( ); void zmien (const string& _nazwa, double _cena); void wypisz ( ) const; }; int main ( ) { Produkt p1("kawa", 30); Produkt p2("nic"); p2=p1; //☺ p1.wypisz ( ); p2.wypisz ( ); p2.zmien ("cukier", 4); p1.wypisz ( ); p2.wypisz ( ); return 0; } 12 Deklaracja i definicja operatora przypisania (tzn. metody operator=). operator=). W linii ☺ zadziałał mechanizm przypisania jednemu obiektowi klasy wartości drugiego obiektu tej samej klasy, który nazywany jest domyślnym przypisaniem składowa po składowej. W zasadzie jeśli domyślne inicjowanie składowa po składowej nie nadaje się dla jakieś klasy, to również nie nadaje się dla niej domyślne przypisanie składowa po składowej. Projektant klasy może sam zaprojektować operator przypisania. W tym celu należy zadeklarować i zdefiniować metodę operator=. class Produkt { private: string nazwa; double *cena; public: Produkt (const string& _nazwa, double _cena=0.0); Produkt (const Produkt& wzor); ~Produkt ( ); void zmien (const string& _nazwa, double _cena); void wypisz ( ) const; Produkt& operator= (const Produkt& wzor); //deklaracja operatora przypisania }; 13 Produkt& Produkt::operator= (const Produkt& wzor) // { if (this!=&wzor) // { Definicja operatora przypisania nazwa=wzor.nazwa; *cena=*wzor.cena; } return *this; // } Metoda operator= gwarantuje nietykalność obiektom przypisywanym (const w ), tzn. poprawne będą następujące linie kodu: const Produkt p1("cos", 34); Produkt p2("COS"); p2=p1; Zapytanie w linii wyklucza przypisanie obiektu na ten sam obiekt tzn. przypisanie p2=p2; nie zostanie wykonane. Metoda operator= jest typu Produkt& linia (w linii zwracana jest wartość tego typu), dzięki czemu w programie można napisać p2=p1=p3; o ile p1, p2, p3 są obiektami klasy Produkt. 15 14 Po tych uzupełnieniach program będzie działał poprawnie. int main ( ) { Produkt p1("kawa", 30); Produkt p2("nic"); p2=p1; // p2.operator= (p1); p1.wypisz ( ); p2.wypisz ( ); p2.zmien ("cukier", 4); p1.wypisz ( ); Produkt: kawa Cena: 30 p2.wypisz ( ); Produkt: kawa Cena: 30 return 0; Produkt: kawa Cena: 30 } Produkt: cukier Cena: 4 16