C++中深拷贝与浅拷贝问题

C++中深拷贝与浅拷贝问题

            在c++中,深拷贝与浅拷贝一直是一个难点(java中也一样,不过不常见),特别对于初学者来说,总是搞不懂其含义,搞不懂也就算了,有时候还会无意中使用了浅拷贝导致出错,对于这类错误,如果不理解深拷贝与浅拷贝的含义是无法检测出来的,对于我们程序员来说,检测不出bug在哪的确是件蛋疼的事,今天我就与大家一起探讨有关深拷贝与浅拷贝的一些问题,帮助大家理解。

       废话不多说,先来一段代码(注意一些老的编译器可能不支持名字空间的头文件用.h结尾)

//MyString类的头文件
#ifndef MYSTRING_H_
#define MYSTRING_H_
#include<iostream>

class MyString{
private:
	char *str; //字符指针
	int length;//字符长度
	static int num;//创建对象的数量
public:
	MyString();
	MyString(const char *s);
	~MyString();

	friend std::ostream & operator<<(std::ostream &os,const MyString & ms);//友元函数重载<<运算符
};
#endif

 

#include<cstring>
#include"MyString.h"
using namespace std;

int MyString::num=0;//初始化静态变量(别问我为什么这样初始化,记住就好了)
//构造函数
MyString::MyString(){
	length=4;
	str = new char[4];
	strcpy(str,"C++");
	num++;
	cout << num << ": " << str << "对象被创建\n" ;
}
MyString::MyString(const char *s){
	length=strlen(s);
	str = new char[length+1];
	strcpy(str,s);
	num++;
	cout << num << ": " << str << "对象被创建\n" ;
}
//析构函数
MyString::~MyString(){
	cout << "\"" << str << "\" 对象被释放";
	num--;
	cout << "      剩余 " << num << " 个对象\n";
	delete []str;//释放heap内存
}
//重载<<运算符
std::ostream & operator<<(std::ostream &os,const MyString & ms){
	os << ms.str;
	return os;
}

 

#include<iostream>
#include"MyString.h"

using namespace std;

//注意一个是传值,一个传引用
void fun1(MyString &ms);
void fun2(MyString ms);
int main()
{
	MyString ms1;
	MyString ms2("Java");
	MyString ms3("Python");

	cout << "ms1 " << ms1 << endl;
	cout << "ms2 " << ms2 << endl;
	cout << "ms3 " << ms3 << endl;

	fun1(ms1);
	cout << "ms1 " << ms1 << endl;
	fun2(ms2);
	cout << "ms2 " << ms2 << endl;

	MyString ms4 = ms3;
	cout << "ms4 " << ms4 << endl;

	return 0;
}
void fun1(MyString &ms){
	cout << "fun1被调用 : " << ms << endl;
}
void fun2(MyString ms){
	cout << "fun2被调用 : " << ms << endl;
}

 

 

 这是我的运行结果,很明显出错了。

可是问题出在哪呢,从代码上看没问题,就连一编译连个警告也不给我,这个问题的确很多蛋疼。

 

首先我们来看我们自己写的类的实现文件,在构造函数中我们使用了 new 关键字,这是必须的,因为我们不不知道传进来的字符串有多大,所以必须动态分配内存。然后,我们也知道c++必须手动释放内存(就这一点还是java方便)所以在析构函数中必须使用delete关键字来释放,这点我也不多说了,学过c++都知道的。

下面我们一步一步的分析。



 直到fun1()函数被调用之前,一切都正常运行



 
 当调用fun2()的时候,第一个问题来了,fun2()函数打印正常,但是紧接着对象被释放,这点我们也好理解,因为fun2()函数传递的是值而不是引用,传值就相当与传递ms2的一个副本所以fun2(ms2)就相当于fun2(MyString(ms2))重新创建了一个对象。在这里我们补充一点,在c++中,按值传递对象或者赋值给另一个对象,编译器会自动调用一个叫做复制构造函数:MyString(MyString &);所以调用fun2()的时候,编译器拷贝了一个ms2副本给fun2的局部变量。当然,打印什么的都很正常。但是当到达函数结束时,析构函数会自己调用,同时释放str的内存。

也许你们会说,这也没错,的确很正常,正常到出了错我们都还不知道,就在这里涉及到了深拷贝与浅拷贝额问题,在这里的传值用的是浅拷贝,那什么是深拷贝,什么又是浅拷贝呢?



 如上图所示:所谓浅拷贝,就是原封不动的全部都拷贝过去。当然静态变量不会拷贝。

既然是原封不动的拷贝过去,那么我们知道str里面存的是地址,所以fun2里面的ms的str拷过去的也是地址

看到这里我想大家都明白了吧,在fun2中已经释放过对象,也就是说,str所指向的“Java”这个字符串内存已经被释放,那么当ms2企图再想访问的时候,打印出来的自然是乱码了。

接着,我们把ms3的值赋给ms4,这相当于ms4=MyString(ms3);我前面讲过,就不重复了。



 我们来看最后一段,首先ms4的对象被释放。由于局部变量储存在stack里,所以遵循后进先出的模式(LIFO)那么当ms4被释放以后,接下来释放的是ms3,然后大家看到ms3中的str已经被释放,再释放一次的话,自然程序就崩溃了。

细心的同学也许发现了一点,就是当前最后一条是运行到释放ms3就被终止,那么如果在释放ms2与ms1呢,那么剩余的不就是负值了吗。的确,那么这又是什么原因呢?其实我前面也说过,当复制或赋值对象的时候会调用默认的复制构造函数,既然是默认的,那么肯定不会帮我们把num++吧,编译器不会那么智能吧(要是真有那么智能我们就不用编代码了,直接让电脑自己编不就得了)。所以我们复制与赋值各一个,分别是在调用fun2()函数与ms4=ms3的时候。所以析构函数里num多减了两次,结果自然是-2了。

讲了那么多,还没讲什么是深拷贝,通俗易懂,所谓深拷贝就是比浅拷贝更深的拷贝呗(好像是废话。。)

也就是说,深拷贝不只是原封不动的拷贝,他会把str的内容拷贝了,而不是地址,所谓拷贝内容,就是重新开辟内存,然后复制内容。

接下来修改一下代码

//MyString类的头文件
#ifndef MYSTRING_H_
#define MYSTRING_H_
#include<iostream>

class MyString{
private:
	char *str; //字符指针
	int length;//字符长度
	static int num;//创建对象的数量
public:
	MyString();
	MyString(const char *s);
	MyString(const MyString &ms);
	~MyString();


	friend std::ostream & operator<<(std::ostream &os,const MyString & ms);//友元函数重载<<运算符
};
#endif

 

#include<cstring>
#include"MyString.h"
using namespace std;

int MyString::num=0;//初始化静态变量(别问我为什么这样初始化,记住就好了)
//构造函数
MyString::MyString(){
	length=4;
	str = new char[4];
	strcpy(str,"C++");
	num++;
	cout << num << ": " << str << "对象被创建\n" ;
}
MyString::MyString(const char *s){
	length=strlen(s);
	str = new char[length+1];
	strcpy(str,s);
	num++;
	cout << num << ": " << str << "对象被创建\n" ;
}
MyString::MyString(const MyString &ms){
	num++;
	length = ms.length;
	str = new char[length+1];
	strcpy(str,ms.str);
}
//析构函数
MyString::~MyString(){
	cout << "\"" << str << "\" 对象被释放";
	num--;
	cout << "      剩余 " << num << " 个对象\n";
	delete []str;//释放heap内存
}
//重载<<运算符
std::ostream & operator<<(std::ostream &os,const MyString & ms){
	os << ms.str;
	return os;
}

 

#include<iostream>
#include"MyString.h"

using namespace std;

//注意一个是传值,一个传引用
void fun1(MyString &ms);
void fun2(MyString ms);
int main()
{
	{
	MyString ms1;
	MyString ms2("Java");
	MyString ms3("Python");

	cout << "ms1 " << ms1 << endl;
	cout << "ms2 " << ms2 << endl;
	cout << "ms3 " << ms3 << endl;

	fun1(ms1);
	cout << "ms1 " << ms1 << endl;
	fun2(ms2);
	cout << "ms2 " << ms2 << endl;

	MyString ms4=ms3;
	cout << "ms4 " << ms4 << endl;
	}
	return 0;
}
void fun1(MyString &ms){
	cout << "fun1被调用 : " << ms << endl;
}
void fun2(MyString ms){
	cout << "fun2被调用 : " << ms << endl;
}

 

 只要我们自己提供复制构造函数,并且进行深拷贝就不会出错啦。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值