Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

667

Однако объекты, определенные локально или распределенные динамически, в начальный

 

//локальные и распределенные из хипа объекты не инициализированы

//до момента явной инициализации или присваивания

Account bar()

{

Account local_acct;

Account *heap_acct = new Account; // ...

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

}

Новички часто полагают, что компилятор автоматически генерирует конструктор, если он не задан, и применяет его для инициализации членов класса. Для Account в том виде, в каком мы его определили, это неверно. Никакой конструктор не генерируется и не вызывается. Для более сложных классов, имеющих члены, которые сами являются классами, или использующих наследование, это отчасти справедливо: конструктор по умолчанию может быть сгенерирован, но и он не присваивает начальных значений членам встроенных или составных типов, таким, как указатели или массивы.

Если мы хотим, чтобы подобные члены инициализировались, то должны сами позаботиться об этом, предоставив один или несколько конструкторов. В противном случае отличить корректное значение члена такого типа от неинициализированного, если объект создан локально или распределен из хипа,3 практически невозможно.

14.2.2. Ограничение прав на создание объекта

Доступность конструктора определяется тем, в какой секции класса он объявлен. Мы можем ограничить или явно запретить некоторые формы создания объектов, если поместим соответствующий конструктор в неоткрытую секцию. В примере ниже конструктор по умолчанию класса Account объявлен закрытым, а с двумя параметрами

class Account {

friend class vector< Account >; public:

explicit Account( const char*, double = 0.0 ); // ...

private:

Account(); // ...

открытым:

};

3 Для тех, кто раньше программировал на C: приведенное выше определение класса Account на C выглядело бы так:

typedef struct {

char *_name; unsigned int _acct_nmbr; double _balance;

} Account;

С++ для начинающих

668

Обычная программа сможет теперь определять объекты класса Account, лишь указав как имя владельца счета, так и начальный баланс. Однако функции-члены Account и дружественный ему класс vector могут создавать объекты, пользуясь любым конструктором.

Конструкторы, не являющиеся открытыми, в реальных программах C++ чаще всего используются для:

предотвращения копирования одного объекта в другой объект того же класса (эта проблема рассматривается в следующем подразделе);

указания на то, что конструктор должен вызываться только в случае, когда данный класс выступает в роли базового в иерархии наследования, а не для создания объектов, которыми программа может манипулировать напрямую (см. обсуждение наследования и объектно-ориентированного программирования в главе 17).

14.2.3. Копирующий конструктор

Инициализация объекта другим объектом того же класса называется почленной инициализацией по умолчанию. Копирование одного объекта в другой выполняется путем последовательного копирования каждого нестатического члена. Проектировщик класса может изменить такое поведение, предоставив специальный копирующий конструктор. Если он определен, то вызывается всякий раз, когда один объект инициализируется другим объектом того же класса.

Часто почленная инициализация не обеспечивает корректного поведения класса. Поэтому мы явно определяем копирующий конструктор. В нашем классе Account это необходимо, иначе два объекта будут иметь одинаковые номера счетов, что запрещено спецификацией класса.

Копирующий конструктор принимает в качестве формального параметра ссылку на объект класса (традиционно объявляемую со спецификатором const). Вот его

inline Account::

Account( const Account &rhs )

: _balance( rhs._balance )

{

_name = new char[ strlen(rhs._name) + 1 ]; strcpy( _name, rhs._name );

// копировать rhs._acct_nmbr нельзя _acct_nmbr = get_unique_acct_nmbr();

реализация:

}

Когда мы пишем:

Account acct2( acct1 );

компилятор определяет, объявлен ли явный копирующий конструктор для класса Account. Если он объявлен и доступен, то он и вызывается; а если недоступен, то

С++ для начинающих

669

определение acct2 считается ошибкой. В случае, когда копирующий конструктор не объявлен, выполняется почленная инициализация по умолчанию. Если впоследствии объявление копирующего конструктора будет добавлено или удалено, никаких изменений в программы пользователей вносить не придется. Однако перекомпилировать их все же необходимо. (Более подробно почленная инициализация рассматривается в разделе 14.6.)

Упражнение 14.1

Какие из следующих утверждений ложны? Почему?

1.У класса должен быть хотя бы один конструктор.

2.Конструктор по умолчанию это конструктор с пустым списком параметров.

3.Если разумных начальных значений у членов класса нет, то не следует предоставлять конструктор по умолчанию.

4.Если в классе нет конструктора по умолчанию, то компилятор генерирует его

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

Упражнение 14.2

Предложите один или несколько конструкторов для данного множества членов.

class NoName { public:

//здесь должны быть конструкторы

//...

protected:

char *pstring; int ival; double dval;

Объясните свой выбор:

};

Упражнение 14.3

Выберите одну из следующих абстракций (или предложите свою собственную). Решите, какие данные (задаваемые пользователем) подходят для представляющего эту абстракцию класса. Напишите соответствующий набор конструкторов. Объясните свое решение.

Книга

Дата

Служащий

Транспортное средство

Объект

Дерево

Упражнение 14.4

Пользуясь приведенным определением класса: