►Конструирование членов класса...204
В предыдущих примерах использовались данные-члены простых типов, такие как float или int. Переменные таких простых типов легко инициализировать, передав необходимое значение конструктору. Но что, если класс содержит данные-члены, которые являются объектами других классов? Рассмотрим приведённый ниже пример.
/* ConstructingMembers — передача параметров */
/* конструктору члена */
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <strings.h>
using namespace std ;
const int MAXNAMESIZE = 40 ;
int nextStudentId = 0 ;
class StudentId
{
public :
StudentId( )
{
value = ++nextStudentId ;
cout << "Присвоение id " << value << endl ;
}
protected :
_________________
204 стр. Часть 3. Введение в классы
int value ;
} ;
class Student
{
public :
Student( char* pName )
{
cout << "Конструктор Student( " << pName
<< " )" << endl ;
strncpy( name , pName , MAXNAMESIZE ) ;
name[ MAXNAMESIZE - 1 ] = '' ;
semesterHours = 0 ;
gpa = 0.0 ;
}
/* ...прочие открытые члены... */
protected :
char name[ MAXNAMESIZE ] ;
int semesterHours ;
float gpa ;
StudentId id ;
} ;
int main( int argcs , char* pArgs[ ] )
{
setlocale ( LC_ALL , ".1251" ) ; /* печать кириллицы */
Student s( "Chester" ) ;
/* Пауза для того, чтобы посмотреть на результат работы программы */
system( "PAUSE" ) ; return 0 ;
}
В момент создания объекту типа Student присваивается собственный идентификатор. В данном примере идентификаторы "раздаются" последовательно, с помощью глобальной переменной nextStudentId.
Наш класс Student содержит член id, который является экземпляром класса StudentId. Конструктор класса Student не может присвоить значение члену id, поскольку Student не имеет доступа к защищённым членам класса StudentId. Можно было бы сделать Student другом класса StudentId, но такой подход нарушил бы положение объектно-ориентированного программирования, утверждающее, что каждый класс должен заниматься своим делом. Нам нужна возможность вызывать конструктор класса StudentId в процессе создания класса Student.
С++ делает это автоматически, инициализируя член id с помощью конструктора по умолчанию StudentId::StudentId( ). Это происходит после вызова конструктора класса Student, но до того, как управление передаётся первой строке этого конструктора. ( Выполните в пошаговом режиме приведённую выше программу, и вы поймёте, о чём я говорю. ) Выполнение приведённой выше программы выведет на экран следующие строки:
Присвоение id 1
Конструктор Student( Chester )
Press any key to continue...
Обратите внимание: сообщение от конструктора StudentId появилось раньше, чем сообщение от конструктора Student. ( Поскольку у нас все конструкторы выводят информацию на экран, вы можете решить, что они всегда должны поступать подобным образом. На самом деле подавляющее большинство конструкторов работают "молча". )
_________________
205 стр. Глава 17. Аргументация конструирования
Если программист не обеспечит свой класс конструктором, то конструктор по умолчанию, созданный С++, вызовет конструкторы всех данных-членов для их инициализации. То же касается и уничтожения объекта. Деструктор класса автоматически вызывает деструкторы всех данных-членов ( у которых они определены ).
Теперь мы знаем, что будет с конструктором по умолчанию. Но что, если мы захотим вызвать другой конструктор? Куда в этом случае нужно поместить объект? Я имею в виду следующее: представьте себе, что вместо автоматической генерации идентификатора студента, необходимо передать его конструктору Student, который в свою очередь, должен передать его конструктору StudentId.
«Для начала я покажу вам способ, который работать не будет. ( Здесь приведена только существенная для понимания часть кода — полностью программа ConstructSeparateID.cpp находится на прилагаемом компакт-диске . )»
[Диск]
class Student
{
public :
Student( char *pName = "no name" , int ssId = 0 )
{
cout << "Конструктор Student( " << pName
<< " )" << endl ;
strncpy( name , pName , MAXNAMESIZE ) ;
name[ MAXNAMESIZE - 1 ] = '' ;
semesterHours = 0 ;
gpa = 0.0 ;
/* Вот это можно и не пытаться делать - толку не будет */
StudentId id( ssId ) ;
}
protected :
char name[ MAXNAMESIZE ] ;
StudentId id ;
} ;
Конструктор класса StudentId был переписан так, чтобы он мог принимать внешнее значение ( значение по умолчанию необходимо для того, чтобы приведённый фрагмент откомпилировался без ошибок, которые появятся в противном случае; почему — станет понятно чуть позже ). Внутри конструктора Student программист ( т.е. я ) попытался невиданным доселе способом сконструировать объект id класса StudentId.
Если вы внимательно посмотрите на сообщения, которые выдаются в результате работы этой программы, то поймёте, в чём проблема.
Присвоение id 0
Конструктор Student( Chester )
Присвоение id 1234
Деструктор id 1234
Сообщение из функции main( )
Press any key to continue...
Деструктор id 0
Первая проблема заключается в том, что конструктор класса StudentId вызывается дважды: сначала с нулём и только затем с ожидаемым числом 1234. Кроме того, объект с идентификатором 1234 ликвидируется перед выводом сообщения от main( ). Очевидно, объект класса StudentId ликвидируется внутри конструктора класса Student.
_________________
206 стр. Часть 3. Введение в классы
Объяснить такое странное поведение программы довольно просто. Член id уже существует к моменту перехода управления к телу конструктора Student. Поэтому вместо инициализации уже существующего члена id объявление в последней строке конструктора Student вызывает создание локального объекта с таким же именем. Этот локальный объект и уничтожается при выходе из конструктора.
«Очевидно, нужен некий механизм конструирования не нового объекта, а уже существующего. Этот механизм должен работать перед открытием фигурной скобки конструктора. Для этого в С++ определена конструкция, использованная в программе ConstructDataMember.»
[Диск]
//
/* ConstructDataMember — передача параметра */
/* конструктору члена */
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <strings.h>
using namespace std ;
const int MAXNAMESIZE = 40 ;
class StudentId
{
public :
StudentId( int id = 0 )
{
value = id ;
cout << "Присвоение id " << value << endl ;
}
~StudentId( )
{
cout << "Деструктор id " << value << endl ;
}
protected :
int value ;
} ;
class Student
{
public :
Student( char *pName = "no name" , int ssId = 0 )
: id( ssId )
{
cout << "Конструктор Student( " << pName
<< " )" << endl ;
strncpy( name , pName , MAXNAMESIZE ) ;
name[ MAXNAMESIZE - 1 ] - '' ;
}
protected :
char name[ 40 ] ;
StudentId id ;
} ;
int main( int argcs , char* pArgs[ ] )
{
setlocale ( LC_ALL , ".1251" ) ; /* печать кириллицы */
Student s( "Chester" , 1234 ) ;
cout << "Сообщение из функции main" << endl ;
/* Пауза для того, чтобы посмотреть на результат работы программы */
system( "PAUSE" ) ; return 0 ;
}
_________________
207 стр. Глава 17. Аргументация конструирования
Обратите особое внимание на первую строку конструктора. В этой строке есть кое-что, с чем вы до этого не встречались. Следующая за двоеточием команда вызывает конструкторы членов данного класса. Компилятор С++ прочтёт эту строку так: "Вызвать конструктор для члена id с аргументом ssId. Все остальные данные-члены, не вызванные явно, строить с использованием конструктора по умолчанию".
Результат работы этой программы свидетельствует, что всё получилось так, как мы и хотели.
Присвоение id 1234
Конструктор Student( Chester )
Сообщение из функции main
Press any key to continue...
Деструктор id 1234
Больше книг — больше знаний!
Заберите 20% скидку на все книги Литрес с нашим промокодом
ПОЛУЧИТЬ СКИДКУ