ďťż

Blog literacki, portal erotyczny - seks i humor nie z tej ziemi


LEKCJA 28: DZIEDZICZENIE ZŁOŻONE.
________________________________________________________________
W trakcie tej lekcji dowiesz się, jak można odziedziczyć wiele
cech po wielu różnych przodkach.
________________________________________________________________

Jeśli zechcemy dziedziczyć dalej według schematu
dziadek-ojciec-syn-wnuk...? Nic nie stoi na przeszkodzie. Przy
okazji zwróć uwagę, że następne pokolenia są coraz bardziej
złożone (tak być nie musi, ale może). W przykładzie poniżej
dziedziczymy według schematu Punkt-Okrąg-Elipsa.

[P100.CPP]

//Przyklad dziedziczenia "wielopokoleniowego"

#include "stdio.h"
#include "conio.h"

struct punkt //BAZOWY typ struktur - punkt(x, y)
{
int x; //wspolrzedne punktu na ekranie
int y;
};

struct kolo: punkt //Str. pochodna - kolo(x, y, R)
{
int promien; //wspolrzedne srodka x,y dziedziczymy
};

struct elipsa: kolo //dziedziczymy x,y i promien
{
int mniejszy_promien; //Str. pochodna elipsa(x, y, R, r)
};

punkt P; //deklarujemy trzy struktury
kolo C;
elipsa E;

main()
{
clrscr();

P.x = C.x = E.x = 1; //Nadajemy wartosci polom struktur
P.y = C.y = E.y = 2;

C.promien = E.promien = 4;
E.mniejszy_promien = 3;
//Sprawdzamy zawartosc pol struktur
printf("%d %d %d %d %d %d \n",
P.x, C.x, E.x, P.y, C.y, E.y);
printf("%d %d %d",
C.promien, E.promien, E.mniejszy_promien );
getch();
return 0;
}

Można dziedziczyć po więcej niż jednym przodku także w inny
sposób. Kwadrat, dla przykładu, dziedziczy cechy po prostokątach

i po rombach jednocześnie (jest jednocześnie szczególnym
przypadkiem prostokąta i szczególnym przypadkiem rombu). Typ
pochodny w tym wypadku, zamiast "dziadka" i "ojca" powinien mieć

DWU RÓŻNYCH OJCÓW (!). W C++ takie dziedziczenie po dwu różnych
typach bazowych jednocześnie nazywa się DZIEDZICZENIEM
WIELOBAZOWYM (ang. multi-base inheritance). A oto przykład
takiego dziedziczenia.

[P101.CPP]

#include

struct BAZOWA1
{ //Struktura bazowa pierwsza
public:
void Funkcja_a(void);
};

struct BAZOWA2
{ //Struktura bazowa druga
public:
void Funkcja_b(void);
};

struct POCHODNA : BAZOWA1, BAZOWA2 //Lista "przodkow"
{
public:
void Funkcja_c(void);
};

void BAZOWA1::Funkcja_a(void){cout << "To ja F_a().\n";}
void BAZOWA2::Funkcja_b(void){cout << "To ja F_b().\n";}
void POCHODNA::Funkcja_c(void){cout << "To ja F_c().\n";}

void main()
{
POCHODNA dziecko; //Dekl. strukt. typu pochodnego

dziecko.Funkcja_a();
dziecko.Funkcja_b();
dziecko.Funkcja_c();
}

Słowo public jest w strukturach zbędne. Zostało użyte wyłącznie
z pobudek "dydaktycznych" - dla zwrócenia uwagi na status
funkcji - członków struktury.

Zarówno pokoleń w schemacie dziadek-ojciec-syn, jak i struktur
(klas) bazowych w schemacie baza_1-baza_2-....-baza_n może być
więcej niż 2.

DZIEDZICZENIE KLAS.

Oto "klasowo-obiektowa" wersja poprzedniego programu
przykładowego ze słonikiem Cholerykiem. Typy struktur Zwierzak i

Slon nazwiemy klasami, (odpowiednio - klasą bazową i klasą
pochodną) a strukturę Slon Choleryk nazwiemy obiektem.

[P102.CPP]

#include

class Zwierzak //Klasa bazowa (base class)
{
public:
int nogi;

void jedz();
void spij();
void oddychaj();
};

void Zwierzak::jedz(void) { cout << "Jem conieco...\n"; }
void Zwierzak::spij(void) { cout << "Cosik mi sie sni...\n"; }
void Zwierzak::oddychaj(void) { cout << "Dysze ciezko...\n"; }

class Slon : public Zwierzak
{
public:
int flaga_ssak;

void trabi();
void tupie();
};

void Slon::trabi(void) { cout << "Tra-ta-ta...\n"; }
void Slon::tupie(void) { cout << "Kroczem...na wschod\n"; }

void main()
{
Slon Obiekt;
/* obiekt Obiekt klasy Slon */
Obiekt.nogi = 4; Obiekt.flaga_ssak = 1;
cout << "\nNogi odziedziczylem: " << Obiekt.nogi;
cout << "\nA teraz kolejno funkcje: \n";
Obiekt.jedz();
Obiekt.spij();
Obiekt.oddychaj();
Obiekt.trabi();
Obiekt.tupie();
if(Obiekt.flaga_ssak) cout << "Jestem ssakiem !";
}

Pamiętając o problemie domyślnego statusu członków
struktur/public i klas/private) możemy przejść do klas i
obiektów.

O KLASACH SZCZEGÓŁOWO.

Aby wykazać możliwość modularyzacji programu zaprojektujemy
moduł w postaci pliku nagłówkowego. Moduł będzie zawierać
definicję naszej prywatnej klasy obiektów ZNAK.

Zaczynamy od danych, które będą nam potrzebne do tworzenia w
programach (różnych !) obiektów typu Znak.

class ZNAK
{
char znak_dany; //Kod ASCII znaku
...

Aby obiekt został zainicjowany (tzn. wiedział jakim znakiem ma
być w danym programie) dodamy do definicji klasy
jednoparametrowy konstruktor

class ZNAK
{
char znak_dany;
public:
ZNAK(...);
...

Dane mogą być prywatne, natomiast konstruktor i funkcje-metody
powinny być publiczne, by można było wywoływać je w programach.
Konstruktor będziemy wywoływać w programach tak:

ZNAK Obiekt('a');

Znaczy to: Utwórz w RAM obiekt klasy ZNAK pod nazwą "Obiekt" i
wytłumacz mu, że jest znakiem 'a'.

Konstruktor powinien pobierać od programu jeden argument typu
char i przekazywać go obiektowi klasy ZNAK na jego pole danych
znak_dany. Definicja konstruktora będzie zatem wyglądać tak:

ZNAK::ZNAK(char x)
{
znak_dany = x;
}

Zakres dopuszczalnych znaków zawęzimy np. do kodów ASCII 65...90

(od A do Z). Jeśli użytkownik "nie trafi", ustawimy zawsze "*"
(asterisk). Dodatkowo, dla "elegancji" zamienimy ewentualne małe

litery na duże.

ZNAK::ZNAK(char x)
{
znak_dany = x;
if(znak_dany < 65 || znak_dany >122) znak_dany = '*';
if(znak_dany > 97) znak_dany -= 32;
}

A jeśli użytkownik nie zechce podać żadnego znaku i zda się na
domyślność obiektu? Żaden problem, wystarczy do klasy ZNAK dodać

bezparametrowy konstruktor domyślny. Konstruktory domyślne
spełniają w C++ taką właśnie rolę:

class ZNAK
{
char znak_dany;
public:
ZNAK(char); //Konstruktor zwykly ("jednoznakowy")
ZNAK(void); //Konstruktor domyślny (bezparametrowy)
...

Słowo void (tu opcjonalne) może nie wystąpić. Aby "kłuło w
oczy", który konstruktor jest konstruktorem domyślnym (ang.
default konstructor), większość programistów zapisuje to tak:

class ZNAK
{
char znak_dany;
public:
ZNAK(char);
ZNAK(); //Z daleka widać, że nic nie ma !
...

Definicja konstruktora bezparametrowego będzie wyglądać tak:

ZNAK::ZNAK() { znak_dany = 'X'; }

W zależności od sposobu zadeklarowania obiektu w programie C++
wywoła automatycznie albo konstruktor ZNAK(char), albo
konstruktor domyślny ZNAK():

ZNAK obiekt; //Nie sprecyzowano jaki, konstruktor domyślny
ZNAK obiekt('m'); //Wiadomo jaki, konstruktor jednoparametrowy


Dzięki temu, że C++ "pedantycznie" sprawdza przed wywołaniem
funkcji zgodność typów argumentów przekazywanych do funkcji
(konstruktor to też funkcja) i porównuje typ argumentów z
życzeniem programisty wyrażonym w prototypie - bezbłędnie
rozpozna (mimo identycznej nazwy), którą funkcję należy
zastosować.

Dodajmy do klasy ZNAK deklaracje (prototypy) funkcji-metod:

class ZNAK
{
char znak_dany;

public:
ZNAK(char);
ZNAK();
void Pokaz_sie();
void Znikaj();
void Skacz();
};

i zdefiniujmy te metody.

void ZNAK::Pokaz_sie(void)
{
cout << znak_dany << '\a';
}

void ZNAK::Znikaj(void)
{
cout << "\b" << ' '; //'\b' == Back Space
}

void ZNAK::Skacz(void)
{
for(int i = 0; i < 100; i++)
{
gotoxy(rand()%50, rand()%50);
cout << znak_dany;
getch();
}
}

Jeśli implementację klasy ZNAK umieścimy w pliku nagłówkowym

A:\ZNAK.H

//_____________________________________________________________
# include
# include
# include

class ZNAK
{
char znak_dany;

public:
ZNAK(char);
ZNAK();
void Pokaz_sie();
void Znikaj();
void Skacz();
};

ZNAK::ZNAK()
{
znak_dany = 'X';
}

ZNAK::ZNAK(char x)
{
znak_dany = x;
if(znak_dany < 65 && znak_dany >122) znak_dany = '*';
if(znak_dany > 97) znak_dany -= 32;
}

void ZNAK::Pokaz_sie(void)
{
cout << znak_dany << '\a';
}

void ZNAK::Znikaj(void)
{
cout << "\b" << ' '; //'\b' == Back Space
}

void ZNAK::Skacz(void)
{
for(int i = 0; i < 100; i++)
{
gotoxy(rand()%50, rand()%50);
cout << znak_dany;
getch();
}
}
//_____________ koniec pliku A:\INCLUDE\ZNAK.H _________________


to nasz program może wyglądać tak:

[P103.CPP]

# include

void main()
{
char litera;

clrscr();
cout << '\n' << "Podaj znak: ";
cin >> litera;

ZNAK Obiekt(litera);
cout << "\nSTART" << "\n\n\n";

getch();
Obiekt.Pokaz_sie();
getch();
Obiekt.Znikaj();
getch();
Obiekt.Skacz();

ZNAK Obiekt2; //To bedzie domyslny 'X'
Obiekt2.Skacz();
}

I tu już widać pewne cechy nowoczesnego obiektowego stylu
programowania. Tym razem sprwdzenie, czy słowo class można
spokojnie zamienić na słowo struct pozostawim dociekliwym
Czytelnikom.
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • qualintaka.pev.pl
  •