lekcja6 F2DODVFRXS7F3ASHJJFIL63FJORUZSS6IHD65XA


LEKCJA 36

FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE.

W trakcie tej lekcji dowiesz się, co mawia żona programisty, gdy nie chce być obiektem klasy abstrakcyjnej.

FUNKCJE W PEŁNI WIRTUALNE (PURE VIRTUAL).

W skrajnych przypadkach wolno nam umieścić funkcję wirtualną w klasie bazowej nie definiując jej wcale. W klasie bazowej umieszczamy wtedy tylko deklarację-prototyp funkcji. W następnych pokoleniach klas pochodnych mamy wtedy pełną swobodę i możemy zdefiniować funkcję wirtualną w dowolny sposób - adekwatny dla potrzeb danej klasy pochodnej. Możemy np. do klasy bazowej (ang. generic class) dodać prototyp funkcji wirtualnej funkcja_eksperymentalna() nie definiując jej w (ani wobec) klasie bazowej. Sens umieszczenia takiej funkcji w klasie bazowej polege na uzyskaniu pewności, iż wszystkie klasy pochodne odziedziczą funkcję funkcja_eksperymentalna(), ale każda z klas pochodnych wyposaży tę funkcję we własną definicję. Takie postępowanie może okazać się szczególnie uzasadnione przy tworzeniu biblioteki klas (class library) przeznaczonej dla innych użytkowników. C++ w wersji instalacyjnej posiada już kilka gotowych bibliotek klas. Funkcje wirtuale, które nie zostają zdefiniowane - nie posiadają zatem ciała funkcji - nazywane są funkcjami w pełni wirtualnymi (ang. pure virtual function).

O KLASACH ABSTRAKCYJNYCH.

Jeśli zadeklarujemy funkcję CZwierzak::Oddychaj() jako funkcję w pełni wirtualną, oprócz słowa kluczowego virtual, trzeba tę informację w jakiś sposób przekazać kompilatorowi C++. Aby C++ wiedział, że naszą intencją jest funkcja w pełni wirtalna, nie możemy zadeklarować jej tak:

class CZwierzak

{

...

public:

virtual void Oddychaj();

...

};

a następnie pominąć definicję (ciało) funkcji. Takie postępowanie C++ uznałby za błąd, a funkcję - za zwykłą funkcję wirtualną, tyle, że "niedorobioną" przez programistę. Naszą intencję musimy zaznaczyć już w definicji klasy w taki sposób:

class CZwierzak

{

...

public:

virtual void Oddychaj() = 0;

...

};

Informacją dla kompilatora, że chodzi nam o funkcję w pełni wirtualną, jest dodanie po prototypie funkcji "= 0". Definiując klasę pochodną możemy rozbudować funkcję wirtualną np.:

class CZwierzak

{

...

public:

virtual void Oddychaj() = 0;

...

};

class CPiesek : public CZwierzak

{

...

public:

void Oddychaj() { cout << "Oddycham..."; }

...

};

Przykładem takiej funkcji jest funkcja Mów() z przedstawionego poniżej programu. Zostawiamy ją w pełni wirtualną, ponieważ różne obiekty klasy CZLOWIEK i klas pochodnych

class CZLOWIEK

{

public:

void Jedz(void);

virtual void Mow(void) = 0; //funkcja WIRTUALNA

};

class NIEMOWLE : public CZLOWIEK

{

public:

void Mow(void); // Tym razem BEZ slowa virtual

};

/* Tu definiujemy metodę wirtualną: -------------------- */

void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; };

mogą mówić na różne sposoby... Obiekt Niemowle, dla przykładu, nie chce mówić wcale, ale z innymi obiektami może być inaczej. Wyobraź sobie np. obiekt klasy Żona (żona to przecież też człowiek !).

class Zona : public CZLOWIEK

{

public:

void Mow(void);

}

W tym pokoleniu definicja wirtualnej metody Mow() mogłaby wyglądać np. tak:

void Zona::Mow(void)

{

cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!! ";

cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY ?!!!";

//... itd., itd., itd...

}

[P128.CPP]

#include "iostream.h"

class CZLOWIEK

{

public:

void Jedz(void);

virtual void Mow(void) = 0;

};

void CZLOWIEK::Jedz(void) { cout << "MNIAM, MNIAM..."; };

class Zona : public CZLOWIEK

{

public:

void Mow(void); //Zona mowi swoje

}; //bez wzgledu na argumenty (typ void)

void Zona::Mow(void)

{

cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!!";

cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY ?!!!";

}

class NIEMOWLE : public CZLOWIEK

{

public:

void Mow(void);

};

void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; };

main()

{

NIEMOWLE Dziecko;

Zona Moja_Zona;

Dziecko.Jedz();

Dziecko.Mow();

Moja_Zona.Mow()

return 0;

}

Przykładowa klasa CZŁOWIEK jest klasą ABSTRAKCYJNĄ. Jeśli spróbujesz dodać do powyższego programu np.:

CZLOWIEK Facet;

Facet.Jedz();

uzyskasz komunikat o błędzie: Cannot create a variable for abstract class "CZLOWIEK" (Nie mogę utworzyć zmiennych dla klasy abstrakcyjnej "CZLOWIEK"

[???] KLASY ABSTRAKCYJNE.

* Po klasach abstrakcyjnych MOŻNA dziedziczyć!

* Obiektów klas abstrakcyjnych NIE MOŻNA stosować bezpośrednio!

Ponieważ wyjaśniliśmy, dlaczego klasy są nowymi typami danych, ięc logika (i sens) innej rozpowszechnionej nazwy klas abstrakcyjnych - ADT - Abstract Data Type (Abstrakcyjne Typy Danych) jest chyba zrozumiała i oczywista.

ZAGNIEŻDŻANIE KLAS I OBIEKTÓW.

Może się np. zdarzyć, że klasa stanie się wewnętrznym elementem (ang. member) innej klasy i odpowiednio - obiekt - elementem innego obiektu. Nazywa się to fachowo "zagnieżdżaniem" (ang. nesting). Jeśli, dla przykładu klasa CB będzie zawierać obiekt klasy CA:

class CA

{

int liczba;

public:

CA() { liczba = 0; } //Konstruktor domyslny

CA(int x) { liczba = x; }

void operator=(int n) { liczba = n }

};

class CB

{

CA obiekt;

public:

CB() { obiekt = 1; }

};

Nasze klasy wyposażyliśmy w konstruktory i od razu poddaliśmy overloadingowi operator przypisania = . Aby prześledzić kolejność wywoływania funkcji i sposób przekazywania parametrów pomiędzy tak powiązanymi obiektami rozbudujemy każdą funkcję o zgłoszenie na ekranie.

class CA

{

int liczba;

public:

CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; }

CA(int x) { liczba = x; cout << "->CA(int) "; }

void operator=(int n) { liczba = n; cout << "->operator "; }

};

class CB

{

CA obiekt;

public:

CB() { obiekt = 1; cout << "->Konstruktor CB() "; }

};

Możemy teraz sprawdzić, co stanie się w programie po zadeklarowaniu obiektu klasy CB:

[P129.CPP]

# include "iostream.h"

class CA

{

int liczba;

public:

CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; }

CA(int x) { liczba = x; cout << "->CA(int) "; }

void operator=(int n) { liczba = n; cout << "->operator "; }

};

class CB

{

CA obiekt;

public:

CB() { obiekt = 1; cout << "->Konstruktor CB() "; }

};

main()

{

CB Obiekt;

return 0;

}

Po uruchomieniu programu możesz przekonać się, że kolejność działań będzie następująca:

C:\>program

-> CA(), CA_O::liczba = 0 ->operator ->Konstruktor CB()

Skoro oprócz zainicjowania obiektu klasy pochodnej nie robimy w programie dokładnie nic, nie dziwmy się ostrzeżeniu

Warning: Obiekt is never used...

Jest to sytuacja trochę podobna do komunikacji pomiędzy konstruktorami klas bazowych i pochodnych. Jeśli zaprojektujemy prostą strukturę klas:

class CBazowa

{

private:

int liczba;

public:

CBazowa() { liczba = 0}

CBazowa(int n) { liczba = n; }

};

class CPochodna : public CBazowa

{

public:

CPochodna() { liczba = 0; }

CPochodna(int x) { liczba = x; }

};

problem przekazywania parametrów między konstruktorami klas możemy w C++ rozstrzygnąć i tak:

class CPochodna : public CBazowa

{

public:

CPochodna() : CBazowa(0) { liczba = 0; }

CPochodna(int x) { liczba = x; }

};

Będzie to w praktyce oznaczać wywołanie konstruktora klasy bazowej z przekazanym mu argumentem 0. Podobnie możemy postąpić w stosunku do klas zagnieżdżonych:

[P130.CPP]

#include "iostream.h"

class CA

{

int liczba;

public:

CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; }

CA(int x) { liczba = x; cout << "->CA(int) "; }

void operator=(int n) { liczba = n; cout << "->operator "; }

};

class CB

{

CA obiekt;

public:

CB() : CA(1) {}

};

main()

{

CB Obiekt;

return 0;

}

Eksperymentując z dwoma powyższymi programami możesz przekonać się, jak przebiega przekazywanie parametrów pomiędzy konstruktorami i obiektami klas bazowych i pochodnych.

JESZCZE RAZ O WSKAŹNIKU *this.

Szczególnie ważnym wskaźnikiem przy tworzeniu klas pochodnych i funkcji operatorowych może okazać się pointer *this. Oto przykład listy.

[P131.CPP]

# include "string.h"

# include "iostream.h"

class CLista

{

private:

char *poz_listy;

CLista *poprzednia;

public:

CLista(char*);

CLista* Poprzednia() { return (poprzednia); };

void Pokazuj() { cout << '\n' << poz_listy; }

void Dodaj(CLista&);

~CLista() { delete poz_listy; }

};

CLista::CLista(char *s)

{

poz_listy = new char[strlen(s)+1];

strcpy(poz_listy, s);

poprzednia = NULL;

}

void CLista::Dodaj(CLista& obiekt)

{

obiekt.poprzednia = this;

}

main()

{

CLista *ostatni = NULL;

cout << '\n' << "Wpisanie kropki [.]+[Enter] = Quit \n";

for(;;)

{

cout << "\n Wpisz nazwe (bez spacji): ";

char TAB[70];

cin >> TAB;

if (strncmp(TAB, ".", 1) == 0) break;

CLista *lista = new CLista(TAB);

if (ostatni != NULL)

ostatni->Dodaj(*lista);

ostatni = lista;

}

for(; ostatni != NULL;)

{

ostatni->Pokazuj();

CLista *temp = ostatni;

ostatni = ostatni->Poprzednia();

delete (temp);

}

return 0;

}

Z reguły to kompilator nadaje wartość wskaźnikowi this i to on automatycznie dba o przyporządkowanie pamięci obiektom. Pointer this jest zwykle inicjowany w trakcie działania konstruktora obiektu.

4



Wyszukiwarka

Podobne podstrony:
Lekcja kliniczna 2 VI rok WL
Lekcja Przysposobienia Obronnego dla klasy pierwszej liceum ogólnokształcącego
Lekcja wychowania fizycznego jako organizacyjno metodyczna forma lekcji ruchu
Lekcja kliniczna nr 2 VI rok WL
04 Lekcja
PF7 Lekcja2
lekcja52
Printing bbjorgos lekcja41 uzupelnienie A
lekcja 18 id 265103 Nieznany
Hydrostatyka i hydrodynamika lekcja ze wspomaganiem komputerowym
Lekcja 6 Jak zapamietywac z notatki Tajemnica skutecznych notatek
lekcja 20
lekcja20
Lekcja 04 Szene 04
LINGO ROSYJSKI raz a dobrze Intensywny kurs w 30 lekcjach PDF nagrania audio audio kurs
Printing bbjorgos lekcja01 05 A
'Half Life', czyli pół życia przed monitorem zagrożenia medialne foliogramy gim modul 3 lekcja 5
Lekcja od mamy
lekcja 3 id 265134 Nieznany

więcej podobnych podstron