C++中动态内存分配引发问题的解决方案
来源:互联网 发布:计算机一级题库软件 编辑:程序博客网 时间:2024/05/19 23:02
假设我们要开发一个String类,它可以方便地处理字符串数据。我们可以在类中声明一个数组,考虑到有时候字符串极长,我们可以把数组大小设为200,但一般的情况下又不需要这么多的空间,这样是浪费了内存。对了,我们可以使用new操作符,这样是十分灵活的,但在类中就会出现许多意想不到的问题,本文就是针对这一现象而写的。现在,我们先来开发一个String类,但它是一个不完善的类。的确,我们要刻意地使它出现各种各样的问题,这样才好对症下药。好了,我们开始吧!复制代码复制代码复制代码运行结果:
大家网
请按任意键继续. . .
大家可以看到,以上程序十分正确,而且也是十分有用的。可是,我们不能被表面现象所迷惑!下面,请大家用test_String.cpp文件替换test_right.cpp文件进行编译,看看结果。有的编译器可能就是根本不能进行编译!复制代码运行结果:
下面分别输入三个范例:
第一个范例。
第二个范例。
第三个范例。
第一个范例。
这个字符串将被删除:第一个范例。
使用正确的函数:
第二个范例。
第二个范例。
使用错误的函数:
第二个范例。
这个字符串将被删除:第二个范例。
这个字符串将被删除:?=
?=
String2: 第三个范例。
String3: 第四个范例。
下面,程序结束,析构函数将被调用。
这个字符串将被删除:第四个范例。
这个字符串将被删除:第三个范例。
这个字符串将被删除:?=
这个字符串将被删除:x =
这个字符串将被删除:?=
这个字符串将被删除:
现在,请大家自己试试运行结果,或许会更加惨不忍睹呢!下面,我为大家一一分析原因。
首先,大家要知道,C++类有以下这些极为重要的函数:
一:复制构造函数。
二:赋值函数。
我们先来讲复制构造函数。什么是复制构造函数呢?比如,我们可以写下这样的代码:String test1(test2);这是进行初始化。我们知道,初始化对象要用构造函数。可这儿呢?按理说,应该有声明为这样的构造函数:String(const String &);可是,我们并没有定义这个构造函数呀?答案是,C++提供了默认的复制构造函数,问题也就出在这儿。
(1):什么时候会调用复制构造函数呢?(以String类为例。)
在我们提供这样的代码:String test1(test2)时,它会被调用;当函数的参数列表为按值传递,也就是没有用引用和指针作为类型时,如:void show_String(const String),它会被调用。其实,还有一些情况,但在这儿就不列举了。
(2):它是什么样的函数。
它的作用就是把两个类进行复制。拿String类为例,C++提供的默认复制构造函数是这样的:复制代码在平时,这样并不会有任何的问题出现,但我们用了new操作符,涉及到了动态内存分配,我们就不得不谈谈浅复制和深复制了。以上的函数就是实行的浅复制,它只是复制了指针,而并没有复制指针指向的数据,可谓一点儿用也没有。打个比方吧!就像一个朋友让你把一个程序通过网络发给他,而你大大咧咧地把快捷方式发给了他,有什么用处呢?我们来具体谈谈:
假如,A对象中存储了这样的字符串:“C++”。它的地址为2000。现在,我们把A对象赋给B对象:String B=A。现在,A和B对象的str指针均指向2000地址。看似可以使用,但如果B对象的析构函数被调用时,则地址2000处的字符串“C++”已经被从内存中抹去,而A对象仍然指向地址2000。这时,如果我们写下这样的代码:cout<<A<<endl;或是等待程序结束,A对象的析构函数被调用时,A对象的数据能否显示出来呢?只会是乱码。而且,程序还会这样做:连续对地址2000处使用两次delete操作符,这样的后果是十分严重的!
本例中,有这样的代码:复制代码假设test1中str指向的地址为2000,而String中str指针同样指向地址2000,我们删除了2000处的数据,而test1对象呢?已经被破坏了。大家从运行结果上可以看到,我们使用cout<<test1时,一点反应也没有。而在test1的析构函数被调用时,显示是这样: “这个字符串将被删除:”。
再看看这段代码:复制代码show_String函数的参数列表void show_String(const String a)是按值传递的,所以,我们相当于执行了这样的代码:String a=test2;函数执行完毕,由于生存周期的缘故,对象a被析构函数删除,我们马上就可以看到错误的显示结果了:这个字符串将被删除:?=。当然,test2也被破坏了。解决的办法很简单,当然是手工定义一个复制构造函数喽!人力可以胜天!复制代码我们执行的是深复制。这个函数的功能是这样的:假设对象A中的str指针指向地址2000,内容为“I am a C++ Boy!”。我们执行代码String B=A时,我们先开辟出一块内存,假设为3000。我们用strcpy函数将地址2000的内容拷贝到地址3000中,再将对象B的str指针指向地址 3000。这样,就互不干扰了。
大家把这个函数加入程序中,问题就解决了大半,但还没有完全解决,问题在赋值函数上。我们的程序中有这样的段代码:复制代码经过我前面的讲解,大家应该也会对这段代码进行寻根摸底:凭什么可以这样做:String3=test4???原因是,C++为了用户的方便,提供的这样的一个操作符重载函数:operator=。所以,我们可以这样做。大家应该猜得到,它同样是执行了浅复制,出了同样的毛病。比如,执行了这段代码后,析构函数开始大展神威^_^。由于这些变量是后进先出的,所以最后的String3变量先被删除:这个字符串将被删除:第四个范例。很正常。最后,删除到test4的时候,问题来了:这个字符串将被删除:?=。原因我不用赘述了,只是这个赋值函数怎么写,还有一点儿学问呢!大家请看:
平时,我们可以写这样的代码:x=y=z。(均为整型变量。)而在类对象中,我们同样要这样,因为这很方便。而对象A=B=C就是 A.operator=(B.operator=(c))。而这个operator=函数的参数列表应该是:const String& a,所以,大家不难推出,要实现这样的功能,返回值也要是String&,这样才能实现A=B=C。我们先来写写看:复制代码是不是这样就行了呢?我们假如写出了这种代码:A=A,那么大家看看,岂不是把A对象的数据给删除了吗?这样可谓引发一系列的错误。所以,我们还要检查是否为自身赋值。只比较两对象的数据是不行了,因为两个对象的数据很有可能相同。我们应该比较地址。以下是完好的赋值函数:复制代码把这些代码加入程序,问题就完全解决,下面是运行结果:
下面分别输入三个范例:
第一个范例
第二个范例
第三个范例
第一个范例
这个字符串将被删除:第一个范例。
第一个范例
使用正确的函数:
第二个范例。
第二个范例。
使用错误的函数:
第二个范例。
这个字符串将被删除:第二个范例。
第二个范例。
String2: 第三个范例。
String3: 第四个范例。
下面,程序结束,析构函数将被调用。
这个字符串将被删除:第四个范例。
这个字符串将被删除:第三个范例。
这个字符串将被删除:第四个范例。
这个字符串将被删除:第三个范例。
这个字符串将被删除:第二个范例。
这个字符串将被删除:第一个范例。
- /* String.h */
- #ifndef STRING_H_
- #define STRING_H_
- class String
- {
- private:
- char * str; //存储数据
- int len; //字符串长度
- public:
- String(const char * s); //构造函数
- String(); // 默认构造函数
- ~String(); // 析构函数
- friend ostream & operator<<(ostream & os,const String& st);
- };
- #endif
- /*String.cpp*/
- #include <iostream>
- #include <cstring>
- #include "String.h"
- using namespace std;
- String::String(const char * s)
- {
- len = strlen(s);
- str = new char[len + 1];
- strcpy(str, s);
- }//拷贝数据
- String::String()
- {
- len =0;
- str = new char[len+1];
- str[0]='"0';
- }
- String::~String()
- {
- cout<<"这个字符串将被删除:"<<str<<'"n';//为了方便观察结果,特留此行代码。
- delete [] str;
- }
- ostream & operator<<(ostream & os, const String & st)
- {
- os << st.str;
- return os;
- }
- /*test_right.cpp*/
- #include <iostream>
- #include <stdlib.h>
- #include "String.h"
- using namespace std;
- int main()
- {
- String temp("大家网");
- cout<<temp<<'"n';
- system("PAUSE");
- return 0;
- }
大家网
请按任意键继续. . .
大家可以看到,以上程序十分正确,而且也是十分有用的。可是,我们不能被表面现象所迷惑!下面,请大家用test_String.cpp文件替换test_right.cpp文件进行编译,看看结果。有的编译器可能就是根本不能进行编译!
- /*test_String.cpp:*/
- #include <iostream>
- #include <stdlib.h>
- #include "String.h"
- using namespace std;
- void show_right(const String&);
- void show_String(const String);//注意,参数非引用,而是按值传递。
- int main()
- {
- String test1("第一个范例。");
- String test2("第二个范例。");
- String test3("第三个范例。");
- String test4("第四个范例。");
- cout<<"下面分别输入三个范例:"n";
- cout<<test1<<endl;
- cout<<test2<<endl;
- cout<<test3<<endl;
- String* String1=new String(test1);
- cout<<*String1<<endl;
- delete String1;
- cout<<test1<<endl; //在Dev-cpp上没有任何反应。
- cout<<"使用正确的函数:"<<endl;
- show_right(test2);
- cout<<test2<<endl;
- cout<<"使用错误的函数:"<<endl;
- show_String(test2);
- cout<<test2<<endl; //这一段代码出现严重的错误!
- String String2(test3);
- cout<<"String2: "<<String2<<endl;
- String String3;
- String3=test4;
- cout<<"String3: "<<String3<<endl;
- cout<<"下面,程序结束,析构函数将被调用。"<<endl;
- return 0;
- }
- void show_right(const String& a)
- {
- cout<<a<<endl;
- }
- void show_String(const String a)
- {
- cout<<a<<endl;
- }
下面分别输入三个范例:
第一个范例。
第二个范例。
第三个范例。
第一个范例。
这个字符串将被删除:第一个范例。
使用正确的函数:
第二个范例。
第二个范例。
使用错误的函数:
第二个范例。
这个字符串将被删除:第二个范例。
这个字符串将被删除:?=
?=
String2: 第三个范例。
String3: 第四个范例。
下面,程序结束,析构函数将被调用。
这个字符串将被删除:第四个范例。
这个字符串将被删除:第三个范例。
这个字符串将被删除:?=
这个字符串将被删除:x =
这个字符串将被删除:?=
这个字符串将被删除:
现在,请大家自己试试运行结果,或许会更加惨不忍睹呢!下面,我为大家一一分析原因。
首先,大家要知道,C++类有以下这些极为重要的函数:
一:复制构造函数。
二:赋值函数。
我们先来讲复制构造函数。什么是复制构造函数呢?比如,我们可以写下这样的代码:String test1(test2);这是进行初始化。我们知道,初始化对象要用构造函数。可这儿呢?按理说,应该有声明为这样的构造函数:String(const String &);可是,我们并没有定义这个构造函数呀?答案是,C++提供了默认的复制构造函数,问题也就出在这儿。
(1):什么时候会调用复制构造函数呢?(以String类为例。)
在我们提供这样的代码:String test1(test2)时,它会被调用;当函数的参数列表为按值传递,也就是没有用引用和指针作为类型时,如:void show_String(const String),它会被调用。其实,还有一些情况,但在这儿就不列举了。
(2):它是什么样的函数。
它的作用就是把两个类进行复制。拿String类为例,C++提供的默认复制构造函数是这样的:
- String(const String& a)
- {
- str=a.str;
- len=a.len;
- }
假如,A对象中存储了这样的字符串:“C++”。它的地址为2000。现在,我们把A对象赋给B对象:String B=A。现在,A和B对象的str指针均指向2000地址。看似可以使用,但如果B对象的析构函数被调用时,则地址2000处的字符串“C++”已经被从内存中抹去,而A对象仍然指向地址2000。这时,如果我们写下这样的代码:cout<<A<<endl;或是等待程序结束,A对象的析构函数被调用时,A对象的数据能否显示出来呢?只会是乱码。而且,程序还会这样做:连续对地址2000处使用两次delete操作符,这样的后果是十分严重的!
本例中,有这样的代码:
- String* String1=new String(test1);
- cout<<*String1<<endl;
- delete String1;
再看看这段代码:
- cout<<"使用错误的函数:"<<endl;
- show_String(test2);
- cout<<test2<<endl;//这一段代码出现严重的错误!
- String::String(const String& a)
- {
- len=a.len;
- str=new char(len+1);
- strcpy(str,a.str);
- }
大家把这个函数加入程序中,问题就解决了大半,但还没有完全解决,问题在赋值函数上。我们的程序中有这样的段代码:
- String String3;
- String3=test4;
平时,我们可以写这样的代码:x=y=z。(均为整型变量。)而在类对象中,我们同样要这样,因为这很方便。而对象A=B=C就是 A.operator=(B.operator=(c))。而这个operator=函数的参数列表应该是:const String& a,所以,大家不难推出,要实现这样的功能,返回值也要是String&,这样才能实现A=B=C。我们先来写写看:
- String& String::operator=(const String& a)
- {
- delete [] str;//先删除自身的数据
- len=a.len;
- str=new char[len+1];
- strcpy(str,a.str);//此三行为进行拷贝
- return *this;//返回自身的引用
- }
- String& String::operator=(const String& a)
- {
- if(this==&a)
- return *this;
- delete [] str;
- len=a.len;
- str=new char[len+1];
- strcpy(str,a.str);
- return *this;
- }
下面分别输入三个范例:
第一个范例
第二个范例
第三个范例
第一个范例
这个字符串将被删除:第一个范例。
第一个范例
使用正确的函数:
第二个范例。
第二个范例。
使用错误的函数:
第二个范例。
这个字符串将被删除:第二个范例。
第二个范例。
String2: 第三个范例。
String3: 第四个范例。
下面,程序结束,析构函数将被调用。
这个字符串将被删除:第四个范例。
这个字符串将被删除:第三个范例。
这个字符串将被删除:第四个范例。
这个字符串将被删除:第三个范例。
这个字符串将被删除:第二个范例。
这个字符串将被删除:第一个范例。
- C++中动态内存分配引发问题的解决方案
- C++中动态内存分配引发问题的解决方案
- C++中动态内存分配引发问题的解决方案
- C++中动态内存分配引发问题的解决方案
- C++中动态内存分配引发问题的解决方案
- C++中动态内存分配引发问题的解决方案
- C++中动态内存分配引发问题的解决方案
- C++内存泄漏——C++中动态内存分配引发问题的解决方案(1)
- C++内存泄漏——C++中动态内存分配引发问题的解决方案(2)
- C++中动态内存分配引发问题的解决方案(转载)
- C++中动态内存分配引发问题的解决方案(一)
- C++中动态内存分配引发问题的解决方案(二)
- C++中动态内存分配引发问题的解决方案(讲的是类的潜复制和深复制) 2011-12-5 09:42
- C/C++中动态内存分配问题
- 动态内存的分配问题
- 第七篇:C/C++动态内存的分配问题
- 动态链接库中分配内存引起的问题
- 动态链接库中分配内存引起的问题(转)
- Android之四种加载方式(standard、singleTop、singleTask、singleInstance)
- 新的开始
- JPA实体关系映射之注释
- 谈《赢》之慧眼识人
- LA - 5031 - Graph and Queries(平衡树Treap)
- C++中动态内存分配引发问题的解决方案
- POJ 1679 The Unique MST - from lanshui_Yang
- Wi-Fi基带芯片和Wi-Fi无线网卡设计方案
- pthread_getspecific()--读线程私有数据|pthread_setspecific()--写线程私有数据
- Range Minimum Query( RMQ )
- 内存泄漏
- const int *p, int const *p, int *const p, const int * const p
- kgdb接收一个数据包详解
- atoi