C++深复制(深拷贝)、浅复制(浅拷贝)和复制构造函数(拷贝构造函数)详解+实例
转载出处:https://blog.csdn.net/sxhelijian/article/details/23209967
对象的复制
对于普通类型的对象来说,它们之间的复制是很简单的,例如:
int a=88;
int b=a;
double f=3.12;
double d(f);
而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种数据成员。下面看一个类对象复制的简单例子。
#include <iostream>
using namespace std;
class Test
{
private:int a,b;
public:Test(int x, int y) //提供的形式参数,是为了给数据成员直接初始化的{a=x;b=y;}Test(const Test& C) //复制构造函数,提供一个同类型对象作为参数{a=C.a;b=C.b;}void show (){cout<<a<<" "<<b<<endl;}
};int main()
{Test a(100,10); //执行构造函数Test::Test(int x, int y)Test b(a); //执行构造函数Test::Test(const Test& C)Test c=a; //也执行构造函数Test::Test(const Test& C)b.show();c.show();return 0;
}
运行程序,屏幕输出两行100 10。
从以上代码的运行结果可以看出,系统在声明对象b和c时,完成了由对象a的复制。
复制构造函数
就类对象而言,相同类型的类对象是通过复制构造函数来完成整个复制过程的。
上例中的Test::Test(const Test& C),就是我们自定义的复制构造函数。
可见,复制构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,通常拷贝构造函数的参数是某个对象的引用名,用来约束作为参数的对象在构造新对象中是不能被改变的。
略一归纳:类X的复制构造函数的形式为X(X& x)。
当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,复制构造函数就会被自动调用。也就是说,当类的对象需要复制时,复制构造函数将会被调用。以下情况都会调用复制构造函数:
- 一个对象以值传递的方式传入函数体
- 一个对象以值传递的方式从函数返回
- 一个对象需要通过另外一个对象进行初始化。
如果在类中没有显式地声明一个复制构造函数,那么,编译器将会自动生成一个默认的复制构造函数,该构造函数完成对象之间的浅复制,后面将进行说明。
自定义复制构造函数是一种良好的编程风格,它可以阻止编译器形成默认的复制构造函数,提高源码效率。
浅复制和深复制
深拷贝意味着拷贝了资源和指针,而浅拷贝只是拷贝了指针,没有拷贝资源。 这样使得两个指针指向同一份资源,造成对同一份析构两次,程序崩溃。
所谓浅复制,如同上面出现过的构造函数中处理的一样,直接为数据成员赋值即可。在很多情况下,这是可以的。创建新的对象,要为对象的数据成员分配存储空间,直接赋值就将值保存在相应的空间中。
然而,这种浅复制,却并不能通行天下,下面的程序中,浅复制带来了问题。
#include <iostream>
#include <cstring>
using namespace std;
class Test
{
private:int a;char *str;
public:Test(int b, char *s){a=b;strcpy(str,s); //肇事地点,但不是祸端}Test(const Test& C){a=C.a;strcpy(str,C.str);}void show (){cout<<a<<","<<str<<endl;}
};int main()
{Test a(100,"hello");Test b(a);a.show();b.show();return 0;
}
程序运行中,会弹出一个窗口:程序的执行意外停止了。面对这个窗口,我们应该有感觉,这和使用指针不当有关系。
我们从main函数看起。
当程序执行到第28行Test a(100,"hello");时,对象a的数据成员a获得实际参数100的值,而数据成员str,即指针,是个随机地址值(指针的值,非指针指向的值)!在程序中,试图通过strcpy(str,s);将形式参数s指向的字符串"hello",复制到str所指向的那个位置,而那个位置,其地址并不是经过系统分配来的,这是个危险的操作。在这里,str这样未经过分配的地址(指针),被称为“野指针”。
在执行第29行Test b(a);时,同样的事情还要发生。
str这样的野指针是多么的霸道!在有的系统里,这样的行为是被接受的,可以想到其有多危险。有些系统中,不允许这样的事情发生的。于是,上面的程序在codeblock中调试会出错。这个错来的好。
解决这样的问题的方法,就是在构造函数中,要为指针类型的成员,分配专门的空间。以这条规则构建的复制,称作为深复制!
上面的程序,改写为:
#include <iostream>
#include <cstring>
using namespace std;
class Test
{
private:int a;char *str;
public:Test(int b, char *s){a=b;str=new char[strlen(s)+1]; //分配str指向的空间,其长度根据s指向的字符串定。为何加1?字符串结束要用\0strcpy(str,s); //前一程序的肇事地点,祸端已经由上一句摘除}Test(const Test& C){a=C.a;str=new char[strlen(C.str)+1]; //同上,这样str就不是野指针了strcpy(str,C.str);}~Test(){delete []str;}void show (){cout<<a<<","<<str<<endl;}
};int main()
{Test a(100,"hello");Test b(a);a.show();b.show();return 0;
}
好了,a和b对象的str成员,明确地给分配了空间,他们再不是野指针了。因为明确地分配了空间,析构函数中要释放对应的空间。我们不能用野指针,当然,也不能对象要撤销了,还占着空间不放,做事不能这样不厚道。
深复制就体现在第13和第19行分配指针指向的空间,这段空间的地址,也将是指针的值(分清指针的值和指针指向的值)。
再一个深复制的例子
下面再给一个例子,类A的数据成员可以保存len个整型数据。类中的数据成员arrayAddr是指向整型的指针,可以作为一个一元数组的起始地址。这个类有指针数据成员,构造函数的定义中,必须要采用深复制的方法,第16行体现了这一点。另外,析构函数中完成了对分配的空间的释放
#include<iostream>
using namespace std;
class A
{
private:int *arrayAddr;//保存一个有len个整型元素的数组的首地址int len; //记录动态数组的长度
public:A(int *a, int n);~A();int sum();
};
A::A(int *a, int n)
{len=n;arrayAddr=new int[n]; //为指针数据成员分配空间,注意,没有上面例子中加1那回事for(int i=0; i<n; i++) //逐个地将a指向的值逐个地复制过来{arrayAddr[i]=a[i];}
}
//析构函数的类外定义,释放指针型数据a所指向的空间
A::~A()
{delete [] arrayAddr;
}
int A::sum() //获得a指向的数组中下标为i的元素的值
{int s=0;for(int i=0; i<len; i++) //逐个地将a指向的值逐个地复制过来{s+=arrayAddr[i];}return s;
}int main(){int b[10]= {75, 99, 90, 93, 38, 15, 5, 7, 52, 4};A r1(b,10);cout<<"和:"<<r1.sum()<<endl;int c[15] = {18,68,10,52,3,19,12,100,56,96,95,97,1,4,93};A r2(c,15);cout<<"和:"<<r2.sum()<<endl;return 0;
}
C++深复制(深拷贝)、浅复制(浅拷贝)和复制构造函数(拷贝构造函数)详解+实例相关推荐
- 文件的复制、移动、压缩等对SELinux属性关系详解
文件的复制.移动.压缩等对SELinux属性关系详解 1.临时修改文件的类型属性 文件的类型属性不正确是常见的SELinux拒绝访问的主要原因 1)修改文件的SELinux属性: [root@loca ...
- 浅谈Android onTouchEvent 与 onInterceptTouchEvent的区别详解
浅谈Android onTouchEvent 与 onInterceptTouchEvent的区别详解 本篇文章小编为大家介绍,Android onTouchEvent 与 onInterceptTo ...
- android onclick执行顺序,浅谈onTouch先执行,还是onClick执行(详解)
有一个Button 按钮,要想为该按钮设置onClick事件和OnTouch事件 mTestButton.setOnClickListener(new View.OnClickListener() { ...
- java 深克隆(深拷贝)与浅克隆(拷贝)详解
java深克隆和浅克隆 基本概念 浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所拷贝的对象,而不复制它所引用的对 ...
- css inport作用,浅谈css和@import区别及用法详解
下面小编就为大家带来一篇浅谈css和@import区别及用法.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 css和@import都是调用外部样式表的方法. 一.用法 ...
- 浅谈 —— AAA认证(认证+授权)详解+配置
目录 一.AAA认证简介: 二.认证流程: 三.相关配置配置: (1)认证: (2)授权: 一.AAA认证简介: AAA是认证(Authentication).授权(Authorization)和计费 ...
- C++拷贝构造函数(深拷贝,浅拷贝)
对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. ...
- C++拷贝构造函数(深拷贝与浅拷贝)
对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. ...
- C++中的深拷贝和浅拷贝(详解)
2020-07-13 拷贝构造函数是一种特殊的构造函数,在创建对象时,它是使用同一类中之前创建过的对象来初始化新创建的对象.如果没有自定义拷贝构造函数,系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造 ...
最新文章
- 第六篇:并发-粒度锁
- 解释上采样和PixelShuffle
- Python小游戏之 - 飞机大战 !
- [JavaWeb]web相关概念回顾
- P2059 [JLOI2013]卡牌游戏
- Android 内存泄漏分析(完)
- 不了解沙特,那你就看不懂硅谷
- FFMpeg写MP4文件例子分析
- 数组的常用方法 Array;
- ibm笔记本电脑电池_笔记本电脑是一直插着电源好,还是拔了电源好?
- 载体构建实例解析——构建 SETD3-pEGFP-N1(Snapgene 设计引物)
- 双系统linux开机黑屏,解决双系统中ubuntu开关机异常,黑屏,出现“nouveau , SCHED_ERROR”字样等的问题...
- Cropper使用(图片裁切)
- 485芯片中slew-rate-limited是什么意思(转)
- 中美跨境电商贸易投资云洽会成功举办;TT Shop和TTforBusiness将互通;PhonePe月破20亿...|洞悉跨境
- 2019网红带货电商新趋势,与传统电商说再见-云秀传媒
- Opencv中的ROI介绍
- 蓝桥杯单片机——串口通信1(11)
- Python+Wind:用Pyautogui轻松下载Wind数据
- 我在印尼工作的日子-工作餐食