Constructorul implicit este C#. Structuri și constructori implicite. Modificarea parametrilor transmisi constructorului

Constructorul implicit este C#. Structuri și constructori implicite. Modificarea parametrilor transmisi constructorului

clasa A ( public: A():a(0),b(0) () explicit A(int x): a(x), b(0) () A(int x, int y): a(x ), b(y) () privat: int a,b);

Clasa A ( public: explicit A(int x=0, int y=0): a(x), b(y) () privat: int a,b; );

Există diferențe? Care este mai bine de folosit?

2 raspunsuri

Vlad din Moscova

Aceste două declarații de clasă nu sunt echivalente.

În declarația de a doua clasă, constructorul este declarat cu specificatorul de funcție explicit, care limitează utilizarea acestui constructor în diferite situații.

În declarația de primă clasă, numai constructorul de conversie este declarat cu specificatorul de funcție explicit. Aceasta înseamnă că puteți apela implicit alți constructori.

Adică, prima declarație oferă mai multe opțiuni pentru utilizarea clasei.

Luați în considerare următorul program demonstrativ

#include struct A ( explicit A(int x = 0, int y = 0) : x(x), y(y) () int x; int y; ); struct B ( B() : x(0), y(0) () explicit B(int x): x(x), y(0) () B(int x, int y): x(x), y(y) () int x; void f(const A &a) ( std::cout<< "a.x = " << a.x << ", a.y = " << a.y << std::endl; } void g(const B &b) { std::cout << "b.x = " << b.x << ", b.y = " << b.y << std::endl; } int main() { // f({}); // f({ 1, 2 }); g({}); g({ 1, 2 }); }

Ieșirea sa către consolă:

B.x = 0, b.y = 0 b.x = 1, b.y = 2

În acest program, două apeluri la funcția f sunt comentate, deoarece dacă nu sunt comentate, compilatorul va genera un mesaj de eroare.

O altă diferență importantă este că o clasă are doar un constructor cu o semnătură dată, în timp ce o altă clasă are trei constructori cu semnături diferite.

Luați în considerare un alt demo

Struct A ( explicit A(int x = 0, int y = 0) : x(x), y(y) () int x; int y; ); struct B ( B() : x(0), y(0) () explicit B(int x): x(x), y(0) () B(int x, int y): x(x), y(y) () int x; struct C ( //prieten A::A(); prieten B::B(); ); int main() ( )

Aici, în clasa C, puteți declara constructorul implicit al clasei B ca prieten al clasei C. Cu toate acestea, nu puteți face același lucru cu constructorul implicit al clasei A pentru a-l declara ca prieten al clasei C, deoarece constructorul implicit din clasa C. clasa A are o semnătură diferită

Deja trebuie să scrii

Struct C ( prieten A::A(int, int); );

și acesta poate să nu fie ceea ce ați dori să primiți. Adică dacă, de exemplu, ai vrut ca prietenul să fie un constructor care este numit exclusiv fără argumente.

Adică, din nou, când există constructori separați, posibilitățile tale sunt mai largi.

Dacă luăm în considerare funcții mai degrabă decât constructori, diferența este și mai semnificativă.

Argumentele implicite nu afectează tipul funcției. Deci, de exemplu, dacă ați declarat o funcție ca

Void f(int, int = 0);

apoi, în ciuda argumentului implicit și a faptului că îl poți numi ca

F(valoare);

cu toate acestea, tipul său este void(int, int) . Acest lucru înseamnă, la rândul său, că nu poți, de exemplu, să scrii

Void h(void f(int)) ( f(10); ) void f(int x, int y = 0) ( std::cout<< "x = " << x << ", y = " << y << std::endl; } // h(f);

deoarece parametrul funcției h este de tip void(int) ., iar funcția folosită ca argument este de tip void(int, int)

Dacă declarați două funcții în loc de una

Void h(void f(int)) ( f(10); ) void f(int x) ( std::cout<< "x = " << x << std::endl; } void f(int x, int y) { std::cout << "x = " << x << ", y = " << y << std::endl; }

atunci această provocare

va fi corectă, deoarece există o funcție cu un singur parametru.

IxSci

Diferențele au fost deja explicate de @Vlad de la Moscova, voi sugera doar, dintre cele două opțiuni din întrebare, a treia variantă:

Clasa A ( public: A():A(0, 0) () explicit A(int x): A(x, 0) () A(int x, int y): a(x), b(y) () privat: int a,b);

După părerea mea, această opțiune este cea mai bună, pentru că... are un constructor explicit cu un singur argument, care este o bună practică și previne unele erori. Pe de altă parte, explicit pentru constructorii care au mai mult sau mai puțin de un argument, în opinia mea, este redundant. Deoarece Crearea accidentală a unui obiect din mai multe argumente este problematică și tocmai acesta este motivul pentru care am atribuit explicit constructorului cu un singur argument - protecție împotriva erorilor accidentale.

Ei bine, și cel mai important, avem un singur constructor care inițializează câmpurile. Toate celelalte acționează prin intermediul acestuia, ceea ce permite minimizarea erorilor de inițializare.

Conceptul de „constructor” este inseparabil de conceptul de clasă. Constructorii sunt funcții speciale care sunt apelate automat atunci când obiectele sunt inițializate. Numele lor sunt aceleași cu numele claselor cărora le aparțin și nu au un tip de returnare. O clasă poate avea mai mult de un constructor, care diferă în semnături. Constructorii sunt utili pentru inițializarea câmpurilor unei clase. Interesant, compilatorul creează un constructor implicit fără parametri, setează câmpurile la 0, fals sau nul (pentru obiecte).

Să ne uităm la un exemplu. Să creăm o clasă „Triunghi” cu trei câmpuri și o metodă care calculează perimetrul acesteia.

Utilizarea sistemului; namespace Constructor ( clasa Triunghi ( int a; int b; int c; public int perimetru() ( return a + b + c; ) ) clasa Program ( static void Main(string args) ( Triunghi tr = new Triangle(); / / creează un obiect Console.WriteLine("perimeter = " + tr.perimeter()); Console.ReadKey();

perimetru = 0

ANALIZĂ

Pentru prima dată, 3 avertismente (dar nu erori!) apar în fereastra de eroare:
Câmpului „Constructor.Triangle.c” nu i se atribuie o valoare nicăieri, așa că va avea întotdeauna o valoare implicită de 0(și pentru câmpuri AȘi b).

Din moment ce constructorul fără parametri a funcționat Triunghi(), apoi toate laturile triunghiului au fost atribuite 0. Să adăugăm linia la definiția clasei: Triunghi();și rulați programul. Vom primi un mesaj de eroare:
Tipul „Constructor.Triangle” definește deja un membru „Triunghi” cu aceleași tipuri de parametri.
Acest lucru confirmă faptul că un constructor cu parametri zero a fost deja creat. Acum să adăugăm un constructor cu trei parametri la descrierea clasei:

Triunghi public (int a, int b, int c) ( this.a = a; this.b = b; this.c = c; )

Și în metoda Main() înlocuim prima linie cu:

Triunghi tr = triunghi nou(3,4,5);

Rezultatul executiei programului: perimetru = 12

Adică am inițializat câmpurile ( a, b, c) obiect tr, ceea ce indică cuvântul cheie acestîn instrucțiunile constructorului. Altfel ar trebui să precizăm diferite nume: a = d; unde d ar fi atunci primul element din lista de parametri. Un operator precum a = a va lăsa valoarea câmpului a=0, iar compilatorul va emite un avertisment:
Se realizează atribuirea aceleiași variabile; îndeplinesc cu adevărat acest scop și nu altul?

Sfaturi practice: Fără a elimina erorile de sintaxă, nu veți obține un program de lucru. Dar uneori avertismentele semnalează și posibile inconsecvențe majore în codul dvs.

Să vă reamintim că, deoarece câmpurile de clasă sunt private în mod implicit, ele nu pot fi modificate direct în program, de exemplu:
tr.c = 7;
În acest caz vom primi un mesaj de eroare:
„Constructor.Triangle.c” nu este disponibil din cauza nivelului său de securitate.

Eroarea va fi eliminată dacă adăugăm un modificator la descrierea câmpului cu public:
public int c;
Cu toate acestea, aceasta nu este o formă bună în OOP; câmpul nu mai este protejat.

Spre deosebire de structuri, clasele sunt tipuri de date de referință instanțele lor (obiecte) „vii” în heap. Prin urmare, atunci când un obiect este creat, câmpurile sunt rezervate în heap și, în funcție de constructor, câmpurile sunt setate la 0, false sau nul (pentru un constructor implicit) sau valorile corespunzătoare (pentru un constructor parametrizat). ).

Verificați în practică corectitudinea următoarelor afirmații:

1) Este posibil să se creeze un constructor implicit? - Da.
2) Dacă vă creați propriul constructor, compilatorul va genera un constructor implicit? - Nu.
3) Dacă unele câmpuri nu sunt inițializate în constructorul dvs., vor fi inițializate automat de către compilator? - Da.
4) Este permisă inițializarea variabilelor acolo unde sunt declarate? - Da.

Exemplu de constructor la moștenirea unei clase

Să folosim clasa gata făcută Random din biblioteca System. Să creăm o clasă copil RwName („Random with Name”), să adăugăm câmpuri și două metode și, de asemenea, să modificăm constructorul clasei:

Utilizarea sistemului; namespace Constructor ( clasa RwName: Random ( public string s; public RwName(string s) ( this.s = s; ) public double NextD() ( return this.NextDouble(); ) public void Rezult() ( Console.WriteLine( this.s + this.NextDouble() ) ) class Program ( static void Main(string args) ( RwName r = new RwName("Număr real: "); Console.WriteLine(r.NextD()); r. Rezultat(); Console.ReadKey();

Acum obiectul clasei va primi un nume prin constructor, iar cele două metode de clasă au devenit mai specifice. Chiar dacă nu specificați un nume „părinte” în declarația clasei, acesta va moșteni în continuare constructorul clasei Object.

Constructor static

Constructorul poate fi, de asemenea, declarat static. Un constructor static este folosit de obicei pentru a inițializa componente care se aplică unei clase întregi, mai degrabă decât unei singure instanțe a unui obiect din acea clasă. Prin urmare, membrii clasei sunt inițializați de un constructor static înainte ca orice obiect din acea clasă să fie creat:

Utilizarea sistemului; namespace Constructor ( clasă StatClass ( public static int q; public int p; // Constructor static static StatClass() ( q = 33; ) // Constructor obișnuit public StatClass() ( p = 44; ) ) class Program ( static void Main (șir argumente) ( StatClass ob = new StatClass(); Console.WriteLine("Câmpul de acces p al instanței: " + ob.p); Console.WriteLine("Câmp de acces q: " + StatClass.q); Console. ReadLine (); ) ))

Este important ca constructorul de tip static să fie apelat automat și înainte de constructorul instanței. Aceasta implică faptul că un constructor static trebuie să fie executat înaintea oricărui constructor de instanță. Constructorii statici nu au modificatori de acces - au acces implicit și, prin urmare, nu pot fi apelați dintr-un program.

CONCLUZIE

Când creați clase prin moștenire, fiți mai precis în alegerea clasei părinte. Este posibil să existe mai mult de un constructor în clasa dvs., în funcție de confortul dvs. de a le folosi.

Constructorul implicit este un construct destul de simplu care echivalează cu crearea unui constructor fără parametri pentru tip. Deci, de exemplu, dacă, atunci când declarați o clasă non-statică, nu declarați un constructor personalizat (indiferent, cu sau fără parametri), atunci compilatorul va genera independent un constructor fără parametri. Cu toate acestea, când vine vorba de constructori impliciti pentru structuri (pentru tipuri de valori), atunci lucrurile devin mai complicate.

Iată un exemplu simplu despre cum ați răspunde la următoarea întrebare: de câte tipuri de valori sunt.NETCadruconține constructori impliciti? Răspunsul intuitiv pare să fie „ Toate", și vei greși, pentru că de fapt, niciunul dintre tipurile semnificative.NETCadrunu conține un constructor implicit.

Lucrul cu matrice nu este singurul loc în care un constructor de tip de valoare existent nu va fi apelat. Următorul tabel arată clar când va fi apelat un astfel de constructor și când nu.
// Numit var cvt = new CustomValueType(); // Denumit var cvt = Activator.CreateInstance(typeof(CustomValueType)); // Nu este numit var cvt = default(CustomValueType); static T CreateAsDefault () ( return default(T); ) // Nu este numit CustomValueType cvt = CreateAsDefault (); static T CreateWithNew () unde T: new() ( return new T(); ) // Nu este apelat!! CustomValueType cvt = CreateWithNew (); // Nu este numit var array = new CustomValueType;

Aș dori să atrag atenția asupra inconsecvenței comportamentului în următorul caz: se știe că o instanță a unui parametru generic este creată folosind Activator.CreateInstance , motiv pentru care atunci când apare o excepție în constructor, utilizatorul metodei generice îl va primi nu în forma sa pură, ci în formă TargetInvocationException. Cu toate acestea, la crearea unei instanțe de tip CustomValueType prin utilizarea Activator.CreateInstance constructorul nostru implicit va fi apelat și când metoda este apelată CreateWithNewși crearea unei instanțe de tipul valoare folosind nouT() - Nu.

Concluzie

Deci, am aflat următoarele:
  1. Constructorul implicit în C# este o instrucțiune pentru a reseta valoarea unui obiect.
  2. Din punct de vedere CLR, constructorii impliciti există și limbajul C# știe chiar cum să-i numească.
  3. C# nu vă permite să creați constructori impliciti personalizați pentru structuri, deoarece acest lucru ar duce la performanțe slabe atunci când lucrați cu matrice și confuzie semnificativă.
  4. În limbile care acceptă crearea de constructori impliciti, tot nu ar trebui să îi declarați din aceleași motive pentru care sunt interzise în majoritatea limbilor platformei .NET.
  5. Tipurile de valori nu sunt atât de simple pe cât par: pe lângă problemele legate de schimbarea (mutabilitate), chiar și cu constructorii impliciti, tipurile de valori nu sunt atât de simple.

Adevărul este că:

1. Dacă creați o clasă și definiți un constructor cu argumente în ea (clasa AClass, care are un singur constructor care acceptă int i), atunci compilatorul nu va mai crea un constructor implicit. Pentru că ar încălca contractul de clasă al AClass, care nu poate fi inițializat fără argumente. Dacă doriți să aveți și un constructor implicit, setați-l acum în mod explicit.

Altfel, nu ar exista nicio modalitate de a preveni crearea unui constructor implicit, ceea ce ar fi rău.

2. Când se creează constructori pentru un BClass care moștenește de la o altă clasă, compilatorul cere ca prima linie a constructorului să fie un apel către un alt constructor (moștenit sau din acea clasă).

De ce? Pentru că odată ce moșteni dintr-o clasă, vrei să-i refolosești logica. Constructorul aduce instanța clasei la o stare integrală inițială. În cazul dumneavoastră, AClass necesită un argument pentru inițializare, fără de care JVM-ul nu știe cum să inițializeze o instanță a clasei.

Dacă o clasă nu are niciun constructor definit, atunci încearcă să creeze un constructor implicit, de exemplu. fara argumente:

Clasa publică ACclass1 ( )

Deoarece nu există constructori explici definiți și clasa nu moștenește de la alte clase, compilatorul creează un constructor implicit.

Aceasta este echivalentă cu această definiție:

Clasa publică AClass1 ( public AClass1() ( ) )

Acum să ne uităm la BClass1:

Clasa publică BClass1 extinde AClass1 ( )

Și aici, niciun constructor nu este definit în mod explicit, iar compilatorul încearcă să creeze un constructor implicit. Deoarece AClass1 are un constructor implicit, va crea un constructor implicit care va apela constructorul lui AClass1. Acest cod este echivalent cu acesta:

Clasa publică BClass1 extinde AClass1 ( public BClass1() ( super(); ) )

În cazul dvs., o clasă este creată FĂRĂ un constructor implicit:

Public AClass ( public AClass(int i) ( ) )

Deoarece (cel puțin un) constructor este descris aici, constructorul implicit nu mai este creat. Adică, acest cod nu va mai compila:

AClass a = nou AClass(); // nu funcționează

nevoie de ceva de genul

AClass a = nou ACclass(1);

În consecință, orice constructor BClass va necesita un apel către un constructor AClass sau BClass. Cu această descriere, compilatorul se va plânge:

Public BClass extinde AClass ( )

Deoarece va exista o încercare de a apela constructorul implicit al clasei AClass, care nu este definit:

Public BClass extinde AClass ( public BClass() ( super(); // eroare; clasa AClass nu are un astfel de constructor) )

Cu toate acestea, este posibil să creați un BClass cu un constructor implicit setând constructorul AClass la ceva de genul acesta:

Clasa publică BClass extinde AClass ( public BClass() ( super(1); ) )

Aceasta se va compila.

Un constructor este o funcție concepută pentru a inițializa obiectele de clasă.

data cursului
{
int zi, lună, an;
public:
set(int, int, int);
};

Nicăieri nu precizează că un obiect trebuie inițializat, iar programatorul poate uita să-l inițialeze sau să o facă de două ori.
OOP permite programatorului să descrie o funcție care are scopul explicit de a inițializa obiecte. Deoarece o astfel de funcție construiește valori de un anumit tip, se numește constructor. Constructorul are întotdeauna același nume ca și clasa în sine și nu are niciodată o valoare returnată. Când o clasă are un constructor, toate obiectele acelei clase vor fi inițializate.

data cursului(
int zi, lună, an;
public:
data(int, int, int); // constructor
};

Dacă constructorul necesită argumente, acestea ar trebui specificate:

data azi = data(6,4,2014); // formular complet
data de Craciun(25,12,0); // forma scurta
// data ziua_mea_; // invalid, inițializarea a fost omisă

Dacă este necesar să se furnizeze mai multe moduri de inițializare a obiectelor unei clase, sunt specificați mai mulți constructori:

data cursului(
int luna, zi, an;
public:
data(int, int, int); // zi lună an
data(car *); // data în reprezentare șir
Data(); // data implicită: astăzi
};

Constructorii sunt supuși acelorași reguli privind tipurile de parametri ca și funcțiile supraîncărcate. Dacă constructorii diferă semnificativ în ceea ce privește tipurile de parametri, atunci compilatorul îl poate alege pe cel corect de fiecare dată când este utilizat:

data 4 iulie ("27 februarie 2014" );
întâlnire tip (27, 2, 2014);
data acum; // inițializat implicit

O modalitate de a reduce numărul de funcții supraîncărcate (inclusiv constructori) este utilizarea valorilor implicite.

Constructor implicit

Este apelat un constructor care nu necesită parametri constructor implicit. Acesta poate fi un constructor cu o listă de parametri goală sau un constructor în care toate argumentele au valori implicite.
Constructorii pot fi supraîncărcați, dar poate exista un singur constructor implicit.

data cursului
{
int luna, zi, an;
public:
data(int, int, int);
data(car *);
Data(); // constructor implicit
};

Când este creat un obiect, constructorul este apelat, cu excepția cazului în care obiectul este creat ca o copie a altui obiect din aceeași clasă, de exemplu:

data data2 = data1;

Cu toate acestea, există cazuri în care crearea unui obiect fără apelarea unui constructor se face implicit:

  • parametru formal – un obiect trecut prin valoare, creat pe stivă în momentul apelării funcției și inițializat cu o copie a parametrului actual;
  • Rezultatul unei funcții este un obiect transmis prin valoare în momentul în care este executată instrucțiunea return, acesta este copiat într-un obiect temporar care stochează rezultatul funcției.

În toate aceste cazuri, traducătorul nu apelează un constructor pentru obiectul nou creat:

  • data2 în definiția de mai sus;
  • pentru un parametru formal creat pe stivă;
  • pentru un obiect temporar care stochează valoarea returnată de funcție.

În schimb, ei copiază conținutul obiectului sursă:

  • data1 în exemplul dat;
  • parametru real;
  • obiectul rezultat din instrucțiunea return.

Constructor de copiere

De regulă, la crearea unui obiect nou pe baza unuia existent, are loc o copiere superficială, adică datele pe care le conține obiectul sursă sunt copiate. Mai mult, dacă obiectul sursă conține pointeri către variabile dinamice și matrice, sau referințe, atunci crearea unei copii a obiectului necesită duplicarea obligatorie a acestor obiecte în obiectul nou creat. În acest scop, este introdus un constructor de copiere, care este apelat automat în toate cazurile de mai sus. Are un singur parametru - o legătură către obiectul sursă:

clasa String
{
char *str;
dimensiune int;
public:
String(String&); // Copiați constructorul
};
String::String(String& dreapta) ( // Creează copii ale dynamic
// variabile și resurse
str = new char ;
strcpy(str, dreapta->str);
dimensiune = dreapta->mărime;
}

Distructori

O clasă definită de utilizator are un constructor care asigură inițializarea corectă. Multe tipuri necesită, de asemenea, acțiune inversă. Destructorul se asigură că obiectele de tipul specificat sunt curățate corespunzător. Numele destructorului este numele clasei precedat de un tilde ~ . Deci, pentru clasa X, destructorul va fi numit ~X() . Multe clase folosesc memoria dinamică, care este alocată de constructor și eliberată de destructor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

data cursului
{
int zi, an;
char *lună;
public:
data(int d, char * m, int y)
{
zi = d;
luna = new char ;
strcpy_s(lună, strlen(m)+1,m);
an = y;
}
~date() ( șterge luna; ) // destructor
};


Să existe un vector de clasă care implementează o matrice protejată și trebuie să stocați mai multe valori pentru fiecare astfel de matrice: vârsta, greutatea și înălțimea unui grup de oameni. Grupăm 3 matrice în interiorul unei noi clase.

Noul constructor de clasă are un corp gol și o listă de constructori de clasă vect care se pot apela, listați după două puncte (:) separate prin virgule (,). Ele sunt executate cu un argument întreg i, creând 3 obiecte din clasa vect: a, b, c.

Constructorii pentru membrii clasei sunt întotdeauna executați înaintea constructorului pentru clasa în care acești membri sunt declarați. Ordinea în care sunt executați constructorii pentru membrii clasei este determinată de ordinea în care sunt declarați membrii clasei. Dacă constructorul unui membru al clasei necesită argumente, acel membru cu argumentele necesare este specificat în lista de inițializare. Destructorii sunt chemați în ordine inversă.
{
system("chcp 1251");
system("cls");
multi_v f(3);
pentru (int i = 0; i<= f.getSize(); i++)
{
f.a.element(i) = 10 + i;
f.b.element(i) = 20 + 5 * i;
f.c.element(i) = 120 + 5 * i;
}
pentru (int i = 0; i<= f.getSize(); i++)
{
cout<< f.a.element(i) << "лет \t" ;
cout<< f.b.element(i) << "кг \t" ;
cout<< f.c.element(i) << "см" << endl;
}
cin.get();
întoarce 0;
}


Când programul se execută, un destructor individual va fi apelat pentru fiecare membru vect înainte de a ieși din blocul principal. Rezultatul programului
vederi