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; }
只要我们自己提供复制构造函数,并且进行深拷贝就不会出错啦。
相关推荐
根据提供的标题、描述和部分无法识别的内容,我们将围绕“C++深拷贝与浅拷贝”的主题进行深入探讨。在C++编程语言中,深拷贝(Deep Copy)与浅拷贝(Shallow Copy)是对象复制时经常遇到的概念。它们在内存管理和...
了解浅拷贝可能导致的问题,并且能够在必要时实现深拷贝构造函数,是C++程序员必备的技能。在实际开发中,深拷贝通常涉及到动态内存管理,这也需要程序员能够熟练掌握内存分配与释放的相关规则,以保证程序的正确性...
08_26_C++ 深浅拷贝的区别08_26_C++ 深浅拷贝的区别08_26_C++ 深浅拷贝的区别08_26_C++ 深浅拷贝的区别08_26_C++ 深浅拷贝的区别08_26_C++ 深浅拷贝的区别08_26_C++ 深浅拷贝的区别08_26_C++ 深浅拷贝的区别08_26_...
在C++编程中,类对象的深拷贝和浅拷贝是两个重要的概念,它们涉及到对象复制时内存管理的问题。当我们创建一个类的实例并将其赋值给另一个实例时,拷贝构造函数会被调用。拷贝构造函数是一种特殊的构造函数,它用于...
在C++中,浅拷贝和深拷贝是两种处理对象复制的重要概念,尤其是在涉及到动态内存分配时。这两个概念主要体现在拷贝构造函数和赋值运算符重载中。 浅拷贝(Shallow Copy)指的是在复制对象时,只复制对象的成员变量...
在C++编程语言中,拷贝构造函数是一个特殊类型的构造函数,它的主要作用是初始化一个新对象为已存在对象的副本。这个过程被称为浅复制或深复制,具体取决于类的成员变量类型。对于初学者来说,理解拷贝构造函数的...
### 深拷贝与浅拷贝的概念及应用 #### 一、深拷贝与浅拷贝的区别 在计算机编程领域中,深拷贝(Deep Copy)与浅拷贝(Shallow Copy)是两种常见的对象复制方法。这两种方法在处理复杂数据结构(如数组、列表等)时...
C++浅拷贝与深拷贝及引用计数分析是C++开发中经常遇到的问题之一,浅拷贝会导致double free、内存泄露等严重的问题。在C++类中,如果成员变量包括指针,而又没有定义自己的拷贝构造函数,那么在拷贝一个对象的情况下...
C++类对象的深拷贝、浅拷贝构造函数学习笔记 在 C++ 中,类对象的复制是通过拷贝构造函数来完成的。拷贝构造函数是类的一种特殊构造函数,用于将一个对象的内容复制到另一个对象中。在了解拷贝构造函数之前,首先...
浅拷贝问题 当用一个类对象去初始化这个类的另一个对象时,如果这个类没有提供拷贝构造函数以及重载=运算符,这时就会发生浅拷贝。如下代码所示 class Name { public: Name(const char *pname) { this->size=...
在默认情况下,C++提供了一个默认的拷贝构造函数,它执行成员级别的浅拷贝。这意味着如果类中包含指针,那么新对象和原对象会共享同一块内存,这不是我们通常希望的。为了实现正确的深拷贝,我们需要自定义拷贝构造...
### 深拷贝与浅拷贝构造函数问题解析 #### 一、问题背景 在进行C++编程过程中,拷贝构造函数的正确使用是非常重要的。本文将通过一个具体的示例来探讨拷贝构造函数中涉及到的深拷贝与浅拷贝的概念及其应用。 ####...
合理的拷贝构造函数可以避免浅拷贝导致的数据共享问题,以及深拷贝造成的性能损失。此外,还需要注意的是,引用可以是常量引用(const reference),这在函数参数传递中非常有用,因为常量引用保证了函数不会修改...
然而,默认的拷贝构造函数通常执行的是浅复制(shallow copy),即仅仅复制对象的指针或其他引用,而不是真正地复制其所指向的数据。这种行为在某些情况下可能导致问题,尤其是在处理动态分配的内存时。 #### 题目...
文件操作是C++编程中常见的任务,理解并熟练掌握这些操作对于开发任何需要与文件系统交互的应用程序都是至关重要的。在编写代码时,不仅要考虑正确性,还要考虑性能、安全性以及对异常情况的处理。
本文将深入探讨C++中浅拷贝出现的错误以及如何解决这些问题。 浅拷贝的错误主要体现在对象复制过程中,只复制了对象的数据成员,而没有复制其动态分配的内存。以一个简单的例子来说明: ```cpp class A { public: ...
### C++中的拷贝构造与赋值运算符详解 在C++编程中,拷贝构造函数和赋值运算符(通常称为拷贝赋值运算符)是非常重要的...在处理资源管理、深拷贝和浅拷贝等复杂问题时,正确地实现拷贝构造函数和赋值运算符尤为关键。
深拷贝和浅拷贝是C++中处理对象复制时的两个关键概念,尤其在涉及到动态内存分配的情况。浅拷贝,也称为位拷贝,是指仅仅复制对象的成员变量的值,而不复制底层的资源。这意味着如果类的成员变量包含指针,那么新...