Единственное синтаксическое ограничение, налагаемое на конструктор, состоит в том, что он не должен иметь тип возвращаемого значения, даже void. Поэтому следующие объявления ошибочны:
// ошибки: у конструктора не может быть типа возвращаемого значения
void Account::Account() { ... }
Account* Account::Account( const char *pc ) { ... }
Количество конструкторов у одного класса может быть любым, лишь бы все они имели разные списки формальных параметров.
Откуда мы знаем, сколько и каких конструкторов определить? Как минимум, необходимо присвоить начальное значение каждому члену, который в этом нуждается. Например, номер счета либо задается явно, либо генерируется автоматически таким образом, чтобы гарантировать его уникальность. Предположим, что он будет создаваться автоматически. Тогда мы должны разрешить инициализировать оставшиеся два члена _name и _balance:
Account( const char *name, double open_balance );
Объект класса Account, инициализируемый конструктором, можно объявить следующим образом:
Account newAcct( "Mikey Matz", 0 );
Если же есть много счетов, для которых начальный баланс равен 0, то полезно иметь конструктор, задающий только имя владельца и автоматически инициализирующий _balance нулем. Один из способов сделать это - предоставить конструктор вида:
Account( const char *name );
Другой способ - включить в конструктор с двумя параметрами значение по умолчанию, равное нулю:
Account( const char *name, double open_balance = 0.0 );
Оба конструктора обладают необходимой пользователю функциональностью, поэтому оба решения приемлемы. Мы предпочитаем использовать аргумент по умолчанию, поскольку в такой ситуации общее число конструкторов класса сокращается.
Нужно ли поддерживать также задание одного лишь начального баланса без указания имени клиента? В данном случае спецификация класса явно запрещает это. Наш конструктор с двумя параметрами, из которых второй имеет значение по умолчанию, предоставляет полный интерфейс для указания начальных значений тех членов класса Account, которые могут быть инициализированы пользователем:
class Account {
public:
// конструктор по умолчанию ...
Account();
// имена параметров в объявлении указывать необязательно
Account( const char*, double=0.0 );
const char* name() { return name; }
// ...
private:
// ...
};
Ниже приведены два примера правильного определения объекта класса Account, где конструктору передается один или два аргумента:
int main()
{
// правильно: в обоих случаях вызывается конструктор
// с двумя параметрами
Account acct( "Ethan Stern" );
Account *pact = new Account( "Michael Lieberman", 5000 );
if ( strcmp( acct.name(), pact-name() ))
// ...
}
C++ требует, чтобы конструктор применялся к определенному объекту до его первого использования. Это означает, что как для acct, так и для объекта, на который указывает pact, конструктор будет вызван перед проверкой в инструкции if.
Компилятор перестраивает нашу программу, вставляя вызовы конструкторов. Вот как, по всей вероятности, будет модифицировано определение acct внутри main():
// псевдокод на C++,
// иллюстрирующий внутреннюю вставку конструктора
int main()
{
Account acct;
acct.Account::Account("Ethan Stern", 0.0);
// ...
}
Конечно, если конструктор определен как встроенный, то он подставляется в точке вызова.
Обработка оператора new несколько сложнее. Конструктор вызывается только тогда, когда он успешно выделил память. Модификация определения pact в несколько упрощенном виде выглядит так:
// псевдокод на C++,
// иллюстрирующий внутреннюю вставку конструктора при обработке new
int main()
{
// ...
Account *pact;
try {
pact = _new( sizeof( Account ));
pact-Acct.Account::Account(
"Michael Liebarman", 5000.0);
}
catch( std::bad_alloc ) {
// оператор new закончился неудачей:
// конструктор не вызывается
}
// ...
}
Существует три в общем случае эквивалентных формы задания аргументов конструктора: