C++常见的一个坑,一个经典的面试考题:深拷贝和浅拷贝问题

我们知道,对一个已知对象进行拷贝,编译系统会自动调用拷贝构造函数,如果用户未定义拷贝构造函数,则会调用默认拷贝构造函数。
我们先来看一个测试案例:

#include <iostream>
#include <string>

using namespace std;

class Student
{
public:
	Student() {
		cout << "无参构造函数" << endl;
	}
	
	Student(int age, int id) {
		m_Age = age;
		m_Id = new int(id);
		cout << "有参构造函数" << endl;
	}
	
	~Student() {
		cout << "析构函数调用" << endl;
	}
	
	int m_Age;
	int *m_Id;
};
void test01()
{
	Student s1(15, 01);
	cout << "s1的年龄为" << s1.m_Age << "  s1的学号为" << *s1.m_Id << endl;
	Student s2(s1);
	cout << "s2的年龄为" << s2.m_Age << "  s2的学号为" << *s2.m_Id << endl;

}
int main()
{
	test01();
	system("pause");
	return 0;
}

在这里插入图片描述
现在,我们运行出来的结果和想象当中一样,看似没有什么问题。但事实上,是这当中是存在问题的,id是我们在堆区new出来内存,这需要我们手动释放,但是我们上面并没有将其释放。那接下我们将代码稍微改一下,在析构函数当中添加代码,将其释放

#include <iostream>
#include <string>

using namespace std;

class Student
{
public:
	Student() {
		cout << "无参构造函数" << endl;
	}
	
	Student(int age, int id) {
		m_Age = age;
		m_Id = new int(id);
		cout << "有参构造函数" << endl;
	}
	
	~Student() {
		//析构函数的作用就是将堆区开辟出来的内存释放
		if (m_Id != NULL)
		{
			delete m_Id;
			m_Id = NULL;	//将指针置空,防止野指针出现
		}
		cout << "析构函数调用" << endl;
	}
	
	int m_Age;
	int *m_Id;
};
void test01()
{
	Student s1(15, 01);
	cout << "s1的年龄为" << s1.m_Age << "  s1的学号为" << *s1.m_Id << endl;
	Student s2(s1);
	cout << "s2的年龄为" << s2.m_Age << "  s2的学号为" << *s2.m_Id << endl;

}
int main()
{
	test01();
	system("pause");
	return 0;
}

这样看起来这代码似乎就比较规范了,但是我们一运行,会发现程序中断,直接崩溃。
在这里插入图片描述
百因必有果,这个错误的原因就是:两个对象的Id指针成员所指向的内存相同,Id指针被分配一次内存,但是程序结束时该内存被释放了两次!

这是由于编译器在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!
什么是浅拷贝,浅拷贝就是对指针Id拷贝后会出现两个指针指向同一个内存空间。

在这里插入图片描述
所以,成员有在堆区开辟的,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了悬空指针问题和内存重复释放问题的发生。
所以我们应该将代码改成这个样子:

#include <iostream>
#include <string>

using namespace std;

class Student
{
public:
	Student()
	{
		cout << "无参构造函数" << endl;
	}
	Student(int age, int id)
	{
		m_Age = age;
		m_Id = new int(id);
		cout << "有参构造函数" << endl;
	}
	Student(const Student &s)
	{
		cout << "拷贝构造函数!" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_Age = s.m_Age;
		m_Id = new int(*s.m_Id);
	}
	~Student()
	{
		//析构函数的作用就是将堆区开辟出来的内存释放
		if (m_Id != NULL)
		{
			delete m_Id;
			m_Id = NULL;	//将指针置空,防止野指针出现
		}
		cout << "析构函数调用" << endl;
	}

	int m_Age;
	int *m_Id;
};
void test01()
{
	Student s1(15, 01);
	cout << "s1的年龄为:" << s1.m_Age << "  s1的学号为:" << *s1.m_Id << endl;
	Student s2(s1);
	cout << "s2的年龄为:" << s2.m_Age << "  s2的学号为:" << *s2.m_Id << endl;

}
int main()
{
	test01();

	system("pause");
	return 0;
}

执行结果
在这里插入图片描述

总结一下:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝对指针和内容都进行拷贝,经深拷贝后的指针是指向两个不同地址的指针
再说一句:如果成员有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C/C++面试之算法系列--几个典型的内存拷贝及字符串函数实现 写一个函数,完成内存之间的拷贝。[考虑问题是否全面,是否考虑内存重叠问题] 返回void *支持链式操作,参数类型是void *以支持任意类型的指针,输入参数加上const修饰,最好加上assert对输入输出指针进行非NULL判断 void* memcpy( void *dest, const void *src, size_t count ) { char* pdest = static_cast<char*>( dest ); const char* psrc = static_cast<const char*>( src ); // 依次从前拷贝,目的地址覆盖了源地址的数,此时从后往前拷贝 if( (pdest>psrc) && (pdest<(psrc+count))) //能考虑到这种情况就行了 { for( size_t i=count-1; i!=-1; --i ) pdest[i] = psrc[i]; } else { for( size_t i=0; i<count; ++i ) pdest[i] = psrc[i]; } return dest; } int main( void ) { char str[] = "0123456789"; memcpy( str+1, str+0, 9 ); cout << str << endl; memcpy( str, str+5, 5 ); cout << str << endl; system( "Pause" ); return 0; // 0012345678 // 4567845678 } strcmp(): Int simple_strcmp (const char *s1, const char *s2) { int ret; while ((ret = *(unsigned char *) s1 - *(unsigned char *) s2++) == 0 && *s1++); return ret; } memcmp(): int simple_memcmp (const char *s1, const char *s2, size_t n) { int ret = 0; while (n--&& (ret = *(unsigned char *) s1++ - *(unsigned char *) s2++) == 0); return ret; } strcmp只判断s1的‘/0’,没有长度的控制;memcmp有长度参数n的控制 memcpy(): char *simple_memcpy (char *dst, const char *src, size_t n) { char *ret = dst; while (n--) *dst++ = *src++; return ret; } 直接的内存之间的copy,不处理内存重叠的情况。 strcpy(): char *simple_strcpy (char *dst, const char *src) { char *ret = dst; while ((*dst++ = *src++) != '\0'); //相当简约,一句话即搞定 return ret; } 与memcpy区别就是对'\0'作为结束符 strncpy(): char *simple_strncpy (char *dst, const char *src, size_t n) { char *ret = dst; while (n--) { if ((*dst++ = *src++) == '\0') { while (n--) *dst++ = '\0'; return ret; } } return ret; } n和‘\0'都作为结束的控制符。如果n过大,则后面全补'\0'
好的,我明白了您的问题。下面是C语言浅拷贝深拷贝的例子: 浅拷贝: ``` #include <stdio.h> #include <string.h> struct Book { char title[50]; char author[50]; int book_id; }; int main() { struct Book book1; struct Book book2; strcpy(book1.title, "C Programming"); strcpy(book1.author, "Author1"); book1.book_id = 1; book2 = book1; printf("Book 1 title : %s\n", book1.title); printf("Book 2 title : %s\n", book2.title); return 0; } ``` 在上面的代码中,`book2 = book1` 会进行浅拷贝,即将 `book1` 的值复制到 `book2` 中,但是两个结构体共享同一个内存空间,如果修改一个结构体的值,另一个也会跟着改变。 深拷贝: ``` #include <stdio.h> #include <string.h> #include <stdlib.h> struct Book { char title[50]; char author[50]; int book_id; }; int main() { struct Book book1; struct Book book2; strcpy(book1.title, "C Programming"); strcpy(book1.author, "Author1"); book1.book_id = 1; // 分配内存并将 book1 的值复制到新的内存空间中 book2.title = (char*)malloc(strlen(book1.title) + 1); strcpy(book2.title, book1.title); // 类似的方式复制 book1 的 author 字段 book2.author = (char*)malloc(strlen(book1.author) + 1); strcpy(book2.author, book1.author); book2.book_id = book1.book_id; printf("Book 1 title : %s\n", book1.title); printf("Book 2 title : %s\n", book2.title); // 需要手动释放内存,避免内存泄漏 free(book2.title); free(book2.author); return 0; } ``` 在上面的代码中,我们为 `book2` 分配了新的内存空间,并且手动将 `book1` 的值复制到新的内存空间中,这样做就可以避免两个结构体共享同一个内存空间的问题。需要注意的是,如果进行了动态内存分配,在结束程序前需要手动释放内存,避免内存泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值