美文网首页
C++入门06 --多态,虚函数,虚函数表,纯虚函数,抽象类

C++入门06 --多态,虚函数,虚函数表,纯虚函数,抽象类

作者: YanZi_33 | 来源:发表于2021-08-12 19:15 被阅读0次

    多态

    • 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态;
    • 多态是面向对象的非常重要的一个特性;
      • 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果;
      • 在运行时,可以识别出真正的对象类型,调用对应子类中的函数;
    #include <iostream>
    
    using namespace::std;
    
    class Animal {
    
    public:
        void run(){
            cout << "Animal run()" << endl;
        }
    };
    
    class Dog : public Animal{
    
    public:
        void run(){
            cout << "Dog run()" << endl;
        }
    };
    
    class Cat : public Animal{
    
    public:
        void run(){
            cout << "Cat run()" << endl;
        }
    };
    
    class Pig : public Animal{
    
    public:
        void run(){
            cout << "Pig run()" << endl;
        }
    };
    
    void liu(Animal *a){
        a->run();
    }
    
    int main(int argc, const char * argv[]) {
        
        liu(new Dog());
        liu(new Cat());
        liu(new Pig());
        
        return 0;
    }
    
    • 由于用Animal *a来接收形参,那么执行run方法是,调用的都是Animal类中的方法,并没有实现根据不同的对象类型,调用对应的函数方法;

    虚函数

    • C++中多态时通过虚函数来实现的;

    • 虚函数:被virtual关键字修饰的函数,称为虚函数;

    • 只要在父类中声明为虚函数,子类中的重写的函数也自动变成虚函数,也就是说子类中的函数可以省略virtual关键字;

    • 多态实现的要素:

      • 子类重写父类的成员函数;
      • 父类指针指向子类对象;
      • 利用父类指针调用子类的成员函数,且成员函数为虚函数;从基类开始虚,从中间类开始虚,也不能实现多态的功能;
    #include <iostream>
    
    using namespace::std;
    
    class Animal {
    
    public:
        virtual void run(){
            cout << "Animal run()" << endl;
        }
    };
    
    class Dog : public Animal{
    
    public:
        void run(){
            cout << "Dog run()" << endl;
        }
    };
    
    class Cat : public Animal{
    
    public:
        void run(){
            cout << "Cat run()" << endl;
        }
    };
    
    class Pig : public Animal{
    
    public:
        void run(){
            cout << "Pig run()" << endl;
        }
    };
    
    void liu(Animal *a){
        a->run();
    }
    
    int main(int argc, const char * argv[]) {
        
        liu(new Dog());
        liu(new Cat());
        liu(new Pig());
        
        return 0;
    }
    
    • 上述的代码实现了多态的功能,即不同的子类对象调用属于自己的函数方法;

    虚表

    • 虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫做虚函数表;
    #include <iostream>
    
    using namespace::std;
    
    class Animal {
    public:
        int m_age;
        
        virtual void speak(){
            cout << "Animal speak()" << endl;
        }
        
        virtual void run(){
            cout << "Animal run()" << endl;
        }
    };
    
    class Cat : public Animal{
    public:
        int m_life;
        
        void speak(){
            cout << "Cat speak()" << endl;
        }
        
        void run(){
            cout << "Cat run()" << endl;
        }
    };
    
    int main(int argc, const char * argv[]) {
        
        Animal *cat = new Cat();
        cat->m_age = 3;
        cat->speak();
        cat->run();
        
        cout << cat << endl;
        
        return 0;
    }
    
    • 当前测试环境是在MacOS,即x64环境,则指针占用8个字节;
    • 当Animal没有虚函数时,cat实例对象有m_age,m_life有两个成员变量,所以cat实例对象占用8个字节;
    • 当Animal有虚函数时,即有virtual关键字修饰函数时,cat实例对象会占用16个字节,前面8个字节存储的是Cat类的虚函数表的地址;
    • cat实例对象的内部布局如下所示:
    Snip20210813_147.png
    • 可通过汇编分析证明上面的描述:
    Snip20210813_148.png
    • movl $0x3, 0x8(%rax):rax存储的是cat实例对象的内存地址,由于虚函数地址占了前8个字节,所以m_age的偏移量为8;
    • movq (%rax), %rcx:取出cat实例对象的内存地址中前8个字节内容即虚函数的地址,存入rcx寄存器;
    • callq *(%rcx):取出虚函数地址的前8个字节内容,即speak函数内存地址,然后调用speak函数;
    • 同理callq *0x8(%rcx),从虚函数地址的第8个字节开始的后8个字节的内容,即run函数内存地址,然后调用run函数;

    总结:

    • 所有cat实例对象,不管在栈区,全局区,堆区都共用一份虚表,也就是说每个类都有自己独立的一份虚表;
    • cat实例对象的前8个字节存储了虚表的内存地址,虚表中存储着cat类的所有虚函数;
    • 若cat类存储虚函数,且cat子类没有实现run方法,那么虚表中会存储父类Animal的run方法,所以会调用Animal的run方法,这也就是说调用目标方法,如果子类没有实现,就调用父类的实现;

    子类调用父类的成员函数

    #include <iostream>
    
    using namespace::std;
    
    class Animal {
    public:
        int m_age;
        
        virtual void speak(){
            cout << "Animal speak()" << endl;
        }
        
        virtual void run(){
            cout << "Animal run()" << endl;
        }
    };
    
    class Cat : public Animal{
    public:
        int m_life;
        
        void speak(){
            Animal::speak();
            cout << "Cat speak()" << endl;
        }
        
        void run(){
            Animal::run();
            cout << "Cat run()" << endl;
        }
    };
    
    int main(int argc, const char * argv[]) {
        
        Animal *cat = new Cat();
        cat->m_age = 3;
        cat->speak();
        cat->run();
        
        cout << cat << endl;
        
        cout << sizeof(Animal) << endl;
        
        return 0;
    }
    
    • 直接在子类的函数中,调用父类的函数;

    虚析构函数

    • 含有虚函数的类,应该将析构函数声明为虚函数即虚析构函数;
    • delete父类指针时,才会调用子类的析构函数,保证析构的完整性,否则会造成内存泄漏;
    #include <iostream>
    
    using namespace::std;
    
    class Animal {
    public:
        int m_age;
        
        virtual void speak(){
            cout << "Animal speak()" << endl;
        }
        
        virtual void run(){
            cout << "Animal run()" << endl;
        }
        
        virtual ~Animal(){
            cout << "~Animal()" << endl;
        }
    };
    
    class Cat : public Animal{
    public:
        int m_life;
        
        void speak(){
            Animal::speak();
            cout << "Cat speak()" << endl;
        }
        
        void run(){
            Animal::run();
            cout << "Cat run()" << endl;
        }
        
        ~Cat(){
            cout << "~Cat()" << endl;
        }
    };
    
    int main(int argc, const char * argv[]) {
        
        Animal *cat = new Cat();
        cat->m_age = 3;
        cat->speak();
        cat->run();
        
        delete cat;
        
        cout << cat << endl;
        
        cout << sizeof(Animal) << endl;
        
        return 0;
    }
    
    • virtual ~Animal():父类Animal的析构函数声明为虚函数,否则Animal *cat = new Cat(),不会调用Cat类的析构函数,造成内存泄漏;

    纯虚函数

    • 纯虚函数:没有函数体且初始化为0的虚函数,用来定义借口规范;
    class Animal {
    public:
        virtual void speak() = 0;
        
        virtual void run() = 0;
    };
    

    抽象类

    • 含有纯虚函数的类即为抽象类,不可以实例化创建对象;例如上述的Animal类,在现实世界中是没有animal动物实例对象的,它是一个抽象的概念;
    • 抽象类也可以包含非纯虚函数;
    • 如果父类是抽象类,子类没有全部实现纯虚函数,那么这个子类依然是抽象类,不能实例化创建对象;

    相关文章

      网友评论

          本文标题:C++入门06 --多态,虚函数,虚函数表,纯虚函数,抽象类

          本文链接:https://www.haomeiwen.com/subject/tstrbltx.html