c++浅拷贝与深拷贝,从内存分析原理

先介绍一下浅拷贝与深拷贝的定义,再结合代码进行分析。

浅拷贝:
浅拷贝是指将一个对象的值复制到另一个对象,包括指针成员的值也进行简单的复制。这意味着两个对象会共享相同的内存地址,当其中一个对象修改了指针指向的内存时,另一个对象的指针也会受到影响。

深拷贝:
深拷贝是指在拷贝对象时,不仅复制对象本身的值,而且还会为指针成员单独分配内存空间,并将原对象指针指向的内容复制到新分配的内存中。这样,即使原对象和拷贝对象有各自独立的内存空间,彼此之间互不影响。

在C++中,对于普通变量的赋值操作,无论是原始数据类型(如int、float等)还是自定义的类对象,都是进行值的复制,这意味着在赋值操作后,原始变量和目标变量是相互独立的,它们存储的是两份完全独立的数值
因此,普通变量的赋值操作不存在浅拷贝和深拷贝的区别。浅拷贝和深拷贝更多地涉及到涉及到指针成员的对象的拷贝行为,而对于普通变量,只需要关注其值的复制即可。

class Person
{
public:
	Person() // 构造函数
	{
		cout << "Person的默认构造函数" << endl;
	}

	Person(int age, int height) // 带参构造函数
	{
		m_Age = age;
		m_Height = new int(height); // 在堆区开辟新内存,返回地址赋值给int型指针
		cout << "Person的有参构造函数" << endl;
	}

	~Person() // 析构函数
	{
        // 注意下面的判断语句暂时被注释了
		/*
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}
		
		*/
		cout << "Person的析构函数" << endl;
	}

	int m_Age;
	int* m_Height;
};

void test()
{
	Person p1(18,165);
	cout << "p1的年龄为:" << p1.m_Age << " p1的身高为:" << *p1.m_Height << endl;

    // 当调用 Person p2(p1) 时,编译器会自动生成一个拷贝构造函数,将 p1 中的成员变量 m_Age 的值复制给 p2 中的对应成员变量。
	Person p2(p1);
	cout << "p2的年龄为:" << p2.m_Age << " p1的身高为:" << *p2.m_Height << endl;
}

int main()
{
	test();
	return 0;
}

这时候输出如下

可以看到p1和p2的年龄身高都是一样的,这是因为调用 Person p2(p1) 时,编译器会自动生成一个拷贝构造函数,将 p1 中的成员变量 m_Age 的值复制给 p2 中的对应成员变量,这就是浅拷贝。
同时,在 test() 函数结束后,会先调用 p2 的析构函数,然后调用 p1 的析构函数,按照对象的创建顺序逆序销毁。

那么这样子有什么问题呢,为什么要出现深拷贝?

现在把代码中注释的部分添加进去,再运行一下,结果如下所示

有些电脑的编译器可能会直接报错,也有可能出现我这样的情况,少了一行析构函数的输出以及无法用任意键关闭窗口。

这是因为没有显式定义拷贝构造函数,编译器会提供一个默认的浅拷贝构造函数。在浅拷贝构造函数中,指针m_Height会被简单地复制到新对象中,这意味着p2和p1会共享相同的内存地址。当其中一个对象被销毁时,其指针成员所指向的内存被释放,导致另一个对象的指针成员成为悬空指针。

总的来说浅拷贝带来的问题时堆区内存的重复释放。

现在用内存逻辑图解释一下

也就是说同一个内存由于浅拷贝的机制导致被析构函数释放了两遍,这明显是不行的。

那么怎么解决呢?

知道了问题所在,我们就可以自己实现拷贝构造函数实现深拷贝

此时p1与p2的构造函数是不同的,最后就不会出现交叉重复释放堆内存的情况。

C/C++面试之算法系列--几个典型的内存拷贝及字符串函数实现 写一个函数,完成内存之间的拷贝。[考虑问题是否全面,是否考虑内存重叠问题] 返回void *支持链式操作,参数类型是void *以支持任意类型的指针,输入参数加上const修饰,最好加上assert对输入输出指针进行非NULL判断 void* memcpy( void *dest, const void *src, size_t count ) { char* pdest = static_cast( dest ); const char* psrc = static_cast( 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'
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值