新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → C++中动态内存分配引发问题的解决方案 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 9535 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: C++中动态内存分配引发问题的解决方案 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     enorm 帅哥哟,离线,有人找我吗?
      
      
      威望:4
      头衔:头衔
      等级:大三暑假(参加全国数模竞赛拿了一等奖)(版主)
      文章:144
      积分:854
      门派:Lilybbs.net
      注册:2005/12/1

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给enorm发送一个短消息 把enorm加入好友 查看enorm的个人资料 搜索enorm在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看enorm的博客楼主
    发贴心情 C++中动态内存分配引发问题的解决方案

    假设我们要开发一个String类,它可以方便地处理字符串数据。我们可以在类中声明一个数组,考虑到有时候字符串极长,我们可以把数组大小设为200,但一般的情况下又不需要这么多的空间,这样是浪费了内存。对了,我们可以使用new操作符,这样是十分灵活的,但在类中就会出现许多意想不到的问题,本文就是针对这一现象而写的。现在,我们先来开发一个Wrong 类,从名称上看出,它是一个不完善的类。的确,我们要刻意地使它出现各种各样的问题,这样才好对症下药。好了,我们开始吧!

      Wrong.h:

    #ifndef WRONG_H_
    #define WRONG_H_
    class Wrong
    {
    private:
    char * str; //存储数据
    int len; //字符串长度

    public:
    Wrong(const char * s); //构造函数
    Wrong(); // 默认构造函数
    ~Wrong(); // 析构函数
    friend ostream & operator<<(ostream & os,const Wrong& st);
    };
    #endif

    Wrong.cpp:

    #include <iostream>
    #include <cstring>
    #include "wrong.h"
    using namespace std;
    Wrong::Wrong(const char * s)
    {
    len = strlen(s);
    str = new char[len + 1];
    strcpy(str, s);

    }//拷贝数据

    Wrong::Wrong()
    {
    len =0;
    str = new char[len+1];
    str[0]='\0';

    }

    Wrong::~Wrong()
    {
    cout<<"这个字符串将被删除:"<<str<<'\n';//为了方便观察结果,特留此行代码。
    delete [] str;
    }

    ostream & operator<<(ostream & os, const Wrong & st)
    {
    os << st.str;
    return os;
    }

    test_right.cpp:

    #include <iostream>
    #include <stdlib.h>
    #include "Wrong.h"
    using namespace std;
    int main()
    {
    Wrong temp("天极网");
    cout<<temp<<'\n';
    system("PAUSE");
    return 0;
    }

      运行结果:

      天极网

      请按任意键继续. . .

      大家可以看到,以上程序十分正确,而且也是十分有用的。可是,我们不能被表面现象所迷惑!下面,请大家用test_wrong.cpp文件替换test_right.cpp文件进行编译,看看结果。有的编译器可能就是根本不能进行编译!

      test_wrong.cpp:

    #include <iostream>
    #include <stdlib.h>
    #include "Wrong.h"
    using namespace std;
    void show_right(const Wrong&);
    void show_wrong(const Wrong);//注意,参数非引用,而是按值传递。
    int main()
    {
    Wrong test1("第一个范例。");
    Wrong test2("第二个范例。");
    Wrong test3("第三个范例。");
    Wrong test4("第四个范例。");
    cout<<"下面分别输入三个范例:\n";
    cout<<test1<<endl;
    cout<<test2<<endl;
    cout<<test3<<endl;
    Wrong* wrong1=new Wrong(test1);
    cout<<*wrong1<<endl;
    delete wrong1;
    cout<<test1<<endl;//在Dev-cpp上没有任何反应。
    cout<<"使用正确的函数:"<<endl;
    show_right(test2);
    cout<<test2<<endl;
    cout<<"使用错误的函数:"<<endl;
    show_wrong(test2);
    cout<<test2<<endl;//这一段代码出现严重的错误!
    Wrong wrong2(test3);
    cout<<"wrong2: "<<wrong2<<endl;
    Wrong wrong3;
    wrong3=test4;
    cout<<"wrong3: "<<wrong3<<endl;
    cout<<"下面,程序结束,析构函数将被调用。"<<endl;
    return 0;
    }
    void show_right(const Wrong& a)
    {
    cout<<a<<endl;
    }
    void show_wrong(const Wrong a)
    {
    cout<<a<<endl;
    }

      运行结果:

      下面分别输入三个范例:

      第一个范例。
      第二个范例。
      第三个范例。

      第一个范例。

      这个字符串将被删除:第一个范例。

      使用正确的函数:
      
      第二个范例。
      第二个范例。

      使用错误的函数:
      第二个范例。

      这个字符串将被删除:第二个范例。

      这个字符串将被删除:?=
      ?=

      wrong2: 第三个范例。
      wrong3: 第四个范例。

      下面,程序结束,析构函数将被调用。

      这个字符串将被删除:第四个范例。

      这个字符串将被删除:第三个范例。

      这个字符串将被删除:?=

      这个字符串将被删除:x =

      这个字符串将被删除:?=

      这个字符串将被删除:

      现在,请大家自己试试运行结果,或许会更加惨不忍睹呢!下面,我为大家一一分析原因。
    首先,大家要知道,C++类有以下这些极为重要的函数:

      一:复制构造函数。

      二:赋值函数。

      我们先来讲复制构造函数。什么是复制构造函数呢?比如,我们可以写下这样的代码:Wrong test1(test2);这是进行初始化。我们知道,初始化对象要用构造函数。可这儿呢?按理说,应该有声明为这样的构造函数:Wrong(const Wrong &);可是,我们并没有定义这个构造函数呀?答案是,C++提供了默认的复制构造函数,问题也就出在这儿。

      (1):什么时候会调用复制构造函数呢?(以Wrong类为例。)

      在我们提供这样的代码:Wrong test1(test2)时,它会被调用;当函数的参数列表为按值传递,也就是没有用引用和指针作为类型时,如:void show_wrong(const Wrong),它会被调用。其实,还有一些情况,但在这儿就不列举了。

      (2):它是什么样的函数。

      它的作用就是把两个类进行复制。拿Wrong类为例,C++提供的默认复制构造函数是这样的:

    Wrong(const Wrong& a)
    {
    str=a.str;
    len=a.len;
    }

      在平时,这样并不会有任何的问题出现,但我们用了new操作符,涉及到了动态内存分配,我们就不得不谈谈浅复制和深复制了。以上的函数就是实行的浅复制,它只是复制了指针,而并没有复制指针指向的数据,可谓一点儿用也没有。打个比方吧!就像一个朋友让你把一个程序通过网络发给他,而你大大咧咧地把快捷方式发给了他,有什么用处呢?我们来具体谈谈:

      假如,A对象中存储了这样的字符串:“C++”。它的地址为2000。现在,我们把A 对象赋给B对象:Wrong B=A。现在,A和B对象的str指针均指向2000地址。看似可以使用,但如果B对象的析构函数被调用时,则地址2000处的字符串“C++”已经被从内存中抹去,而A对象仍然指向地址2000。这时,如果我们写下这样的代码:cout<<A<<endl;或是等待程序结束,A对象的析构函数被调用时, A对象的数据能否显示出来呢?只会是乱码。而且,程序还会这样做:连续对地址2000处使用两次delete操作符,这样的后果是十分严重的!

      本例中,有这样的代码:

    Wrong* wrong1=new Wrong(test1);
    cout<<*wrong1<<endl;
    delete wrong1;

      假设test1中str指向的地址为2000,而wrong中str指针同样指向地址2000,我们删除了2000处的数据,而test1对象呢?已经被破坏了。大家从运行结果上可以看到,我们使用cout<<test1时,一点反应也没有。而在test1的析构函数被调用时,显示是这样:“这个字符串将被删除:”。

      再看看这段代码:

    cout<<"使用错误的函数:"<<endl;
    show_wrong(test2);
    cout<<test2<<endl;//这一段代码出现严重的错误!

      show_wrong函数的参数列表void show_wrong(const Wrong a)是按值传递的,所以,我们相当于执行了这样的代码:Wrong a=test2;函数执行完毕,由于生存周期的缘故,对象a被析构函数删除,我们马上就可以看到错误的显示结果了:这个字符串将被删除:?=。当然, test2也被破坏了。解决的办法很简单,当然是手工定义一个复制构造函数喽!人力可以胜天!

    Wrong::Wrong(const Wrong& a)
    {
    len=a.len;
    str=new char(len+1);
    strcpy(str,a.str);
    }

      我们执行的是深复制。这个函数的功能是这样的:假设对象A中的str指针指向地址2000,内容为“I am a C++ Boy!”。我们执行代码Wrong B=A时,我们先开辟出一块内存,假设为3000。我们用strcpy函数将地址2000的内容拷贝到地址3000中,再将对象B的str指针指向地址 3000。这样,就互不干扰了。

      大家把这个函数加入程序中,问题就解决了大半,但还没有完全解决,问题在赋值函数上。我们的程序中有这样的段代码:

    Wrong wrong3;
    wrong3=test4;

      经过我前面的讲解,大家应该也会对这段代码进行寻根摸底:凭什么可以这样做:wrong3=test4???原因是,C++为了用户的方便,提供的这样的一个操作符重载函数:operator=。所以,我们可以这样做。大家应该猜得到,它同样是执行了浅复制,出了同样的毛病。比如,执行了这段代码后,析构函数开始大展神威^_^。由于这些变量是后进先出的,所以最后的wrong3变量先被删除:这个字符串将被删除:第四个范例。很正常。最后,删除到 test4的时候,问题来了:这个字符串将被删除:?=。原因我不用赘述了,只是这个赋值函数怎么写,还有一点儿学问呢!大家请看:

      平时,我们可以写这样的代码:x=y=z。(均为整型变量。)而在类对象中,我们同样要这样,因为这很方便。而对象A=B=C就是A.operator= (B.operator=(c))。而这个operator=函数的参数列表应该是:const Wrong& a,所以,大家不难推出,要实现这样的功能,返回值也要是Wrong&,这样才能实现A=B=C。我们先来写写看:

    Wrong& Wrong::operator=(const Wrong& a)
    {
    delete [] str;//先删除自身的数据
    len=a.len;
    str=new char[len+1];
    strcpy(str,a.str);//此三行为进行拷贝
    return *this;//返回自身的引用
    }

      是不是这样就行了呢?我们假如写出了这种代码:A=A,那么大家看看,岂不是把A对象的数据给删除了吗?这样可谓引发一系列的错误。所以,我们还要检查是否为自身赋值。只比较两对象的数据是不行了,因为两个对象的数据很有可能相同。我们应该比较地址。以下是完好的赋值函数:

    Wrong& Wrong::operator=(const Wrong& a)
    {
    if(this==&a)
    return *this;
    delete [] str;
    len=a.len;
    str=new char[len+1];
    strcpy(str,a.str);
    return *this;
    }

      把这些代码加入程序,问题就完全解决,下面是运行结果:

      下面分别输入三个范例:

      第一个范例
      第二个范例
      第三个范例

      第一个范例

      这个字符串将被删除:第一个范例。

      第一个范例

       使用正确的函数:

      第二个范例。

      第二个范例。

       使用错误的函数:

      第二个范例。

      这个字符串将被删除:第二个范例。

      第二个范例。

      wrong2: 第三个范例。
      wrong3: 第四个范例。

      下面,程序结束,析构函数将被调用。

      这个字符串将被删除:第四个范例。
      这个字符串将被删除:第三个范例。
      这个字符串将被删除:第四个范例。
      这个字符串将被删除:第三个范例。
      这个字符串将被删除:第二个范例。
      这个字符串将被删除:第一个范例。

      关于动态内存分配的问题就介绍到这儿,希望大家都能热爱编程,热爱C++!


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    天亮了

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2005/12/7 12:21:00
     
     acid 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:0
      积分:67
      门派:XML.ORG.CN
      注册:2006/3/28

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给acid发送一个短消息 把acid加入好友 查看acid的个人资料 搜索acid在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看acid的博客2
    发贴心情 
    什么东西嘛~!
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/9/1 10:02:00
     
     lx_microsoft 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:1
      积分:62
      门派:XML.ORG.CN
      注册:2006/3/1

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给lx_microsoft发送一个短消息 把lx_microsoft加入好友 查看lx_microsoft的个人资料 搜索lx_microsoft在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看lx_microsoft的博客3
    发贴心情 
    难那 !我都不懂
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/9/9 9:38:00
     
     elfstone 帅哥哟,离线,有人找我吗?射手座1983-12-6
      
      
      等级:大四(总算啃完XML规范了)
      文章:185
      积分:1177
      门派:IEEE.ORG.CN
      注册:2006/2/25

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给elfstone发送一个短消息 把elfstone加入好友 查看elfstone的个人资料 搜索elfstone在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看elfstone的博客4
    发贴心情 
    对指针的操作要慎之又慎,我们编译老师如是说

    ----------------------------------------------
    Ich liebe erst meines Leben...

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/9/14 12:32:00
     
     34520 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:1
      积分:58
      门派:XML.ORG.CN
      注册:2006/10/8

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给34520发送一个短消息 把34520加入好友 查看34520的个人资料 搜索34520在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看34520的博客5
    发贴心情 
    呵呵
      很高兴嘛
    希望朋友多努力
       我还能多学点
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/10/8 21:04:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/13 1:12:31

    本主题贴数5,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    78.613ms