zoukankan      html  css  js  c++  java
  • C++ 脑筋急转弯

    最近重新温习一下C++的基础知识,这里给大家分享一下,独痛苦不如众痛苦。

    先贴出一段示例代码如下:

    class CTest{
    public:
        CTest(){
            this->p = new char(5);
        };
      
    ~CTest(){ if (!this->p) { return; } delete this->p; this->p = NULL; }
      void mFree(){
        delete this->p;
        this->p = NULL;
      }
    private: char *p; };
    int main(){
      CTest *CCTest = new CTest();
      CTest tCTest = *CCTest
      delete CCTest;
      return 0;
    }

    博主是在vs2013环境下跑的这段代码,就是这么一段人畜无害的代码,运行的时候竟然崩溃了...,没错是崩溃了。

    这里面涉及的是最基本的深拷贝和浅拷贝的知识,tCTest 做为临时变量拷贝了CCTest里面的成员参数,但是tCTest.p 和 CCTest->p指向的是同一片内存。

    我认为崩溃原因是这样的:当main函数执行结束的时候,会执行 tCTest 的析构函数,而释放 CCTest 也会执行析构函数,同一个内存释放两次所以崩溃。

    但vs是在CCTest释放内存时抛出这样的异常:p指向的内存被临时变量tCTest占用所以不能释放,这个要研究一下C++的内存调度机制,和源码才能确定深层次的原因。

    那么怎么避免这个问题?老手们肯定知道啦,重构一下拷贝函数就OK啦:

    class CTest{
    public:
        CTest(){
            this->p = new char(5);
        };
        
        CTest(CTest &t){
            this->p = new char(5);
            if(t.p){memcpy(this->p, t.p, 5);}
        };
        ~CTest(){
            if (!this->p)
            {
                return;
            }
            delete this->p;
            this->p = NULL;
        }
      void mFree(){
         delete this->p; 
        this->p = NULL; 
      }
    private:
        char *p;
    };

    老鸟们肯定熟的很了:CTest a = b; 和 CTest a(b);效果是一样的。

    好,我们继续再延伸一下,增加点新玩法:

    class CParant{
    public:
        CParant(const char* pchIn){
            this->chOutPut = pchIn;
        }
        ~CParant(){ 
            printf("exit
    "); 
        };
        string  & mGetOutputAddr();
    private:
        string chOutPut;
    };
    
    class CChild : public CParant{
    public:
        CChild(const char* pchIn) :CParant(pchIn){
            this->p = new char(5);
        };
        
        CChild(const CChild& CCChild) :CParant(CCChild){
            this->p = new char(5);
            if(CCChild.p) memcpy(this->p, CCChild.p, 5);
        };
    
        void mFree(){
            delete this->p;
            this->p = NULL;
        }
    
        ~CChild(){
            if (!this->p)
            {
                return;
            }
            delete this->p;
            this->p = NULL;
        }
    private:
        char *p;
    };
    int main(){
      CParant* CCParant = new CCParant("father");
      CCParant tCCParant = *CCParant
      delete CCParant;
      return 0;
    }

     各位猜一猜这回会不会报错?当然不会啦,你都已经改了拷贝函数,怎么会报错呢?

    确实不会报错,不过不是因为我重构了拷贝函数,而是因为根本就没有拷贝 char *p 这个成员变量,父对象拷贝是不会拷贝到子类里面的变量,即使赋值一方是由子类实例化的。上面这个例子虽然不会造成程序崩溃,但是也不是完美的,它会造成内存泄露。至于原因就是因为CCParant 在析构的时候没有调用子类的析构函数,怎么办嘞?解决方法如下:

    class CParant{
    public:
        CParant(const char* pchIn){
            this->chOutPut = pchIn;
        }
        virtual ~CParant(){ 
            printf("exit
    "); 
        };
        string  & mGetOutputAddr();
    private:
        string chOutPut;
    };

    把父类的析构函数变为虚函数,这样析构的时候就会先去调用子类的析构函数,避免子类成员无法释放带来的内存泄露。至于加一个虚函数有什么影响,我们看看下面两张对比图片:

    没有设置虚析构函数时:

    这是加了virtual 关键字之后的:

     可以看到加了虚析构函数后CCParant 内存中多出了一张虚函数的函数指针表,这里面会存储虚函数实现的指针地址。我们还可以换个方式验证:

    int main(){
       CCParant t("test");
       printf("%d", sizeof(t));    
      return 0; }

    对比一下修改前后的size大小,看看是否相差了4byte,这篇就说这么多,都是比较基础浅显的东西,下一篇我们要深入挖掘,加大力度。

  • 相关阅读:
    html学习总结
    16--二叉树的镜像
    17-- 从上到下打印二叉树,按层打印。
    14--反转链表
    14--合并两个排序的链表
    15-- 输入两个二叉树A和B,判断B树是否包含于A树。
    13--输入一个整数数组,实现一个函数来调整数组中数字的顺序
    13--输出链表中倒数第k个节点,
    12--打印1到最大的N为数字。
    10--输入一个十进制的整数,转化为二进制,输出有多少个1
  • 原文地址:https://www.cnblogs.com/cnblogs-wangzhipeng/p/8025464.html
Copyright © 2011-2022 走看看