Програмен език C++
Част 38
.
Визуализиране на конструкторите и деструкторите или за запазване на променливите
.
(съдържание)
.
В тази лекция ще разгледаме една примерна програма, взета от превъзходния курс на Лафоор [1]. С нея не се въвеждат нови концепции, а само се илюстрира концепцията за запазване на променливите (Storage Classes), когато променливите са обекти, дефинирани от програмиста. Специално за целта, в двата  конструктора на класа има редове, които разпечатват кой конструктор се извиква и кой обект се създава, а в деструктора има редове, които посочват кой обект се унищожава при излизане на програмата от неговия обхват. На практика, така на читателя се изяснява предаването и връщането по стойност от функциите и ролята на различните по вид конструктори и деструктора на класа.
.
1. Програмата destru.cpp. Това е изменена версия на програмата copycon3.cpp от лекция 36. Всеки обект на класа omega има име, което се пази в променливата obname. Името се задава или с едноаргумтния конструктор или с изрично написания от програмиста копиращ конструктор. За разлика от предишната програма, в класа omega на настоящата програма destru.cpp има две статични променливи, total_now и total_ever, като първата брои обектите от класа, които съществуват в момента, а вторта - обектите, които въобще са създадени. Също така, функцията void func(omega og) е запазена, но в нея са добавени две съобщения - кога функцията стартира и завършва, а също така в нея се създава локален обект с едноаргументния конструктор.

// destru.cpp
// demonstrates constructors, destructors,
// and storage classes

#include <iostream.h>
#include <string.h>           // for strncpy()

class omega
{
   private:
       static const int size = 20;  // array size
       char obname[size];           // array for obname string
       static int total_now;        // objects in existence now
       static int total_ever;       // objects ever created
       int snumber;                 // serial number of this object

   public:
       // one-arg constructor
       omega(char str[]) : snumber(++total_ever)
       {
          strncpy(obname, str, size);
          cout << "\n   1-arg constructor creating "
               << obname << "-" << snumber
               << ". Total=" << ++total_now;
       }

       // copy constructor
       omega(const omega & om) : snumber(++total_ever)
       {
          strncpy(obname, om.obname, size);
          cout << "\n   Copy constructor creating  "
               << obname << "-" << snumber
               << ". Total=" << ++total_now;
       }

       // destructor
      ~omega()
       {
          cout << "\n   Destructor destroying      "
               << obname << "-" << snumber
               << ". Total = " << --total_now;
       }
   };  // end class omega

int omega::total_now = 0;
int omega::total_ever = 0;

omega om0("Adam");            // external object

////////////////////////////////////////////////////////////////

void main()
{
   cout << "\nmain() starting";

   void func(omega);          // declaration

   omega om1("Jane");         // uses one-arg constructor
   omega om2("Paul");         // uses one-arg constructor
   omega om3(om2);            // uses copy constructor

   cout << "\nmain() calling func(om1)";
   func(om1);

   cout << "\nmain() calling func(om2)";
   func(om2);

   cout << "\nmain() terminating";

}   // main()

void func(omega og)           // argument passed by value
{
   cout << "\nfunc() starting";

   omega om4("Mike");         // object is local to func()

   cout << "\nfunc() terminating";
}

В програмата нарочно не е поставен ред, който извиква функцията getch(), която ни служи в другите програми за задържане на екрана преди връщането към интегрираната работна среда (IDE). Това е така, защото е наложително програмата да бъде стартирана извън IDE.

! Така написана, програмата трябва да се стартира извън интегрираната работна среда (IDE, Integrated Development Environment), понеже е необходимо да се види какво става след излизането от главната функция на програмата, main(). Това може да се осъществи при стартиране на Command Prompt от менюто Start/Programs/Accessories, преминаване на директорията където е файлът destr.exe и стартирането му чрез изписване на името му.

Работна директория при мен е \PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>destr на диск D:. Изходът от изпълнението на програмата е следният (дадени са и двата реда с показалеца (промта) на операционната система):

D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>destru

   1-arg constructor creating Adam-1. Total=1
main() starting
   1-arg constructor creating Jane-2. Total=2
   1-arg constructor creating Paul-3. Total=3
   Copy constructor creating  Paul-4. Total=4
main() calling func(om1)
   Copy constructor creating  Jane-5. Total=5
func() starting
   1-arg constructor creating Mike-6. Total=6
func() terminating
   Destructor destroying      Mike-6. Total = 5
   Destructor destroying      Jane-5. Total = 4
main() calling func(om2)
   Copy constructor creating  Paul-7. Total=5
func() starting
   1-arg constructor creating Mike-8. Total=6
func() terminating
   Destructor destroying      Mike-8. Total = 5
   Destructor destroying      Paul-7. Total = 4
main() terminating
   Destructor destroying      Paul-4. Total = 3
   Destructor destroying      Paul-3. Total = 2
   Destructor destroying      Jane-2. Total = 1
   Destructor destroying      Adam-1. Total = 0
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>

2. Кога и как се създават и унищожават обектите в програмата?. Отместените надясно линии на този изход са изписани от конструкторите и деструктора, а другите (изпъкналите наляво) - от твърденията в главната функция  main() и функцията func().

Както се вижда от изхода, общо се създават осем обекта от класа omega. Първият създаден обект е с име Adam и е създаден от едноаргумнтния конструктор при дефиницията на глобален обект om0, още преди главната функция main(). Той е т.н. външна променлива (External Variable). Той не само първи се създава, но и последен се разрушава и то чак след излизане на програмата от главната функция. Както може да се види, обектите се създават в даден ред и се разрушават в обратен на него.

В главната функция main() се създават два обекта с едноаргумнтния конструктор - om1 и om2, с имена Jane и Paul. Тъй като са втори и трети подред, техните номера са 2 и 3. След това с помощта на копиращия конструктор се създава четвърти обект, копие на третия (с име Paul)  - ето защо той има същото име, но е с номер 4.

След това два пъти подред се извиква функцията func() с аргумент, първия път om1, и втория - om2. Тя прави техни копия (в og с копиращия конструктор), които имат същите имена, но други номера, и отделно създава локален обект om4 с име Mike.

Всеки път при излизане от функцията func(),  локалният обект om4 се унищожава първи, а след него и копирания аргумент og.

3. Връщане по стойност. В горната програма няма връщане по стойност от функция, където отново става създаване на нов обект с копиращия конструктор. Ето защо в следващата програма, която е променената destru.cpp, сме променили функцията func(), така че да връща по стойност променлива от типа на класа omega. Обърнете внимание, че в C++ функциите, които връщат стойност могат да се извикват по два начина - func(om1); и om3 = func(om1);, така както е направено в програмата destru1.cpp,.

// destru1.cpp
// demonstrates constructors, destructors,
// and storage classes

#include <iostream.h>
#include <string.h>           // for strncpy()

class omega
{
   private:
       static const int size = 20;  // array size
       char obname[size];           // array for obname string
       static int total_now;        // objects in existence now
       static int total_ever;       // objects ever created
       int snumber;                 // serial number of this object

   public:
       // one-arg constructor
       omega(char str[]) : snumber(++total_ever)
       {
          strncpy(obname, str, size);
          cout << "\n   1-arg constructor creating "
               << obname << "-" << snumber
               << ". Total=" << ++total_now;
       }

       // copy constructor
       omega(const omega & om) : snumber(++total_ever)
       {
          strncpy(obname, om.obname, size);
          cout << "\n   Copy constructor creating  "
               << obname << "-" << snumber
               << ". Total=" << ++total_now;
       }

       // destructor
      ~omega()
       {
          cout << "\n   Destructor destroying      "
               << obname << "-" << snumber
               << ". Total = " << --total_now;
       }
   };  // end class omega

int omega::total_now = 0;
int omega::total_ever = 0;

omega om0("Adam");            // external object

////////////////////////////////////////////////////////////////

void main()
{
   cout << "\nmain() starting";

   omega func(omega);          // declaration

   omega om1("Jane");         // uses one-arg constructor
   omega om2("Paul");
   omega om3(om2);            // uses copy constructor

   cout << "\nmain() calling func(om1)";
   func(om1);

   cout << "\nmain() calling om3 = func(om1)";
   om3 = func(om1);

   cout << "\nmain() terminating";

}   // main()

omega func(omega og)           // argument passed by value
{
   cout << "\nfunc() starting";

   omega om4("Mike");         // object is local to func()

   cout << "\nfunc() terminating";

   return og;
}

Изходът от програмата е следният:

D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>destru1

   1-arg constructor creating Adam-1. Total=1
main() starting
   1-arg constructor creating Jane-2. Total=2
   1-arg constructor creating Paul-3. Total=3
   Copy constructor creating  Paul-4. Total=4
main() calling func(om1)
   Copy constructor creating  Jane-5. Total=5
func() starting
   1-arg constructor creating Mike-6. Total=6
func() terminating
   Copy constructor creating  Jane-7. Total=7
   Destructor destroying      Mike-6. Total = 6
   Destructor destroying      Jane-5. Total = 5
   Destructor destroying      Jane-7. Total = 4
main() calling om3 = func(om1)
   Copy constructor creating  Jane-8. Total=5
func() starting
   1-arg constructor creating Mike-9. Total=6
func() terminating
   Copy constructor creating  Jane-10. Total=7
   Destructor destroying      Mike-9. Total = 6
   Destructor destroying      Jane-8. Total = 5
   Destructor destroying      Jane-10. Total = 4
main() terminating
   Destructor destroying      Jane-10. Total = 3
   Destructor destroying      Paul-3. Total = 2
   Destructor destroying      Jane-2. Total = 1
   Destructor destroying      Adam-1. Total = 0
D:\PROGRAMMING\C++\Cpp_Lafore\Programs\ch05>

При първото извикване на функцията, във вида func(om1);, промяната в изхода при излизане от функцията са вторият и последният ред в тези пет реда от изхода:

func() terminating
   Copy constructor creating  Jane-7. Total=7
   Destructor destroying      Mike-6. Total = 6
   Destructor destroying      Jane-5. Total = 5
   Destructor destroying      Jane-7. Total = 4

Вторият ред показва, че при излизане от функцията се създава нов обект, който е с името на аргумента Jane (защото просто се връща аргумента в кода на функцията). Чак след това се унищожават локалният обект om4  (Mike-6) и обектът og (Jane-5), който бе копиран от аргумента. Разбира се, накрая се унищожава и върнатия обект Jane-7.

При второто извикване, във вида om3 = func(om1);, промяната в изхода при излизане от функцията са също вторият и последният ред в тези пет реда от изхода:

func() terminating
   Copy constructor creating  Jane-10. Total=7
   Destructor destroying      Mike-9. Total = 6
   Destructor destroying      Jane-8. Total = 5
   Destructor destroying      Jane-10. Total = 4

Логиката е същата като горното обяснение. Само трябва да се каже, че вече върната стойност е копирана в om3, както това се вижда при унищожаване на обекта om3 от следните редове на изхода:

main() terminating
   Destructor destroying      Jane-10. Total = 3

Хубаво е читателят да компилира и стартира (извън интегрираната работна среда) двете разгледани програми и да осмисли изхода от тях.

В следващите лекции започваме темата с т.н. предефиниране на оператори (Operator Overloading).
.

(съдържание)
.
Литература
.
[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.
.
Автор: Проф. Процесор Аритметиков
.
[ това е материал от брой 44 от октомври 2010 г на списание "Коснос" www.kosnos.com ]

Keywords: С++,  OOP programming , C++ , Classes , Inheritance , Reusability , Creating New Data Types , Polymorphism and Overloading
Ключови думи: клас , обект, обектно ориентирано програмиране , полиморфизъм switch if else ?
External Variables
Variables in main()
Passing by Value
Returning by Value