美文网首页
C++面向对象-多态

C++面向对象-多态

作者: 码无不至 | 来源:发表于2020-06-21 10:17 被阅读0次

    父类和子类指针

    1 父类指针可以指向子类对象,这是安全的,开发中经常用到,继承方式必须是public方式。
    2 子类指针指向父类是不安全的,因为子类指针可能访问到父类以外的数据,而子类对象并没有创建。

    class Person {
    public:
        int m_age;
    };
    
    class Student : public Person {
    public:
        int m_score;
    };
    
    int main() {
        //父类指针指向子类对象,安全
        Person *p = (Person *) new Student();
        p->m_age = 10;
    
        //子类指针指向父类对象,不安全
        Student *s = (Student *) new Person();
        //指向父类对象的子类指针访问了自己的成员变量,指针指向超出了范围
        s->m_score = 10;
        return 0;
    }
    

    多态

    多态是面向对象一个非常重要的特性,同一操作作用于不同的对象,产生不同的执行结果,在运行时,可以识别出真正的对象类型,调用对应子类的函数。产生多态的条件如下:子类重写父类的成员函数,父类指针指向子类对象,利用父类指针调用重写的成员函数,这个成员函数必须是由Virtual修饰的成员函数,父类只要声明了Virtual,子类自动转为Virtual函数

    class Animal {
    public:
        virtual void run() {
            cout << "Animal::run()" << endl;
        }
    };
    
    class Dog : public Animal {
    public:
        void run() {
            cout << "Dog::run()" << endl;
        }
    };
    
    class ErHa : public Dog {
    public:
        void run() {
            cout << "ErHa::run()" << endl;
        }
    };
    
    int main() {
        
        Dog *dog0 = new Dog();
        dog0->run(); //调用dog下的run函数
        
        Dog *dog1 = new ErHa();
        dog1->run();//调用ErHa下的run函数
        
    
        return 0;
    }
    

    以上就是虚函数实现的列子,我们再进一步,为什么不加Virtual 就不能实现多态了,加了Virtual 就能实现多态呢,多态的实现是靠虚表实现的。我们先看看这几个类的大小

        //8
        cout << sizeof(Dog) << endl;
        //8
        cout << sizeof(ErHa) << endl;
        //8
        cout << sizeof(Animal) << endl;
    

    这几个类sizeof大小是8,我们这是64位的,其实这个新增加的大小就是虚表的地址,指向虚表,而且这个虚表地址在类的最前面,而虚表里面存着本类虚函数的地址,从而进行真正的调用,每一个类只有一份虚表,多个对象共享一份虚表,无论这个对象是在堆还是栈还是全局对象。我们可以通过反汇编和内存调试也能看出来,这里我就不演示了。当父类实现了虚函数,而子类没有实现该虚函数的时候,我们来看看这个情况:

    class Animal {
    public:
        virtual void run() {
            cout << "Animal::run()" << endl;
        }
        virtual void speak() {
            cout << "Animal::speak()" << endl;
        }
    };
    
    class Dog : public Animal {
    public:
        int m_age; //dog的age
    };
    
    int main() {
        
        Animal *animal = new Animal();
        animal->run();会调用Animal::run()
        animal->speak();会调用Animal::speak()
        
        Dog *dog0 = new Dog();  
        dog0->run(); //会调用Animal::run()
        dog0->speak(); //会调用Animal::speak()
    
        return 0;
    }
    

    当子类没有重写父类虚函数的时候,它也会调用父类的虚函数,其底层实现也是通过虚表查找到函数调用,也就是子类即时没有虚函数,也有自己的虚表,我反汇编看到此时父类子类的虚表地址一样,可能不同的平台有不同的处理,虚表没有继承一说。如果子类想调用父类的虚函数方法的时候,应该显示调用,注意C++ 没有super等类似关键字,正确调用如下:

    class Animal {
    public:
        virtual void run() {
            cout << "Animal::run()" << endl;
        }
        virtual void speak() {
            cout << "Animal::speak()" << endl;
        }
    };
    
    class Dog : public Animal {
    public:
        int m_age; //dog的age
        
        void run() {
            Animal::run(); //直接用类::显示调用
            cout << "Dog::run()" << endl;
        }
        void speak() {
            Animal::speak(); //直接用类::显示调用
            cout << "Dog::speak()" << endl;
        }
    };
    
    int main() {
        Dog *dog0 = new Dog();
        dog0->run();
        dog0->speak();
    
        return 0;
    }
    

    含有虚虚函数实现的父类时候,父类的析构函数也需要声明为virtual函数,此时析构函数变成了虚析构函数,这样才能够保证销毁对象的时候父类子类析构函数调用,保证析构的完整性,子类可不加(virtaul)。

    纯虚函数

    没有函数体,且初始化为0的虚函数,用来定义接口规范

    class Animal {
    public:
        virtual void speak() = 0;
        virtual void run() = 0;
    };
    

    含有纯虚函数的类是抽象类,不可以实例化,不能创建对象,抽象类成也可以包含其它非纯虚函数,以及其他成员变量,抽象类的指针可以指向子类对象,如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类

    多继承

    C++允许一个类继承多个类,可以拥有多个类的特性,多继承增加了设计的复杂性,不建议使用。

    #include <iostream>
    using namespace std;
    
    class Student {
    public:
        int m_score;
        Student(int score = 0) :m_score(score) { }
        void study() {
            cout << "Student::study() - score = " << m_score << endl;
        }
        ~Student() {
            cout << "~Student" << endl;
        }
    };
    
    class Worker {
    public:
        int m_salary;
        Worker(int salary = 0) :m_salary(salary) { }
        void work() {
            cout << "Worker::work() - salary = " << m_salary << endl;
        }
        ~Worker() {
            cout << "~Worker" << endl;
        }
    };
    
    class Undergraduate : public Student, public Worker {
    public:
        int m_grade;
        Undergraduate(
                      int score = 0,
                      int salary = 0,
                      int grade = 0) :Student(score), Worker(salary), m_grade(grade) {
            
        }
        void play() {
            cout << "Undergraduate::play() - grade = " << m_grade << endl;
        }
        ~Undergraduate() {
            cout << "~Undergraduate" << endl;
        }
    };
    
    int main() {
        {
            Undergraduate ug;
            ug.m_score = 100;
            ug.m_salary = 2000;
            ug.m_grade = 4;
            ug.study();
            ug.work();
            ug.play();
        }
        
        cout << sizeof(Undergraduate) << endl;
        
        return 0;
    }
    

    注意:这种多继承,父类的成员变量在子类前面,先继承谁,谁的成员就在前面。多继承的构造函数一样需要使用初始化列表调用父类构造函数,

    父类如果都含有虚函数,子类多继承多个父类后,子类对象会产生多个虚函数表,顺序跟继承顺序有关。

    #include <iostream>
    using namespace std;
    
    class Student {
    public:
        virtual void study() {
            cout << "Student::study()" << endl;
        }
    };
    
    class Worker {
    public:
        virtual void work() {
            cout << "Worker::work()" << endl;
        }
    };
    
    class Undergraduate : public Student, public Worker {
    public:
        void study() {
            cout << "Undergraduate::study()" << endl;
        }
        void work() {
            cout << "Undergraduate::work()" << endl;
        }
        void play() {
            cout << "Undergraduate::play()" << endl;
        }
    };
    
    int main() {
    
        //含有16个字节,因为有2张虚表
        cout << sizeof(Undergraduate) << endl;
        
        Student *stu = new Undergraduate();
        stu->study();
        
        Worker *worker = new Undergraduate();
        worker->work();
        
        return 0;
    }
    
    

    同名成员

    C++允许同名成员函数,同名成员变量,子类不会覆盖,访问的时候加上类名表示作用域,如下所示:

    #include <iostream>
    using namespace std;
    
    class Student {
    public:
        int m_age;
    };
    
    class Worker {
    public:
        int m_age;
    };
    
    class Undergraduate : public Student, public Worker {
    public:
        int m_age;
    };
    
    int main() {
        Undergraduate ug;
        ug.m_age = 10;
        ug.Student::m_age = 20;
        ug.Worker::m_age = 30;
        ug.Undergraduate::m_age = 40;
    
            //这里等于12
        cout << sizeof(Undergraduate) << endl;
    
        return 0;
    }
    

    虚继承

    虚继承是为了解决菱形继承带来的成员变量冗余,重复。而且最底层子类因为二义性无法访问基类的的成员变量。我们先来看看菱形继承:

    #include <iostream>
    using namespace std;
    
    class Person {
    public:
        int m_age;
    };
    
    class Student :  public Person {
    public:
        int m_score;
    };
    
    class Worker :  public Person {
    public:
        int m_salary;
    };
    
    class Undergraduate : public Student, public Worker {
    public:
        int m_grade;
    };
    
    int main() {
         Undergraduate ug;
         ug.m_grade = 10;
         ug.m_score = 20;
         ug.Student::m_age = 20;
         ug.Worker::m_age = 30;
         cout << sizeof(Undergraduate) << endl; //20
         return 0;
    }
    

    这里我们看到Undergraduate的大小是20,因为这样继承Undergraduate的父类两个类都有m_age成员变量,而且访问的时候我们需要通过作用域去访问,直接访问会报错。这种继承方式,基类的成员变量在最底层子类就冗余了,没有必要,为了解决这种问题,可以使用虚继承。加上virtual关键字:

    #include <iostream>
    using namespace std;
    
    class Person {
    public:
        int m_age;
    };
    
    class Student :virtual public Person {
    public:
        int m_score;
    };
    
    class Worker :virtual public Person {
    public:
        int m_salary;
    };
    
    class Undergraduate : public Student, public Worker {
    public:
        int m_grade;
    };
    
    
    class Person1
    {
        
    };
    
    int main() {
         Undergraduate ug;
         ug.m_grade = 10;
         ug.m_score = 20;
         ug.m_age = 30;
    
         return 0;
    }
    

    此时三个类比如:Student、Worker、Undergraduate都有了虚函数表指针,此时成员变量m_age在最底层子类只有一份内存。此时Person类被称为虚基类

    相关文章

      网友评论

          本文标题:C++面向对象-多态

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