Programowanie obiektowe, w4, 6


6. Dziedziczenie.

6.1. Uwagi wstępne.

Sytuacja: - mamy klasę, a chcielibyśmy ją uzupełnić nowymi danymi lub nowymi metodami. Uruchamiamy wtedy klasę z brakującymi składnikami. Wygląda to następująco:

//klasy pochodne

#include <iostream.h>

enum RASA { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

class Ssak

{

protected:

int jegoWiek;

int jegoWaga;

public:

// Konstruktory

Ssak():jegoWiek(2), jegoWaga(5){}

~Ssak(){}

// funkcje dostepu

int PobierzWiek()const { return jegoWiek; }

void UstawWiek(int wiek) { jegoWiek = wiek; }

int PobierzWaga() const { return jegoWaga; }

void UstawWaga(int waga) { jegoWaga = waga; }

// Inne metody

void Mow()const { cout << "Odglos Ssaka!\n"; }

void Spij()const { cout << "Cicho. Ja teraz spie.\n"; }

};

class Pies : public Ssak

{

RASA jegoRasa;

public:

// Konstruktory

Pies():jegoRasa(YORKIE){}

~Pies(){}

// Funkcje dostepu

RASA PobierzRasa() const { return jegoRasa; }

void UstawRasa(RASA rasa) { jegoRasa = rasa; }

// Inne metody

void MachajOgonem() { cout << "Machanie ogonem...\n"; }

void ProsOJedzenie() { cout << "Prosba o jedzenie...\n"; }

};

int main()

{

Pies fido;

fido.Mow();

fido.MachajOgonem();

cout << "Fido ma " << fido.PobierzWiek() << " lata\n";

return 0;

}

W powyższym programie mamy dwie klasy - ogólną SsakPze standardowymi metodami i klasę pies z metodami dodatkowymi. Istotne jest, że w klasie Pies dołączono funkcje publiczne klasy Ssak. Zastosowano następujące wyrażenie:

class Pies : public Ssak

Wyrażenie to powoduje, że klasę Pies uważamy za klasę pochodną klasy Ssak. Klasa Ssak stała się klasą podstawową dla klasy Pies. Obiekt klasy Pies stał się w ten sposób obiektem klasy Ssak (ale nie odwrotnie).

Konstrukcję taką nazywamy dziedziczeniem - klasa pochodna dziedziczy po klasie podstawowej. Korzyści z dziedziczenia określają następujące instrukcje:

Pies fido;

fido.Mow();

Obiekt klasy pochodnej wywołuje funkcję z klasy podstawowej.

Zauważmy, że dane w klasie podstawowej mają atrybut protected. Oznacza to, że dostęp dla klas pochodnych dostęp jest taki, jakby był public, a dla reszty świata - jakby był private.

Następną zaletą korzystania z dziedziczenia jest możliwość nadpisania funkcji składowej klasy podstawowej. Jeśli funkcja Mow() by nam nie odpowiadała, w klasie pochodnej można zdefiniować nową funkcję Mow(), bardziej odpowiadającą naszym potrzebom. Ma to szczególne znaczenie w przypadku, gdy sprzedano nam program już skompilowany i w postaci źródłowej dostępna jest jedynie deklaracja klasy podstawowej.

6.2. Zagadnienia dostępu.

Klasa pochodna ma dostęp do składowych public i protected klasy podstawowej. Do składowych private klasy podstawowej nie ma dostępu, choć je odziedziczyła.

Rozpatrzmy linię, która decyduje, że klasa Pies jest klasą pochodną:

class Pies : public Ssak

Słowo public umieszczone w powyższej linii oznacza, że składniki public z klasy podstawowej mają w klasie pochodnej dostęp public, a odziedziczone składniki protected mają w klasie pochodnej także dostęp protected.

Gdy słowo public zamienić słowem protected, odziedziczone składniki publiczne zmienią dostęp na protected.

Gdyby zaś słowo public zamienić słowem private, wszystkie odziedziczone po klasie podstawowej składniki public i private miałyby dostęp private.

Możemy również słowo public wyrzucić całkowicie. Wtedy przez domniemanie klasa pochodna przyjmuje dziedziczenie private. Jeśli pochodna jest klasą, wówczas przez domniemanie dziedziczenie jest private, jeśli zaś strukturą - public..

Czasami chcemy ograniczyć dostęp do większości składowych, ale dla kilku z nich chcielibyśmy zostawić dostęp oryginalny. Stosujemy wtedy deklarację dostepu.

Np.

class przodek

{

protected:

int n;

float x;

void funprot(float*, int &);

public:

int szer;

float *funpunl(int );

};

class potomek: private przodek

{

// ......

protected:

przodek::x; //deklaracja dostepu

przodek::funprot;

public:

przodek::szer;

przodek::funpubl;

};

Uwaga: deklaracjami dostępu możemy tylko powtórzyć taki dostęp, jaki w klasie podstawowej, miały poszczególne dziedziczone wskaźniki. Zmiana typu dostępu jest niemożliwa.

6.3. Składniki niedziedziczone.

Pewnych składników klasy podstawowej dziedziczyć nie można. Mianowicie

a) konstruktorów

b) operatora przypisania =

c) destruktora.

Należy pamiętać, żeby w razie konieczności napisać fragmenty programów, przejmujące zadania wykonywane przez te składniki. Dotyczy to zwłaszcza operatora przypisania. Konstruując nowy operator przypisania w klasie pochodnej, możemy oczywiście wywołać operator przypisania klasy podstawowej w sposób jawny:

*(this).przodek::operator=(wzor);

Korzystamy tutaj z faktu, że obiekt klasy pochodnej możemy traktować jako obiekt klasy podstawowej.

6.4. Kolejność wykonywania konstruktorów i destruktorów.

Dziedziczenie może być kilkupokoleniowe:

class A

{

.........

};

class B: public A

{

..........

};

class C: public B

{

.........

};

Jest to tak zwane dziedziczenie kaskadowe. Prócz tego klasa może mieć jako składniki obiekty innych klas.

Przy uruchomianiu obiektów danej klasy najpierw włączają się konstruktory klas starszych, potem obiektów składowych, a na końcu samej klasy. W naszym przypadku najpierw ruszy do pracy konstruktor klasy A, potem klasy B, na końcu klasy C. Destruktory włączają się w odwrotnej kolejności - destruktor klasy A włącza się na końcu.

Jeśli chcemy wywołać konstruktor klasy podstawowej z jakimś parametrem, wywołanie to umieszczamy w liście inicjalizacyjnej konstruktora klasy pochodnej. Ilustruje to poniższy program.

//Konstruktory - wywolywanie

#include <iostream.h>

enum RASA { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

class Ssak

{

public:

// konstruktory

Ssak();

Ssak(int wiek);

~Ssak();

//Funkcje dostepu

int PobierzWiek() const { return jegoWiek; }

void UstawWiek(int wiek) { jegoWiek = wiek; }

int PobierzWaga() const { return jegoWaga; }

void UstawWaga(int waga) { jegoWaga = waga; }

// Inne metody

void Mow() const { cout << "Odglos Ssaka!\n"; }

void Spij() const { cout << "Cicho. Ja teraz spie.\n"; }

protected:

int jegoWiek;

int jegoWaga;

};

class Pies : public Ssak

{

public:

// Konstruktory

Pies();

Pies(int wiek);

Pies(int wiek, int waga);

Pies(int wiek, RASA rasa);

Pies(int wiek, int waga, RASA rasa);

~Pies();

// Funkcje dostepu

RASA PobierzRasa() const { return jegoRasa; }

void UstawRasa(RASA rasa) { jegoRasa = rasa; }

// Inne metody

void MachajOgonem() { cout << "Machanie ogonem...\n"; }

void ProsOJedzenie() { cout << "Prosba o jedzenie...\n"; }

private:

RASA jegoRasa;

};

Ssak::Ssak():

jegoWiek(1),

jegoWaga(5)

{

cout << "Ssak - konstruktor...\n";

}

Ssak::Ssak(int wiek):

jegoWiek(wiek),

jegoWaga(5)

{

cout << "Ssak(int) - konstruktor...\n";

}

Ssak::~Ssak()

{

cout << "Ssak - destruktor...\n";

}

Pies::Pies():

Ssak(),

jegoRasa(YORKIE)

{

cout << "Pies - konstruktor...\n";

}

Pies::Pies(int wiek):

Ssak(wiek),

jegoRasa(YORKIE)

{

cout << "Pies(int) - konstruktor...\n";

}

Pies::Pies(int wiek, int waga):

Ssak(wiek),

jegoRasa(YORKIE)

{

jegoWaga = waga;

cout << "Pies(int, int) - konstruktor...\n";

}

Pies::Pies(int wiek, int waga, RASA rasa):

Ssak(wiek),

jegoRasa(rasa)

{

jegoWaga = waga;

cout << "Pies(int, int, RASA) - konstruktor...\n";

}

Pies::Pies(int wiek, RASA rasa):

Ssak(wiek),

jegoRasa(rasa)

{

cout << "Pies(int, RASA) - konstruktor...\n";

}

Pies::~Pies()

{

cout << "Pies - destruktor...\n";

}

int main()

{

Pies fido;

Pies rover(5);

Pies buster(6,8);

Pies yorkie (10,YORKIE);

Pies dobbie (4,20,DOBERMAN);

fido.Mow();

rover.MachajOgonem();

cout << "Yorkie ma " << yorkie.PobierzWiek() << " lat\n";

cout << "Dobbie wazy " << dobbie.PobierzWaga() << " kilogramow\n";

return 0;

}

W zacieniowanych fragmentach konstruktor klasy podstawowej jest wywoływany w liście inicjalizacyjnej klasy pochodnej. W pierwszej ramce domyślny konstruktor Psa wywołuje domyślny konstruktor Ssaka. Nie jest to konieczne - konstruktor domyślny i tak zostanie w razie potrzeby wywołany.

Oto wynik działania powyższego programu. Wydać tu kolejność wywoływania konstruktorów i destruktorów.

Ssak - konstruktor...

Pies - konstruktor...

Ssak(int) - konstruktor...

Pies(int) - konstruktor...

Ssak(int) - konstruktor...

Pies(int, int) - konstruktor...

Ssak(int) - konstruktor...

Pies(int, RASA) - konstruktor...

Ssak(int) - konstruktor...

Pies(int, int, RASA) - konstruktor...

Odglos Ssaka!

Machanie ogonem...

Yorkie ma 10 lat

Dobbie wazy 20 kilogramow

Pies - destruktor...

Ssak - destruktor...

Pies - destruktor...

Ssak - destruktor...

Pies - destruktor...

Ssak - destruktor...

Pies - destruktor...

Ssak - destruktor...

Pies - destruktor...

Ssak - destruktor...

6.5. Dziedziczenie wielokrotne (wielobazowe)

Klasa może pochodzić od więcej niż jednej klasy podstawowej

class samochod

{

// ....

};

class lodka

{

//......

};

class amfibia: public samochod, public lodka

{

.....

};

Na liście inicjalizacyjnej konstruktora amfibii możemy wywoływać konstruktory zarówno samochodu , jak i lodki.

Przy dziedziczeniu wielokrotnym zachodzi niebezpieczeństwo niejednoznaczności. Na przykład w obu klasach samochod i lodka niech wystąpi zmienna int x. Wtedy musimy odwoływać się do tej zmiennej z użyciem operatora zakresu:

lodka::x

samochod::x

6.6. Konwersje

Przy konwersjach musimy mieć świadomość, że klasa pochodna jest na ogół "bogatsza" od klasy podstawowej.

W związku z tym mogą nastąpić niejawne konwersje - wskaźnik do klasy pochodnej może być niejawnie przekształcony na wskaźnik klasy podstawowej. To samo dotyczy referencji. Dziedziczenie jednak musi być publiczne.

To znaczy, że jeśli argumentem jakiejś funkcji jest referencja lub wskaźnik do obiektu klasy podstawowej, możemy te funkcje na ogół wywołać ze wskaźnikiem lub referencją do klasy pochodnej.

Natomiast nie można wysłać adresu tablicy obiektów klasy pochodnej do funkcji, która spodziewa się adresu tablicy obiektów klasy podstawowej (obiekty klasy pochodnej są na ogół większe).

Załóżmy, że dziedziczenie przebiega według następującego schematu

0x08 graphic
0x08 graphic
A

0x08 graphic
0x08 graphic
A1 A2

A12

Klasa podstawowa A ma dwie klasy pochodne A1 i A2. Każdy z obiektów typu A1 i A2 ma podobiekty typu A. Jeśli teraz klasa A12 odziedziczy po A1 i A2, każdy obiekt klasy A12 będzie miał dwa podobiekty typu A. Unikamy tego zjawiska stosując dziedziczenie wirtualne lub wirtualne klasy podstawowe:

class A {...};

class A1: virtual public A {...};

class A2: virtual public A {...};

class A12: public A1, public A2 {...};

Dzięki kluczowemu słowu virtual w liniach 2 i 3, A jest wirtualną klasą podstawową i obiekty klasy A12 mają tylko po jednym podobiekcie klasy A. Gdyby ominąć słowo virtual, miałyby po dwa takie podobiekty.

7. Polimorfizm i funkcje wirtualne.

7.1. Uwagi wstępne.

Kiedy pracujemy z obiektami różnych typów, musimy często stosować różne funkcje w zależności od typu obiektu. Można wykorzystywać do tego celu instrukcje typu switch, bądź zagnieżdżone instrukcje if...else. Kiedy pojawiają się nowe obiekty, trzeba instrukcje te rozbudowywać, łatwo przy tym popełnić błąd. W C++ istnieje możliwość programowania polimorficznego. Ten sam komunikat ( na przykład wywołanie funkcji składowej) przesłany do wielu różnych typów obiektów przybiera wiele form. Idea ta jest realizowana za pomocą funkcji wirtualnych. Rozpatrzmy program:

//Zastosowanie funkcji wirtualnych.

# include <iostream.h>

class zwierz

{ public:

virtual void print()

{cout << "Nie wiadomo, co za zwierz" <<endl;}

protected:

int licznog;

};

class ryba: public zwierz

{ public:

ryba(int n) {licznog=n;}

void print()

{cout << "Ryba ma " <<licznog << " nog"<<endl;

}

};

class ptak: public zwierz

{ public:

ptak(int n) {licznog=n;}

void print()

{cout << "Ptak ma " <<licznog << " nog"<<endl;

}

};

class ssak: public zwierz

{ public:

ssak(int n) {licznog=n;}

void print()

{cout << "Ssak ma " <<licznog << " nog"<<endl;

}

};

void badacz_nog(zwierz &jakis_zwierz)

{

jakis_zwierz.print();

}

main()

{

zwierz *p[4];

p[0]= new ryba(0);

p[1]= new ptak(2);

p[2]= new ssak(4);

p[3]= new zwierz;

for (int i=0; i<4;i++) p[i]->print();

cout << "teraz referencja" << endl;

zwierz cos;

ryba karp(0);

ptak kiwi(2);

ssak kot(4);

badacz_nog(cos);

badacz_nog(karp);

badacz_nog(kiwi);

badacz_nog(kot);

}

Zauważmy - wskaźnikowi do klasy podstawowej przyporządkowaliśmy obiekty klasy pochodnej. Wynik działania powyższego programu jest następujący:

Ryba ma 0 nog

Ptak ma 2 nog

Ssak ma 4 nog

Nie wiadomo, co za zwierz

teraz referencja

Nie wiadomo, co za zwierz

Ryba ma 0 nog

Ptak ma 2 nog

Ssak ma 4 nog

Jak widać, każde wywołanie funkcji printf() powoduje odwołanie się do właściwego obiektu. Podobnie wywołanie funkcji badacz_nog( zwierz &) mimo że funkcja odwołuje się do referencji do klasy podstawowej - trafia w końcu do obiektów pochodnych. Dzieje się tak dlatego, że w klasie podstawowej funkcje print() opatrzyliśmy słowem virtual. Słowa tego nie musimy umieszczać w klasach pochodnych, choć wszystkie funkcje o takiej sygnaturze będą automatycznie wirtualne.

Gdybyśmy nie umieścili w programie słowa virtual, w powyższym programie wywoływałaby się zawsze funkcja z klasy podstawowej. Oczywiście, moglibyśmy "zdławić" mechanizm wirtualny za pomocą operatora zakresu, np. instrukcją:

p[1]zwierz::print();

Kiedy mówimy o funkcjach wirtualnych, mówimy często o późnym wiązaniu. Termin ten oznacza, że wybór zastosowanej funkcji (w tym wypadku wersji print()) dokonuje się w trakcie działania programu, a nie w czasie kompilacji (wczesne wiązanie).

Uwaga!

Jeśli w klasie zadeklarowana jest chociaż jedna funkcja wirtualna, należy słowo virtual umieścić również przed deklaracją destruktora (wystarczy w klasie podstawowej). Zapewnia to uruchomienie destruktora właściwego obiektu, a nie jedynie destruktora obiektu klasy podstawowej.

Konstruktory nie mogą być wirtualne. Można jednak obejść to ograniczenie, budując wirtualne funkcje spełniające tę samą rolę co konstruktory. Poniżej przykład wirtualnego "konstruktora kopiującego".

//Wirtualny konstruktor kopiujacy

#include <iostream.h>

class Ssak

{

public:

Ssak():jegoWiek(1) { cout << "Konstruktor Ssaka...\n"; }

~Ssak() { cout << "Destruktor Ssaka...\n"; }

Ssak (const Ssak & rhs);

virtual void Mow() const { cout << "Odglos Ssaka!\n"; }

virtual Ssak* Klonuj() { return new Ssak(*this); }

int GetWiek()const { return jegoWiek; }

protected:

int jegoWiek;

};

Ssak::Ssak (const Ssak & rhs):jegoWiek(rhs.GetWiek())

{

cout << "Konstruktor kopiujacy Ssaka...\n";

}

class Pies : public Ssak

{

public:

Pies() { cout << "Konstruktor Psa...\n"; }

~Pies() { cout << "Destruktor Psa...\n"; }

Pies (const Pies & rhs);

void Mow()const { cout << "Hau!\n"; }

virtual Ssak* Klonuj() { return new Pies(*this); }

};

Pies::Pies(const Pies & rhs):

Ssak(rhs)

{

cout << "Konstruktor kopiujacy Psa...\n";

}

class Kot : public Ssak

{

public:

Kot() { cout << "Konstruktor Kota...\n"; }

~Kot() { cout << "Destruktor Kota...\n"; }

Kot (const Kot &);

void Mow()const { cout << "Miau!\n"; }

virtual Ssak* Klonuj() { return new Kot(*this); }

};

Kot::Kot(const Kot & rhs):

Ssak(rhs)

{

cout << "Konstruktor kopiujacy Kota...\n";

}

enum ANIMALS { SSAK, PIES, KOT};

const int LiczbaTypowZwierzat = 3;

int main()

{

Ssak *TablicaJeden[LiczbaTypowZwierzat];

Ssak* wsk;

int choice,i;

for (i = 0; i<LiczbaTypowZwierzat; i++)

{

cout << "(1)pies (2)kot (3)ssak: ";

cin >> choice;

switch (choice)

{

case PIES: wsk = new Pies;

break;

case KOT: wsk = new Kot;

break;

default: wsk = new Ssak;

break;

}

TablicaJeden[i] = wsk;

}

Ssak *TablicaDwa[LiczbaTypowZwierzat];

for (i=0;i<LiczbaTypowZwierzat;i++)

{

TablicaJeden[i]->Mow();

TablicaDwa[i] = TablicaJeden[i]->Klonuj();

}

for (i=0;i<LiczbaTypowZwierzat;i++)

TablicaDwa[i]->Mow();

return 0;

}

Zacieniowane fragmenty odpowiadają "wirtualnemu konstruktorowi kopiującemu". A oto wynik programu:

(1)pies (2)kot (3)ssak: 1

Konstruktor Ssaka...

Konstruktor Psa...

(1)pies (2)kot (3)ssak: 2

Konstruktor Ssaka...

Konstruktor Kota...

(1)pies (2)kot (3)ssak: 2

Konstruktor Ssaka...

Konstruktor Kota...

Hau!

Konstruktor kopiujacy Ssaka...

Konstruktor kopiujacy Psa...

Miau!

Konstruktor kopiujacy Ssaka...

Konstruktor kopiujacy Kota...

Miau!

Konstruktor kopiujacy Ssaka...

Konstruktor kopiujacy Kota...

Hau!

Miau!

Miau!

Zasada programowania polimorficznego polega na tym, że jeśli stosujemy już wskaźnik do klasy podstawowej, korzystamy jedynie z funkcji wirtualnych. Jednak jeśli mamy w którejś z klas jakąś metodę niewirtualną, musimy wskaźnik ten zrzutować na odpowiednią klasę. Służy do tego operator dynamic_cast

Poniższy przykład ilustruje zastosowanie tego operatora: wywołanie niewirtualnej metody Mrucz() w klasie Kot. Jeśli konwersja się nie uda, wskaźnik będzie równy NULL.

// dynamic_cast

#include <iostream.h>

class Ssak

{

public:

Ssak():jegoWiek(1) { cout << "Konstruktor Ssaka...\n"; }

~Ssak() { cout << "Destruktor Ssaka...\n"; }

virtual void Mow() const { cout << "Odglos Ssaka!\n"; }

protected:

int jegoWiek;

};

class Kot: public Ssak

{

public:

Kot() { cout << "Konstruktor Kota...\n"; }

~Kot() { cout << "Destruktor Kota...\n"; }

void Mow()const { cout << "Miau\n"; }

void Mrucz() const { cout << "Mrrrrrrrrrrr\n"; }

};

class Pies: public Ssak

{

public:

Pies() { cout << "Konstruktor Psa...\n"; }

~Pies() { cout << "Destruktor Psa...\n"; }

void Mow()const { cout << "Hau!\n"; }

};

int main()

{

const int NumerSsaka = 3;

Ssak* Zoo[NumerSsaka];

Ssak* pSsak;

int wybor,i;

for (i=0; i<NumerSsaka; i++)

{

cout << "(1)Pies (2)Kot: ";

cin >> wybor;

if (wybor == 1)

pSsak = new Pies;

else

pSsak = new Kot;

Zoo[i] = pSsak;

}

cout << "\n";

for (i=0; i<NumerSsaka; i++)

{

Zoo[i] ->Mow();

Kot *pPrawdziwyKot = 0;

pPrawdziwyKot = dynamic_cast< Kot *> (Zoo[i]);

if (pPrawdziwyKot)

pPrawdziwyKot->Mrucz();

else

cout << "Ojej! To nie byl Kot...\n";

delete Zoo[i];

cout << "\n";

}

return 0;

}

Oto wynik powyższego programu:

(1)Pies (2)Kot: 1

Konstruktor Ssaka...

Konstruktor Psa...

(1)Pies (2)Kot: 2

Konstruktor Ssaka...

Konstruktor Kota...

(1)Pies (2)Kot: 1

Konstruktor Ssaka...

Konstruktor Psa...

Hau!

Ojej! To nie byl Kot...

Destruktor Ssaka...

Miau

Mrrrrrrrrrrr

Destruktor Ssaka...

Hau!

Ojej! To nie byl Kot...

Destruktor Ssaka...

7.2. Abstrakcyjne typy danych.

Gdy posługujemy się funkcjami wirtualnymi, często klasa podstawowa służy nam do porządkowania danych i funkcji wirtualnych , a nie do tworzenia konkretnych obiektów. Piszemy więc takie klasy w ten sposób, by obiektów takich nie można było uruchomić. Klasy te nazywamy klasami abstrakcyjnymi. Rozważmy program:

// Implementacja funkcji czysto wirtualnych

#include <iostream.h>

enum BOOL { FALSZ, PRAWDA };

class Ksztalt

{

public:

Ksztalt(){}

~Ksztalt(){}

virtual long PobierzPole() = 0;

virtual long PobierzObwod()= 0;

virtual void Rysuj() = 0;

};

void Ksztalt::Rysuj()

{

cout << "Abstrakcyjny mechanizm rysowania!\n";

}

class Kolo : public Ksztalt

{

public:

Kolo(int promien):jegoPromien(promien){}

~Kolo(){}

long PobierzPole() { return 3 * jegoPromien * jegoPromien; }

long PobierzObwod() { return 9 * jegoPromien; }

void Rysuj();

private:

int jegoPromien;

};

void Kolo::Rysuj()

{

cout << "Tutaj powinna byc procedura rysownia kola!\n";

Ksztalt::Rysuj();

}

class Prostokat : public Ksztalt

{

public:

Prostokat(int dlugosc, int szerokosc):

jegoDlugosc(dlugosc), jegoSzerokosc(szerokosc){}

~Prostokat(){}

long PobierzPole() { return jegoDlugosc * jegoSzerokosc; }

long PobierzObwod() {return 2*jegoDlugosc + 2*jegoSzerokosc; }

virtual int PobierzDlugosc() { return jegoDlugosc; }

virtual int PobierzSzerokosc() { return jegoSzerokosc; }

void Rysuj();

private:

int jegoSzerokosc;

int jegoDlugosc;

};

void Prostokat::Rysuj()

{

for (int i = 0; i<jegoDlugosc; i++)

{

for (int j = 0; j<jegoSzerokosc; j++)

cout << "x ";

cout << "\n";

}

Ksztalt::Rysuj();

}

class Kwadrat : public Prostokat

{

public:

Kwadrat(int dlugosc);

Kwadrat(int dlugosc, int szerokosc);

~Kwadrat(){}

long PobierzObwod() {return 4 * PobierzDlugosc();}

};

Kwadrat::Kwadrat(int dlugosc):

Prostokat(dlugosc,dlugosc)

{}

Kwadrat::Kwadrat(int dlugosc, int szerokosc):

Prostokat(dlugosc,szerokosc)

{

if (PobierzDlugosc() != PobierzSzerokosc())

cout << "Blad, to nie jest kwadrat...\n";

}

int main()

{

int wybor;

BOOL fKoniec = FALSZ;

Ksztalt * sp;

while (1)

{

cout << "(1)Kolo (2)Prostokat (3)Kwadrat (0)Koniec: ";

cin >> wybor;

switch (wybor)

{

case 1: sp = new Kolo(5);

break;

case 2: sp = new Prostokat(4,6);

break;

case 3: sp = new Kwadrat (5);

break;

default: fKoniec = PRAWDA;

break;

}

if (fKoniec)

break;

sp->Rysuj();

cout << "\n";

}

return 0;

}

O tym, że nie będziemy tworzyć obiektów danej klasy, mówią zacienione instrukcje.

virtual long PobierzObwod()= 0;

Powyższa funkcja jest jedynie zadeklarowana - nie ma potrzeby jej definiować. Funkcje, których deklaracjom w klasie podstawowej przypisano zero, muszą być nadpisane w klasach pochodnych. Funkcje te nazywamy czysto wirtualnymi. Jeśli w którejś klasie funkcja ta nie jest nadpisana, cała klasa staje się klasą abstrakcyjną. (Bo odziedziczyła funkcję czysto wirtualną). Czasami jest to efekt zamierzony - mamy wtedy hierarchie klas abstrakcyjnych, co możemy potraktować jako jeszcze jeden sposób porządkowania naszego programu.

3

Piotr Staniewski

C++ Studia Zaoczne. Wykład 4



Wyszukiwarka

Podobne podstrony:
Programowanie obiektowe(ćw) 1
Zadanie projekt przychodnia lekarska, Programowanie obiektowe
Programowanie obiektowe w PHP4 i PHP5 11 2005
Programowanie Obiektowe ZadTest Nieznany
Egzamin Programowanie Obiektowe Głowacki, Programowanie Obiektowe
Jezyk C Efektywne programowanie obiektowe cpefpo
Programowanie Obiektowe Ćwiczenia 5
Programowanie obiektowe(cw) 2 i Nieznany
programowanie obiektowe 05, c c++, c#
Intuicyjne podstawy programowania obiektowego0
Programowanie obiektowe, CPP program, 1
wyklad5.cpp, JAVA jest językiem programowania obiektowego
projekt01, wisisz, wydzial informatyki, studia zaoczne inzynierskie, programowanie obiektowe, projek
przeciazanie metod i operatorow, Programowanie obiektowe
projekt06, wisisz, wydzial informatyki, studia zaoczne inzynierskie, programowanie obiektowe, projek
projekt07, wisisz, wydzial informatyki, studia zaoczne inzynierskie, programowanie obiektowe, projek
Programowanie Obiektowe Cz2, Dziedziczenie proste
Programowanie obiektowe, w2, 2

więcej podobnych podstron