Procedeul
de derivare permite definirea unei clase noi, numita clasa
derivata, care mosteneste proprietatile unei
clase deja definite, numita clasa de baza,
pe care le completeaza cu proprietati noi.
|
| class Persoana {
protected: char *nume; char *adresa; public: Persoana(char *N, char *A); ~Persoana(); char * Nume(); char *AdresaActuala(); void SchimbaAdresa(char *A); void AfiseazaDate(); }; class Student: public Persoana { //derivare
publica
class Angajat: public Persoana { //derivare
publica
|
Pentru a putea controla drepturile de acces ale clasei derivate asupra membrilor clasei de baza, declaratia poate contine specificatori de control al accesului. Derivarea poate fi de tip public, protected sau private. Daca nu se specifica nici unul, se considera implicit private pentru o clasa class, respectiv public, pentru una struct.
Specificatorul de acces coduce la urmatoarele reguli de accesare:
Un
exemplu
de definire a constructorului clasei Student, derivata din clasa Persoana:
| Student:: Student(char *N, char *A, int a)
: Persoana(N,A) {
an=a; } |
Definitiile noi din clasa derivata nu înlocuiesc definitiile din clasa de baza, ci se adauga lor. Clasa derivata are la dispozitie si definitiile din clasa de baza, pe care le poate specifica utilizând operatorul de rezolutie ::.
Compatibilitatea se manifesta sub forma unor conversii explicite de tip, dintr-un obiect derivat într-un obiect de baza, sau dintr-un pointer sau referinta la clasa derivata într-un pointer la clasa de baza.
Exemple
de
conversii
permise:
| Persoana p1, *pp1, *pp2;
Student s1; p1=s1; //se copiaza doar campurile mostenite pp1=new Student("Ion Ionescu","str. Mare", 3); pp2=new Angajat("Vasile","str. XXX", "ABC S.A"); pp1->AfiseazaDate(); |
De exemplu, în secventa anterioara, apelul
pp1->AfiseazaDate();
determina apelarea functiei membra a clasei Persoana, desi obiectul în cauza este un angajat si functia a fost redefinita la nivelul clasei Angajat. Aceasta reduce utilitatea redefinirii functiilor membre care s-a facut.
Pentru a se putea urmari tipul obiectului adresat de pointer la un moment oarecare, este necesara identificarea versiunii functiei care trebuie apelata în timpul executiei programului. Acest mod de lucru se numeste legare dinamica (late binding). Functiile membre pentru care se realizeaza legatura dinamica se numesc functii virtuale si se declara cu ajutorul cuvântului cheie virtual.
De exemplu, declaratia functiei AfiseazaDate, se va modifica astfel în cadrul clasei Persoana:
virtual void AfiseazaDate();
A se retine ca destructorii pot fi declarati virtuali, constructorii, nu.
Principala forta a relatiei de mostenire este data de facilitatea de a trata obiectele claselor cuprinse intr-o ierarhie ca si cum ar fi instante ale clasei din varful ierarhiei. Aceasta se numeste polimorfism - obiecte cu caracteristici comune pot fi tratate unitar.
Cand o functie virtuala nu este redefinita de o clasa derivata, va fi folosita versiunea definita in clasa de baza. C++ admite functii virtuale pure pentru urmatoarele situatii:
virtual tip nume_functie(lista_parametri)=0;
Clasele care contin astfel de functii se numesc clase abstracte. Nu pot fi instantiate, dar se pot declara pointeri sau referinte la acestea.. Se pot crea obiecte numai din clase derivate din acestea, cu conditia ca sa se furnizeze corpuri pentru functiile virtuale pure.
Exemplu - vezi si Exercitiul 3:
Clasa de baza pentru obiecte grafice, poate fi definita:
class
BaseGraphicalObject {
.....
public:
virtual void draw() = 0;
.....
}
Toate
clasele derivate trebuie sa-si implementeze propria functie draw.
|
|
Clase
generice
S-a prezentat anterior un exemplu de implementare a clasei Stiva, ca fiind o stiva de intregi. În mod tipic, programatorii vor avea nevoie de stive de elemente apartinând unor tipuri diferite. S-ar putea sa apara necesitatea definirii unor tipuri ca: StivaDeIntregi, StivaDeCaractere, StivaDeNrComplexe, StivaDeFiguriGeometr, etc.. Toate acestea ar implica scrierea câte unei clase stiva pentru fiecare tip de elemente continute. Este probabil ca cel care scrie tipul Stiva sa nu cunoasca toate tipurile de elemente pe care ulterior, alti programatori vor dori sa le introduca în stive. Oricum, scrierea unui numar mare de asemenea stive (o familie de stive) ar fi total ineficienta în ceea ce priveste reutilizarea codului.
Solutia la aceasta problema ar fi posibilitatea ca tipul Stiva sa fie exprimat astfel încât sa primeasca drept parametru tipul elementelor.
În C++, exista posibilitatea de a crea familii de functii sau de clase cu ajutorul sabloanelor (template).
Sabloanele nu au facut parte dintre specificatiile originale ale limbajului C++, ci au fost adaugate in 1990, fiind definite de ANSI C++.
Se
poate reconsidera acum exemplul stivei.
| //
Fisierul Stiva_T.hpp
#define BOOL int
template <class TipElement> class Stiva
{
template <class TipElement>
template <class TipElement>
template <class TipElement>
template <class TipElement>
template <class TipElement>
template <class TipElement>
template <class TipElement>
|
Un
exemplu
de program care utilizeaza trei stive, o stiva de numere întregi,
una de caractere si una de numere complexe este prezentat în continuare.
| //
Fisierul ProgStT.cpp
#include "Stiva_T.hpp"
typedef struct c { int x,y;} complex;
void main(void){
|
Definitia unui sablon de clasa nu este nici clasa, nici obiect. Sablonul unei clase este o descriere a modului în care compilatorul va genera o noua clasa, pornind de la tipurile date ca parametru. În C++, o clasa este inutila daca nu se declara o instanta (obiect) a clasei respective. La fel, un sablon este inutil daca nu se declara o instanta (clasa) a sablonului respectiv.
Un alt aspect legat de folosirea sabloanelor este legat de faptul ca ele pot fi folosite numai cu tipuri care accepta operatiile necesare sablonului. În exemplul cu stiva, este necesar ca tipul elementelor stivei sa accepte operatia de atribuire.
Deci o clasa generica se defineste:
template <class Tip> class nume_clasa {
//definitie clasa
}
Se observa faptul ca în fisierul antet Stiva_T.hpp este inclusa atât declaratia cât si definitia sablonului Stiva, pentru a permite compilatorului sa genereze clasele necesare (instantele sablonului) fiecarui modul utilizator care foloseste clasa generica Stiva.
Functii
generice
Pe langa sabloane de clasa - clase generice - pot fi definite si sabloane de functii - functii generice. O functie generica defineste un set general de operatii care vor fi aplicate unor tipuri de date variate; tipul de date asupra caruia va opera, va fi transmis ca parametru:
template <class Tip> tip_rezultat nume_functie
(lista_parametri) {
//corp functie
}
Exemplu:
Programul
de mai jos defineste o functie generica ce inverseaza intre ele valorile
a doua variabile trimise ca parametri prin referinta:
| #include
<iostream.h>
//definitia functiei generice ( sablon ) template <class T> void interschimba(T &x,T &y){ T aux; aux=x; x=y; y=aux; } main(){
cout<<intregii:<<i1<<' '<<i2<<endl;
cout<<caracterele:<<i1<<' '<<i2<<endl;
cout<<realii:<<i1<<' '<<i2<<endl;
return 0;
|
|
|
1.Iata doua seturi de intrebari - primul dat la un interviu pentru un post de Entry-Level Software Engineer (C++ Programmer), al doilea la unul pentru Experienced Software Engineer (C++ Programmer). Sa vedem cum v-ati descurca :-) Cu 8 raspunsuri corecte, postul poate fi ocupat:
|
1.What’s the difference between a class and a struct?
2.What is a constructor? 3.What is meant by a "deep copy"? 4.What is the difference between "pass by reference" and "pass by value"? 5.What is a static member function? 6.What is meant by a derived class? 7.How do use command-line arguments? 8.What is a pure virtual function? 9.cout is an object of what class? 9.Explain how you would write a program to read a file and change every occurrence of the word "hello" to "goodby" and then write the file back out. |
|
1.What’s the difference between a class and
a struct?
2.What does "return by reference" mean, and when can you return a local variable by reference? 3.Where should you initialize a static data member? 4.What is meant by protected inheritance? 5.What is the string class? 6.How would you set up a multiple file application that had five classes? 7.What is meant by a generic function ( parameterized manipulator )? 8.Explain polymorphism. 9.Why should you write a virtual destructor? 10.If you create and open a file inside a function, why might the file be closed after the function terminates? |
2.Rulati programul de mai jos care defineste o clasa de baza si una derivata, observand apelul constructorilor si valorile campurilor instantelor claselor.
Definiti
o clasa derivata privat din clasa Derivata, care sa aiba in plus un camp
privat de tip float; cum rezolvati tiparirea campurilor de date din obiectele
acestei clase?
| #include
<iostream.h>
class
Baza
Baza::Baza(int
n)
class
Derivata : public Baza
Derivata::Derivata(int
x, int y) : Baza(x)
void
Derivata::print(void) const
int
main()
Derivata d1(3,4);
d1.printBaza();
cout << "Dim int: " << sizeof(int) << endl;
|
3.Studiati si rulati programul de mai jos care defineste o ierarhie de clase: shape - clasa abstracta de baza care are ca membru un obiect de tip location; derivate square, triangle, circle. Observati declararea functiilor virtuale pure in clasa de baza si redefinirea lor in clasele derivate, definirea constructorilor. Decomentati linia din main care instantiaza clasa abstracta si recompilati - ce observati?
Definiti
clasa rectangle
( dreptunghi ) derivata din shape
si clasa equilateral_triangle
derivata din triangle.
| #include
<iostream.h>
#include <iomanip.h> #include <math.h> #include <stdlib.h> const double pi = 3.14159; class
location //
clasa ce defineste coordonatele plane ale centrului unei figuri
void
location::print_loc(void)
class
shape //
clasa abstracta
shape::shape(double c_x, double c_y) : center(c_x,c_y) {} void
shape::print_center(void)
class
square : public shape //
clasa derivata
square::square(double
c_x, double c_y, double s) : shape(c_x,c_y)
double
square::area(void) const
double
square::perim(void) const
class
triangle : public shape //
clasa derivata
triangle::triangle(double
c_x, double c_y, double s1, double s2, double s3)
void
triangle::print_sides(void)
double
triangle::area(void) const
double
triangle::perim(void) const
class
circle : public shape //
clasa derivata
circle::circle(double
c_x, double c_y, double r) : shape(c_x,c_y)
double
circle::area(void) const
double
circle::perim(void) const
int
main()
//
shape sh(6,7); //
necomentata, linia da eroare, nu se poate instantia o clasa abstracta
square s(5.,2.,1.);
triangle t(0,0,3,4,5);
cout << "sizeof(location)=" << sizeof(location) << endl;
return 0;
|
4.Scrieti o functie generica ce sa realizeze sortarea bubblesort a unui tablou. Se va scrie un program de test care va sorta un tablou de intregi si unul de reali.