Конструктори
(Constructors)
.
(съдържание)
В тази лекция ще
бъдат разгледани конструкторите, които представляват специална функция
на класа и които се използват при създаването на обектите на класа за инициализаране
на някои променливи на класа. Също така, ще бъдат въведени деструкторите
без да се разглеждат подробно. В тази и следващите лекции ще видим, че
конструкторите могат да бъдат както с, така и без аргументи.
1. Инициализиране
на обектите на класа.
В
лекция
16 беше разгледан класът Стек (The Stack Class),
който имитира конструкцията стек и бе дадена една примерна
програма от превъзходния курс на Лафоор [1]. Но в тази
програма (написана за тогавашното ниво на знание на читателя) има функция,
която стои изкуствено в светлината на C++ програмирането - това
е функцията init().
Задачата на тази функция е да бъде иниализирана променливата на обекта
top
с -1.
Проблемите са няколко (особено в голяма програма) и първият е, че програмистът
може да забрави да извика тази функция в главната функция и по този начин
да допусне логическа грешка, която се открива изключително трудно. Освен
това, всеки един друг програмист ще трябва да вниква каква е тази функция
и защо е написана и къде трябва да се извиква. Последното е особено важно
и даже при тази малка програма трябва да се съобрази, че init()
трябва да се извиква преди да е вкарана някаква стойност в стека с функцията
void
push(int var).
Освен това, с тази функция имаме двустепенен процес - създаваме обект и
после го инициализираме, което води до заблудата, че е необходимо да имаме
два различни процеса. Това също е странно, защото нещата които се създават
се създават с определени характеристики, а не първо се създават абстрактно
и после да се доискусуряват. В т. 4 ще бъде разгледана
една променена версия на гореспоменатата програма, но нека преди това се
запознаем с конструкторите.
2. Конструктор (Constructor). Конструкторът е особена функция на класа, която има същото име като това на класа и която се изпълнява при създаване на обект от класа. Нека разгледаме една елементарна програма, която илюстрира някои от характеристиките на конструкторите и деструкторите и която е взета от курса на Лафоор [1].
! Първо, така написана, програмата трябва да се стартира извън интегрираната работна среда (IDE, Integrated Development Environment), понеже е необходимо да се види какво става след излизането от главната функция на програмата, main(). Това може да се осъществи при стартиране на Command Prompt от менюто Start/Programs/Accessories.
//
condest.cpp
//
demonstrates constructors and destructors
#include
<iostream.h>
class
Omega
{
public:
Omega() // constructor
{
cout << "I am the constructor" << endl;
}
~Omega() // destructor
{
cout << "I am the destructor" << endl;
}
};
void
main()
{
cout << "Starting main()" << endl;
Omega om1, om2, om3; // create three objects
cout << "Ending main()" << endl;
}
// main()
В този странен клас, Omega, има само конструктор, Omega(), и деструктор, ~Omega(). Както споменахме първият има същото име като това на класа, докато деструкторът има име, започващо с тилда, ~, и последвано от името на класа. Това е така, за да може те да се разпознават от компилатора и програмистите. В тази програма, тези две функции нищо полезно не извършват, освен да ни "осветляват" кога се извикват. Както се вижда, в главната функция се създават три обекта на класа, изписва се Ending main() и програмата завършва. Ако се стартира програмата (от Command Prompt!) ще се получи следният изход на екрана:
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>condest
Starting
main()
I
am the constructor
I
am the constructor
I
am the constructor
Ending
main()
I
am the destructor
I
am the destructor
I
am the destructor
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>
От него ясно се вижда, че конструкторът е бил извикан три пъти, т.е. по един път за всеки от трите обекта при дефиницията им - Omega om1, om2, om3;. В главната функция се изпълнява cout << "Ending main()" << endl; и тогава програмата излиза, като при това се извикват деструкторите на трите обекта. Обърнете внимание, че в главната функция не се извикват конструкторите и деструкторите с явен код! Т.е. първото нещо, което трябва да се отбележи е, че конструкторът се извиква автоматично от програмата при създаване на обекти от класа, а деструкторът при разрушаване на обектите. Последното на практика означава, че деструкторът се извиква при излизане на програмата от обхвата на съответния обект. Написахме "излизане от обхвата", защото обектът може да е автоматична променлива и тогава нейният деструктор се извиква при нейното унищожаване, което става (вижте лекция 27) при излизането от процедурата или съставното твърдение и пр. или ако е глобална променлива, нейният деструктор се извиква при излизане от праграмата, както е в случая с трите обекта om1,om2 и om3.
Друга особеност е, че даже и да не напишете Ваши собствени конструктор или деструктор (explicit constructor / desructor), то конструктори и деструктори по подразбиране пак съществуват и извършват създаването и унищожаването на обектите.
И конструкторът и деструкторът не могат да връщат стойност (No Return Value)! Това е така, защото връщането на стойност от тях не е необходимо при програмирането в C++, а освен това какво би могъл да върне един деструктор!? Интересно е, че конструкторът не може да се извика с обект на класа, например om1.Omega(), докато деструкторът може - вижте примера в следващата точка.
3. Още нещо за конструкторите и деструкторите. Нека променим малко горната програма като добавим една статична и една обикновена променливи на класа. Първата, om_count, ще брои колко обекта се създават, а втората, om_number, ще ги номерира с едно, две и т.н. Допълнително в главната функция сме извикали деструктора на втория обект - om2.~Omega(); - нещо, което е куриозно и е предназначено само за експеримент, който да покаже, че деструкторът не унищожава обекта, а просто извършва някакви действия при неговото унищожаване.
//
condest1.cpp
//
demonstrates constructors and destructors
#include
<iostream.h>
class
Omega
{
public:
Omega() // constructor
{ om_number = ++om_count;
cout << "I am the constructor # " << om_number << endl;
}
~Omega() // destructor
{
cout << "I am the destructor # " << om_number << endl;
}
private:
static int om_count;
int om_number;
};
int Omega::om_count = 0;
void
main()
{
cout << "Starting main()" << endl;
Omega om1, om2, om3; // create three objects
om2.~Omega();
cout << "Ending main()" << endl;
}
// main()
Изходът на екрана от програмата е следният:
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>condest1
Starting
main()
I
am the constructor # 1
I
am the constructor # 2
I
am the constructor # 3
I
am the destructor # 2
Ending
main()
I
am the destructor # 3
I
am the destructor # 2
I
am the destructor # 1
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>
От този изход се вижда, че конструкторите се извикват един след друг автоматично. След това програмата изпълнява странното твърдение om2.~Omega();, написано от нас, а после се отпечатва Ending main() на екрана. И накрая ясно се вижда, че обектите се унищожават обратно на реда, по който са създавани.
!Явното извикване на деструктора с om2.~Omega(); беше само експеримент, който няма смисъл в програмирането и даже е свързан с рискове във Ваша реална програма да загубите отделена памет или да объркате логиката на програмата! Ако се опитате да напишете извикване за конструктора (аналогично на това за деструктора), например, om2.Omega(); компилаторът ще ви отговори с 'Omega' is not a member of 'Omega'.
С тези две програми само показахме кога и как се извикват конструкторите и деструкторите, но не обяснихме тяхната роля. Тя ще стане ясно в следващите лекции, но тук ще кажем предварително, че обикновенно с написан от Вас конструктор Вие инициализирате определени променливи на класа, запазвате памет и/или извършвате други необходими действия с Вашите обекти. С деструкторите обикновено се освобождава заетата памет.
Нека първо разгледаме
инициализацията на променливи в конструктора.
.
4. Конструктор,
инициализиращ променливи на класа.
В
т.1 обсъдихме
недостатъците на допълнителна инициализираща функция и ето един вариант
за нейната замяна, чрез инициализация на една от променливите на обектите
в конструктора: следва променената програма за поддържане на стек, която
е една значително по-добра алтернатива на програмата от лекция
16. Функцията init()
е премахната и е добавен конструкторът Stack(),
който инициализира променливата top
на обектите от класа Stack.
Програмата stackcon.cpp
е взета от курса на Лафоор [1].
//
stackcon.cpp
//
class models a stack; uses a constructor
#include
<iostream.h>
#include
<conio.h>
// for getch()
class
Stack
// a stack holds up to 20 ints
{
private:
int st[20];
// integers are stored in array
int top;
// index of last item pushed
public:
Stack() : top(-1) // constructor
{ }
void push(int var) // place an item on the
stack
{
st[++top] = var;
}
int pop()
// remove an item from the stack
{
return st[top--];
}
};
void
main()
{
Stack s1;
// create a stack object
// (no need to initialize it)
s1.push(11);
// push 3 items onto stack
s1.push(12);
s1.push(13);
cout << s1.pop() << endl; // pop 3 items and display
them
cout << s1.pop() << endl;
cout << s1.pop() << endl;
getch();
}
// main()
Забележете, че няма функция init() в класа Stack, както и няма извиквания от рода на "s1.init();" в главната функция. Както се и очаква, екранът след изпълнение на програмата е същият като този, който се получава с предишната версия. Тук трябва да обърнем внимание не само на това улеснение за програмиста, но и на самото инициализиране на променливата top. То би могло да се запише във вида
Stack()
// constructor: not a practice!
{ top = -1; }
но е прието инициализацията да става с така наречения инициализиращ списък (Initialization List), който се изписва след името на конструктора и двете точки във вида - променлива, отваряща скоба, инициализираща стойност, затваряща скоба, запетая, следваща променлива и т.н.
Stack() : top(-1) // constructor OK!
{ }
Разбира се, тук имаме само една променлива, но ако са две ще трябва да има запетая между тях - например "Stack() : top(-1), number(0)"!
И горният вариант (с присвояването в тялото на конструктора) работи, но се предпочита долният. Също така, ако променлива на класа е обект от друг клас, то не е възможно тя да се инициализира по друг начин, освен с инициализиращия списък.
Това, че тялото на функцията-конструктор остава празно не е проблем - просто трябва да се сложат двете големи скоби, иначе компилаторът ще се "оплаче". Но тялото на конструктора в повечето случаи не е празно, защото има и други задачи за вършене, както ще видим в следващите лекции. Освен това, например, масивите не могат да се инициализират по начин подобен на top(-1).
Деструктор на класа няма, защото никаква допълнителна памет не е отделена в конструктора и програмата автоматично изпълнява имплицитен деструктор, заложен от компилатора.
И последно ще споменем, че конструктор като горния, който не приема аргументи се нарича конструктор по подразбиране (Default Constructor). В следващите лекции ще видим, че конструкторът може да има аргументи, както и че може да има няколко конструктора (разбира се с едно и също име - това на класа), но с различен списък от параметри.
! Допълнение за инициализацията: Всяка една променлива, включително и предефинираните променливи (например целочислените) са също обекти за компилатора и могат да се инициализират при тяхната дефиниция. Разгледайте следния код, който присвоява 0 на k и 1 на m.
int k(0), m(1); // initialize k & m
cout << "\nk = " << k; // display k
cout << "\nm = " << m; // display m
Keywords: С++,
OOP programming , C++ , Classes , Inheritance , Reusability , Creating
New Data Types , Polymorphism and Overloading
Ключови думи: клас
, обект, обектно ориентирано програмиране , полиморфизъм switch if else
?