C++ 默认拷贝构造函数 深度拷贝和浅拷贝
C++类默认拷贝构造函数的弊端
C++类的中有两个特殊的构造函数,(1)无参构造函数,(2)拷贝构造函数。它们的特殊之处在于:
(1) 当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数且其函数体为空;
(2) 当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝。(这个拷贝操作是浅拷贝)
这里只讲拷贝构造函数。在C语言中,
int a = 5; //初始化
int b;
b = 6; //赋值
上面的初始化及赋值操作是最正常不过的语法,C++语言肩挑兼容C语言语法的责任,所以在类的设计上,也兼容这种操作:
class cls
{
pubic:
//...
}
int main(void)
{
cls c1;
cls c2 = c1; //初始化类,还可以 cls c2(c1);
cls c3;
c3 = c1; //赋值类
//...
return 0;
}
如上的初始化类需要调用到cls类的默认实现的拷贝构造函数,为类赋值需要调用的是cls类的默认实现的赋值操作符重载函数,它们都是浅度拷贝的。前者其原型为:
cls(const cls& c)
默认的拷贝构造函数存在弊端,看如下类定义:
class TestCls{
public:
int a;
int *p;
public:
TestCls() //无参构造函数
{
std::cout<<"TestCls()"<<std::endl;
p = new int;
}
~TestCls() //析构函数
{
delete p;
std::cout<<"~TestCls()"<<std::endl;
}
};
类中的指针p在构造函数中分配的空间,在析构函数中释放。
int main(void)
{
TestCls t;
return 0;
}
编译运行确实不会出错:
类在我们没有定义拷贝构造函数的时候,会默认定义默认拷贝构造函数,也就是说可以直接用同类型的类间可以相互赋值、初始化:
int main(void)
{
TestCls t1;
TestCls t2 = t1; //效果等同于TestCls t2(t1);
return 0;
}
编译通过,运行却出错了:
原因就在于,默认的拷贝构造函数实现的是浅拷贝。
深度拷贝和浅拷贝
深度拷贝和浅拷贝在c语言中就经常遇到的了,在这里我简单描述。
一般的赋值操作是深度拷贝:
//深度拷贝
int a = 5;
int b = a;
简单的指针指向,则是浅拷贝:
//浅拷贝
int a = 8;
int *p;
p = &a;
char* str1 = "HelloWorld";
char* str2 = str1;
将上面的浅拷贝改为深度拷贝后:
//深度拷贝
int a = 8;
int *p = new int;
*p = a;
char* str1 = "HelloWorld";
int len = strlen(str1);
char *str2 = new char[len];
memcpy(str2, str1, len);
总而言之,拷贝者和被拷贝者若是同一个地址,则为浅拷贝,反之为深拷贝。
例:以字符串拷贝为例,浅拷贝后,str1和str2同指向0x123456,不管哪一个指针,对该空间内容的修改都会影响另一个指针。
深拷贝后,str1和str2指向不同的内存空间,各自的空间的内容一样。因为空间不同,所以不管哪一个指针,对该空间内容的修改都不会影响另一个指针。
解决默认拷贝构造函数的弊端
类的默认拷贝构造函数只会用被拷贝类的成员的值为拷贝类简单初始化,也就是说二者的p指针指向的内存空间是一致的。以前面TestCls可以知道,编译器为我们默认定义的拷贝构造函数为:
TestCls(const TestCls& testCls)
{
a = testCls.a;
p = testCls.p; //两个类的p指针指向的地址一致。
}
解释:main函数将要退出时,拷贝类t2的析构函数先得到执行,它把自身p指向的堆空间释放了;接下来,t1的析构函数得到调用,被拷贝类t1的析构函数得到调用,它同样要去析构自身的p指向指向的堆空间,但是该空间和t2类中p指向的空间一样,造成重复释放,程序运行崩溃。
解决办法十分简单,自定义拷贝构造函数,里面用深度拷贝的方式为拷贝类初始化:
class TestCls{
public:
int a;
int *p;
public:
TestCls()
{
std::cout<<"TestCls()"<<std::endl;
p = new int;
}
TestCls(const TestCls& testCls)
{
std::cout<<"TestCls(const TestCls& testCls)"<<std::endl;
a = testCls.a;
//p = testCls.p;
p = new int;
*p = *(testCls.p); //为拷贝类的p指针分配空间,实现深度拷贝
}
~TestCls()
{
delete p;
std::cout<<"~TestCls()"<<std::endl;
}
};
int main(void)
{
TestCls t1;
TestCls t2 = t1;
return 0;
}
编译运行正常:
关于c++拷贝构造函数的深度拷贝和浅拷贝的介绍到这里,其实还可以将它们的地址打印出来看看,不过这一步就不再赘述了。
拷贝构造函数其它妙用
自定义拷贝构造函数,并设置为private属性,其实现体可以什么都不写,那么这个类将变成一个不可被复制的类了。
[参考博文: https://blog.csdn.net/qq_29344757/article/details/76037255]
C++ 默认拷贝构造函数 深度拷贝和浅拷贝的更多相关文章
- C++拷贝构造函数(深拷贝,浅拷贝)
对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. #i ...
- C++拷贝构造函数(深拷贝,浅拷贝)
http://www.cnblogs.com/BlueTzar/articles/1223313.html C++拷贝构造函数(深拷贝,浅拷贝) 对于普通类型的对象来说,它们之间的复制是很简单的,例如 ...
- c++ 拷贝构造函数、拷贝运算符、析构函数
拷贝构造函数.拷贝运算符.析构函数 拷贝构造函数.拷贝运算符.析构函数 定义行为像值的类 class HasPtr{ public: HasPtr(const string &s = stri ...
- Python [拷贝copy / 深度拷贝deepcopy] | 可视化理解
Python 是一门面向对象的语言, 在Python一切皆对象. 每一个对象都有由以下三个属性组成: ------------------------------------------------- ...
- 【转】C++的拷贝构造函数深度解读,值得一看
建议看原帖 地址:http://blog.csdn.net/lwbeyond/article/details/6202256 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很 ...
- C++拷贝构造函数(深拷贝与浅拷贝)
转自http://blog.csdn.net/lwbeyond/article/details/6202256/ 一. 什么是拷贝构造函数 对于普通类型的对象来说,它们之间的复制是很简单的,例如:in ...
- C++之拷贝构造函数、深拷贝、浅拷贝
C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...
- c++拷贝构造函数,深拷贝,浅拷贝,对象内存
https://blog.csdn.net/lwbeyond/article/details/6202256 防止默认拷贝发生 通过对对象复制的分析,我们发现对象的复制大多在进行“值传递”时发生,这里 ...
- C++ 拷贝构造函数、拷贝赋值运算符、析构函数
每一次都会忘,做个笔记吧.想到哪里写到哪里. 拷贝构造函数 第一个参数必须是自身类类型的引用,且任何额外参数都有默认值.(为什么必须是引用?见后解释) 合成拷贝构造函数:如果我们没有为一个类定义拷贝构 ...
随机推荐
- JavaScript对象的两类属性(数据属性与访问器属性)
对JavaScript来说,属性并非只是简单的名称和值,JavaScript用一组特征(attribute)来描述属性 (property). 第一类属性数据属性具有四个特征. value:就是属性的 ...
- python 语法规范
在python shell 中输入 import this 可以看到python之禅 The Zen of Python, by Tim Peters Beautiful is better than ...
- Loj#2769-「ROI 2017 Day 1」前往大都会【最短路树,斜率优化】
正题 题目链接:https://loj.ac/p/2769 题目大意 给出\(n\)个点\(m\)条地铁线路,每条线路是一条路径. 求\(1\)到\(n\)的最短路且在最短路径的情况下相邻换乘点的距离 ...
- Java 使用 Socket 实现客户端和服务器的信息交互
服务器 public class Server{ private ServerSocket serverSocket; private Socket socket; private BufferedR ...
- JavaWeb#JSP内置对象
[1.JSP内置对象简介] 内置对象:不加声明就可以在JSP页面脚本中使用的成员变量.(使用这些对象可以更容易收集客户端发送的请求信息,响应客户端的请求及存储客户信息.) 主要介绍:out,reque ...
- 基于Tesseract组件的OCR识别
基于Tesseract组件的OCR识别 背景以及介绍 欲研究C#端如何进行图像的基本OCR识别,找到一款开源的OCR识别组件.该组件当前已经已经升级到了4.0版本.和传统的版本(3.x)比,4.0时代 ...
- 洛谷P7078 [CSP-S2020] 贪吃蛇 题解
比赛里能做出这题的人真的非常厉害,至少他的智商和蛇一样足够聪明. 首先有一个结论: 当前最强的蛇吃了最弱的蛇之后,如果没有变成最弱的蛇,他一定会选择吃! 证明: 假设当前最强的蛇叫石老板. 如果下一条 ...
- CF536D Tavas in Kansas(博弈论+dp)
貌似洛谷的题面是没有翻译的 QWQ 大致题面是这个样子,但是可能根据题目本身有不同的地方 完全懵逼的一个题(果然博弈论就是不一样) 首先,我们考虑把题目转化成一个可做的模型. 我们分别从\(s\)和\ ...
- SpringBoot配置文件application
配置文件 SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的,有两种文件格式: application.properties 语法结构 :key=value application. ...
- appium+Andriod环境搭建遇到问题
报错:Caused by: org.openqa.selenium.WebDriverException: An unknown server-side error occurred while pr ...