详解常见面试题---深拷贝和浅拷贝(C++)

简介: 详解常见面试题---深拷贝和浅拷贝(C++)

C++类对象的赋值操作分为深拷贝和浅拷贝两种,我们所说的的浅拷贝就是赋值操作,这种拷贝比较常见,比如:

class A
{
public:
  A();
  ~A();
public:
  int i;
};
A::A()
{
  i = 5;
}
A::~A()
{
}
void main()
{
  A a1;//执行默认构造函数  初始化a1.i = 7
  A a2 = a1;//发生赋值操作  此刻a2.i = 5
  a2.i = 7;//只修改a2中的成员,对a1中的不造成影响
  return;
}

这就是一个简单的浅拷贝,当我们拷贝之后,发现变量 i 的值确实被拷贝过来了,此时a1和a2中的变量值都一样。

image.png

而我们对a2中的 i 进行操作,修改其中的内容的时候,并不影响a1中 i 的值。

image.png

但是如果我们的类中涉及到动态内存的申请与释放,那么简单的浅拷贝就会出现问题:

class A
{
public:
  A();
  ~A();
public:
  char* ptr1;
};
A::A()
{
  ptr1 = new char[5];
  memset(ptr1, 'h', 5);
}
A::~A()
{
  if (ptr1 != nullptr)
  {
  delete[]ptr1;
  ptr1 = nullptr;
  }
}
void main()
{
  A a1;//执行默认构造函数
  A a2 = a1;//发生赋值操作
  return;
}

涉及到动态内存的分配与释放的时侯就比较危险,可能会造成对内存的二次释放,出现内存堆被破坏,导致内存奔溃的情况发生!这是为什么呢?我们上述代码中,对于类对象的赋值是简单的拷贝操作,也就是直接把a1中申请的那块内存的值复制给了a2中的ptr1对象,它相当于做了这样一个操作:

a2.ptr1 = a1.ptr1;

我们也可以通过调试信息来验证我们的这个说法:

image.png

我们可以从图中看到两个指针指向的地址是同一个地址,所以这只是指针的简单赋值,并没有开辟新的内存空间,那么我们都知道,当类的对象销毁的时候,类的析构函数会被执行,这样我们就会执行析构函数中的内存释放操作,我们目前在main函数中有两个对象a1和a2,那么肯定会分别执行各自的析构函数。那么就会对同一块内存区域释放两次,肯定会导致堆的奔溃,自然就导致内存奔溃!我们可以从动图中看到,第一次释放内存没问题!第二次释放内存导致内存奔溃!

image.png


但是我们可以用深拷贝来解决这个问题!

class A
{
public:
  A();//默认构造函数
  A(const A& A1);//拷贝构造函数
  ~A();
public:
  char* ptr1;
};
A::A()
{
  ptr1 = new char[5];
  memset(ptr1, 'h', 5);
}
A::A(const A &A1)
{
  ptr1 = new char[5];
  strcpy(ptr1, A1.ptr1);
}
A::~A()
{
  if (ptr1 != nullptr)
  {
  delete[]ptr1;
  ptr1 = nullptr;
  }
}
void main()
{
  A a1;//执行默认构造函数  
  A a2 = a1;//发生深拷贝赋值操作,另外开辟了内存空间
  A a3(a1);//发生深拷贝赋值操作,另外开辟了内存空间
  return;
}

在类中定义一个拷贝构造函数,传参传入当前类的引用,然后对内存进行重新的申请,再将指针指向内存中的内容进行复制操作,这样我们就完成了深拷贝,在内存析构时候自然就不会出现二次释放同一片内存的问题,我们可以从如下的调试截图中看到三个ptr1指针指向了不同的三个地址空间!

image.png

最后来看一下最终析构时候的调试动图,没有发生任何的内存奔溃。

image.png

目录
相关文章
|
7月前
|
存储 缓存 数据库
C/C++工程师面试题(数据库篇)
C/C++工程师面试题(数据库篇)
122 9
|
7月前
|
存储 算法 C++
C/C++工程师面试题(STL篇)
C/C++工程师面试题(STL篇)
148 6
|
6月前
|
存储 算法 编译器
C++面试题其一
C++文件编译与执行的四个阶段 预处理:处理#include、#define等预处理指令。 编译:将源码翻译为目标代码。 汇编:将目标代码转换为机器指令。 链接:将目标文件和库文件合并生成可执行文件。 STL中的vector的实现,是怎么扩容的? vector通过动态数组实现,当容量不足时,分配更大的内存(通常是原来的两倍),复制旧数据到新内存,并释放旧内存。
90 2
|
6月前
|
存储 程序员 编译器
C++面试题其二
extern "C" 用于告诉编译器按照C语言的链接方式处理代码,通常用于C++代码与C代码混合编程,以防止因名字修饰(name mangling)引起的链接错误。例如: extern "C" { void c_function(); } 通过这些问题的深入理解和解答,能够更好地掌握C++编程的核心概念和实际应用,为面试做好充分的准备。
81 1
|
6月前
|
存储 网络协议 编译器
【干货总结】Linux C/C++面试知识点
Linux C/C++基础与进阶知识点,不仅用于面试,平时开发也用得上!
628 17
|
4月前
|
存储 JavaScript 前端开发
JS浅拷贝及面试时手写源码
JS浅拷贝及面试时手写源码
|
7月前
|
存储 算法 C语言
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
71 5
|
7月前
|
存储 编译器 C语言
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(下)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
70 1
|
7月前
|
存储 编译器 Linux
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(中)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
72 1
|
7月前
|
JSON 前端开发 JavaScript
【JavaScript】面试手撕深拷贝(2),2024年最新nacos面试题及答案
【JavaScript】面试手撕深拷贝(2),2024年最新nacos面试题及答案
【JavaScript】面试手撕深拷贝(2),2024年最新nacos面试题及答案