1. 我们可以给构造函数的参数提供一个缺省的实参。
class Account {
public:
// 缺省构造函数
Account();
// 声明中的参数名不是必需的
Account( const char*, double=0.0 );
const char* name() { return _name; }
// ...
private:
// ...
};
下面是两个合法的 Account 类对象定义,它们向构造函数传递了一个或两个实参:
int main()
{
// ok: 都调用双参数构造函数
Account acct( "Ethan Stern" );
Account *pact = new Account( "Michael Lieberman", 5000 );
if ( strcmp( acct.name(), pact->name() ))
// ...
}
2. 构造函数形式
// 推荐的构造函数形式
Account acct1( "Anna Press" );
C++语言新手常犯的错误是:按如下方式声明一个用缺省构造函数初始化的对象
// 喔! 并没有像期望的那样工作
Account newAccount();
它能通过编译 但是 当我们试图使用它时
// 编译错误
if ( !newAccount.name() ) ...
编译器会抱怨我们不能把成员访问符应用到函数上,定义
// 定义了一个函数 newAccount,
// 不是一个 Account 类对象
Account newAccount();
被编译器解释为定义了一个没有参数,返回一个 Account 类型对象的函数——完全不是我们的意图,用缺省构造函数初始化类对象的正确声明是去掉尾部的小括号。
// ok: 定义了一个类对象
Account newAccount;
只有当没有构造函数或声明了缺省构造函数时,我们才能不指定实参集来定义类对象。 如果定义了带参数的构造函数,最好也定义一个默认的构造函数:
// 缺省 Account 构造函数
inline Account::
Account() {
_name = 0;
_balance = 0.0;
_acct_nmbr = 0;
}
3. 成员初始化表
类的初始化有一种可替换的语法,成员初始化表(member initialization list ),是由逗号分开的成员名及其初值的列表。例如。缺省的Account 构造函数可以这样写:
// 使用成员初始化表的缺省 Account 构造函数
inline Account::Account()
: _name( 0 ),
_balance( 0.0 ), _acct_nmbr( 0 )
{}
成员初始化表只能在构造函数定义中被指定,而不是在其声明中 。该初始化表被放在参数表和构造函数体之间,由冒号开始,下面是双参数的构造函数,部分利用了成员初始化表:
inline Account::
Account( const char* name, double opening_bal )
: _balance( opening_bal )
{
_name = new char[ strlen(name)+1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
4. 构造函数用作转换操作符
考虑如下程序段 :
// 在某个头文件中
extern void print( const Account &acct );
// ...
int main()
{
// 把 "oops" 转换成一个 Account 对象
// 用 Account::Account( "oops", 0.0 )
print( "oops" );
// ...
}
缺省情况下,单参数构造函数或者构造函数有多个参数(除了第一个参数外, 其他都有缺省实参 )被用作转换操作符,在上面的程序段中的print()的调用里 Account 构造函数被编译器隐式地应用,以便把一个文字字符串转换成一个 Account 对象,尽管这种转换在这种情况下并不合适。
无意的隐式类转换,如把 oops 转换成一个 Account 对象,已经被证明是很难跟踪的错误源,关键字 explicit 被引入到标准 C++中 以帮助我们抑制这种不受欢迎的编译器辅助行为 ,explicit 修饰符通知编译器不要提供隐式转换:
class Account{
public:
explicit Account( const char*, double=0.0 );
// ...
};
explicit 只能被应用在构造函数上。
5. 拷贝构造函数
用一个类对象初始化该类的另一个对象被称为缺省按成员初始化(default memberwise initialization ),在概念上 ,一个类对象向该类的另一个对象作拷贝是通过依次拷贝每个非静态数据成员来实现的。
缺省按成员的初始化对于类的正确行为常常是不合适的, 通过定义拷贝构造函数的显式实例,我们可以改变缺省的行为,我们的Account 类要求我们这样做, 否则两个Account 对象会有相同的帐号,这在该类的规范中显然是不允许的。
拷贝构造函数有一个指向类对象的引用作为形式参数,传统上被声明为 const ,下面是它的实现:
inline Account::
Account( const Accout &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的定义就是一个编译时刻错误; 如果没有声明拷贝构造函数的实例,则执行缺省的按成员初始化。如果我们后来引入或去掉了一个拷贝构造函数的声明,则用户程序无需改变,但是,需要重新编译它们 。