Clase și obiecte

Observați unde descriem corpul unei anumite metode. Modelele () și protecția () sunt descrise împreună cu corpurile lor direct în interiorul structurii. Dar puteți face altfel: puneți prototipurile metodei în interiorul structurii și definiția corpului funcției este în afara structurii, așa cum am făcut-o cu operatorul "+".







Prima metodă este folosită pentru metode simple și scurte, care nu ar trebui să fie schimbate în viitor. Acest lucru se datorează în parte faptului că descrierile de clasă sunt de obicei plasate în fișiere antet care sunt apoi incluse în programul de aplicație folosind directiva #include. În plus, prin această metodă, instrucțiunile mașinii generate de compilator atunci când accesează aceste funcții sunt introduse direct în textul tradus. Acest lucru reduce costurile punerii în aplicare a acestora, ca punerea în aplicare a unor astfel de metode nu este asociat cu apelul funcției și mecanismul de întoarcere, în creștere în rândul său, dimensiunea codului executabil (de exemplu, astfel de metode sunt în linie sau încorporate). A doua metodă este preferabilă pentru metode complexe. Declarată astfel functia înlocuită automat de compilator în apelurile subrutină, dar atunci când adăugați cuvinte cheie inline poate fi substituit în text ca și în primul caz.

În plus față de metoda de creare a funcțiilor embeddable de mai sus (pentru a scrie corpul metodei direct în structură), există un alt mod - inserați specificatorul inline înainte de definirea metodei:

inline _3d _3d :: operator + (_3d b)
_3d c;
c.x = x + b.x;
c.y = y + b.y;
c.z = z + b.z;
return c;
>

Acum, operatorul "+" devine o funcție inline.

Funcțiile încorporate funcționează în același mod ca definițiile macro cu parametrii, dar au un număr de avantaje înaintea celor din urmă. În primul rând, metodele inline oferă o modalitate mai armonioasă de a încorpora o funcție scurtă într-un program. În al doilea rând, compilatorul C ++ funcționează mult mai bine cu funcții încorporate decât cu definiții macro.

Este important să înțelegeți că inline nu este o comandă pentru compilator, este mai degrabă o solicitare de a face metoda în linie. Dacă dintr-un motiv oarecare (de exemplu, dacă funcția are o buclă, o comutare sau o funcție), compilatorul nu va executa cererea, atunci funcția va fi compilată ca non-configurabilă.

Constructori și destructori

_3d vectorA;
dublu m;
vectorA.x = 17,56;
vectorA.y = 35,12;
vectorA.z = 1,0;
m = vectorAmod ();

Cu toate acestea, conform dogmelor PLO, acest stil de programare ar trebui să fie recunoscut ca eronat, deoarece, în teorie, toate elementele care descriu starea unui obiect trebuie să fie ascunse în el și accesibile numai prin mesajele trimise obiectului. Dacă modificați definiția anterioară a clasei _3d la următoarele:

obținem o soluție "competentă din punct de vedere ideologic". Dar, la compilarea fragmentului anterior, va fi diagnosticată încercarea de a aborda elementul protejat al unei clase. Acest lucru este grozav deoarece vă forțează să adăugați o interfață de clasă cu metode care vă permit să atribuiți valori coordonatelor vectoriale.

Urmatoarea versiune a clasei ar putea arata cam asa:

Metoda set () vă permite să atribuiți unele valori inițiale coordonatelor vectorului (și numai această metodă!).

Încă un lucru: deși x, y și z aparțin acum membrilor protejați ai clasei, este în continuare permisă menționarea explicită a acestor elemente ale obiectului trecut ca parametru (vezi metoda de proiecție (.) Și "+").

Constructorii din C ++ au nume care se potrivesc cu numele clasei. Constructorul poate fi definit de către utilizator sau compilatorul însuși va genera constructorul implicit. Constructorul poate fi numit explicit sau implicit. Compilatorul apelează automat constructorul corespunzător unde definiți un nou obiect de clasă. Constructorul nu returnează nici o valoare și atunci când este descris constructorul, cuvântul cheie void nu este utilizat.

Funcția, constructorul invers, este distrugătorul. Această funcție este de obicei apelată atunci când un obiect este șters. De exemplu, dacă un obiect a fost alocat dinamic la crearea acestuia, atunci ar trebui să fie eliberat atunci când obiectul este șters. Obiectele locale sunt șterse când părăsesc domeniul de aplicare. Obiectele globale sunt șterse când programul se termină.

În C ++, distrugătorii au numele: "

"Ca și constructorul, distrugătorul nu aduce nici o valoare, dar spre deosebire de constructor, el nu poate fi numit în mod explicit. Constructorul și distrugătorul nu pot fi descrise în partea închisă a clasei.

_3d :: _ 3d () // constructorul de clasă _3d

x = y = z = 0;
cout <<'Работа конструктора _3d \n';
>

main ()
_3d A; // obiectul A este creat și elementele sale sunt inițializate
// A.x = A.y = A.z = 0;
A.set (3,4,0); // Acum A.x = 3,0, A.y = 4,0, A.z = 0,0
cout <>

Rezultatul programului:

Lucrarea constructorului _3d
5
Lucrarea distrugătorului

Constructori cu supraîncărcarea parametrilor și constructorilor

clasa _3d
dublu x, y, z;
publice:
_3d ();
_3d (dublu initX, dublu init, initZ dublu);
.
>;

_3d :: _ 3d (dublu initX, dublu initY, initZ dublu)
// constructor de clasa _3d cu parametri
x = initX;
y = initY;
z = initZ;
cout <<'Работа конструктора _3d \n';
>

main ()
_3d A; // obiectul A este creat și elementele sale sunt inițializate
// A.x = A.y = A.z = 0;
A.set (3,4,0); // Acum A.x = 3,0, A.y = 4,0, A.z = 0,0
_3d B (3,4,0); // crea obiectul B și inițializa elementele sale
// B.x = 3,0, B.y = 4,0, B.z = 0,0
>

Această metodă de a apela constructorul este o formă abreviată de a scrie o expresie

Spre deosebire de constructor, distrugătorul nu poate avea parametri. Este de înțeles că nu există niciun mecanism pentru trecerea parametrilor la obiectul care trebuie șters.







În acest exemplu, este ușor de înțeles care este necesitatea de a suprasolicita constructorii. Este supraîncărcarea, deoarece aici se găsesc funcții care au aceleași nume, dar diferite liste de parametri. Principalul motiv pentru supraîncărcarea constructorilor este acela de a oferi programatorului cea mai potrivită metodă de inițializare a obiectului.

În exemplul nostru, este reprezentată cea mai comună versiune a supraîncărcării constructorului, adică constructor fără parametri și constructor cu parametri. În mod normal, în program sunt necesare ambele aceste specii, cu parametri ca și constructor este mai convenabil atunci când se lucrează cu obiecte simple, dar nu poate fi utilizat la inițializarea dinamice elemente obiect matrice (pentru matrice statică poate).

Deși constructorul poate fi supraîncărcat ori de câte ori doriți, este mai bine să nu îl abuzați. Constructorul ar trebui să fie supraîncărcat numai pentru cele mai frecvente situații.

Atribuirea obiectelor

class ClassName1
int a, b;
publice:
setul void (int ia, int ib)
>;

clasa ClassName2
int a, b;
publice:
setul void (int ia, int ib)
>;

Deci, o încercare de a îndeplini

ClassName1 c1;
ClassName2 c2;
c2 = c1;

Ca urmare a programului, obținem "c2.b = 111" și nu 11, după cum era de așteptat.

Operatorul de atribuire supraîncărcat

Pentru a evita astfel de neînțelegeri, folosind operatorul de reacoperire, care este descris în mod explicit (adică, controlat) să prelucreze elementele de date de atribuire a unui obiect la elementele corespunzătoare ale unui alt obiect de date.

Dar cum vom arăta acum exemplul nostru cu un vector tridimensional.

Ar fi naiv să presupunem că pentru fiecare nouă variabilă de tipul _3d este creată o copie a funcției care implementează operatorii "+" și "=". Fiecare funcție este reprezentată într-o singură instanță și la momentul apelului primește un parametru ascuns - un indicator pentru instanța variabilei pentru care este apelată. Acest pointer este denumit acest lucru. Dacă variabila folosită nu este descrisă în interiorul funcției, ea nu este globală, se consideră că ea este membră a structurii și aparține acestei variabile. Prin urmare, atunci când implementăm funcțiile operatorilor, am omis calea de acces la câmpurile structurii pentru care va fi apelat acest operator.

Argumentele funcțiilor operatorului sunt operanzii, iar valoarea returnată este rezultatul aplicării operatorului. În special, pentru operatorul "=", acest lucru este necesar pentru a asigura posibilitatea alocării secvențiale (a = b = c). Operatorii binari au un argument - al doilea este trecut prin aceasta. Unari, respectiv, unul - acest lucru.

Transmisie în funcție și obiect returnare

Obiectele pot fi transmise funcțiilor ca argumente în același fel în care sunt transmise date de alte tipuri. Am folosit deja toate acestea când am implementat metodele de class_3d. în care tipul de obiect _3d a fost trecut ca parametri ai metodelor de proiecție (.), operator + (.) și operator = (.).

Trebuie reținut faptul că metoda C ++ de trecere a parametrilor este, în mod implicit, transferul obiectelor în funcție de valoare. Aceasta înseamnă că o copie a argumentului obiect este creată în interiorul funcției, iar această copie, și nu obiectul în sine, este utilizată de funcție. Prin urmare, modificările la o copie a unui obiect în cadrul unei funcții nu afectează însuși obiectul.

Când transferați un obiect la o funcție, apare un obiect nou. Când lucrarea funcției la care a fost transferat obiectul se termină, se elimină o copie a argumentului. Și aici trebuie să acordați atenție. Cum este o copie a obiectului format și este distrugătorul obiectului numit când copia sa este ștearsă? Faptul că se numește destructorul de copiere este probabil ușor de înțeles, deoarece obiectul (o copie a obiectului trecut ca parametru) iese din sfera de aplicare. Dar să ne amintim că obiectul în interiorul funcției - Bit-înțelept este o copie a obiectului trecut, ceea ce înseamnă că, în cazul în care obiectul conține, de exemplu, este creat un pointer la o anumită zonă de memorie alocată dinamic atunci când copierea unui obiect, arătând spre aceeași locație de memorie. Și de îndată ce se declanșează distrugătorul de copiere, unde, de regulă, este acceptată eliberarea memoriei, atunci zona de memorie indicată de obiectul "original" este eliberată, ceea ce duce la distrugerea obiectului original.

className
publice:
Nume de clasă ()
cout <<'Работа конструктора \n';
>

Nume de clasă ()
cout <<'Работа деструктора \n';
>
>;

void f (ClassName o)
cout <<'Работа функции f \n';
>

Acest program va efectua următoarele

Lucrări de designer
Funcția f
Lucrarea distrugătorului
Lucrarea distrugătorului

Constructorul este chemat o singură dată. Acest lucru se întâmplă atunci când creați c1. Cu toate acestea, destructorul este declanșat de două ori: o dată pentru o copie a lui o. a doua oară pentru obiectul c1 însuși. Faptul că distrugătorul este chemat de două ori poate deveni o sursă potențială de probleme, de exemplu, pentru obiectele a căror distrugător eliberează o zonă de memorie alocată dinamic.

O problemă similară apare atunci când se utilizează un obiect ca valoare de retur.

Pentru ca o funcție să returneze un obiect, este necesar: mai întâi, să se declare o funcție astfel încât valoarea ei de returnare să aibă un tip de clasă și, în al doilea rând, să returneze un obiect utilizând declarația obișnuită de returnare. Cu toate acestea, dacă obiectul returnat conține un destructor, atunci în acest caz există probleme similare asociate cu distrugerea "neașteptată" a obiectului.

ClassName publică:
Nume de clasă ()
cout <<'Работа конструктора \n';
>

Nume de clasă ()
cout <<'Работа деструктора \n';
>
>;

Nume_clasa f ()
ClassName obj;
cout <<'Работа функции f \n';
return vol;
>

Acest program va efectua următoarele

Lucrări de designer
Lucrări de designer
Funcția f
Lucrarea distrugătorului
Lucrarea distrugătorului
Lucrarea distrugătorului

Constructori de copiaturi

Constructorul este chemat de două ori: pentru c1 și obj. Cu toate acestea, există trei distrugători. De ce? Este clar că un destructor distruge c1, unul mai mult - obj. Apelul "extra" al destructorului (cel de-al doilea sub cont) este apelat pentru așa-numitul obiect temporar. care este o copie a obiectului returnat. Această copie este generată atunci când funcția returnează un obiect. După ce funcția returnează valoarea sa, destructorul obiectului temporar este executat. Este clar că dacă un destructor, de exemplu, eliberează memorie alocată dinamic, distrugerea obiectului temporar va duce la distrugerea obiectului returnat.

O modalitate de a rezolva această problemă este crearea unui tip special de constructori, constructori de copiat. Constructorul de copiere sau constructorul de copiere vă permite să specificați ordinea în care este creată o copie a obiectului.

Orice constructor de copiatoare are următoarea formă:

nume_clasă (const. nume_clasă obj)
. // corpul constructorului
>

className
publice:
Nume de clasă ()
cout <<'Работа конструктора \n';
>
Nume_clasă (const obj)
cout <<'Работа конструктора копирования\n';
>

Nume de clasă ()
cout <<'Работа деструктора \n';
>
>;

main ()
Nume de clasă c1; // sunați constructorul
Nume de clasă c2 = c1; // sunați la constructorul de copiat
>

Notă: Constructorul de copie nu afectează operația de atribuire.

Acum că există un constructor de copiere, puteți transfera în siguranță obiecte ca parametri funcționali și returnează obiecte. În acest caz, numărul de apeluri de designeri se va potrivi cu numărul de apeluri destructor, și pentru că copii ale procesului de învățământ a devenit acum un controlat, reduce în mod substanțial riscul de distrugere neașteptată a obiectului.

Indicatori și referințe la obiecte

Până în prezent, accesul la membrii obiectului a fost efectuat folosind operația ".". Acest lucru este corect dacă lucrați cu un obiect. Cu toate acestea, puteți accesa, de asemenea, obiectele prin intermediul indicatorului obiect. În acest caz, se utilizează de obicei săgeata "->".

Un pointer la un obiect este declarat exact la fel ca un pointer la o variabilă de orice tip.

void ToZero (_3d * vec)

În C ++, puteți face același lucru fără a utiliza indicii, utilizând parametrul de referință.

void ToZero (_3d VEC)
vec (0,0,0); // utilizate "."
>

Spre deosebire ca parametru al obiectului, folosind parametrul de referință sugerează că funcția, care a transmis parametrul manipulării nu este o copie a obiectului transmis, și anume acelea obiect transmise.

_3d _3d :: operator = (_3d b)

Utilizarea unei referințe ca valoare de retur asigură faptul că obiectul care a numit operația "=" mai degrabă decât copia sa este returnat ca rezultat al acestei funcții.







Articole similare

Trimiteți-le prietenilor: