C++中的构造函数、虚函数、析构函数

来源:互联网 发布:史密斯 24升 jsq48 js 编辑:程序博客网 时间:2024/06/07 00:42

一、构造函数

初始化函数应该是一种特殊的成员函数,能够在创建对象时被自动调用。这就是构造函数。

构造函数的名字和类名字相同,它没有返回值类型(注意:不是void类型)。构造函数的参数通常为数据成员提供初始值。构造函数可以重载,在创建对象时,编译器会根据初始值的类型和个数来调用相应的构造函数,因而构造函数的形式决定了初始化对象的方式。

1.默认构造函数

可以不提供实参就能调用的构造函数称为默认构造函数。在很多情况下可能不便为对象提供初始值。例如:

X ax[10]; 这里需要调用X()来初始化数组的每个元素,如果X类没有默认构造函数,就会产生编译错误。通常需要为类定义一个默认构造函数,在定义对象时如果没有提供初始值会调用默认构造函数进行初始化。默认构造函数可以是没有形参的构造函数,也可以有形参但每个参数都有默认值。

如果一个类没有定义任何构造函数,编译器会在需要时生成一个默认构造函数。类中一旦定义了构造函数,即使不是默认构造函数,编译器也不再自动生成。

2.成员化列表

成员r是引用类型,不能用赋值的形式初始化。对于const数据成员和类类型的数据成员存在类似问题。

初始化由构造函数完成,引用成员的初始化也应该在构造函数中,但是又不能在函数中使用赋值显示初始化。针对这种特殊的情况有一种特殊的语法,称为构造函数初始化列表。初始化列表的形式如下:

成员1(初始值1)[,成员2(初始值2),·······]

初始化列表位于构造函数的参数表之后,函数体之前:

构造函数(参数表):初始化列表{函数体}

例如:

Class X{

int m;

int &r;

public:

X(int v=0):r(m){//初始化引用成员

m=v;

}

}

普通的成员函数也可以用这种格式进行初始化,例如,上面的构造函数定义也可以写成:

X(int v=0):m(v),r(m){}

3.类型转换构造函数

带一个参数的构造函数也被看做是进行类型转换的函数,它可以将参数类型的数据转换为类类型。在需要隐式类型转换时编译器会自动调用转换构造函数。例如:

Class X{

int m;

public:

explicit X(int v):m(v){}//转换构造函数,可以将int转换为X类型

};

void f(X obj){}

int main()

{

int iv=10;

f(iv);//ok,调用X(int)进行隐含的参数类型转换

}

如果不希望编译器自动进行这种类型转换,可以在构造函数的声明前面加上explicit关键字,禁止隐式转换。

4.拷贝构造函数

a初始化b时需要一个构造函数XX&),称为拷贝构造函数。如果类中没有定义这样的构造函数,编译器会自动生成一个,默认的行为是按成员拷贝。如果X是上面定义的只包含简单数据成员类,这种按成员拷贝的行为可以适用。X b(a)就是用a中的每个成员分别去初始化b的每个对应的成员,这种行为成为浅拷贝。(普通的数值赋值-浅拷贝

深拷贝:

X(const X& a):m(a.m),r(m),p(&m){}//拷贝构造函数

拷贝构造函数的一般形式是XX&)或者X(const X&),这两种形式是有差别的。如果拷贝构造函数是XX&),那么就不能用const对象来初始化另一个X类型的对象,因为参数类型不匹配。如果是X(const X&),那么既可以用const对象也可以用非const对象来初始化另一个X类型的对象。

以下3种情况都是用一个对象初始化另一个同类对象的语义,会调用拷贝构造函数:

1)用一个对象显式或者隐式初始化另一个同类对象。

2)函数调用时,按传值方式传递对象参数。

3)函数返回时,按传值方式返回对象。

需要注意的是,按引用传递对象或返回对象时并不是创建和初始化新对象的语义,因而不会引起拷贝构造函数的调用。

5.构造函数中的this指针

成员函数隐含的第一个参数是this指针,X类成员函数的this指针类型是*This指针是一个常量,用于表示当前对象的地址。

如果是在类的普通成员函数中,this指向当前正在接收消息的对象。

如果是在类的构造函数中,this指向正在被创建的新对象,当前它还是一块没有完成初始化的内存区域,构造函数的作用就是正确地初始化该内存区域。

二、析构函数

C++中,由析构函数负责对象生存期结束时返回相关资源和自动释放资源。当对象离开作用域时,或者用delete释放堆上的对象时,析构函数都会被自动调用。析构函数的名字是类名字前加上波浪线“~”。析构函数没有返回类型,也没有任何参数。析构函数不能重载,只能为一个类定义唯一一个析构函数。

~Vector(){delete []p};//析构函数

一般情况下,如果一个类值包含按值存储的数据成员,则无须析构函数。析构函数主要被用来放弃在类对象的构造函数或生存期中获得的资源,如释放互斥锁或归还new分配的空间。不过,析构函数的作用并不局限在释放资源上,一般地,析构函数可以执行类设计者希望在最后一次使用对象之后执行的任何操作。

析构函数在大多数情况下都是被自动隐式调用的,但有时需要显示调用析构函数。最典型的情况是在使用定位new的时候。

三、构造函数和析构函数的调用

何时调用构造函数?

Ø  创建全局对象或局部对象时,根据初始值的类型和个数调用相应的构造函数。

Ø  创建类类型的数组时,调用默认构造函数初始化每个数组的元素。

Ø  函数参数按值传递对象和按值返回对象时,调用拷贝构造函数。

Ø  new在堆上创建对象时,先为对象分配空间,再调用构造函数初始化对象,最后返回对象的地址。

Ø  在需要隐式类型转换时,会调用类型转换构造函数。

创建包含成员对象的组合对象时,成员对象的构造函数会先于组合对象的构造函数被调用。

如何调用析构函数?

Ø  对象在离开作用域时自动调用析构函数,同一作用域内的对象的析构函数调用次序和构造函数的调用次序相反。

Ø  delete删除堆上的对象时,先调用析构函数清除对象,再释放堆上的空间。

Ø  表达式中创建的临时对象在表达式结束时会调用析构函数。

Ø  撤销包含成员对象的组合对象时,先调用组合对象的析构函数,再调用成员对象的析构函数。

原创粉丝点击