哈飞扬 / 特殊函数以及... / 复制构造函数,赋值构造函数,析构函数

分享

   

复制构造函数,赋值构造函数,析构函数

2013-06-06  哈飞扬
复制构造函数(即拷贝构造函数),赋值构造函数以及析构函数
 
它们会在一起使用,这被称为“三法则”。
我们知道一个类的构造函数指明了当我们定义一个类的对象时会发生什么,这一小节主要讨论另外几个与类的创建及删除有关的概念:复制构造函数(当复制一个类的对象时会发生什么),赋值构造操作符(当对类的对象进行赋值操作时会发生什么),以及析构函数(撤销这个类的对象时会发生什么)。这三个函数统称为复制控制。从它们的作用来看,其实它们非常重要,但是为什么我们平时不太注意他们,甚至有些书籍也对他们不太提及呢?因为如果我们没有显式的定义它们,编译器会自动为了定义最基本的这些操作。

先看复制构造函数
顾名思义,当发生类的对象的复制时,是通过调用复制构造函数完成的。具体的说,下面几种情况会调用复制构造函数:
用一个同类型的对象初始化一个对象。
当一个对象作为函数的实参时,实参到形参的转化会发生复制操作。
当以一个类作为函数的返回值时,也会返回return语句中的值的副本。
初始化顺序容器的元素时。
根据元素的初始化列表初始化元素时。

其实,不论我们是否自己定义了复制构造函数,编译器都会为我们合成一个所谓的“合成构造函数”,这个函数的为类的每一个成员初始化(不包括static成员,因为它属于这个类而不是这个类的某一个对象)。初始化的方式是由这个成员的类型决定的:内置类型的成员直接复制,类类型的成员使用它的复制构造函数复制,而数组,需要特别注意:虽然我们说不能直接复制数组,但是如果数组是一个类成员,复制构造函数会复制整个数组。
既然编译器会为你合成一个复制构造函数,为什么我们还要自己写一个呢?如果一个类的数据成员是指针,或者有成员在构造函数中分配其他资源,或者想在创建新对象时干一些其他的事情,(其实是前两种情况)就需要自己编写复制构造函数了。

那么如何定义复制构造函数呢?其实跟构造函数差不多,不过形参是类类型的引用,这个引用通常是const修饰。

 

也可用构造函


也可以同初始化列表的方法定义复制构造函数:

有一点需要特别注意:复制构造函数的形参一定是一个引用!原因很简单,如果形参是普通的非引用类,那么形参到实参的传递过程是通过“值传递”发生的:即实参将值复制给形参,可是复制的规则却还没有定义呢!

再看赋值构造函数。假设我们有一个类Employee,并定义了两个它的对象a,b,那么这个函数定义了当我使用a=b时,具体都干了什么事情。
同样的,当我们没有定义它时,编译器会合成一个,它会对又操作数对象的每个成员赋值给做操作数对象的对应成员:内置类型常规赋值,类类型使用这个类的赋值构造函数赋值,数组则是对数组的每个元素赋值。
它的定义方法依赖于另一个概念:重载操作符。简单的说,重载操作符就是定义了平时我们使用的+、-、*、/、=等操作符在操作数是类而并非普通的内置类型时,这些操作表达的含义。所以可以看出,所谓的赋值构造函数,就是重载=这个操作,是得它的操作数为类时,也能得到我们想要的结果。重载方法说起来很绕口,先看一个例子:

 

其中的Employee& operator=(const Employee &rhs)便是对“=”的重载。其中operator=表明重载的是“=”操作符。形参必须与操作符对应的操作数一致:按理来说,应该是“=”左右两边的类型都有,但这里隐式的包含了*this指针,指向了“=”的做操作数,所以只有一个形参。函数体的具体内容很明显,就是用一个对象的数据成员给另一个对象的数据成员复制。返回this指针意味着我们可以进行a=b=c这样连续赋值的操作。

最后我们看看析构函数。
可以把它认为是构造函数的“反函数”,当撤销一个对象时,会调用析构函数。举几个例子,比如你在函数体内定义了一个局部对象,当这个函数使用完(遇到})时,这个对象的生命周期就结束了,需要调用析构函数对其撤销。同理,撤销一个容器(不论是标准库还是数组)时,对于容器的每一个元素,都会使用析构函数对其撤销。
同样的,当你不定义析构函数时,系统会为你合成一个默认的析构函数。但这个析构函数却并不一定能够符合你对它的期望:释放全部应该释放的内存,原因很简单,因为假如对象的成员中,有动态分配的对象,那么只有当该对象的指针被删除时,才能释放内存。这个工作编译器是不会帮你完成的,你只能自己写析构函数来完成,正如第一个程序中的:

通常,复制构造函数,赋值构造函数以及析构函数会在一起使用。这被称为“三法则”。
最后通过一个例子来说明这三个函数在什么时候会被调用。程序很变态,它定义了一个类,这个类没有数据成员,但还是为它定义了复制控制函数,而每个函数并没有做它们本来该做的事情,而是打印自己,这使得我们能够清楚的看出来调用的是谁。然后定义了3个函数,函数本身也没有做什么事情,但是通过参数的传递(引用,非引用)和返回值,使得复制控制被调用了,最后还验证了容器和动态创建时的调用情况:


主函数如下,注释分析了每一行代码要调用什么函数。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多
    喜欢该文的人也喜欢 更多

    ×
    ×

    ¥.00

    微信或支付宝扫码支付:

    开通即同意《个图VIP服务协议》

    全部>>