C++拷贝构造函数详解
来源:互联网 发布:用c语言判断是否是闰年 编辑:程序博客网 时间:2024/05/17 04:15
本文转载自C++拷贝构造函数详解
一. 什么是拷贝构造函数
对于普通类型的对象来说,它们之间的复制是很简单的。
int a=100; int b=a;而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。下面看一个类对象拷贝的简单例子。
#include<iostream> using namespace std; class CExample { private: int a; public: CExample(int b) {a=b;} void Show() { cout<<a<<endl; } }; int main() { CExample A(100); CExample B=A; B.Show(); }
从以上代码的运行结果可以看出,系统为对象B分配了内存并完成了与对象A的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。下面举例说明拷贝构造函数的工作过程。
#include<iostream> using namespace std; class CExample{ private: int a; public: CExample(int b) { a=b;} CExample(const CExample& C) { a=C.a; } void Show() { cout<<a<<endl; } }; int main() { CExample A(100); CExample B=A; B.Show(); }
CExample(const CExample& C)就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。
二. 拷贝构造函数的调用时机
在C++中,下面三种对象需要调用拷贝构造函数。
1. 对象以值传递的方式传入函数参数
#include<iostream>using namespace std;class CExample { private: int a; public: CExample(int b) { a=b; cout<<"creat: "<<a<<endl; } CExample(const CExample& C) { a=C.a; cout<<"copy"<<endl; } ~CExample() { cout<< "delete: "<<a<<endl; } void Show() { cout<<a<<endl; } }; void g_Fun(CExample C) { cout<<"test"<<endl; } int main() { CExample test(1); g_Fun(test); }
调用g_Fun()时,会产生以下几个重要步骤:
(1)test对象传入形参时产生一个临时变量temp。
(2)调用拷贝构造函数把test的值给temp。
(3)等g_Fun()执行完后析构掉temp对象。
2. 对象以值传递的方式从函数返回
#include<iostream>using namespace std;class CExample { private: int a; public: CExample(int b) { a=b; cout<<"creat: "<<a<<endl; } CExample(const CExample& C) { a=C.a; cout<<"copy"<<endl; } ~CExample() { cout<<"delete: "<<a<<endl; } void Show() { cout<<a<<endl; } }; CExample g_Fun() { CExample test(1); return test; } int main() { g_Fun(); }
当g_Fun()执行到return时,会产生以下几个重要步骤:
(1)产生一个临时变量temp。
(2)调用拷贝构造函数把test的值给temp。
(3)在函数执行到最后先析构test局部变量。
(4)等g_Fun()执行完后再析构掉temp对象。
3. 对象需要通过另外一个对象进行初始化
CExample A(100); CExample B=A; CExample B(A);后两句都会调用拷贝构造函数。
三. 浅拷贝和深拷贝
1. 默认拷贝构造函数
很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是默认拷贝构造函数。这个构造函数很简单,仅仅使用老对象的数据成员的值对新对象的数据成员一一进行赋值,它一般具有下面形式。
Rect::Rect(const Rect& r) { width=r.width; height=r.height; }当然,以上代码不用我们编写,编译器会为我们自动生成。但是如果认为这样就可以解决对象的复制问题,那就错了。
#include<iostream>using namespace std;class Rect { public: Rect() { count++; } ~Rect() { count--; } static int getCount() { return count; } private: int width; int height; static int count; }; int Rect::count=0; int main() { Rect rect1; cout<<"The count of Rect: "<<Rect::getCount()<<endl; Rect rect2(rect1); cout<<"The count of Rect: "<<Rect::getCount()<<endl; }
#include<iostream>using namespace std;class Rect { public: Rect() { count++; } Rect(const Rect& r) { width=r.width; height=r.height; count++; } ~Rect() { count--; } static int getCount() { return count; } private: int width; int height; static int count; }; int Rect::count=0; int main() { Rect rect1; cout<<"The count of Rect: "<<Rect::getCount()<<endl; Rect rect2(rect1); cout<<"The count of Rect: "<<Rect::getCount()<<endl; }
所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下浅拷贝已经能很好地工作了,但是一旦对象存在了动态成员,浅拷贝就会出问题了。
#include<iostream>using namespace std;class Rect { public: Rect() { p=new int(100); } ~Rect(){ if(p!=NULL) { delete p; } } private: int width; int height; int *p; }; int main() { Rect rect1; Rect rect2(rect1); }
执行赋值后的内存情况如下:
当然,这不是我们所期望的结果。在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用深拷贝。
3. 深拷贝
在深拷贝的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照下面的方式进行处理。
#include<iostream>using namespace std;class Rect { public: Rect() { p=new int(100); } Rect(const Rect& r) { width=r.width; height=r.height; p=new int; *p=*(r.p); } ~Rect() { if(p!=NULL) { delete p; } } private: int width; int height; int *p; };int main() { Rect rect1; Rect rect2(rect1); }执行赋值后的内存情况如下:
此时rect1的p和rect2的p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的深拷贝。
4. 防止默认拷贝发生
通过对对象复制的分析,我们发现对象的复制大多在进行值传递时发生,这里有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。
#include<iostream>using namespace std;class CExample { private: int a; CExample(const CExample& C); public: CExample(int b) { a=b; cout<<"creat: "<<a<<endl; } ~CExample() { cout<< "delete: "<<a<<endl; } void Show() { cout<<a<<endl; } }; void g_Fun(CExample C) { cout<<"test"<<endl; } int main() { CExample test(1); //g_Fun(test); 按值传递将出错 }四. 拷贝构造函数的几个细节
1. 拷贝构造函数里能调用private成员变量吗?
解答:这个问题是在网上见的,当时一下子有点晕。其时从名子我们就知道拷贝构造函数其实就是一个特殊的构造函数,操作的还是自己类的成员变量,所以不受private的限制。
2. 以下函数哪个是拷贝构造函数,为什么?
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&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数。
3. 一个类中可以存在多于一个的拷贝构造函数吗?
解答:类中可以存在超过一个拷贝构造函数。如果一个类中只存在一个参数为 X& 的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化。如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数。这个默认的参数可能为 X::X(const X&)或 X::X(X&),由编译器根据上下文决定选择哪一个。
- 拷贝构造函数详解
- 拷贝构造函数详解
- 拷贝构造函数详解
- 拷贝构造函数详解
- 详解拷贝构造函数
- 详解拷贝构造函数
- 拷贝构造函数详解
- 拷贝构造函数详解
- 【C++】拷贝构造函数
- C++:拷贝构造函数
- c++"拷贝构造函数
- C++: 拷贝构造函数
- 【C++】拷贝构造函数
- c++::拷贝构造函数
- 【C++】:拷贝构造函数
- 【C++】拷贝构造函数
- C++:拷贝构造函数
- C++拷贝构造函数详解
- AJAX——核心XMLHttpRequest对象
- 终于在windows上成功编译caffe,特分享过程
- 面试没通过?因为你忽略了这些!
- 几个常用的操作系统进程调度算法
- 程序猿怎么拿高工资
- C++拷贝构造函数详解
- Android Dev Intro - Camera addCallbackBuffer
- JQuery操作表单总结(不断总结中……)
- 海量数据的topK问题
- myeclipse2014/eclipse luna 等最新版本安装aptana插件
- EFX.EVX.v6.0.WiNNT2K 1CD(注塑模具工具)
- git 远程仓库 remote 使用总结
- SVN的使用
- iOS 三种录制视频方式