Шаг 6.
Конструкторы и деструкторы

    На этом шаге мы закончим разговор о конструкторах и введем понятие деструктора.

    Конструктор существует для любого класса, причем он может быть создан без явных указаний программиста. Таким образом, для классов complex1 и goods существуют автоматически созданные конструкторы.

    По умолчанию формируются конструктор без параметров и конструктор копирования вида

     Т::Т(const T&) 
 где T - имя класса. Например,
     class F {
        ...
         public: F(const F&);
        ...
     }

    Такой конструктор существует всегда. По умолчанию конструктор копирования создается общедоступным.

    Сформулируем несколько правил использования конструкторов.

  1. В классе может быть несколько конструкторов (перегрузка), но только один с умалчиваемыми значениями параметров.
  2. Нельзя получить адрес конструктора.
  3. Параметром конструктора не может быть его собственный класс, но может быть ссылка на него, как у конструктора копирования.
  4. Конструктор нельзя вызывать как обычную компонентную функцию. Для явного вызова конструктора можно использовать две разные синтаксические формы:
     имя_класса имя_объекта(фактические_параметры_конструктора);
     имя_класса(фактические_параматры_конструктора);

    Первая форма допускается только при непустом списке фактических параметров. Она предусматривает вызов конструктора при определении нового объекта данного класса:

        complex SS(10.3,0.22); // SS.real == 10.3;
                               // SS.imag == 0.22;
        complex EE(2.345);     // ЕЕ.real == 2.345;
                               //  По умолчанию ЕЕ.imag = 0.0.
        complex DD() ; // Ошибка! Компилятор решит, что это 
                       // прототип функции без параметров, 
                       //  возвращающей значение типа complex.

    Вторая форма явного вызова конструктора приводит к созданию объекта, не имеющего имени. Созданный таким вызовом безымянный объект может использоваться в тех выражениях, где допустимо использование объекта данного класса. Например:

        complex ZZ = complex(4.0,5.0);

    Этим определением создается объект zz, которому присваивается значение безымянного объекта (с элементами real == 4.0, imag == 5.0), созданного за счет явного вызова конструктора.

    Существуют два способа инициализации данных объекта с помощью конструкторов. Первый способ, а именно передача значений параметров в тело конструктора, уже продемонстрирован на примерах. Второй способ предусматривает применение списка инициализаторов данных объекта. Этот список помещается между списком параметров и телом конструктора:

   имя-класса(список_параметров):
      список_инициализаторов_компонентных_данных 
                {   тело_конструктора  }

    Каждый инициализатор списка относится к конкретному компоненту и имеет вид:

   имя_компонента_данных (выражение)
Например:
   class AZ
     { int ii; float ee; char cc; 
        public:
          AZ(int in, float en, char сn) : ii(5),
                                          ee(ii * en + in), cc(cn) { }
     };
  AZ A(2,3.0 , 'd');      // Создается именованный объект A
                          //  с компонентами A.ii == 5, 
                          //  A.ee == 17, A.cc == 'd'.
  AZ X = AZ(0,2.0,'z');   // Создается безымянный объект, в
                          //  котором ii == 5, ee == 10, 
                          //  cc == 'z', и копируется в объект X.

    Перечисленные особенности конструкторов, соглашения о статусах доступа компонентов и новое понятие "деструктор" иллюстрирует следующее определение класса "символьная строка":

//STROKA.CPP - файл с определением класса   "символьная
//	строка"
#include <string.h>  // Для библиотечных строковых функций.
#include <iostream.h>
class stroka
{ // Скрытые от внешнего доступа данные:
     char *ch; // Указатель на текстовую строку.
     int len;  // Длина текстовой строки.
     public: // Общедоступные функции:
         // Конструкторы объектов класса:
         // Создает объект как новую пустую строку:
         stroka(int N = 80):
           // Строка не содержит информации:
              len(0)
             { ch = new char[N + 1]; // Память выделена для массива.
               ch[0] = '\0';
             }
         // Создает объект по заданной строке: 
         stroka (const char *arch)
         { len = strlen(arch); 
           ch = new char[len+1]; 
           strcpy(ch,arch);
         }
       int& len_str(void) // Возвращает ссылку на длину строки.
       { return len; }
       char *string(void) // Возвращает указатель на строку.
       { return ch; }
       void display(void) // Печатает информацию о строке.
       { cout << "\nДлина строки: " << len; 
         cout << "\nСодержимое строки: " << ch;
       }
      // Деструктор - освобождает память объекта: 
      ~stroka() { delete [] ch; }
};
Текст этого класса можно взять здесь.

    В следующей программе создаются объекты класса stroka и выводится информация на дисплей об их компонентах:

//РR6_2.СРР - программа с классом "символьные строки".
#include "stroka.cpp" // Текст определения класса.
void main() 
{ 
   stroka LAT ("Non Multa, Sed Multum!");
   stroka RUS ("Hе много, но многое!");
   stroka CTP(20);
   LAT.display();
   cout << "\nB объекте RUS: " << RUS.string();
   CTP.display(); 
}
Текст этой программы можно взять здесь.

    Результат выполнения программы:

   Длина строки: 22
   Содержимое строки: Non Multa, Sed Multum!
   В объекте RUS: Не много, но многое!
   Длина строки: 0
   Содержимое строки:

    Так как класс stroka введен с помощью служебного слова class, то элементы char *ch и int len недоступны для непосредственного обращения. Чтобы получить значение длины строки из конкретного объекта, нужно использовать общедоступную компонентную функцию len_str(). Указатель на строку, принадлежащую конкретному объекту класса stroka, возвращает функция string(). У класса stroka два конструктора - перегруженные функции, при выполнении каждой из которых динамически выделяется память для символьного массива. При вызове конструктора с параметром int N массив из N+1 элементов остается пустым, а длина строки устанавливается равной 0. При вызове с параметром char *arch длина массива и его содержание определяются уже существующей строкой, которую адресует фактический параметр, соответствующий указателю-параметру arch.

    Заслуживает внимания компонентная функция ~stroka(). Это деструктор. Объясним его назначение и рассмотрим его свойства.

    Динамическое выделение памяти для объектов какого-либо класса создает необходимость в освобождении этой памяти при уничтожении объекта. Например, если объект некоторого класса формируется как локальный внутри блока, то целесообразно, чтобы при выходе из блока, когда уже объект перестает существовать, выделенная для него память была возвращена системе. Желательно, чтобы освобождение памяти происходило автоматически и не требовало вмешательства программиста. Такую возможность обеспечивает специальный компонент класса - деструктор (разрушитель объектов) класса. Для него предусматривается стандартный формат:

         ~имя_класса() { операторы_тела_деструктора };

    Название деструктора в C++ всегда начинается с символа тильда '~', за которым без пробелов или других разделительных знаков помещается имя класса. Основные правила использования деструкторов следующие.

  1. У деструктора не может быть параметров (даже типа void).
  2. Деструктор не имеет возвращаемого значения (даже типа void).
  3. Вызов деструктора выполняется неявно, автоматически, как только объект класса уничтожается.

    В нашем примере в теле деструктора только один оператор, освобождающий память, выделенную для символьного массива при создании объекта класса stroka.

    На следующем шаге мы более подробно остановимся на компонентных данных и функциях (на данных и методах класса).




Предыдущий шаг Содержание Следующий шаг