24.03.2015 Views

13 skyrius - techmat.vgtu.lt

13 skyrius - techmat.vgtu.lt

13 skyrius - techmat.vgtu.lt

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

<strong>13</strong>. PAVELDIMUMAS<br />

Paveldimumas (angl. inheritance) yra vienas iš objektinio programavimo būdų taupiai rašyti<br />

programos kodą, leidžiantis patogiai kurti naujas klases iš jau turimų klasių, paveldint jų savybes –<br />

laukus ir metodus. Aišku, procedūrinis programavimas irgi turi panašius dalykus – esant reikalui<br />

praplėsti turimos funkcijos galimybes, jos kvietimą galima „apvilkti“ naujos funkcijos kūnu ir<br />

papildomai pridėti naujas galimybes. Vis tik paveldimumo mechanizmas yra taupesnis ir<br />

elegantiškesnis; jis greta vartotojo duomenų formatų (t.y. klasių) ir polimorfizmo (žr. tolesnį skyrių)<br />

laikomas vienu pagrindinių objektinio programavimo principų.<br />

Paveldimumas yra ypač patogus instrumentas taikant klasių bibliotekas: galima naudoti kitų<br />

programuotojų parašytas klases ir jas pritaikyti savo reikmėms ne modifikuojant, o paveldint jų<br />

savybes.<br />

<strong>13</strong>.1 BENDROS ŽINIOS<br />

Klasę, iš kurios paveldėsime visas savybes, vadinsime bazine klase (angl. base class), o ją<br />

naujomis savybėmis plečiančią naująją ar kelias naująsias klases – išvestinėmis klasėmis (angl.<br />

derived class). Tai pavaizduosime principine paveldimumo schema:<br />

Bazinė klasė<br />

Savybė 1<br />

Savybė 2<br />

Savybė3<br />

paveldimumas<br />

Išvestinė klasė<br />

Savybė 1<br />

Savybė 2<br />

Savybė 3<br />

Savybė 4<br />

Sintaksės pavyzdys: anksčiau jau nagrinėta skaitiklio klasė Counter išplečiama išvestine<br />

klase CounterDown, kuri turės vieną papildomą metodą skaitiklio reikšmės mažinimui. Jei, tarkim,<br />

neturėtume prieigos prie Counter klasės (kad tiesiog įdėtume metodą į jos kūną), tai<br />

paveldimumas būtų pats patogiausias kelias Counter funkcionalumui išplėsti.<br />

1 pavyzdys.<br />

162


#include <br />

using namespace std;<br />

//<br />

class Counter{<br />

protected: // 1 pastaba<br />

unsigned int count;<br />

public:<br />

Counter( ): count( 0 ) { } // konstruktorius<br />

Counter( int c ): count( c ) { } // konstruktorius<br />

//<br />

unsigned int returnCount( ){<br />

return count;<br />

}<br />

Counter operator++( ){ // “++” perkrovimas<br />

return Counter( ++count );<br />

}<br />

};<br />

//<br />

class CounterDown: public Counter{ // 2 pastaba<br />

public:<br />

Counter operator--( ){ // “- -” perkrovimas<br />

return Counter( --count );<br />

}<br />

};<br />

//<br />

int main( ){<br />

Counter c1;<br />

CounterDown c2; // 3 pastaba<br />

//<br />

cout


Rezu<strong>lt</strong>atai:<br />

c1: 0<br />

c2: 0<br />

c2: 3<br />

c2: 1<br />

c1: 1<br />

Pastabos:<br />

1. Kad išvestinės klasės metodams būtų prieinami bazinės klasės laukai, būtinas toks<br />

raktažodis. Tačiau jei programoje būtų paveldimumu nesusietų klasių, protected laukai<br />

gretimų klasių metodams – neprieinami. Jei laukai būtų griežtai užverti – private – jie būtų<br />

prieinami tik pačios bazinės klasės viduje; išvestinei klasei – taip pat neprieinami.<br />

2. Taip nurodoma, kad klasė paveldi kitos klasės savybes; čia public reiškia, kad realizuojamas<br />

bendrasis paveldimumas (apie tai skyriaus pabaigoje).<br />

3. Išvestinės klasės konstruktorių nėra – jos objektui formuoti tokiu atveju pasitelkiamas<br />

bazinės klasės atitinkamas konstruktorius (t. y. konstruktorius be argumentų).<br />

4. Išvestinės klasės objektams prieinami bazinės klasės metodai returnCount( ) ir<br />

operator++(), o metodui operator--( ) – ir bazinės klasės konstruktorius su argumentais.<br />

5. Bazinė klasė ją išplėtus nepakinta, todėl jos objektui neprieinami išvestinės klasės<br />

papildomi metodai (čia – operator--( ) ).<br />

Kai programa sudėtinga, patogu pateikti jos klasių struktūrą ir paveldimumo schemą<br />

vadinamąja UML ( angl. Unified Modeling Language, unifikuota modeliavimo kalba) diagrama.<br />

UML diagramoje parodomi klasių pavadinimai, atskiruose laukeliuose išvardinami laukai,<br />

konstruktoriai, destruktoriai ir metodai. Paveldimumas žymimas rodykle link bazinės klasės.<br />

Išvestinės klasės laukeliuose užrašomas tik klasės pavadinimas ir visi papildomi laukai,<br />

konstruktoriai, destruktoriai ir metodai. Jei papildomų savybių laukų ar metodų skyriuose nėra –<br />

šie skyriai paliekami tušti.<br />

Pirmosios programos UML schema:<br />

Counter<br />

count<br />

Bazinė klasė<br />

Counter( )<br />

Counter( int )<br />

returnCounter( )<br />

operator++( )<br />

Išvestinė klasė<br />

CounterDown<br />

operator--( )<br />

164


Dažnai programuotojai projektą sudaro kiek kitaip nei parodyta 1-ajame pavyzdyje: klasių<br />

apibrėžimai sukeliami į antraštinius failus, o programos pradiniame faile lieka tik startinė funkcija.<br />

Aišku, prieš ją direktyvomis kompiliatoriui include turi būti prijungti visi reikiami antraštiniai failai<br />

su klasių apibrėžimais. 1-osios programos projektą galime sukurti taip: 2 pavyzdys:<br />

Antraštinis failas bazinei klasei basicClass.h:<br />

class Counter{<br />

protected:<br />

};<br />

public:<br />

unsigned int count;<br />

Counter( ): count( 0 ) { }<br />

Counter( int c ): count( c ) { }<br />

//<br />

~Counter( ) { }<br />

//<br />

unsigned int returnCount( ){<br />

return count;<br />

}<br />

Counter operator++( ){<br />

return Counter( ++count );<br />

}<br />

Antraštinis failas išvestinei klasei derivedClass.h:<br />

class CounterDown: public Counter{<br />

public:<br />

Counter operator--( ){<br />

return Counter( --count );<br />

}<br />

};<br />

Pradinis programos failas:<br />

#include <br />

#include "basicClass.h"<br />

#include "derivedClass.h"<br />

using namespace std;<br />

//<br />

int main( ){<br />

Counter c1;<br />

CounterDown c2;<br />

165


}<br />

//<br />

cout


}<br />

Counter operator++( ){<br />

return Counter( ++count );<br />

}<br />

};<br />

//<br />

class CounterDown: public Counter{<br />

public:<br />

CounterDown( ): Counter( ) { // 1 pastaba<br />

cout


3. Dabar metodas reikšmę turi grąžinti jau naudodamasis išvestinės klasės konstruktoriumi.<br />

Grąžinti reikšmės klasės Counter konstruktoriumi nevalia, nes kompiliatorius nežinos, kaip<br />

pertvarkyti Counter formato objektą į CounterDown formatą panašiose situacijose, kaip 4-a<br />

pastaba pažymėtame operatoriuje.<br />

4. Tokia sintaksė irgi kviečia išvestinės klasės konstruktorių su argumentu. A<strong>lt</strong>ernatyvi šio<br />

operatoriaus sintaksė būtų CounterDown c3 (--c2 ); . Tokia sintaksė, kai išvestinės klasės<br />

objektai sukonstruojami kopijuojant ir dar atliekant papildomus skirtingose klasėse<br />

apibrėžtus veiksmus (čia – -- arba ++), visad yra kebli. Pabandykit į programą įrašyti<br />

operatorių CounterDown c4 = ++c1; – aišku, bus deklaruota klaida. Turėdami galvoje 3-iąją<br />

pastabą, pabandykit klaidą ištaisyti. Visa tai yra specifiniai sintaksės dalykai, be kurių galima<br />

išsiversti. Jie išsamiai aprašyti <strong>13</strong>.3 skyriuje; pradėdami pažintį su paveldimumu galite šį<br />

skyrių praleisti.<br />

Paleiskite programą ir pamatysite, kad iš tikrųjų prieš konstruojant išvestinės klasės objektą<br />

yra suformuojamas atitinkamas bazinės klasės objektas. Tai yra būtina, kadangi išvestinė klasė<br />

paveldi visus bazinės klasės laukus (ir gali papildyti laukų sąrašą), o jų reikšmėms suformuoti<br />

būtina paleisti bazinės klasės konstruktorių. Galima netgi išreikštai nekviesti bazinės klasės<br />

konstruktorių – jie vis tiek bus iškviesti. Galite tuo įsitikinti taip pakeitę išvestinės klasės<br />

konstruktorius:<br />

CounterDown( ) {<br />

cout


. . .<br />

Atvirkštiniam pertvarkymui būtina parašyti vadinamąjį pertvarkantįjį konstruktorių. Visą<br />

sintaksę paaiškinsim 3-osios programos pagrindu.<br />

4 pavyzdys.<br />

#include <br />

using namespace std;<br />

//<br />

class Counter{<br />

protected:<br />

unsigned int count;<br />

public:<br />

Counter( ): count( 0 ) { }<br />

Counter( int c ): count( c ) { }<br />

//<br />

~Counter( ) { }<br />

//<br />

unsigned int returnCount( ){<br />

return count;<br />

}<br />

Counter operator++( ){<br />

return Counter( ++count );<br />

}<br />

};<br />

//<br />

class CounterDown: public Counter{<br />

public:<br />

CounterDown( ): Counter( ) { }<br />

CounterDown( int c ): Counter( c ) { }<br />

//<br />

~CounterDown( ){ }<br />

//<br />

CounterDown( Counter c ) { // pertvarkantysis konstruktorius<br />

};<br />

//<br />

int main( ){<br />

count = c.returnCount( ); // iš Counter tipo į CounterDown<br />

}<br />

//<br />

CounterDown operator--( ){<br />

return CounterDown( --count );<br />

//return Counter( --count ); // dabar galimas ir toks<br />

// reikšmės grąžinimas: neišreikštai būtų<br />

// kviečiamas pertvarkantysis konstruktorius<br />

}<br />

169


CounterDown c1, c2 = 10;<br />

//<br />

++c2; // gerai: grąžinamas Counter objektas, bet rodyklė<br />

// jį nenutaikyta<br />

c1 = c2; // gerai: abu objektai CounterDown formato<br />

//<br />

CounterDown c3 = --c1; // gerai: metodas "--" grąžina<br />

// CounterDown formato objektą<br />

}<br />

CounterDown c4 = ++c1; // neišreikštai kviečiamas pertvarkantysis<br />

// konstruktorius: "++" grąžina Counter<br />

// objektą; jį reikia pertvarkyti į CounterDown<br />

// formatą<br />

Counter c5 = c1; // neišreikštinis pertvarkymas iš CounterDown į bazinį<br />

// Counter formatą: visada leidžiamas<br />

cout


}<br />

Counter operator++( ){<br />

return Counter( ++count );<br />

}<br />

};<br />

//<br />

class CounterDown: public Counter{<br />

public:<br />

CounterDown( ): Counter( ) { }<br />

CounterDown( int c ): Counter( c ) { }<br />

//<br />

~CounterDown( ){ }<br />

//<br />

explicit CounterDown( Counter c ) { // išreikštinis<br />

count = c.returnCount( ); // pertvarkantysis konstruktorius<br />

}<br />

//<br />

CounterDown operator--( ){<br />

return CounterDown( --count );<br />

//return Counter( --count ); // dabar nebegalimas<br />

}<br />

};<br />

//<br />

int main( ){<br />

CounterDown c1, c2 = 10;<br />

//<br />

++c2;<br />

//<br />

c1 = c2;<br />

//<br />

CounterDown c3 = --c1;<br />

//<br />

//CounterDown c4 = ++c1; // dabar nebegalimas<br />

CounterDown c4 = static_cast( ++c1 ); // išreikštinis<br />

// tipų pertvarkymas;<br />

// rekomenduojamas stilius<br />

//CounterDown c4 = (CounterDown)( ++c1 ); // a<strong>lt</strong>ernatyvi sintaksė<br />

//<br />

Counter c5 = static_cast( c1 ); // išreikštinis pertvarkymas<br />

//Counter c5 = (Counter)c1;<br />

//<br />

cout


}<br />

system( "pause" );<br />

return 0;<br />

<strong>13</strong>.4 PAVELDIMUMAS IR METODŲ PERKROVIMAS<br />

Nagrinėsime bazinės klasės metodų perrašymą išvestinėje klasėje. Dabar išvestinės klasės<br />

metodų vardai sutampa su bazinės klasės metodų vardais. Kurie metodai bus kviečiami bazinės ir<br />

išvestinės klasių objektams?<br />

6 pavyzdys: steko iš trijų elementų klasė. Stekas – paprasta duomenų struktūra, nedidelio<br />

mato vienmatis masyvas laikinam duomenų saugojimui. Priklausomai nuo to, kokia tvarka į steką<br />

įdedami ir iš jo išimami duomenys, stekai būna įvairių rūšių: FIFO (angl. First In, First Out – pirmas<br />

įėjo, pirmas išėjo), FILO, LIFO ir pan. Čia parašysime steką trims sveikiesiems duomenims saugoti<br />

„paskutinis įėjo, pirmas išėjo“ (LIFO). Tegu turim nesaugią klasę Stack, kuri netikrina, ar stekas<br />

neperpildytas ir ar nėra tuščias. Stekas turi turėti bent du metodus: push duomeniui į steką<br />

(vienamtį masyvą stack) įdėti ir pop – duomeniui iš steko išimti ir grąžinti. Į masyvą įdedamo ir<br />

išimamo elemento vietą masyve žymėsime indeksu top. Klasės funkcionalumą paveldėsime ir<br />

papildysime duomenų įdėjimo ir išėmimo metodais su reikiamomis patikromis – tai bus jau<br />

saugaus steko klasė SafeStack. Masyvo matas Stack klasėje užduodamas konstrukcija enum: tik<br />

taip galima „apgauti“ kompiliatorių ir klasės viduje pranešti konkrečią duomens reikšmę 3.<br />

#include <br />

#include <br />

using namespace std;<br />

//<br />

class Stack{<br />

protected:<br />

enum{ MAX = 3 };<br />

int stack[ MAX ];<br />

int top;<br />

public:<br />

Stack( ): top(-1) { }<br />

//<br />

~Stack( ) { }<br />

//<br />

void push( int var ) { stack[ ++top ] = var; }<br />

int pop( ) { return stack[ top-- ]; }<br />

};<br />

//<br />

class SafeStack: public Stack {<br />

public:<br />

void push( int var ){<br />

if( top >= MAX-1 ){<br />

cout


}<br />

int pop( ){<br />

}<br />

}<br />

if( top < 0 ){<br />

}<br />

system( "pause" );<br />

exit( 1 );<br />

Stack::push( var ); // pastaba<br />

cout


Iš rezu<strong>lt</strong>atų aiškiai matyti, kad SafeStack formato objektui s kviečiami klasėje SafeStack<br />

perrašyti metodai. Paskelbkite objektą s Stack formato ir pamatysite, kad programa spausdins 4 iš<br />

steko grąžinamus skaičius, kurių paskutinis bus neapibrėžtos reikšmės – t.y. bus kviečiami<br />

nesaugūs Stack klasės atitinkami metodai.<br />

<strong>13</strong>.5 BENDRASIS IR DALINIS PAVELDIMUMAS<br />

Pirmą kartą skaitydami paveldimumo skyrių, galite šį skyrelį praleisti.<br />

Visose programose anksčiau rodydami, kad išvestinė klasė paveldi savybes iš bazinės,<br />

taikėme bendrąjį paveldimumą: class SafeStack: public Stack { . . . Vietoje raktažodžio public<br />

galima rašyti ir private: tokiu atveju turėsime vadinamąjį dalinį paveldėjimą. Šie terminai nurodo,<br />

kokią prieigą prie bazinės klasės laukų turi išvestinių klasių objektai. Visi galimi prieigos lygiai<br />

parodyti 7 pavyzdžio abstrakčioje, jokios prasmės neturinčioje programoje:<br />

#include <br />

using namespace std;<br />

//<br />

class A{<br />

private:<br />

int privateData;<br />

protected:<br />

int protectedData;<br />

public:<br />

int publicData;<br />

//<br />

A( ): privateData( 0 ), protectedData( 0 ), publicData( 0 ){ };<br />

};<br />

//<br />

class B: public A{ // bendrasis paveldimumas<br />

public:<br />

void m( ){<br />

int data;<br />

//data = privateData; // klaida<br />

data = protectedData;<br />

data = publicData;<br />

};<br />

//<br />

}<br />

class C: private A{ // dalinis paveldimumas<br />

public:<br />

void m( ){<br />

int data;<br />

//data = privateData;<br />

// klaida<br />

174


}<br />

data = protectedData;<br />

data = publicData;<br />

};<br />

//<br />

int main( ){<br />

int data;<br />

A a;<br />

B b;<br />

C c;<br />

//<br />

//data = a.privateData; // klaida<br />

//data = a.protectedData; // klaida<br />

data = a.publicData;<br />

//<br />

//data = b.privateData; // klaida<br />

//data = b.protectedData; //klaida<br />

data = b.publicData;<br />

//<br />

}<br />

//<br />

system( "pause" );<br />

return 0;<br />

//data = c.privateData; // klaida<br />

//data = c.protectedData; //klaida<br />

//data = c.publicData; // klaida<br />

Kaip matyti, nepaisant paveldimumo tipų, išvestinių klasių metodai vienodai „nemato“<br />

uždarų bazinės klasės laukų, o apsaugotus ir viešus laukus – „mato“. Su klasių objektais – kitokia<br />

istorija: private ir protected laukai visų klasių objektams yra neprieinami. Vienintelis abiejų<br />

paveldimumo tipų skirtumas – kad esant daliniam paveldimumui išvestinės klasės objektams<br />

neprieinami ir bazinės klasės public laukai (o taip pat ir metodai, tik paskutinis dalykas schemoje<br />

nėra iliustruotas).<br />

Dalinį paveldėjimą reikėtų taikyti, norint nuo išvestinės klasės objektų paslėpti visus<br />

bazinių klasių laukus ir metodus – tai būtų aktualu, pavyzdžiui, SafeStack klasės atveju, norint jokiu<br />

būdu neleisti taikyti nesaugių atvirų klasės Stack metodų.<br />

<strong>13</strong>.6 DAUGYBINIS PAVELDIMUMAS<br />

Idėja yra akivaizdi: išvestinė klasė paveldi iš karto kelių klasių savybes. Sintaksiškai tai ne<br />

visada saugu, nes kartais kelių klasių funkcionalumas gali tarpusavyje nederėti. Pavyzdys UML<br />

diagramos pavidalu:<br />

175


A<br />

B<br />

C<br />

Sintaksė:<br />

class A{ };<br />

class B{ };<br />

class C: public A, public B { };<br />

Jei nėra bazinių klasių nesuderinamumų, tai daugybinis paveldimumas nuo vienatinio<br />

skiriasi faktiškai tik konstruktorių naudojimu.<br />

8 pavyzdys. Rašoma klasė Lumber informacijai apie medienos gaminius talpinti. Tarkim, ji<br />

gali dalį funkcionalumo paveldėti iš jau parašytos klasės Distance (int ir double formato laukus -<br />

gaminio ilgį pėdomis ir coliais) ir dalį – iš klasės Type (string formato – skerspjūvio duomenis ir<br />

rūšį), o šiuos laukus dar papildo int formato duomeniu kiekiui ir double – kainai talpinti.<br />

#include <br />

#include <br />

using namespace std;<br />

//<br />

class Type{<br />

protected:<br />

string dimensions;<br />

string grade;<br />

public:<br />

Type( ): dimensions( "N/A" ), grade( "N/A" ) { }<br />

Type( string d, string g ): dimensions( d ), grade( g ) { }<br />

//<br />

~Type( ) { }<br />

//<br />

void getType( ){<br />

coutdimensions>>grade;<br />

}<br />

void showType( ){<br />

cout


};<br />

//<br />

class Distance{<br />

private:<br />

public:<br />

}<br />

int feet;<br />

double inches;<br />

Distance( ): feet( 0 ), inches( 0. ) { }<br />

Distance( int ft, double in): feet( ft ), inches( in ) { }<br />

//<br />

~Distance( ) { }<br />

//<br />

void getDistance( ){<br />

coutfeet>>inches;<br />

}<br />

void showDistance( ){<br />

cout


int main( ){<br />

Lumber p1;<br />

Lumber p2( "2x4", "A", 8, 0., 100, 50.00 );<br />

//<br />

p1.getLumber( );<br />

p1.showLumber( );<br />

p2.showLumber( );<br />

//<br />

system( "pause" );<br />

return 0;<br />

}<br />

Nors programos tekstas ilgas, logiškai ji paprastutė. Ši programa neatlieka jokių veiksmų, tik<br />

įveda dviejų Lumber klasės objektų duomenis ir juos parodo. Komentarų ženklu 1 pažymėti<br />

operatoriai parodo, kaip kviečiami abiejų bazinių klasių konstruktoriai, o vėliau užpildomi<br />

išvestinės klasės papildomi laukai. Operatoriai su komentarais 2 rodo, kad bazinės klasės<br />

metodams kviesti būtina priklausomybės operacija :: .<br />

Minėjome, kad daugybinis paveldimumas ne visada saugus. Matyt, todėl vėlesnėje C++<br />

pagrindu sukurtoje kalboje Java daugybinio paveldimumo iš viso nebeliko; panašius efektus ten<br />

pasiekti galima tik sudėtingomis programinėmis schemomis. Čia parodysime tik kelis galimus<br />

daugybinio paveldimumo neapibrėžtumus.<br />

9 pavyzdys. Bazinių klasių funkcionalumas kartojasi (čia – vienodi metodai):<br />

#include <br />

using namespace std;<br />

//<br />

class A{<br />

public:<br />

void m( ){ }<br />

};<br />

//<br />

class B{<br />

public:<br />

void m( ){ }<br />

};<br />

//<br />

class C: public A, public B{ };<br />

//<br />

int main( ){<br />

C c;<br />

//c.m( ); // klaida! Neaišku, kurį metodą kviesti<br />

c.A::m( ); // gerai: kviečiamas iš A paveldėtas metodas<br />

c.B::m( ); // gerai: kviečiamas iš B paveldėtas metodas<br />

//<br />

system( "pause" );<br />

return 0;<br />

178


}<br />

10 pavyzdys. Trijų lygių paveldimumas. Dabar klasė B paveldi visas klasių A1 ir A2 savybes –<br />

taigi ir metodą m, kurį šios savo ruožtu nukopijavo iš klasės A. Nors metodas visiškai vienodas<br />

visose trijose klasėse A, A1 ir A2, klasėje B tampa nebeaišku, kurią konkrečiai iš metodo kopijų<br />

reikia kviesti:<br />

#include <br />

using namespace std;<br />

//<br />

class A{<br />

public:<br />

void m( ){<br />

cout

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!