C++ 多态 超详细讲解

C++ 多态 超详细讲解文章目录多态概念引入 1 C 中多态的实现 1 1 多态的构成条件 1 2 虚函数 1 3 虚函数的重写 1 4C 11override amp amp final1 5 重载 覆盖 重写 重定义 隐藏 2 抽象类 2 1 抽象类的概念 2 2 接口继承和实现继承 3 多态的原理 3 1 虚函数表 3 2 多态的原理 3 3 动态绑定与静态绑定 4 继承中的虚函数表 4 1 单继承中的虚函数表 4 2 多继承中的虚函数表 other 一些有意思的题目多态概念引入多态字面意思就是多种形态 我们先来想一想在日常生活中


多态概念引入

1、C++中多态的实现

1.1 多态的构成条件

class Person //成人 { 
     public: virtual void fun() { 
     cout << "全价票" << endl; //成人票全价 } }; class Student : public Person //学生 { 
     public: virtual void fun() //子类完成对父类虚函数的重写 { 
     cout << "半价票" << endl;//学生票半价 } }; void BuyTicket(Person* p) { 
     p->fun(); } int main() { 
     Student st; Person p; BuyTicket(&st);//子类对象切片过去 BuyTicket(&p);//父类对象传地址 } 

1.2 虚函数

virtual void fun() //error! 在类外面的函数不能是虚函数 { 
    } 

1.3虚函数的重写

子类和父类中的虚函数拥有相同的名字,返回值,参数列表,那么称子类中的虚函数重写了父类的虚函数,或者叫做覆盖。

class Person { 
     public: virtual void fun() { 
     cout << "Person->fun()" << endl; } }; class Student { 
     public: //子类重写的虚函数可以不加virtual,因为子类继承了父类的虚函数, //编译器会认为你是想要重写虚函数。 //void fun() 可以直接这样,也对,但不推荐。  virtual void fun()//子类重写父类虚函数 { 
     cout << "Student->fun()" << endl; } }; 

虚函数重写的两个例外:

  1. 协变:
    子类的虚函数和父类的虚函数的返回值可以不同,也能构成重载。但需要子类的返回值是一个子类的指针或者引用,父类的返回值是一个父类的指针或者引用,且返回值代表的两个类也成继承关系。这个叫做协变。

class Person { 
     public: virtual Person* fun()//返回父类指针 { 
     cout << "Person->fun()" << endl; return nullptr; } }; class Student { 
     public: //返回子类指针,虽然返回值不同,也构成重写 virtual Student* fun()//子类重写父类虚函数 { 
     cout << "Student->fun()" << endl; return nullptr; } }; 

也可以这样,也是协变,

class A { 
    }; class B : public A { 
    }; //B继承A class Person { 
     public: virtual A* fun()//返回A类指针 { 
     return nullptr; } }; class Student { 
     public: //返回B类指针,虽然返回值不同,也构成重写 virtual B* fun()//子类重写父类虚函数 { 
     return nullptr; } }; 
//B继承了A,他们的析构函数没有重写。 class A { 
     public: ~A() { 
     cout << "~A()" << endl; } }; class B : public A { 
     public: ~B() { 
     cout << "~B()" << endl; } }; A* a = new B; //把B的对象切片给A类型的指针。 delete a; //调用的是谁的析构函数呢?你希望调用谁的呢? 
class A { 
     public: virtual ~A() { 
     cout << "~A()" << endl; } }; class B : public A { 
     public: virtual ~B() { 
     cout << "~B()" << endl; } }; 

1.4 C++11 override && final

C++11新增了两个关键字。用final修饰的虚函数无法重写。用final修饰的类无法被继承。final像这个单词的意思一样,这就是最终的版本,不用再更新了。

class A final //A类无法被继承 { 
     public: virtual void fun() final //fun函数无法被重写 { 
    } }; class B : public A //error { 
     public: virtual void fun() //error { 
     cout << endl; } }; 

被override修饰的虚函数,编译器会检查这个虚函数是否重写。如果没有重写,编译器会报错。

class A { 
     public: virtual void fun() { 
    } }; class B : public A { 
     public: //这里我想重写fun,但写成了fun1,因为有override,编译器会报错。 virtual void fun1() override { 
     cout << endl; } }; 

1.5 重载,覆盖(重写),重定义(隐藏)

2、抽象类

2.1 抽象类的概念

再虚函数的后面加上=0就是纯虚函数,有纯虚函数的类就是抽象类,也叫做接口类。抽象类无法实例化出对象。抽象类的子类也无法实例化出对象,除非重写父类的虚函数。

class Car { 
     public: virtual void fun() = 0; //不用实现,只写接口就行。 } 

2.2 接口继承和实现继承

class A { 
     public: virtual void fun(int val = 0)//父类虚函数 { 
     cout <<"A->val = "<< val << endl; } void Fun() { 
     fun();//传过来一个子类指针调用fun() } }; class B: public A { 
     public: virtual void fun(int val = 1)//子类虚函数 { 
     cout << "B->val = " << val << endl; } }; B b; A* a = &b; a->Fun(); 

3、 多态的原理

3.1 虚函数表

class A { 
     public: virtual void fun() { 
    } protected: int _a; }; 
class A { 
     public: void fun1() { 
    } virtual void fun2() { 
    } }; A* ap = nullptr; ap->fun1(); //调用成功,因为这是普通函数的调用 ap->fun2(); //调用失败,虚函数需要对指针操作,无法操作空指针。 

我们先来看看继承的虚函数表。

class A { 
     public: virtual void fun1() { 
    } virtual void fun2() { 
    } }; class B : public A { 
     public: virtual void fun1()//重写父类虚函数 { 
    } virtual void fun3() { 
    } }; A a; B b; //我们通过调试看看对象a和b的内存模型。 

66
子类跟父类一样有一个虚表指针。
子类的虚函数表一部分继承自父类。如果重写了虚函数,那么子类的虚函数会在虚表上覆盖父类的虚函数。
本质上虚函数表是一个虚函数指针数组,最后一个元素是nullptr,代表虚表的结束。
所以,如果继承了虚函数,那么
1 子类先拷贝一份父类虚表,然后用一个虚表指针指向这个虚表。
2 如果有虚函数重写,那么在子类的虚表上用子类的虚函数覆盖。
3 子类新增的虚函数按其在子类中的声明次序增加到子类虚表的最后。
234
下面来一道面试题:
虚函数存在哪里?
虚函数表存在哪里?
虚函数是带有virtual的函数,虚函数表是存放虚函数地址的指针数组,虚函数表指针指向这个数组。对象中存的是虚函数指针,不是虚函数表。
虚函数和普通函数一样存在代码段。
那么虚函数表存在哪里呢?
我们创建两个A对象,发现他们的虚函数指针相同,这说明他们的虚函数表属于类,不属于对象。所以虚函数表应该存在共有区。
堆?堆需要动态开辟,动态销毁,不合适。
虚函数表放在了全局数据段。


































3.2多态的原理

我们现在来看看多态的原理。

class Person //成人 { 
     public: virtual void fun() { 
     cout << "全价票" << endl; //成人票全价 } }; class Student : public Person //学生 { 
     public: virtual void fun() //子类完成对父类虚函数的重写 { 
     cout << "半价票" << endl;//学生票半价 } }; void BuyTicket(Person* p) { 
     p->fun(); } 

77
这样就实现了不同对象去调用同一函数,展现出不同的形态。
满足多态的函数调用是程序运行是去对象的虚表查找的,而虚表是在编译时确定的。
普通函数的调用是编译时就确定的。






3.3动态绑定与静态绑定

#include  
      using namespace std; class Base { 
     public: virtual void func() { 
     cout << "Base func\n"; } }; class Son : public Base { 
     public: void func() { 
     Base::func(); cout << "Son func\n"; } }; int main() { 
     Son b; b.func(); return 0; } 

4 、继承中的虚函数表

4.1 单继承中的虚函数表

这里DV继承BV。

class BV { 
     public: virtual void Fun1() { 
     cout << "BV->Fun1()" << endl; } virtual void Fun2() { 
     cout << "BV->Fun2()" << endl; } }; class DV : public BV { 
     public: virtual void Fun1() { 
     cout << "DV->Fun1()" << endl; } virtual void Fun3() { 
     cout << "DV->Fun3()" << endl; } virtual void Fun4() { 
     cout << "DV->Fun4()" << endl; } }; 

我们想个办法打印虚表,

typedef void(*V_PTR)(); //typedef一下函数指针,相当于把返回值为void型的 //函数指针定义成 V_PTR. void PrintPFTable(V_PTR* table)//打印虚函数表 { 
     //因为虚表最后一个为nllptr,我们可以利用这个打印虚表。 for (size_t i = 0; table[i] != nullptr; ++i) { 
     printf("table[%d] : %p->", i, table[i]); V_PTR f = table[i]; f(); cout << endl; } } BV b; DV d; // 取出b、d对象的前四个字节,就是虚表的指针, //前面我们说了虚函数表本质是一个存虚函数指针的指针数组, //这个数组最后面放了一个nullptr // 1.先取b的地址,强转成一个int*的指针 // 2.再解引用取值,就取到了b对象前4个字节的值,这个值就是指向虚表的指针 // 3.再强转成V_PTR*,这是我们打印虚表函数的类型。 // 4.虚表指针传给PrintPFTable函数,打印虚表 // 5,有时候编译器资源释放不完全,我们需要清理一下,不然会打印多余结果。 PrintPFTable((V_PTR*)(*(int*)&b)); PrintPFTable((V_PTR*)(*(int*)&d)); 

4.2 多继承中的虚函数表

我们先来看一看一道题目,

class A { 
     public: virtual void fun1() { 
     cout << "A->fun1()" << endl; } protected: int _a; }; class B { 
     public: virtual void fun1() { 
     cout << "B->fun1()" << endl; } protected: int _b; }; class C : public A, public B { 
     public: virtual void fun1() { 
     cout << "C->fun1()" << endl; } protected: int _c; }; C c; //sizeof(c) 是多少呢? 
//Derive继承Base1和Base2 class Base1 { 
     public: virtual void fun1() { 
     cout << "Base1->fun1()" << endl; } virtual void fun2() { 
     cout << "Base1->fun2()" << endl; } }; class Base2 { 
     public: virtual void fun1() { 
     cout << "Base2->fun1()" << endl; } virtual void fun2() { 
     cout << "Base2->fun2()" << endl; } }; class Derive : public Base1, public Base2 { 
     public: virtual void fun1() { 
     cout << "Derive->fun1()" << endl; } virtual void fun3() { 
     cout << "Derive->fun3()" << endl; } }; 
Derive d; PrintPFTable((V_PTR*)(*(int*)&d)); PrintPFTable((V_PTR*)(*(int*)((char*)&d+sizeof(Base2)))); 

Ret:ret

(全文完)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/203316.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午10:05
下一篇 2026年3月19日 下午10:06


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号