【C++】初始化与清除(构造函数与析构函数)

时间:2022-09-09 16:53:42

一、构造函数

    C++提供了构造函数(constructor)来处理对象的初始化。

    在建立对象时自动执行。

    构造函数的名字必须与类名同名,它不具有任何类型,不返回任何值。

    构造函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数。在类对象进入其作用域时调用构造函数。构造函数没有返回值,因此也不需要在定义构造函数时声明类型,这是它和一般函数的一个重要的不同之点。

    如果构造函数不带参数,那么在函数体中对数据成员赋初值,使该类的每一个对象都得到同一组初值。

    采用带参数的构造函数,在调用不同对象的构造函数时,从外面将不同的数据传递给构造函数,以实现不同的初始化。构造函数首部的一般格式为 

 构造函数名(类型1  形参1,类型2  形参2,…)


实参是在定义对象时给出的。定义对象的一般格式为   

类名 对象名(实参1,实参2,…);


#include <iostream>
using namespace std;
class Box
{
public:
Box(int,int,int);
int volume();
private:
int height;
int width;
int length;
};
Box::Box(int h,int w,int len)
{
height=h;
width=w;
length=len;
}
int Box::volume()
{
return(height*width*length);
}
int main( )
{
Box box1(12,25,30);
cout<<"the volume of box1 is"<<box1.volume()<<endl;
Box box2(15,30,21);
cout<<"the volume of box2 is"<<box2.volume()<<endl;
return 0;
}

    C++还提供另一种初始化数据成员的方法—— 参数初始化表 来实现对数据成员的初始化。这种方法不在函数体内对数据成员初始化,而是在函数首部实现。形参的具体数值由创建对象时实参来替换。

Box∷Box(int h,int w,int len):height(h),width(w),length(len){ }

    另一种,在声明构造函数时,直接指定默认参数。

Box::Box(int h = 10, int w = 10, int len = 10):height(h), width(w), length(len){}

   还可以这样书写:

Box::Box(int h):height(h), width(10), length(20){}


    构造函数的重载构造函数具有相同的名字,而参数的个数或参数的类型不相同。

    默认构造函数调用构造函数时不必给出实参的构造函数,即没有形参。

    一个类只能有一个默认构造函数,尽管在一个类中可以包含多个构造函数,但是 对于每一个对象来说,建立对象时只执行其中一个构造函数。

Box( );                   //声明一个无参的构造函数
Box(int h,int w,int len):height(h),width(w),length(len){ } //声明一个有参的构造函数,用参数的初始化表对数据成员初始化
Box(int h=10,int w=10,int len=10); //在声明构造函数时指定默认参数
Box::Box(int h,int w,int len)
{
height=h;
width=w;
length=len;
}

   应该在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。

#include <iostream>
using namespace std;
class Box {
public:
Box(int h=10,int w=10,int len=10); //在声明构造函数时指定默认参数
int volume( );
private:
int height;
int width;
int length;
};
Box::Box(int h,int w,int len) //在定义函数时可以不指定默认参数
{
height=h;
width=w;
length=len;
}
int Box::volume( )
{
return(height*width*length);
}
int main( )
{
Box box1; //没有给实参
cout<<"The volume of box1 is"<<box1.volume( )<<endl;
Box box2(15); //只给定一个实参
cout<<"The volume of box2 is"<<box2.volume( )<<endl;
Box box3(15,30); //只给定2个实参
cout<<"The volume of box3 is"<<box3.volume( )<<endl;
Box box4(15,30,20); //给定3个实参
cout<<"The volume of box4 is"<<box4.volume( )<<endl;
return 0;
}

    如果定义了一个带参构造函数,还想使用(默认的)无参构造函数的话,就需要自己定义。


    注意:没有默认构造函数的类类型的成员,以及 const 或者引用类型的成员,不管是哪种成员,都必须在构造函数初始化列表中进行初始化。

    因为,const 对象或者引用类型的对象,可以被初始化,但不能被赋值。所以唯一的机会就是使用初始化列表。

例如:

class ConstRef {
public:
ConstRef(int a);
private:
int i;
const int ci;
int &ri;
}
ConstRef::ConstRef(int a) {
i = a; //OK
ci = a; //error
ri = a; //error
}

正确的做法是:

ConstRef::ConstRef(int a):i(a), ci(a), ri(a) {}   //使用初始化列表


二、析构函数

       析构函数(destructor)也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“~”符号,析构函数是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数

    ①如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前 自动执行析构函数。

    ②static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。

    ③如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。

    ④如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。


    析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。

    析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。

    一个类可以有多个构造函数,但只能有一个析构函数当然,析构函数也可被用来执行“用户希望在最后一次使用对象之后所执行的任何操作”,例如输出有关的信息。如果用户没有定义析构函数,C++编译系统会自动生成一个析构函数,实际上什么操作都不进行。

    在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。

【C++】初始化与清除(构造函数与析构函数)

    (1)在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件, 而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。

    (2)如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。

    (3)如果在函数中定义静态(static)局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。
    基类析构函数通常是虚函数。

     一个类只有一个析构函数。

三、对象的赋值和复制

1)如果对一个类定义了两个或多个对象,则这些同类的对象之间可以互相赋值对象之间的赋值也是通过赋值运算符“=”进行的。这是通过对赋值运算符的重载实现的。

对象名1 = 对象名2;

对象的赋值只对其中的数据成员赋值,而不对成员函数赋值。类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果。

2)用一个已有的对象快速地复制出多个完全相同的对象。如

Box box2(box1);

其作用是用已有的对象box1去克隆出一个新对象box2。

其一般形式为

类名 对象2(对象1);

用对象1复制出对象2。
    通过复制建立对象时调用一个特殊的构造函数—复制构造函数(copy constructor)。形式如下:

//The copy constructor definition.
Box∷Box(const Box &b) {
height=b.height;
width=b.width;
length=b.length;
}

另一种方便用户的复制形式,用赋值号代替括号,如

Box box2=box1;          //用box1初始化box2

其一般形式为  类名 对象名1 = 对象名2;

可以在一个语句中进行多个对象的复制。如

Box box2=box1,box3=box2;

请注意普通构造函数和复制构造函数的区别。

(1) 在形式上

类名(形参表列);      //普通构造函数的声明,如Box(int h,int w,int len);

类名(类名& 对象名);     //复制构造函数的声明,如Box(Box &b);

(2) 在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或复制构造函数。如 

Box box1(12,15,16);           //实参为整数,调用普通构造函数

Box box2(box1);               //实参是对象名,调用复制构造函数

(3) 在什么情况下被调用

普通构造函数在程序中建立对象时被调用。

复制构造函数在用已有对象复制一个新对象时被调用在以下3种情况下需要克隆对象: 

① 程序中需要新建立一个对象,并用另一个同类的对象对它初始化。

② 当函数的参数为类的对象时。在调用函数时需要将实参对象完整地传递给形参,系统是通过调用 复制构造函数来实现的。

③ 函数的返回值是类的对象。此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。