C++中复制构造函数与重载赋值操作符的深入分析

    在C++中复制控制是一个比较重要的话题,主要包括复制构造函数、重载赋值操作符、析构函数这三部分,这三个函数是一致的,如果需要手动定义了其中了一个,那么另外的两个也需要定义,通常在存在指针或者前期相关操作的情况下,都需要手动的定义。复制构造函数与重载赋值操作符实现的大题相同,如果没有手动的实现,那么编译器会自动生成一个,而且这两个函数的参数也是一致的,是不能够改变的,这是为什么呢?这是值得我们去分析和推敲的。析构函数相比前面的两个存在一个巨大的差别,就是无论我们是否定义这个函数,编译器都会自动生成一个析构函数。析构函数我认为主要是完成对象的释放操作,我就不去仔细的分析啦。
   
    我们现在着重来分析一下为什么复制构造函数与重载赋值操作符在没有定义的情况下,编译器会为我们生成一个,这说明这两个函数是一个类必不可少的部分。由此可知如果一个类没有定义任何的东西,编译器也会帮助我们生成下面的4个函数:1、一个构造函数,也就是所谓的类名比如classname(),这是在没有定义构造函数时,编译器会自动生成的。2、析构函数,这个前面已经提到了。3、复制构造函数。4、重载赋值操作符。
 
    为什么复制构造函数和重载赋值操作符如此重要呢?
   
    首先介绍一下复制构造函数与重载赋值操作符的声明形式,这两个函数的参数是固定的,是不能改变的。假设存在一个类Base。那么它的这两个函数声明分别如下所示:

点击(此处)折叠或打开

  1. class Base
  2. {
  3. public:
  4.     //构造函数
  5.     Base();
  6.     //复制构造函数
  7.     Base(const Base &);
  8.     //重载赋值操作符
  9.     Base &operator=(const Base &);
  10.     //析构函数
  11.     ~Base();
  12.     ...
  13. private:
  14.     ...
  15. };
    面的形式可以知道,复制构造函数与重载赋值操作符的参数是一致的,都是该类的const引用类型。为什么不能重载呢?这些都是存在的问题。我觉得要搞清楚这些问题,首先我们就不能只能片面的去理解,而是应该对C++的面向对象编程有了一个较好的理解以后再来分析这个问题。
 
    么是引用,很多书上都会说是为了避免复制,这是其中的一个原因,如果说只是这个原因,完全可以只用指针就可以了,对于系统而言,不在乎多几个指针的存储空间。
首先说明一下在C++中的继承问题,一般而言我们的继承主要是指公共继承,也就说如下的派生类定义所示:

点击(此处)折叠或打开

  1. class Derived : public Base
  2. {
  3.  public:
  4.    ...
  5. private:
  6.    ...
  7. }
    公共继承而言,基类的公共部分成为了派生类的公共部分,私有部分成为了派生类的私有部分。也就是说派生类中包含基类的部分。也就是说在实现派生类的过程中,基类的公用接口会被派生类直接继承。而基类的私有成员也作为派生类的私有成员。但是对于其他的继承类型,我就不去讨论啦。这时派生类的成员可以访问到基类的私有成员。也就是说相当于派生类是在基类的基础上增加了自己的特色。
 
    需要介绍一个问题就是采用派生类对象的引用初始化基类的引用。多态性的动态绑定中存在两个条件:1、必须是virtual函数(虚函数),2、必须是通过基类的引用或者基类的指针进行成员虚函数的调用。
只有上面两个条件满足才能发生动态调用问题。
 
    由于派生类中存在基类的成员,也就相当于一个派生类对象中包含了一个基类对象(我不知道这样是否合理),所以可以采用一个基类引用来绑定一个派生类对象。引用实质上是针对一块内存区域,引用是一个标号,是这块内存区域的一个名字,一个引用与一块内存区域绑定,因为派生对象中存在基类部分,可以认为派生对象的区域中存在基类对象,这时可用基类的引用来表明这块内存区域,即采用一个基类的别名来表示(绑定)这段内存区域,派生对象的地址(这段内存)以及内容都没有发生改变,也没有重现创造出一个新的对象,基类的引用还是指向这个派生对象。对于指针的分析方式相似。因此可以采用基类的引用绑定派生类对象。
   
    但是如何实现派生类对象到基类的转换呢?
    这时候的转换与前面的绑定存在很大的差别,因为这是重新分配一个基类对象,而不再是引用问题,不再是绑定问题,是依据一个派生类对象生成一个新的基类对象。因为派生类对象中存在一个基类对象基本的信息,完全可以生成一个基类对象,完全将此过程看作是一个初始化或者赋值的问题。也就是采用派生类创建一个新的对象或者赋值一个对象。
    从上面的分析我们可以采用下面的形式来实现:

点击(此处)折叠或打开

  1. Base(const Derived &);
  2. Base &operator=(const Derived &);
    是在基类函数中采用构造函数基于派生类来重载一系列的构造函数,但是这也存在一个问题,如果存在很多派生类,这时候就要重载很多构造函数,这肯定不是我们需要的。
 
    这时候是否发现与前面的问题关联起来了?
    这时候我们发现对于一个类而言,为什么复制构造函数和重载赋值操作符这么重要了。因为这两个函数都是接受一个基类的引用,根据前面的分析我们知道一个基类引用完全可以绑定一个派生类的对象,而派生类对象中又包含了一个基类对象的基本信息。我们能够实现一个从一个派生对象到基类的构造过程。
    我们用一个基类引用绑定一个派生对象,然后采用基类引用对基类成员进行访问,完成了一个基类对象基本要素的填充操作,相当于完成了基类对象的创建,也就是构造问题。这样也就能完成由派生类对象到基类对象的构造过程。
    至于为什么是一个const的引用,我认为主要是因为不去修改派生类对象和扩大能够接受的参数情况,由const引用可以绑定不同类型的对象特性,说明const引用会扩大接受实参的能力。
 
现在我可以总结起来说了,因为在复制构造函数中,C++中的基类引用可以绑定一个派生类的对象,如果在允许访问的情况下,采用基类引用可以访问基类的成员以及派生类的其他成员,采用引用可以复制派生类对象中基类成员的值到新创建的基类成员中,完成一个基类成员数据的填充操作,这时候一个完整的基类对象就创建完成了。
 
    重载赋值操作符则是发生在使用一个派生对象来赋值一个基类对象时,这时候也是const基类引用绑定一个派生类对象,然后复制对应的基类成员到基类对象对于的成员中,完成一个基类对象成员的更新操作。
 
    来说,复制构造函数不仅仅实现了同类型之间的初始化操作,同时也完成了采用一个派生类对象初始化一个基类对象的操作,重载赋值操作符实现了同类型之间的赋值操作,也完成了采用派生类对象赋值基类对象的操作。如果没有这两个函数的存在,也就不能完成派生类到基类的赋值和初始化操作。这也是为什么一定会存在这两个函数的原因。
 
上一篇:【学】jQuery的源码思路2——$符号是如何封装的


下一篇:java 程序运行过程 简介