===14.2 类的构造函数===
1.为构造函数指定实参有三种等价形式
// 一般等价的形式
Account acct1( "Anna Press" );
Account acct2 = Account( "Anna Press" );
Account acct3 = "Anna Press";
acct3 的形式只能被用于指定单个实参的情形,对于两个以上的实参只能使用acct1 和acct2 的形式
2.在实践中,往往需要缺省构造函数,
因为容器类(比如vector) 要求它们的类元素或者提供缺省的构造函数或者不提供构造函数,类似地对于类对象的动态数组,在分配内存的时候也要求或者有缺省构造函数或者没有构造函数
Account *pact = new Account[ new_client_cnt ];//需要有缺省构造函数
在实践中,如果定义了其他构造函数则也有必要提供一个缺省构造函数
3.构造函数不能用const 或volatile 关键字来声明
4.一个const 类对象在从其构造函数完成到析构函数开始这段时间内才被认为是const 的
5.explicit 只能被应用在构造函数上,已防止隐式的转换
class Account{
public:
explicit Account( const char*, double=0.0 );
// ...
};
void print( const Account &acct );
print( "oops" );//如果没有explicit,这里将会隐式转换
===14.3 类的析构函数===
C++语言在内部保证,不会用delete 操作符删除不指向任何对象的指针,所以我们无需再编写代码来保证这一点
Account *pact = 0;
pact = new Account( "Stephen Dedalus" );
// if判断 没有必要——由编译器隐式执行
if ( pact != 0 ) delete pact;
===14.4 类对象数组和vector===
1.如果该类没有定义缺省构造函数,则初始化表必须为数组的每个元素提供构造函数实参
Account pooh_pols[] = {
Account( "Woozle", 10.0 ),
Account( "Heffalump", 10.0 ),
Account()
};
按下面的写法获得三个元素的等价数组
Account pooh_pols[3] = { //第三个元素会调用默认构造函数
Account( "Woozle", 10.0 ),
Account( "Heffalump", 10.0 )
};
2.如果希望支持通过new 表达式分配数组,则类必须提供一个缺省构造函数,或不提供构造函数.
声明
Account *pact = new Account[ 10 ];
创建了一个在堆中分配的包含10 个Account 类对象的数组,它们都用Account 类缺省构造函数初始化
// pact 指向一个数组
delete [] pact;
空的方括号表明pact 指向一个数组,编译器获得数组中元素的个数,保证在每个元素上都应用析构函数
===14.4.1 堆数组的初始化===
堆数组的一种初始化方法:利用定位new 操作符
Account* Account::init_heap_array(...) //返回数组第一个元素的指针
{
// 找到一块不用的内存来保存数组
char *p = new char[sizeof(Account)*elems];
// 每个元素的独立初始化
int offset = sizeof( Account );
for ( int ix = 0; ix < elems; ++ix )
{
// 偏移到第 ix 个元素
// 如果提供了一个初始值对
// 把该对传递给构造函数
// 否则, 调用缺省构造函数
if ( ix < vec_size )
new( p+offset*ix ) Account( init_values[ix].first, init_values[ix].second );
else
new( p+offset*ix ) Account;
}
// ok: 元素被分配并初始化
// 返回第一个元素的指针
return (Account*)p;
}
初始化:
定位new 操作符允许我们把一个类构造函数应用到预分配的内存上,在这种情况下,我们用定位操作符new 在每个预分配的数组元素上依次应用Account 类型的构造函数
释放:
delete [] ps;//错误,因为ps(假设它是通过调用init_heap_array()而被初始化的)不是用普通的数组操作符new 分配的,因此与ps 相关联的元素个数是未知的
void Account::dealloc_heap_array( Account *ps, size_t elems )
{
//先析构
for ( int ix = 0; ix < elems; ++ix )
ps[ix].Account::~Account(); //显式调用
//后释放
delete [] reinterpret_cast<char*>(ps); //reinterpret_cast的说明参见《dynamic_cast、const_cast 、static_cast、reinterpret_cast》
}
===14.4.2 类对象的vector===
当我们定义一个含有五个类对象的vector 时,如
vector< Point > vec( 5 );
元素的初始化过程如下
1. 创建一个底层类类型的临时对象在其上应用该类的缺省构造函数
2. 在vector 的每个元素上依次应用拷贝构造函数,用临时类对象的拷贝初始化每一个类对象
3. 删除临时类对象
尽管最终结果等同于定义五个类对象的数组,比如
Point pa[ 5 ];
但是,初始化vector 代价比较大,临时对象的构造和析构,以及拷贝构造函数往往比缺省构造函数计算上更复杂
把一个类对象插入到每一种容器类型中,都是通过拷贝构造函数来实现的
===14.5 成员初始化表===
1、使用初始化表和在构造函数内使用数据成员的赋值之间的区别:
如:
inline Account::
Account( const char *name, double opening_bal )
: _name( name ), _balance( opening_bal )
{
_acct_nmbr = get_unique_acct_nmbr();
}
和
inline Account::
Account( const char *name, double opening_bal )
{
_name = name;
_balance = opening_bal;
_acct_nmbr = get_unique_acct_nmbr();
}
区别是成员初始化表只提供该类数据成员的初始化,在构造函数体内对数据成员设置值是一个赋值操作
如果含有自定义类的成员则会有比较明显的区别,成员初始化表将调用成员的拷贝构造函数,而构造函数体内的赋值将调用operator = 操作符。参见《拷贝构造函数和赋值操作符operator=分别在什么时候被调用?》
2、我们可以认为构造函数的执行过程被分成两个阶段:
a、隐式或显式初始化阶段,成员初始化表,这是初始化.(隐式初始化阶段按照声明的顺序依次调用所有基类的缺省构造函数)
b、一般的计算阶段,计算阶段由构造函数体内的所有语句构成,这是赋值
注意:成员类对象应该总是在成员初始化表中被初始化,而不是在构造函数体内被赋值
3、用成员初始化表和在构造函数体内初始化_balance 是否等价
回答是不,对于非类数据成员的初始化或赋值除了两个例外,两者在结果和性能上都是等价的
两个例外:任何类型的const 和引用数据成员
const 和引用数据成员也必须是在成员初始化表中被初始化,否则就会产生编译时刻错误
4、执行顺序
inline Account::
Account( const char *name, double bal )
: _name( name ), _balance( bal )
{
_acct_nmbr = get_unique_acct_nmbr();
}
初始化的顺序是_balance _name 然后是_acct_nmbr
先初始化表(按声明顺序),后执行体
===14.6 按成员初始化===
用一个类对象初始化另一个类对象比如
Account oldAcct( "Anna Livia Plurabelle" );
Account newAcct( oldAcct );//没有提供自定义的拷贝构造函数
被称为缺省的按成员初始化
除了提供拷贝构造函数,另一种替代的方案是完全不允许按成员初始化这可以通过下列两个步骤实现
1、把拷贝构造函数声明为私有的,这可以防止按成员初始化发生在程序的任何一个地方除了类的成员函数和友元之外
2、通过有意不提供一个拷贝构造函数的定义(但是我们仍然需要第1 步中的声明)可以防止在类的成员函数和友元中出现按成员初始化
这样,任何试图调用拷贝构造函数的动作虽然在编译系统中是合法的,但是会产生链接错误,因为无法为它找到可解析的定义
===14.7 按成员赋值===
Account oldAcct( "Anna Livia Plurabelle" );
Account newAcct;
newAcct = oldAcct;//调用operator =
Account& Account::operator=( const Account &rhs )
{
// 避免向自身赋值
if ( this != &rhs ) //如果没有这步,后面将出错,因为含有删除自身操作
{
delete [] _name;
_name = new char[strlen(rhs._name)+1];
strcpy( _name,rhs._name );
_balance = rhs._balance;
_acct_nmbr = rhs._acct_nmbr;
}
return *this;
}
===14.8 效率问题===
1、通过指针或引用向一个函数传递一个类对象比传值更有效率
2、虽然返回类对象的指针和引用也比按值返回类对象有效,但是正确的编程实现却非常困难,因为返回的是临时对象
C++语言不能有效地返回一个类对象,这被视为C++语言的一个重大缺陷!