• 多态中的指针


    一、父类指针指向子类对象

    首先,来看一段代码:

    //用父类指针指向子类对象
    class A {
    public:
    A() { printf("A 
    "); }
    ~A() { printf(" ~A 
    "); } // 这里不管写不写virtual,删除B对象的时候,都会被执行。因为这个例子是B*指针指向B对象,不是A*指针指向B对象。
    };
    
    class B : public A
    {
    public:
    B() { printf("B 
    "); }
    ~B() { printf("~B 
    "); }
    };
    
    int main(int argc, char* argv[])
    {
    B* b = new B;
    
    delete b;
    return 0;
    }
    int main(int argc, char* argv[])
    {
    B b; // 虽然正确,但其实是非正常情况,因为你防不住程序员定义A* a = new B(); 这样就错了。
    return 0;
    }
    执行结果:
    A
    B
    ~B
    ~A

    结论:删除子类指针,无论如何会自动调用祖先类的析构函数(即使祖先类的习惯函数不是虚拟的),虽然这是非正常情况,但还是记一下。

    第二段代码:

    class A {
    public:
    A() { printf("A 
    "); }
    virtual ~A() { printf(" ~A 
    "); } // 增加了虚拟关键字
    };
    
    class B : public A
    {
    public:
    B() { printf("B 
    "); }
    ~B() { printf("~B 
    "); }
    };
    
    int main(int argc, char* argv[])
    {
    A* a = new B;
    
    delete a;
    return 0;
    }
    执行结果:
    A
    B
    ~B
    ~A

    结论:正常情况下,子类祖先类的析构函数都是虚拟的,这样删除祖先类指针、子类对象的时候,可正确同时调用子类和祖先类的析构函数。

    知识点:

    1,如果以一个基础类指针指向一个衍生类对象(派生类对象),那么经由该指针只能访问基础类定义的函数(“实函数”暂且这么叫)(静态联翩)

    2,如果以一个衍生类指针指向一个基础类对象,必须先做强制转型动作(explicit cast),这种做法很危险,也不符合生活习惯,在程序设计上也会给程序员带来困扰。(一般不会这么去定义)

    3,如果基础类和衍生类定义了相同名称的成员函数,那么通过对象指针调用成员函数时,到底调用那个函数要根据指针的原型来确定,而不是根据指针实际指向的对象类型确定。
     
    虚拟函数就是为了对“如果你以一个基础类指针指向一个衍生类对象,那么通过该指针,你只能访问基础类定义的成员函数”这条规则反其道而行之的设计。

    如果你预期衍生类由可能重新定义一个成员函数,那么你就把它定义成虚拟函数( virtual )。
     
    多态(polymorphism),就是让处理基础类别对象的程序代码能够通透的继续适当地处理衍生类对象。
     
    纯虚拟函数:virtual void myfunc ( ) =0;
     
    纯虚拟函数不许定义其具体动作,它的存在只是为了在衍生类钟被重新定义。只要是拥有纯虚拟函数的类,就是抽象类,它们是不能够被实例化的(只能被继承)。如果一个继承类没有改写父类中的纯虚函数,那么他也是抽象类,也不能被实例化。
     
    抽象类不能被实例化,不过我们可以拥有指向抽象类的指针,以便于操纵各个衍生类。
     
    虚拟函数衍生下去仍然是虚拟函数,而且还可以省略掉关键字“virtual”。
     

    派生类构造函数和析构函数的执行顺序

    当创建对象时,编译系统会自动调用构造函数。当撤销对象时,编译系统会自动调用析构函数。

    当创建派生类的对象时,首先执行基类的构造函数,然后执行派生类的构造函数。当撤销对象时,则先执行派生类的析构函数,然后再执行基类的析构函数。

     
    最后,第三段代码:
    class A {
    public:
    A() { printf("A 
    "); }
    ~A() { printf(" ~A 
    "); } // 增加了虚拟关键字
    };
    
    class B : public A
    {
    public:
    B() { printf("B 
    "); }
    ~B() { printf("~B 
    "); }
    };
    
    int main(int argc, char* argv[])
    {
    A* a = new B;
    
    delete a;
    return 0;
    }
    执行结果:
    A
    B
    ~A

    结论:析构函数本应该设置成虚函数。在父类指针指向子类对象做对象销毁时,由于析构函数不是虚函数,则delete时,父类指针只能调用父类自己的析构函数,这就造成了上述对象部分销毁的错误状况。

    《Effective C++》条款 07 p44:

    • 带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数
    • Classes的设计目的如果不是作为base classes 使用,或不是为了具备多态性,就不该声明virtual析构函数。

    父类指针指向子类对象,而子类对象却经由父类指针被删除,当父类有个non-virtual析构函数是,就会引起灾难。

    C++明确指出,当子类对象经由一个父类指针被删除,而该父类带有一个non-virtual析构函数,其结果未定义--实际执行时通常发生的是对象的derived成分没有被销毁。

    子类的析构函数也未能被执行。然而其base class成分通常会被销毁,于是造成了诡异的“局部销毁”对象。这可能造成资源泄露,败坏数据结构等。

    二、子类指针指向父类对象

    简单来说,C++的多态就是靠父类指针指向子类对象+虚函数来实现的。

    父类指针指向子类对象,可以调用子类从父类继承来的那一部分,但如果父类中声明了virtual,则可以调用子类中的方法,这样就实现了多态。

    而子类指针指向父类对象,可能会调用到父类中没用的方法,因此这是不对的。

    例如:

    class a
    {
        public:
        int aa
    };
    
    class b:public a
    {
        public:
        int bb;从内存的来Class a:

    ---------|
    |占一个int数据大小--|
    |----(aa数据)------ |
    |---------
    Class b:
    ---------|---------
    |占一个int数据大小--|占一个Int数据大小--|
    |从a中继承而来------|---(bb数据----------|
    |------------------
    当定义一个基类类型的指针时
    a *p;这时,这个指针指向的是a类型的数据
    当p指针指向派生类的时候,因为p是a类型的指针,所以*p只解释为a类型数据的长度,即
    ————————-|---------
    |占一个int数据大小--|占一个Int数据大小--|
    |从a中继承而来------|-----(bb数据)-------|
    |------------------
    |------------|------------|
    |-p只指向这个区域_--|

    因此,当基类的指针(P)指向派生类的时候,只能操作派生类中从基类中继承过来的数据。

    指向派生类的指针,因为内存空间比基类长,会导致严重了后果,所以不允许派生类的指针指向基类。而基类的指针可以指向派生类。

    子类指针指向父类对象的强制转换:

    b* pb = (b*)new a;

    参考:

    https://bbs.csdn.net/topics/390626395

    https://blog.csdn.net/lijian2017/article/details/80894219

    https://www.cnblogs.com/yyx1-1/p/6087440.html

    https://www.cnblogs.com/rednodel/p/4122781.html

  • 相关阅读:
    win10--安装python3.7.0和selenium
    mac下Jmeter的安装与环境变量配置
    Mac--PyCharm社区版配置git
    fiddler之简单的接口性能测试
    性能测试服务器瓶颈定位思路
    JMeter性能测试入门-不同类型线程组的使用
    jmeter连接数据库
    性能测试常见问题
    jmeter模拟spike尖峰测
    jmeter逻辑控制器详解(2)
  • 原文地址:https://www.cnblogs.com/zzdbullet/p/9987271.html
Copyright © 2020-2023  润新知