zoukankan      html  css  js  c++  java
  • C++拷贝构造函数具体解释

    一. 什么是拷贝构造函数

    首先对于普通类型的对象来说,它们之间的复制是非常easy的,比如:


    而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。
    以下看一个类对象拷贝的简单样例。

    执行程序,屏幕输出100。从以上代码的执行结果能够看出,系统为对象 B 分配了内存并完毕了与对象 A 的复制过程。就类对象而言,同样类型的类对象是通过拷贝构造函数来完毕整个复制过程的

    以下举例说明拷贝构造函数的工作过程。


    CExample(const CExample& C) 就是我们自己定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个參数是本类型的一个引用变量


    二. 拷贝构造函数的调用时机

    在C++中,以下三种对象须要调用拷贝构造函数!
    1. 对象以值传递的方式传入函数參数


    调用g_Fun()时,会产生下面几个重要步骤:
    (1).test对象传入形參时,会先会产生一个暂时变量,就叫 C 吧。
    (2).然后调用拷贝构造函数把test的值给C。 整个这两个步骤有点像:CExample C(test);
    (3).等g_Fun()运行完后, 析构掉 C 对象。

    2. 对象以值传递的方式从函数返回


    当g_Fun()函数运行到return时,会产生下面几个重要步骤:
    (1). 先会产生一个暂时变量,就叫XXXX吧。
    (2). 然后调用拷贝构造函数把temp的值给XXXX。整个这两个步骤有点像:CExample XXXX(temp);
    (3). 在函数运行到最后先析构temp局部变量。
    (4). 等g_Fun()运行完后再析构掉XXXX对象。

    3. 对象须要通过另外一个对象进行初始化;

    后两句都会调用拷贝构造函数。


    三. 浅拷贝和深拷贝

    1. 默认拷贝构造函数

        非常多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数參数或者函数返回对象都能非常好的进行,这是由于编译器会给我们自己主动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数非常easy,只使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,它一般具有下面形式:

     
        当然,以上代码不用我们编写,编译器会为我们自己主动生成。可是假设觉得这样就能够解决对象
    的复制问题,那就错了,让我们来考虑下面一段代码:

      这段代码对前面的类,增加了一个静态成员,目的是进行计数。在主函数中,首先创建对象rect1,输出此时的对象个数,然后使用rect1复制出对象rect2,再输出此时的对象个数,依照理解,此时应该有两个对象存在,但实际程序执行时,输出的都是1,反应出仅仅有1个对象。此外,在销毁对象时,因为会调用销毁两个对象,类的析构函数会调用两次,此时的计数器将变为负数。

    说白了,就是拷贝构造函数没有处理静态数据成员。

    出现这些问题最根本就在于在复制对象时,计数器没有递增,我们又一次编写拷贝构造函数,例如以下

    2. 浅拷贝

        所谓浅拷贝,指的是在对象复制时,仅仅对对象中的数据成员进行简单的赋值,默认拷贝构造函数运行的也是浅拷贝。大多情况下“浅拷贝”已经能非常好地工作了,可是一旦对象存在了动态成员,那么浅拷贝就会出问题了,让我们考虑例如以下一段代码:

        在这段代码执行结束之前,会出现一个执行错误。原因就在于在进行对象复制时,对于动态分配的内容没有进行正确的操作。我们来分析一下:

        在执行定义rect1对象后,因为在构造函数中有一个动态分配的语句,因此执行后的内存情况大致例如以下:

     

     

        在使用rect1复制rect2时,因为运行的是浅拷贝,仅仅是将成员的值进行赋值,这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间,例如以下图所看到的:

     

    当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两,这就是错误出现的原因。我们须要的不是两个p有同样的值,而是两个p指向的空间有同样的值,解决的方法就是使用“深拷贝”。


    3. 深拷贝

        在“深拷贝”的情况下,对于对象中动态成员,就不能只简单地赋值了,而应该又一次动态分配空间,如上面的样例就应该依照例如以下的方式进行处理:

    此时,在完毕对象的复制后,内存的一个大致情况例如以下:

     

    此时rect1的p和rect2的p各自指向一段内存空间,但它们指向的空间具有同样的内容,这就是所谓的“深拷贝”。


    3. 防止默认拷贝发生

        通过对对象复制的分析,我们发现对象的复制大多在进行“值传递”时发生,这里有一个小技巧能够防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,假设用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而能够避免按值传递或返回对象。

    四. 拷贝构造函数的几个细节

    1. 拷贝构造函数里能调用private成员变量吗?
    解答:
    这个问题是在网上见的,当时一下子有点晕。其时从名子我们就知道拷贝构造函数其时就是
    一个特殊的构造函数,操作的还是自己类的成员变量,所以不受private的限制。


    2. 下面函数哪个是拷贝构造函数,为什么?


    解答:对于一个类X, 假设一个构造函数的第一个參数是下列之中的一个:
    a) X&
    b) const X&
    c) volatile X&
    d) const volatile X&
    且没有其它參数或其它參数都有默认值,那么这个函数是拷贝构造函数.


    3. 一个类中能够存在多于一个的拷贝构造函数吗?
    解答:
    类中能够存在超过一个拷贝构造函数。


    注意,假设一个类中仅仅存在一个參数为 X& 的拷贝构造函数,那么就不能使用const X或volatile X的
    对象实行拷贝初始化.


    假设一个类中未定义拷贝构造函数,那么编译器会自己主动产生一个默认的拷贝构造函数。
    这个默认的參数可能为 X::X(const X&)X::X(X&),由编译器依据上下文决定选择哪一个。

  • 相关阅读:
    Dot Net WinForm 控件开发 (七) 为属性提下拉式属性编辑器
    WinForm 程序的界面多语言切换
    c#遍历HashTable
    Dot Net WinForm 控件开发 (三) 自定义类型的属性需要自定义类型转换器
    Dot Net WinForm 控件开发 (六) 为属性提供弹出式编辑对话框
    Dot Net WinForm 控件开发 (一) 写一个最简单的控件
    Dot Net WinForm 控件开发 (四) 设置属性的默认值
    Dot Net WinForm 控件开发 (二) 给控件来点描述信息
    Dot Net WinForm 控件开发 (八) 调试控件的设计时行为
    Dot Net WinForm 控件开发 (五) 复杂属性的子属性
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4510350.html
Copyright © 2011-2022 走看看