C++ 的浅拷贝和深拷贝(结构体)
关于浅拷贝和深拷贝这个问题遇上的次数不多,这次遇上整理一下,先说这样一个问题,关于浅拷贝的问题,先从最简单的说起。
假设存在一个结构体:
struct Student
{
string name;
int age;
};
int main()
{
struct Student stu = {"liming", 18};
struct Student stu2 = {"wanger", 20};
stu2 = stu; cout<<"age is : "<< stu2.age <<endl;
cout<<"name is :"<< stu2.name<<endl; }
这样可以看到的结果是:
age is : 18
name is :liming
说明此时的拷贝是成功的,此时的结构体可以通过“=”来直接进行赋值操作,但是接下来的问题产生了,假设存在如下的结构体:
struct stu
{
int i;
char c;
char* p;
}; int main()
{
struct stu s1,s2;
char * str = "rabbit is cute";
s1.i = 345;
s1.c = 'y';
s1.p = (char*)str;
s2 = s1;
printf("s2 %d, %c, %s\n", s2.i, s2.c, s1.p);
printf("s1 ptr: %d, s2 ptr : %d\n", s1.p, s2.p); }
产生的结果是这样的:
s2 345, y, rabbit is cute
s1 ptr: 7934, s2 ptr : 7934
可以看到的是S2 确实得到了S1 传递的值,但是第二句的话却说明这样的一个问题,其实S2和S1的指针p都指向一个内存地址,这又说明了什么?
这说明指针的并没有将内容复制一块给新指针来指向,只是让新指针指向原来的那个内存,这样就相当于,指针在这个复制的过程中只是复制了地址,而不是内容。
原理:
在拷贝过程中,如果没有自定义拷贝构造函数,系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型成员变量,调用其相应类型的拷贝构造函数。但是注意缺省的构造函数却是这样的:缺省拷贝构造函数在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标--浅拷贝。
这就是产生问题的原因了,浅拷贝出现了。。。
用下图来解释这个问题:

在进行对象复制后,事实上s1、s2里的成员指针p都指向了一块内存空间(即内存空间共享了),在s1析构时,delete了成员指针p所指向的内存空间,而s2析构时同样指向(此时已变成野指针)并且要释放这片已经被s1析构函数释放的内存空间,这就让同样一片内存空间出现了“double free” ,从而出错。而浅拷贝还存在着一个问题,因为一片空间被两个不同的子对象共享了,只要其中的一个子对象改变了其中的值,那另一个对象的值也跟着改变了。
为了实现深拷贝,往往需要自己定义拷贝构造函数,在源代码里,我们加入自定义的拷贝构造函数如下:
在结构体中加入自己的拷贝构造函数:
struct stu
{
int i;
char c;
char* p;
stu operator=(stu& stuTmp)
{
i = stuTmp.i;
c = stuTmp.c;
p = new char(strlen(stuTmp.p) + 1);
strcpy(p, stuTmp.p);
return *this;
};
}; int main()
{
struct stu s1,s2;
char * str = "rabbit is cute";
s1.i = 345;
s1.c = 'y';
s1.p = (char*)str;
s2 = s1;
printf("s2 %d, %c, %s\n", s2.i, s2.c, s1.p);
printf("s1 ptr: %d, s2 ptr : %d\n", s1.p, s2.p); }
测试demo
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string> using namespace std; class stu
{
public:
int i;
char c;
char* p;
stu operator=(stu& stuTmp)
{
this->i = stuTmp.i;
this->c = stuTmp.c;
this->p = new char(strlen(stuTmp.p) + 1);
for (int i = 0; i < strlen(stuTmp.p); i++)
{
this->p[i] = stuTmp.p[i];
}
//strcpy(pp, stuTmp.p);
return *this;
};
}; int main()
{
struct stu s1, s2;
char * str = "rabbit is cute";
s1.i = 345;
s1.c = 'y';
s1.p = (char*)str;
s2 = s1;
printf("s2 %d, %c, %s\n", s2.i, s2.c, s1.p);
printf("s1 ptr: %d, s2 ptr : %d\n", s1.p, s2.p);
cin.get(); }
相当于重载operator=方法,这样还是运行,产生的结果就是这样的:
s2 345, y, rabbit is cute
s1 ptr: 7910, s2 ptr : 1050000
此时s1和s2中的指针p指向了不同的地址,可以打印一下此时这两个指针的内容是否一样,加入一下代码:
printf("s1 ptr: %s, s2 ptr : %s\n ", s1.p, s2.p);
产生的结果是:s1 ptr: rabbit is cute, s2 ptr : rabbit is cute
此时s1和s2中的p指针地址不同,但是指向的内容一致,所以这拷贝成功。
其实类的结构和上面的结构体是类似的,其实可以将结构体看成一个类来处理,结构体也可有自己的构造、析构、重载运算符河函数,可以简单的认为结构体是类的一种形式。
拷贝有两种:深拷贝,浅拷贝
当出现类的等号赋值时,会调用拷贝函数 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。 但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。 所以,这时,必须采用深拷贝。 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。 简而言之,当数据成员中有指针时,必须要用深拷贝。
建议:
我们在定义类或者结构体,这些结构的时候,最后都重写拷贝构造函数,避免浅拷贝这类不易发现但后果严重的错误产生。
C++ 的浅拷贝和深拷贝(结构体)的更多相关文章
- [c/c++] programming之路(28)、结构体存储和内存对齐+枚举类型+typedef+深拷贝和浅拷贝
一.结构体存储 #include<stdio.h> #include<stdlib.h> struct info{ char c; //1 2 4 8 double num; ...
- C语言中结构体的深拷贝和浅拷贝
C++ 的浅拷贝和深拷贝(结构体) 拷贝有两种:深拷贝,浅拷贝 浅拷贝:拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标 (1)结构体中不存在指针成员变量时 typ ...
- PAT 甲级 1032 Sharing (25 分)(结构体模拟链表,结构体的赋值是深拷贝)
1032 Sharing (25 分) To store English words, one method is to use linked lists and store a word let ...
- C#浅拷贝与深拷贝区别
也许会有人这样解释C# 中浅拷贝与深拷贝区别: 浅拷贝是对引用类型拷贝地址,对值类型直接进行拷贝. 不能说它完全错误,但至少还不够严谨.比如:string 类型咋说? 其实,我们可以通过实践来寻找答案 ...
- 17.结构体(typedef)
1.结构体 a.结构体类型定义b.结构体变量定义c.结构体变量的初始化d.typedef改类型名e.点运算符和指针法操作结构体f.结构体也是一种数据类型,复合类型,自定义类型 2.结构体变量的定义 ( ...
- C语言复习:结构体
结构体专题 01.结构体类型定义及结构体变量定义 char c1,char c2, char name[62]; int age char name[62]; int age,char ...
- C# 之String以及浅拷贝与深拷贝
一.String到底是值类型还是引用类型 MSDN 中明确指出 String 是引用类型而不是值类型,但 String 表面上用起来却像是值类型,这又是什么原因呢? 首先从下面这个例子入手: //值 ...
- ObjectiveC中的赋值,对象拷贝,浅拷贝与深拷贝
在开发过程中我们经常会遇到对象拷贝的问题,下面我们分别讨论赋值操作.对象拷贝.以及浅拷贝(Shallow copy)与深拷贝(Deep copy)的区别与各自的实现方式. 一.不同对象的赋值操作 Ob ...
- C语言提高 (5) 第五天 结构体,结构体对齐 文件
1昨日回顾 2作业讲解 3 结构体的基本定义 //1 struct teacher { int id; char name[64]; }; struct teacher t5 = { 5, " ...
随机推荐
- Seesion和Cookie详解2
转载来自: https://www.toutiao.com/a6693986851193094664/?tt_from=weixin&utm_campaign=client_share& ...
- Tomcat配置,Myeclipse破解和各种设置
转自:http://www.cnblogs.com/tyjsjl/archive/2006/11/14/2156111.html 根据tomcat来配置eclipse和MyEclipse结合使用起来, ...
- 【读书笔记】iOS-GCD-用法
代码: -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { dispatch_async(dispatch_get_gl ...
- sanic官方文档解析之Example(一)
1,示例 这部的文档是简单的示例集合,它能够帮助你快速的启动应用大部分的应用,这些应用大多事分类的,并且提供给ini工作的连接代码: 1.1,基础示例 这部分示例集成了提供简单sanic简单的代码 单 ...
- debian old version cd and distribution archives
1 debian old version cd/dvd 官网的old version image,下载速度很慢 http://cdimage.debian.org/cdimage 下面这个靠谱,是镜像 ...
- Vue实现仿淘宝商品详情属性选择的功能
Vue实现仿淘宝商品详情属性选择的功能 先看下效果图:(同个属性内部单选,属性与属性之间可以多选) 主要实现过程: 所使用到的数据类型是(一个大数组里面嵌套了另一个数组)具体格式如下: attrA ...
- 一步一步学Silverlight 2系列(26):基本图形
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- [yii2]urlmanger
首先配置下nginx,确保可以不使用index.php来访问 server{ listen 8082; server_name yii2.dev; access_log logs/yii2.acces ...
- bzoj 1098 办公楼biu —— 链表+栈
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1098 首先,没有连边的人一定得在一个连通块里: 先把所有人连成一个链表,然后从第一个人开始, ...
- Oracle 安装报错 [INS-06101] IP address of localhost could not be determined 解决方法输入日志标题
安装Oracle 11gR2,报错:[INS-06101] IP address of localhost could not be determined 出现这种错误是因为主机名和/etc/host ...