SDA Lucrarea 2

De la WikiLabs
Versiunea din 8 martie 2017 22:58, autor: Radu Hobincu (Discuție | contribuții) (Constructorul)

În acest laborator se introduc noțiuni noi de Programare Orientată pe Obiecte: clasa, obiectul, metoda, constructorul, destructorul, namespace și template.

Structurile in C

Ne amintim că în C, o structură e definită printr-un nume, care reprezintă numele tipului de date, precum și un număr de câmpuri, adică una sau mai multe varibile, definite prin tip și nume, ce reprezintă datele componente ale structurii:

struct Person {
    char name[128];
    uint8_t age;
    char cnp[14];
    char address[256];
};

Structura Person este un tip de dată, prin urmare putem declara și utiliza variabile de tipul acestei structuri:

int main() {
    struct Person somePerson;
    return 0;
}

În codul de mai sus, s-a declarat o varibilă de tip struct Person, cu numele somePerson. Prin definirea unei variabile de tipul structurii, de fapt s-au definit în mod automat toate variabilele membre ale acesteia: name, age etc. Având o variabilă de tipul structurii, membrii acesteia se pot accesa cu operatorul .:

int main() {
    struct Person somePerson;
    somePerson.age = 20;
    return 0;
}

Dacă avem un pointer la o structură, putem accesa membrii acesteia în două moduri:

  1. Prin dereferențierea pointerului (adică obținerea valorii de la adresa respectivă), folosind operatorul *.
  2. Prin utilizarea operatorului -> care este analog operatorului ., dar se folosește cu variabile de tip pointer-la-structură, în loc de variabile de tip structură:
int main() {
    struct Person somePerson;
    somePerson.age = 20;

    struct Person * somePersonPointer = &somePerson;

    /* Varianta 1 */
    strcpy((*somePersonPointer).name, "Andrei");

    /* Varianta 2 */
    strcpy(somePersonPointer->name, "Andrei");

    return 0;
}

Noțiuni introductive de Programare Orientată pe Obiecte (POO)

Ideea de bază de la care a pornit paradigma POO este faptul că o structură poate modela orice obiect din jurul nostru, pentru că orice obiect are proprietăți, care pot fi stocate în variabile membre ale structurii. Aceste obiecte pot fi atât fizice (o minge, un șurub, un tranzistor, o sticlă cu apă), cât și obiecte abstracte (un număr natural, un număr complex, o funcție matematică sau un sortator de valori în virgulă mobilă). Fiecare din aceste obiecte au un set de proprietăți care pot fi identificate. Spre exemplu, o minge are o anumită culoare, o anumită formă, un anumit volum, o anumită masă și un anumit proprietar. Sigur, există și alte proprietați pe care o minge le poate avea (de exemplu materialul de fabricație sau gazul cu care este umplută), dar în general când analizăm un obiect, ne gândim exclusiv la proprietățile relevante pentru aplicația noastră (de exemplu dacă avem o bază de date cu angajați, probabil nu ne înteresează culoarea părului fiecărui angajat, dar ne interesează vârsta și vechimea acestuia). Putem observa că o parte din proprietăți sunt constante pe toată durata de viață a obiectului (cum ar fi culoarea, masa și forma), acestea numindu-se imutabile, iar altele se pot schimba pe perioada de viață a obiectului (de exemplu proprietarul).

Clasa și obiectul

Haideți să definim o structură care să modeleze obiecte de tip minge:

enum Color {
    WHITE,
    RED,
    BLUE,
    GREEN
};

enum Shape {
    SPHERE,
    OVOID
};

struct Ball {
    enum Color color;
    enum Shape shape;
    float volume;
    float mass;
    Person owner;
};

Pentru a modela complet obiecte reale însă, mai lipsește ceva. Obiectele din lumea reală nu au doar proprietăți, ci și interacționează între ele. Acțiunile se modelează în programare prin funcții, acestea fiind cele care prin execuție modifică proprietățile obiectelor asupra cărora acționează. Să spunem că o minge își poate schimba proprietarul. Definim deci următoarea funcție:

void changeBallOwner(Ball * ball, Person newOwner) {
    ball->owner = newOwner;
}

Se vede aici că funcția changeBallOwner se apelează întotdeauna pentru un obiect de tip Ball. De altfel, această funcție nu are sens decât în contextul unei mingi (altfel spus, trebuie să am o minge ca să-i pot schimba proprietarul). Prin urmare, o metodă mai bună de reprezentare a obiectelor de tip Ball ar fi ca și funcția changeBallOwner să facă parte din structură, ca și proprietățile acesteia. Astfel, C++ introduce o modificare foarte simplă structurilor din C: acestea pot acum să conțină și funcții:

enum Color {
    WHITE,
    RED,
    BLUE,
    GREEN
};

enum Shape {
    SPHERE,
    OVOID
};

struct Ball {
    enum Color color;
    enum Shape shape;
    float volume;
    float mass;
    Person owner;

    void changeOwner(Person newOwner) {
        owner = newOwner;
    }
};

Se observă următoarele modificări:

  1. am redenumit funcția din changeBallOwner în changeOwner, pentru că funcția făcând parte din structura Ball, este evident pentru ce tip de obiect se apelează.
  2. a dispărut primul argument al funcției, cel de tip Ball, deoarece această funcție se va apela acum pentru un obiect de tip Ball folosind operatorul de acces la membri, așa cum se accesează și proprietățile:
int main() {
    Ball myBall;
    Person me;
    myBall.changeOwner(me);
    return 0;
}


Definiție: În POO, o structură care conține și funcții se numește clasă.


Definiție: O variabilă de tipul unei clase se numește obiect.


Definiție: Funcțiile care aparțin unei clase se numesc metode.


Definiție: Variabilele care aparțin unei clase se numesc câmpuri sau proprietăți.


Definiție: Câmpurile și metodele sunt membrii clasei.

Supraîncărcarea

În limbajul C, era syntactic incorect să existe mai multe funcții cu același nume. În C++ s-a introdus noțiunea de supraîncărcare, adică posibilitatea de a avea mai multe funcții cu același nume, dar care pot fi diferențiate prin numărul, tipul și/sau ordinea argumentelor:

int max(int a, int b) {
    return a > b ? a : b;
}

float max(float a, float b) {
    return a > b ? a : b;
}


Constructorul

Constructorii sunt metode speciale ale unei clase care sunt apelați automat atunci când se creează un nou obiect. Constructorii pot fi recunoscuți după două particularități:

  1. Au același nume cu numele clasei.
  2. Nu au tip returnat (nici măcar void).

O clasă poate avea unul sau mai mulți constructori, în funcție de necesități, prin mecanismul de supraîncărcare:

enum Color {
    UNKNOWN_COLOR,
    WHITE,
    RED,
    BLUE,
    GREEN
};
 
enum Shape {
    UNKNOWN_SHAPE,
    SPHERE,
    OVOID
};
 
class Ball {
    Color color;
    Shape shape;
    float volume;
    float mass;
    Person owner;
public:
    Ball() {
        color = UNKNOWN_COLOR;
        shape = UNKNOWN_SHAPE;
        volume = -1;
        mass = -1;
    }

    Ball(Color newColor, Shape newShape, float newVolume, float newMass, Person newOwner) {
        color = newColor;
        shape = newShape;
        volume = newVolume;
        mass = newMass;
        owner = newOwner;
    }

    void changeOwner(Person newOwner) {
        owner = newOwner;
    }
};

Rolul unui constructor este de a inițializa proprietățile obiectului cu valori primite ca argumente și de a aloca memoria necesară funcționării corecte a obiectului.

Se observă apariția unei noi linii în codul de mai sus: public:. Acesta este un modificator de acces care specifică faptul că toți membrii declarați mai jos față de el în clasă pot fi accesați (cu operatorii . sau ->) în funcții sau metode din afara clasei. Opusul lui public este private. Acest modificator de acces trebuie să existe în codul de mai sus datorită diferenței dintre un struct și o class în C++:

  • Membrii unei clase au modificatorul implcit de acces private
  • Membrii unei structuri au modificatorul implcit de acces public

Destructorul

Destructorul este o metodă specială ce aparține unei clase, care se apelează automat când memoria alocată unui obiect este eliberată. Acest lucru se întâmplă în două situații:

  1. când un obiect este alocat pe stivă (prin declararea lui în interiorul unei funcții), destructorul se apelează automat când funcția blocul de instrucțiuni unde este declarat obiectul se încheie.
  2. când un obiect este alocat în HEAP, destructorul se apelează automat când memoria este eliberată manual de către programator.

Fiecare clasă are cel mult un destructor care poate fi identificat prin următoarele două caracteristici:

  1. are numele format din caracterul ~ urmat de numele clasei
  2. nu are tip returnat (nici măcar void).
enum Color {
    UNKNOWN_COLOR,
    WHITE,
    RED,
    BLUE,
    GREEN
};
 
enum Shape {
    UNKNOWN_SHAPE,
    SPHERE,
    OVOID
};
 
class Ball {
    Color color;
    Shape shape;
    float volume;
    float mass;
    Person owner;
public:
    Ball() {
        color = UNKNOWN_COLOR;
        shape = UNKNOWN_SHAPE;
        volume = -1;
        mass = -1;
    }
 
    Ball(Color newColor, Shape newShape, float newVolume, float newMass, Person newOwner) {
        color = newColor;
        shape = newShape;
        volume = newVolume;
        mass = newMass;
        owner = newOwner;
    }
 
    void changeOwner(Person newOwner) {
        owner = newOwner;
    }

    ~Ball() {
    }
};

Crearea de Obiecte

Template-ul

Namespace-ul