前言

这篇文章将对C++中复制构造函数和重载赋值操作符进行总结,包括以下内容:

1.复制构造函数和重载赋值操作符的定义;

2.复制构造函数和重载赋值操作符的调用时机;

3.复制构造函数和重载赋值操作符的实现要点

4.复制构造函数的一些细节。

复制构造函数和重载赋值操作符的定义

我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数、析构函数、复制构造函数和重载赋值操作;即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数。例如以下类:

class CTest
{
public:
     CTest();
     ~CTest();
     CTest(const CTest &);
     void operator=(const CTest &);
};

对于构造函数和析构函数不是今天总结的重点,今天的重点是复制构造函数和重载赋值操作。类的复制构造函数原型如下:

class_name(const class_name &src);

一般来说,如果我们没有编写复制构造函数,那么编译器会自动地替每一个类创建一个复制构造函数(也叫隐式复制构造函数);相反的,如果我们编写了一个复制构造函数(显式的复制构造函数),那么编译器就不会创建它。

类的重载赋值操作符的原型如下:

void operator=(const class_name &);

重载赋值操作符是一个特别的赋值运算符,通常是用来把已存在的对象指定给其它相同类型的对象。它是一个特别的成员函数,如果我们没有定义这个成员函数,那么编译器会自动地产生这个成员函数。编译器产生的代码是以单一成员进行对象复制的动作。

总结了复制构造函数和重载赋值操作符的定义,只是让我们了解了它们,而没有真正的深入它们。接下来,再仔细的总结一下它们的调用时机。关于它们的调用时机,我一直都没有真正的明白过,所以这里一定要好好的总结明白了。

复制构造函数和重载赋值操作符的调用时机

对复制构造函数和重载赋值操作符的调用总是发生在不经意间,它们不是经过我们显式的去调用就被执行了。对于这种隐式调用的地方一定要多注意了,这也一般是有陷阱的地方。现在我就用实际的例子来进行验证;例子如下:

#include <iostream>
using namespace std;
class CTest
{
public:
     CTest(){}
     ~CTest(){}
     CTest(const CTest &test)
     {
          cout<<"copy constructor."<<endl;
     }
     void operator=(const CTest &test)
     {
          cout<<"operator="<<endl;
     }
     void Test(CTest test)
     {}
     CTest Test2()
     {
          CTest a;
          return a;
     }
     void Test3(CTest &test)
     {}
     CTest &Test4()
     {
          CTest *pA = new CTest;
          return *pA;
     }
};
int main()
{
     CTest obj;
     CTest obj1(obj); // 调用复制构造函数
     obj1 = obj; // 调用重载赋值操作符
     /* 传参的过程中,要调用一次复制构造函数
     * obj1入栈时会调用复制构造函数创建一个临时对象,与函数内的局部变量具有相同的作用域
     */
     obj.Test(obj1);
     /* 函数返回值时,调用复制构造函数;将返回值赋值给obj2时,调用重载赋值操作符
     * 函数返回值时,也会构造一个临时对象;调用复制构造函数将返回值复制到临时对象上
     */
     CTest obj2;
     obj2 = obj.Test2();
     obj2.Test3(obj); // 参数是引用,没有调用复制构造函数
     CTest obj3;
     obj2.Test4(); // 返回值是引用,没有调用复制构造函数
     return 0;
}

在代码中都加入了注释,这里就不再做详细的说明了。再次总结一下,如果对象在声明的同时将另一个已存在的对象赋给它,就会调用复制构造函数;如果对象已经存在了,然后再将另一个已存在的对象赋给它,调用的就是重载赋值运算符了。这条规则很适用,希望大家能记住。

复制构造函数和重载赋值操作符的实现要点

在一般的情况下,编译器给我们生成的默认的复制构造函数和重载赋值操作符就已经够用了;但是在一些特别的时候,需要我们手动去实现自己的复制构造函数。

我们都知道,默认的复制构造函数和赋值运算符进行的都是"shallow copy",只是简单地复制字段,因此如果对象中含有动态分配的内存,就需要我们自己重写复制构造函数或者重载赋值运算符来实现"deep copy",确保数据的完整性和安全性。这也就是大家常常说的深拷贝与浅拷贝的问题。下面我就提供一个比较简单的例子来说明一下:

#include <iostream>
using namespace std;
const int MAXSIZE = 260;
class CTest
{
public:
     CTest(wchar_t *pInitValue)
     {
          // Here, I malloc the memory
          pValue = new wchar_t[MAXSIZE];
          memset(pValue, 0, sizeof(wchar_t) * MAXSIZE);
          wcscpy_s(pValue, MAXSIZE, pInitValue);
     }
     ~CTest()
     {
          if (pValue)
          {
               delete pValue;
               pValue = NULL;
          }
     }
     CTest(const CTest &test)
     {
          // Malloc the new memory for the pValue
          pValue = new wchar_t[MAXSIZE];
          memset(pValue, 0, sizeof(wchar_t) * MAXSIZE);
          wcscpy_s(pValue, MAXSIZE, test.pValue);
     }
     CTest &operator=(const CTest &test)
     {
          // This is very important, please remember
          if (this == &test)
          {
               return *this;
          }
          // Please delete the memory, this maybe cause the memory leak
          if (pValue)
          {
               delete pValue;
          }
          // Malloc the new memory for the pValue
          pValue = new wchar_t[MAXSIZE];
          memset(pValue, 0, sizeof(wchar_t) * MAXSIZE);
          wcscpy_s(pValue, MAXSIZE, test.pValue);
          return *this;
     }
     void Print()
     {
          wcout<<pValue<<endl;
     }
private:
     wchar_t *pValue; // The pointer points the memory
};
int main()
{
     CTest obj(L"obj");
     obj.Print();
     CTest obj2(L"obj2");
     obj2.Print();
     obj2 = obj;
     obj2.Print();
     obj2 = obj2;
     obj2.Print();
     return 0;
}

特别是在实现重载赋值构造函数时需要多多的注意,在代码中我也添加了注释,大家可以认真的阅读一下代码,然后就懂了,如果不懂的就可以留言问我;当然了,如果我哪里理解错了,也希望大家能给我提出,我们共同进步。

复制构造函数的一些细节

1.以下哪些是复制构造函数

X::X(const X&);  
X::X(X);  
X::X(X&, int a=1);  
X::X(X&, int a=1, int b=2);

这些细节问题在这里也说一说,我也是从别人的博客里看到的,这里自己也总结一下。对于一个类X, 如果一个构造函数的第一个参数是下列之一:

a) X&
b) const X&
c) volatile X&
d) const volatile X&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数。

X::X(const X&);  //是拷贝构造函数  
X::X(X&, int=1); //是拷贝构造函数 
X::X(X&, int a=1, int b=2); //当然也是拷贝构造函数

2.类中可以存在超过一个拷贝构造函数

class X {
public:     
  X(const X&);      // const 的拷贝构造
  X(X&);            // 非const的拷贝构造
};

注意,如果一个类中只存在一个参数为 X& 的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化。如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数。这个默认的参数可能为 X::X(const X&)或 X::X(X&),由编译器根据上下文决定选择哪一个。在我的Visual Studio 2012中,当定义了多个复制构造函数以后,编译器就会有warning,但是程序还能正确运行。

总结

这篇文章对复制构造函数和重载赋值操作符进行了一些总结,重点是在复制构造函数与重载赋值操作符的调用时机上;对于大家喜欢总结的深拷贝与浅拷贝问题,我没有用过多的文字进行说明,我认为上面的代码就足以说明问题了。最后自己纠结已久的问题也就这样总结了,自己也彻底的明白了。

2014年2月21日 于大连,东软。

本文版权归果冻说所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
如果这篇文章对你有帮助,你可以请我喝杯咖啡。

» 本文链接:http://www.jellythink.com/archives/378
» 订阅本站:http://www.jellythink.com/feed

C++中复制构造函数与重载赋值操作符总结的更多相关文章

  1. C++中复制构造函数与重载赋值操作符

    我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类:   class CTe ...

  2. C++11六大函数(构造函数,移动构造函数,移动赋值操作符,复制构造函数,赋值操作符,析构函数)

    在C++中,有三大函数复制控制(复制构造函数,赋值操作符,析构函数),而在C++11中,加入了移动构造函数,移动赋值操作符.我就斗胆将他们命名为六大函数好了. 一.构造函数 c++primer中说过: ...

  3. C++ 之 重载赋值操作符

    Widget 类中,定义了一个 Bitmap 类型的私有数据成员 -- pb 指针 class Bitmap { ... }; class Widget { private: Bitmap *pb; ...

  4. 5.5 C++重载赋值操作符

    参考:http://www.weixueyuan.net/view/6383.html 总结: 重载赋值操作符同重载类的是拷贝构造函数的原因是一样,将一个对象拷贝给另一个对象,同时当类中存在指针类型的 ...

  5. C++重载赋值操作符

    1.C++中重载赋值操作函数应该返回什么? 类重载赋值操作符一般都是作为成员函数而存在的,那函数应该返回什么类型呢?参考内置类型的赋值操作,例如 int x,y,z; x=y=z=15; 赋值行为相当 ...

  6. Effective C++(10) 重载赋值操作符时,返回该对象的引用(retrun *this)

    问题聚焦: 这个准则比较简短,但是往往就是这种细节的地方,可以提高你的代码质量. 细节决定成败,让我们一起学习这条重载赋值操作符时需要遵守的准则吧. 还是以一个例子开始: Demo // 连锁赋值 x ...

  7. C++中复制构造函数和赋值操作符

    先看一个例子: 定义了一个类:

  8. C++中复制构造函数

    复制构造函数 复制构造函数用于: 根据另一个同类型的对象显示或隐式初始化一个对象 复制一个对象,将它作为实参传给一个函数 从函数返回时复制一个对象 初始化顺序容器中的元素 根据元素初始化式列表初始化数 ...

  9. Java中的构造函数和重载

    一.Java中的构造函数 构造函数是对象被创建时初始化对象的成员方法,它具有和它所在的类完全一样的名字.构造函数只能有入口参数,没有返回类型,因为一个类的构造方法的返回类就是类本身.构造函数定义后,创 ...

随机推荐

  1. Linux设备驱动开发环境的搭建(转)

    经过两周的摸索,终于对Linux设备驱动开发有了个初步的认识,下面对Linux设备驱动开发环境的搭建做个小结,以方便自己以后查询,同时也能给同道的初学者一点帮助. 刚接触Linux设备驱动时,初学者往 ...

  2. 新写的c++日志库:log4K

    网是开源的c/c++日志库也不少,但用起来总觉得不方便,于是动手写了一个C++日志框架Log4K. 测试代码: #include "log4k.h" #pragma comment ...

  3. ct任务添加与中控机批量后台操作

    ct 任务nohup sh ./bin/start.sh </dev/null >/dev/null 2>&1 & 中控机批量 for h in `get_hosts ...

  4. Hadoop的shell脚本分析

    你会发现hadoop-daemon.sh用于启动单独的本机节点 而hadoop-daemons.sh 会批量的ssh到别的机器启动 前记: 这些天一直学习hadoop,学习中也遇到了许多的问题,主要是 ...

  5. CentOS学习笔记—启动、ROOT密码

    启动流程一览 简单来说,系统启动的经过可以汇整成底下的流程的: 加载 BIOS 的硬件资讯与进行自我测试,并依据配置取得第一个可启动的装置: 读取并运行第一个启动装置内 MBR 的 boot Load ...

  6. asp.net跨页面传值

    a.aspx.cs //获取a中的id HttpCookie objCookie = new HttpCookie("myCookie", id); Response.Cookie ...

  7. Winform登录、控制软件只运行一次、回车登录

    Winform登录对很多程序猿来说都有些困惑,登录进入主窗体后要销毁登录窗体,而不是隐藏哦,怎么实现呢? 先贴一段Program.cs的代码 static void Main() { Mutex mu ...

  8. Silverlight中本地化的实现(语言切换)

    对于本地化这个功能我个人一直觉得在需求阶段就要确定该项目需不需要实行本地化,这样后面就可以减轻许多工作量,Sl中对本地化功能的支持与ASP.NET 中一样,主要使用CultureInfo类来完成的. ...

  9. 深入浅出MongoDB(三)环境搭建

    上次的博文深入浅出MongoDB(二)概述中我们已经将MongoDB的相关概念讲解了一下,接下来我们继续进行MongoDB学习.在学习之前,大家首先需要在自己的电脑上安装MongoDB. 1.安装 安 ...

  10. 前端javascript发送ajax请求、后台书写function小案例

    HTML端页面: <td> <input class="pp_text" type="text" name="" valu ...