Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

// в каком-то заголовочном файле

extern void print( const Account &acct );

// ...

int main()

{

//преобразует строку "oops" в объект класса Account

//с помощью конструктора Account::Account( "oops",

0.0)

print( "oops" );

// ...

}

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

Непреднамеренные неявные преобразования классов, например трансформация "oops" в объект класса Account, оказались источником трудно обнаруживаемых ошибок. Поэтому в стандарт C++ было добавлено ключевое слово explicit, говорящее

class Account { public:

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

компилятору, что такие преобразования не нужны:

};

Данный модификатор применим только к конструктору. (Операторы преобразования и слово explicit обсуждаются в разделе 15.9.2.)

14.2.1. Конструктор по умолчанию

Конструктором по умолчанию называется конструктор, который можно вызывать, не задавая аргументов. Это не значит, что такой конструктор не может принимать аргументов; просто с каждым его формальным параметром ассоциировано значение по

// все это конструкторы по умолчанию Account::Account() { ... } iStack::iStack( int size = 0 )

{ ... }

умолчанию:

Complex::Complex(double re=0.0, double im=0.0) { ... }

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

int main()

{

Account

acct

;

// ...

}

то компилятор сначала проверяет, определен ли для класса Account конструктор по умолчанию. Возникает одна из следующих ситуаций:

1.Такой конструктор определен. Тогда он применяется к acct.

2.Конструктор определен, но не является открытым. В данном случае определение acct помечается компилятором как ошибка: у функции main() нет прав доступа.

3.Конструктор по умолчанию не определен, но есть один или несколько конструкторов, требующих задания аргументов. Определение acct помечается

как ошибка: слишком мало аргументов у конструктора.

4.Нет ни конструктора по умолчанию, ни какого-либо другого. Определение считается корректным, acct не инициализируется, конструктор не вызывается.

Пункты 1 и 3 должны быть уже достаточно понятны (если это не так, перечитайте данную главу) Посмотрим более внимательно на пункты 2 и 4.

Допустим, что все члены класса Account объявлены открытыми и не объявлено никакого

class Account { public:

char *_name; unsigned int

_acct_nmbr; double _balance;

конструктора:

};

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

//статический класс хранения

//вся ассоциированная с объектом память обнуляется

Account global_scope_acct; static Account file_scope_acct;

Account foo()

{

static Account local_static_acct; // ...

классов):

}

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

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

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

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();

//...

открытым:

};

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

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

typedef struct {

char *_name; unsigned int

_acct_nmbr; double _balance;

} 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. Если он объявлен и доступен, то он и вызывается; а если недоступен, то определение 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 Пользуясь приведенным определением класса: