• C++ 多态


    多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了
    Person。Person对象买票全价,Student对象买票半价

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public:
    7. virtual void BuyTicket() { cout << "买票-半价" << endl; }
    8. /*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
    9. 为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
    10. 这样使用*/
    11. /*void BuyTicket() { cout << "买票-半价" << endl; }*/
    12. };
    13. void Func(Person& p)
    14. //虚函数重写的两个例外:
    15. //1. 协变(基类与派生类虚函数返回值类型不同)
    16. //派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
    17. //针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解)
    18. //2. 析构函数的重写(基类与派生类析构函数的名字不同)
    19. //如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
    20. //都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
    21. //看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
    22. //理,编译后析构函数的名称统一处理成destructor。
    23. {
    24. p.BuyTicket();
    25. }
    26. int main()
    27. {
    28. Person ps;
    29. Student st;
    30. Func(ps);
    31. Func(st);
    32. return 0;
    33. }

     原理图

     

    普通继承是子类继承父类的全部,是一种复用。

    虚函数在多态情况下是子类复用父类的接口,重写父类的实现。

     多态条件

     举例(ps:下面就是通过修改多态的条件从而不满足多态)

    1. void Func(Person p)
    2. {
    3. p.BuyTicket();
    4. }

    要求三同,假设我们这里让参数不同:

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public:
    7. virtual void BuyTicket(int ) { cout << "买票-半价" << endl; }
    8. };

    重写虚函数

    1. Person* p=new Person;
    2. delete p;
    3. p = new Student;
    4. delete p;

    我们发现子类的析构函数没有被正确调用。

    这是因为delete是根据指针类型来调用的。

    但是person指针可能指向person,也可能指向student。

    它的调用如下:先调用destrutor函数再调用析构函数。 destrutor是普通函数,普通函数调用看类型,因为是person指针类型,所以调用person析构。

    我们不希望它是普通继承,因为如果是普通继承就不会调子类析构,会造成内存泄漏。

    那我们怎么样让它变多态呢?第一:三同(参数相同,函数名相同,返回值相同)二:虚函数重写

    ~Person()    ~Student()参数没有,返回值没有,函数名不同,但是为了满足多态进行了特殊处理。

    现在只差虚函数重写这一项了,我们给析构加上virtual:

    1. virtual ~Person()
    2. {
    3. cout << "~Person()" << endl;
    4. }
    5. };
    6. virtual ~Student()
    7. {
    8. cout << "~Student()" << endl;
    9. }

    练习题

    说出如下代码打印结果:

    1. class Person
    2. {
    3. public:
    4. virtual void func(int a = 1) { cout << "A->" << a << endl; }
    5. virtual void test() { func(); }
    6. };
    7. class Student: public Person
    8. {
    9. void func(int a = 0) {cout << "B->" << a << endl;}
    10. };
    11. int main()
    12. {
    13. Student* B=new Student;
    14. B->test();
    15. return 0;
    16. }

    结果:

    B->1

    解析:

    那如果是这样呢?我不访问test(),我直接访问func*()

    1. class Person
    2. {
    3. public:
    4. virtual void func(int a = 1) { cout << "A->" << a << endl; }
    5. virtual void test() { func(); }
    6. };
    7. class Student: public Person
    8. {
    9. public:
    10. void func(int a = 0) {cout << "B->" << a << endl;}
    11. };
    12. int main()
    13. {
    14. Student* B=new Student;
    15. B->func();
    16. return 0;
    17. }

     答案很明显,不构成多态,因为多态需要父类指针,而下面是子类的指针,不构成多台那就是普通的,普通的话就会隐藏,会访问子类的。

     要想访问父类我们指定父类域就可以了:

    1. #include
    2. using namespace std;
    3. class Person
    4. {
    5. public:
    6. virtual void func(int a = 1) { cout << "A->" << a << endl; }
    7. virtual void test() { func(); }
    8. };
    9. class Student : public Person
    10. {
    11. public:
    12. void func(int a = 0) {
    13. Person::func(1); // 调用基类Person的func函数
    14. cout << "B->" << a << endl;
    15. }
    16. };
    17. int main()
    18. {
    19. Student* B = new Student;
    20. B->Person::func();
    21. return 0;
    22. }

    要想形成多态我们改为父类指针就行了:

    1. class Person
    2. {
    3. public:
    4. virtual void func(int a = 1) { cout << "A->" << a << endl; }
    5. virtual void test() { func(); }
    6. };
    7. class Student: public Person
    8. {
    9. public:
    10. void func(int a = 0) {cout << "B->" << a << endl;}
    11. };
    12. int main()
    13. {
    14. Person* B = new Person;
    15. B=new Student;
    16. B->func();
    17. return 0;
    18. }

    抽象类

    虚函数+“=0”就是纯虚函数包含纯虚函数的类就叫抽象类,抽象类不能实例化:

    1. class A
    2. {
    3. public:
    4. virtual void fun() = 0;
    5. };

     

    那抽象类要怎么才能实例化呢?不能实例化这个类就没有意义,实例化要通过虚函数重写:

    如下,指针指向谁就访问谁

    1. class A
    2. {
    3. public:
    4. virtual void fun() = 0;
    5. };
    6. class B: public A
    7. {
    8. void fun()
    9. {
    10. cout << "B->舒适" << endl;
    11. }
    12. };
    13. class C : public A
    14. {
    15. void fun()
    16. {
    17. cout << "B->巴适" << endl;
    18. }
    19. };
    20. void fun(A* p)
    21. {
    22. p->fun();
    23. }
    24. int main()
    25. {
    26. fun(new B);
    27. fun(new C);
    28. return 0;
    29. }

     

    虚函数表

    如下代码求出来的字节大小为多少:

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public:
    7. void BuyTicket() { cout << "买票-半价" << endl; }
    8. private:
    9. int _a;
    10. };
    11. int main()
    12. {
    13. cout << sizeof(Student) << endl;
    14. return 0;
    15. }

     答案:

     原因,只要有虚函数,大小就要把多算一个虚函数指针的大小,如果没有虚函数,那它的大小就为它的成员int_a的大小,就是4:

     虚函数指针存放在 虚函数表里面。虚函数表我们称为vf

    ptr(virtaul function  ptr  table ),而vfptr在代码段里存放着。

     现在在子类中再加个char _b成员变量,然后在64位操作系统下运行,此刻的大小为多少呢?

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. };
    5. class Student : public Person {
    6. public:
    7. void BuyTicket() { cout << "买票-半价" << endl; }
    8. private:
    9. int _a;
    10. char _b;
    11. };
    12. int main()
    13. {
    14. Student b;
    15. cout << sizeof(Student) << endl;
    16. return 0;
    17. }

     答案:

     因为64位操作系统按8进行内存对齐。

    如下,如果fun1,fun2是虚函数,fun3不是虚函数,那么fun3就不在vfptr里面。

    虚函数实际上在代码段里面,只是把地址放在vfptr里面而已。

    1. class Student : public Person {
    2. public:
    3. virtual void fun1() { cout << "fun1()" << endl; }
    4. virtual void fun2(){ cout << "fun2()" << endl; }
    5. void fun3(){ cout << "fun3()" << endl; }
    6. };
    7. int main()
    8. {
    9. Student b;
    10. return 0;
    11. }

    多态是怎么来的

    观察下面地址和代码,我们发现子类只是把父类的BuyTicket()函数重写了,func()函数没有重写,还是同一个func()。

    1. class Person {
    2. public:
    3. virtual void BuyTicket() { cout << "买票-全价" << endl; }
    4. virtual void fun(){}
    5. };
    6. class Student : public Person {
    7. public:
    8. virtual void BuyTicket() { cout << "买票-半价" << endl; }
    9. };
    10. void Func(Person& p)
    11. {
    12. p.BuyTicket();
    13. }
    14. int main()
    15. {
    16. Person ps;
    17. Student st;
    18. Func(ps);
    19. Func(st);
    20. return 0;
    21. }

    调用BuyTick()函数的时候它也不知道调父类的BuyTick()函数还是子类的BuyTick()函数,但是它会去调vfptr,如果调父类就通过虚函数表去调父类的BuyTick(),如果调子类就对父类的vfptr切片,仍然通过vfptr调BuyTick()​​​​​​​,不过此时BuyTick()​​​​​​​已经是子类的BuyTick()了。

  • 相关阅读:
    RN自定义lodding提示框(带遮罩层)
    es6---模块化
    http请求头部(header)详解
    软件测试|Python自动化测试实现的思路
    【数据结构笔记06】数据结构之队列的顺序表示和实现(普通队列、循环队列)
    SpringMVC 05: SpringMVC中携带数据的页面跳转
    【数据结构】手撕单链表
    【数组】最大人工岛
    数据结构栈的实现
    python --PDF转Word
  • 原文地址:https://blog.csdn.net/m0_65143871/article/details/133967066