C++面向对象-多态

父类和子类指针

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 
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 
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 
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 
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 
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++面向对象-多态)