4.2什么是构造函数和析构函数
每个数列都适合设计为class,一个数列的clas object可以表现出该数列在某范围内的元素。默认情形下,起始的位置为1.例如:
Fibonacci fib1(7,3);
便定义了拥有7个元素的Fibonacci object,起始位置为3
pell pel(10);
则定义了具有10个元素的pell object,起始位置默认为1
Fibonacci fib2(fib1);
定义了一个fibonacci object fib2,并以fib1作为fib2的初值。换句话说,fib2是fib1的副本。
每个class都必须记住它自己的长度——数列元素的个数——起始位置。但起始位置不得为0值或者负值。
所以,我以整数存储长度和起始位置。此刻我再定义第三个member
_next,用来记录迭代iterate操作的下一个元素:
class triangular
{
public:
private:
int_length;//元素的个数
int_beg_pos;//元素的位置
int_next;//下一个迭代目标
};
每个triangular class object(Triangular对象例如tri1,tri2........)内都拥有这些data member
当我写下:triangular tri(8,3);
tri对象内含一份_length(初值为8),一份_beg_pos(初值3),一份_next(初值2,因为vector的第三个元素的索引值为2)。
注意:它没有包含实际上用来存储triangular数列元素的vector。
为什么?因为我们不希望在每个class object中都复制一份这个vector;
所有的class object共享一份vector便足够,我们可以再4.5中看到如何实现。
constructor(构造函数的初始化)
这些data member如何被初始化呢?编译器不会自动为我们处理。如果我们提供一个或多个初始化函数,编译器就会在每次class object被定义出来时,调用适当的函数加以处理。这些特别的初始化函数被称为constructor(构造函数)。
constructor的函数名称必须与class名称相同。语法规定,consrtuctor不应该指定返回类型,亦不用任何返回值。它可以被重载(overload)例如,triangular class可能有三个constuctor:
class triangular
{
public:
//一组重载的constructor
triangular();//defalt constructors
triangular(int len);
triangular(int len,int beg_pos);
//....
};
class object定义出来后,编译器便自动根据获得的参数,挑选出应被调用的constructor。例如:triangular t;
会对t应用default constructor(无需任何参数的constructor)
而triangular t2(10,3);会调用带有两个参数的cunstructor.括号内的值会被视为传给constructor的参数。
同样,triangular t3=8;//请注意,这究竟是调用constructor亦或assignment operator?答案是constructor,会带有单一参数的constructor.
出人意料的是,以下代码无法成功定义一个triangular object:
triangular t5();//实际结果出人意料
此行将t5定义为一个函数,其参数列表是空的,返回triangular object。很显然这是一个奇怪的解释。为什么它会这样被解释呢?因为c++必须兼容于c,对c而言,t5之后会带有小括号,会使t5被视为函数,正确的声明方式,应该和先前t一样:triangular t5;
最简单的constructor应该是所谓的default construct。
它不需要任何参数,这意味着两种情况,第一它不接受任何参数:
triangular::triangular()
{
//default constructor
_length=1;
_beg_pos=1;
_next=0;
}
第二,这种情况更常见,它为每个参数提供了默认值:
class triangular
{
public:
//也是default constructor
triangular(int len=1,int bp=1);
//
triangular::triangular(int len,int bp)
{
//_lengtn和_beg_pos都必须>=0
//最好不要相信用户永远是对的这句话
_length=len>0?len:1;
_beg_pos=bp>0?bp:1;
_next=_beg_pos-1;
}
由于我们为两个整数提供了默认值,所以这个default constructor同时支持原本的三个constructor:
triangular tril;//triangular(1,1)
triangular tril2(12);//triangular(12,1)
triangular tril3(8,3);
Member initialization list(成员初始化列表)
constructor定义的第二种初始化语法,是所谓的member initialization list(成员初始化列表):
triangular(const triangular &rhs)
:_length(rhs._length),
_beg_pos(rhs._beg_pos),_next(rhs._beg_pos-1)
{}//是的,空的
member initialization list紧接在参数列表最后的冒号后面,是一个以逗号分隔的列表。其中,欲赋值给member的数值被放在member名称后面的小括号中;这使它们看起来像是调用constructor
就本例来讲,第一种和第二种constructor定义的方式是等价的,并没有是谁干扰谁的问题。
member initialization list主要用于将参数传给member class object的constructor,假如我们重新定义constructor,令它包含一个string member:
class triangular
{
public:
//...
private:
string _name;
int _next,_length,_beg_pos;
};
为了将_name的初值传给string constructor,我们必须以member initialization list完成,比如:
triangular::triangular(int len,int bp)
:_name("triangular")
{
length=len>0?len:1;
_beg_pos=bp>0?bp:1;
_next=_beg_pos-1;
}
和constructor对立的是destructor
所谓denstructor乃是用户自己定义的一个class member,一旦某个class提供destructor,当其中object结束生命的时候,便会自动调用destructor处理善后。destructor主要
用来释放在constructor中或对象生命周期中分配的资源。
destructor的名称有严格规定:class名称再加上'~'前缀。它绝对不会有返回值,也没有任何参数,由于其参数列表是空的,所以也绝不可能被重载overload
考虑以下的matrix class,其中constructor使用new表达式从heap中分配double数组所需要的空间,其destructor则负责释放这些内存:
class matrix
{
public:
matrix(int row,int col)
:_row(row),_col(col)
{
//constructor进行资源分配
//注意:此处未检查成功与否
_pmat=new double[row*col];
}
~matrix()
{
//desstructor进行资源释放
delete[]_pmat;
}
private:
int _row,_col;
double*_pmat;
};
于是我们通过matrix本身的constructor和destructor,完成了heap的内存自动管理,例如下面这个语句块:
{
matri mat(4,4);
//此处应用constructor
//...
//此处应用destructor
}
编译器会在mat被定义出来的下一刻,暗暗应用matrix constructor,于是_pmat被初始化为一个指针,指向程序空闲空间free store中的一块内存,代表一个具有16个double元素的数组。语句块结束之前,编译器又会暗暗应用matrix destructor,于是释放_pmat所指向的那块具有16个double元素的数组。matrix的用户不需要知道内存的管理细节,这种写法有点类似于容器的设计。
destructor并非绝对必要,以我们的triangular为例,三个data member皆以储值方式来存放,这些member在class object被定义以后就已存在,并在class object结束其生命时被释放。因此,triangular destructor没有什么事好做。我们没有义务非得提供destructor,事实上,c++编程最难得部分之一,便是了解何时需要定义destructor而何时不需要。
Memberwise initialization(成员逐一初始化)
默认情形下,当我们以某个class object做为另一个object的初值,例如:
triangular tril(8);
triangular tril2=tril;
class data member会依次复制。本例中的_lengeh,_beg_pos,_next都会依次从tril复制到tril2此即所谓的default memberwise initialization (默认成员逐一初始化操作)
在triangular中,default memberwise initialization会正确复制所有的data member,我们不必特意做其他事,但对先前介绍的matrix class而言,default memberwise initilization并不适合。让我们看看下面代码:
{
matrix mat(4,4);
//此处,constructor发生作用
{
matrix mat2=mat;
//此处进行default memberwise initialization
//...在这里使用mat2
//此处mat2的destructor发生作用
}
}
其中,default memberwise initialization会将mat2的值设为mat的_pmat的值:
mat2._pmat=mat._pmat;
这会使得两个对象的_pmat都指向heap内的同一个数组。当matrix destructor应用于mat2上,该数组空间会被释放。不幸的是,此时mat的_pmat仍指向那个数组,而你知道,对空间已被释放的数组操作,是非常严重的错误行为。
这个问题应该如何修正呢?本例中我们必须改变这种“成员逐一初始化”的行为模式。我们可以通过“为matri提供另一个copy constructor”达到目的。
如果matri设计者提供了一个copy constructor,它就可以改变“成员初始化”的默认行为模式。客户端虽然需要重新编译,但其源代码不必有任何的更改。
这个copy constructor看起来会像什么样子呢?其唯一的参数是一个const reference,指向(代表)一个matri object:
matrix::matrix(const string&rhs)
{
//这里应该写一些什么呢?
}
其内容应该如何实现呢?我们可以产生一个独立的复本,这样便可以使某个对象的析构操作不至于影响到另一个对象:
matrix::matrix(const matrix &rhs)
:_row(rhs._row),col(rhs._col)
{
//对rhs._pmat所指的数组产生一份完全复本
int elem_cnt=_row*_col;
_pmat=new double[elem_cnt];
for(int ix=0;ix<elem_cnt;++ix)
_pmat[ix]=rhs._pmat[ix];
}
当我们设计class时,我们必须问问自己,在此class之上进行“成员逐一初始化”的行为模式是否适当?答案如果是肯定,我们就不需要另外提供copy constructor,那么童颜各有必要为它编写copy assianment operator.