C++中复制构造函数与重载赋值操作符总结
前言
这篇文章将对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++中复制构造函数与重载赋值操作符总结的更多相关文章
- C++中复制构造函数与重载赋值操作符
我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类: class CTe ...
- C++11六大函数(构造函数,移动构造函数,移动赋值操作符,复制构造函数,赋值操作符,析构函数)
在C++中,有三大函数复制控制(复制构造函数,赋值操作符,析构函数),而在C++11中,加入了移动构造函数,移动赋值操作符.我就斗胆将他们命名为六大函数好了. 一.构造函数 c++primer中说过: ...
- C++ 之 重载赋值操作符
Widget 类中,定义了一个 Bitmap 类型的私有数据成员 -- pb 指针 class Bitmap { ... }; class Widget { private: Bitmap *pb; ...
- 5.5 C++重载赋值操作符
参考:http://www.weixueyuan.net/view/6383.html 总结: 重载赋值操作符同重载类的是拷贝构造函数的原因是一样,将一个对象拷贝给另一个对象,同时当类中存在指针类型的 ...
- C++重载赋值操作符
1.C++中重载赋值操作函数应该返回什么? 类重载赋值操作符一般都是作为成员函数而存在的,那函数应该返回什么类型呢?参考内置类型的赋值操作,例如 int x,y,z; x=y=z=15; 赋值行为相当 ...
- Effective C++(10) 重载赋值操作符时,返回该对象的引用(retrun *this)
问题聚焦: 这个准则比较简短,但是往往就是这种细节的地方,可以提高你的代码质量. 细节决定成败,让我们一起学习这条重载赋值操作符时需要遵守的准则吧. 还是以一个例子开始: Demo // 连锁赋值 x ...
- C++中复制构造函数和赋值操作符
先看一个例子: 定义了一个类:
- C++中复制构造函数
复制构造函数 复制构造函数用于: 根据另一个同类型的对象显示或隐式初始化一个对象 复制一个对象,将它作为实参传给一个函数 从函数返回时复制一个对象 初始化顺序容器中的元素 根据元素初始化式列表初始化数 ...
- Java中的构造函数和重载
一.Java中的构造函数 构造函数是对象被创建时初始化对象的成员方法,它具有和它所在的类完全一样的名字.构造函数只能有入口参数,没有返回类型,因为一个类的构造方法的返回类就是类本身.构造函数定义后,创 ...
随机推荐
- C# 多线程传参
using System; using System.Threading; //多线程调试: 2013.10.08 namespace ThreadExample { class App { publ ...
- 蘑菇街iOS客户端应用源码
蘑菇街iOS客户端应用源码 随着蘑菇街由导购向电商转型,蘑菇街自己的IM也应运而生,IM起初只是用于商家和买家之间沟通的工具.后面我们问自己,既然已经有了用于客服的IM,为什么不自己做一个IM,用于公 ...
- CentOS学习笔记—软件管理程序RPM、YUM
软件管理程序 Linux的软件安装分为源代码编译安装和打包安装.RPM是一种打包安装方式,是由 Red Hat 这家公司开发出来的,后来实在很好用,因此很多 distributions 就使用这个机制 ...
- 安装 centos7 注意事项
最近一直没有写博客,把之前的折腾记录写下. 1 下载好镜像文件,刻录光盘. 2 用DVD安装CENTOS7 3 有些处理器不支持Cento7安装,需要重新编译内核文件.我不懂 . 我用的ACER ...
- js动态显示可输入字数并提示还可以输入的字数
动态显示可输入的字数提示还可以输入的字数. 代码: <input name="title" type="text" size="50" ...
- 基于jQuery打造的选项卡向上弹出jquery焦点图切换特效
基于jQuery打造的选项卡向上弹出jquery焦点图切换特效 鼠标经过标题栏,会出现层特效向上滑动,并且在同时进行幻灯片切换,效果十分不错. 有兴趣的童鞋可以下载看看,在IE6方面兼容性也不错,只有 ...
- uglifyjs压缩JS
一.故事总有其背景 年末将至,很多闲适的时间,于是刷刷微博,接触各种纷杂的信息——美其名曰“学习”.运气不错,遇到了一个新名词,uglifyjs. 据说是用来压缩JS文件的,据说还能优化JS,据说是基 ...
- 关于delphi XE7中的动态数组和并行编程(第一部分)
本文引自:http://www.danieleteti.it/category/embarcadero/delphi-xe7-embarcadero/ 并行编程库是delphi XE7中引进的最受期待 ...
- DevExpress GridControl 使用方法技巧 总结 收录整理
一.如何解决单击记录整行选中的问题 View->OptionsBehavior->EditorShowMode 设置为:Click 二.如何新增一条记录 ().gridView.AddNe ...
- 关于FragmentManager findFragmentById 返回nul
先看Fragment的两种生成方式 一.用xml标签生成 在fragment的宿主activity中添加xml标签 <fragment android:id="@+id/fragmen ...