C++中的多态特性总结

一、分类:

1、静态多态:在编译时完成,例如:函数重载,泛型编程。

2、动态多态:程序执行期间,例如:虚函数{(1)一定要在派生类中对基函数进行重写。(2)基类的指针或引用调用虚函数}


二、虚函数:{virtual 返回值类型  函数名 (参数列表){}}

1、同名隐藏:如果基类与派生类具有相同的函数,则在调用函数时优先调用派生类的成员函数。

2、重写(覆盖):在继承体系中,如果基类中有虚函数,在派生类中有和基类虚函数原型完全相同的函数,则会发生函数重载。

    注:(1)若返回类型不同,则不能构成重写,但若都返回本类的指针或引用也可以构成重写。

            (2)析构函数也可以构成重写。【建议:最好将基类中析构函数写成虚函数】

3、纯虚函数:在成员函数的形参列表后面写上“=0”;

    virtual 返回值 函数名(参数列表)=0 {}

4、如果一个函数具有虚函数,则在内存中存放的时候会将前四个字节变成虚表指针,后面是成员变量。

虚表指针指向虚表,里面存放着虚函数地址。

5、虚函数的调用:

(1)取对象地址

(2)取对象的前4个字节的内容,-->虚表指针

(3)相对应的虚函数

6、虚函数的缺点:

(1)对象多了4个字节

(2)效率低

7、构造函数不能做虚函数:

    因为调用构造函数后才可以形成虚表以及虚表指针,如果将构造函数变成虚函数,则必须要先调用虚表才能调用构造函数,但因为没有调用构造函数就没有虚表,所以,构造函数就不能变成虚函数。

8、友元函数静态成员函数不能做虚函数:因为没有this指针。

总结:不同对象的虚表函数表不一样,所以实现了动态多态。


三、虚表(虚函数地址表)

1、基类中的虚函数按照按照在类中的声明次序,派生类中自己的虚函数则放在基类虚函数后,也按照顺序存放,如下图所示:

2、派生类:

(1)单继承:

a.检测基类中是否对基类虚函数进行重写,若重写则调用重写后的,反之,调用基类函数。

b.派生类中自己的虚函数在基类后,并按定义顺序放。

(2)多继承:

先继承的虚表在前,若派生类有单独的虚函数,则放在第一张虚表后。

3、总结虚拟机承德虚表格式:

四、几个小问题:

1、Base b1,b2,b3;    Derived d1,d2,d3;  

b1,b2,b3是否共用一张虚表?  共用

d1,d2,d3是否与b1,b2,b3共用一张虚表?  不共用

2、为什么下述代码会实现多态?

void Test(Base &b)

{b.Test();}

因为b是一个引用,所以会调用虚表

3、Base *pd=(Base*)&d1;(d1是派生类变量)调用谁的虚表?

答:调用的是派生类的虚表,因为d1里面的虚表指针指向的是派生类的虚表,尽管取得是Base类大小的空间但是虚表指针指向的是派生类的虚表指针,所以最终调用的是自己的虚表。

### C++多态特性 C++ 中的多态分为两种主要形式:编译时多态和运行时多态。这两种多态机制分别通过不同的技术实现。 --- #### **编译时多态** 编译时多态通常由函数重载和运算符重载实现。这种类型的多态在编译阶段就已经确定,因此也被称为静态绑定或多态。 - 函数重载是指在同一作用域下定义多个具有相同名称但参数列表不同的函数。这些差异可以通过参数的数量、类型或顺序体现出来[^1]。 ```cpp class MathOperations { public: int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int add(int a, int b, int c) { return a + b + c; } }; int main() { MathOperations mo; std::cout << mo.add(2, 3) << std::endl; // 调用 int add(int, int) std::cout << mo.add(2.5, 3.5) << std::endl; // 调用 double add(double, double) std::cout << mo.add(2, 3, 4) << std::endl; // 调用 int add(int, int, int) return 0; } ``` - 上述代码展示了如何利用函数重载实现编译时多态。具体调用哪个 `add` 方法取决于传入的实际参数类型及其数量[^1]。 --- #### **运行时多态** 运行时多态依赖于继承和虚函数(virtual function),其核心在于动态绑定。这意味着具体的函数调用是在程序执行过程中决定的,而不是在编译时固定下来。 - 动态绑定的关键是使用 `virtual` 关键字标记基类中的方法。一旦某个方法被声明为虚拟函数,则在其派生类中可以对其进行重新定义(覆盖)。此时,即使通过基类指针或引用访问该方法,实际调用的是派生类版本的方法[^4]。 ```cpp #include <iostream> using namespace std; class A { public: virtual void ff() { cout << "A::ff()" << endl; } // 声明为虚函数 void gg() { cout << "A::gg()" << endl; } // 非虚函数 void hh(int x) { cout << "A::hh()" << endl; } // 非虚函数 }; class B : public A { public: void ff() override { cout << "B::ff()" << endl; } // 覆盖虚函数 void gg() { cout << "B::gg()" << endl; } // 新定义非虚函数 void hh(float x) { cout << "B::hh()" << endl; } // 不同签名的新函数 }; int main() { B b; A* pa = &b; // 使用基类指针指向派生类对象 B* pb = &b; pa->ff(); // 输出 "B::ff()", 展示了运行时多态 pb->ff(); // 同样输出 "B::ff()" pa->gg(); // 输出 "A::gg()", 因为未涉及虚函数 pb->gg(); // 输出 "B::gg()", 此处无多态行为 pa->hh(3); // 输出 "A::hh()", 只能匹配基类方法 pb->A::hh(3); // 明确指定调用基类方法 return 0; } ``` - 在上面的例子中,`pa->ff()` 和 `pb->ff()` 表现出典型的运行时多态特征,因为尽管两者都经由基类接口调用,但实际上触发了派生类的行为[^3]。 --- #### **总结对比** | 特性 | 编译时多态 | 运行时多态 | |--------------------|------------------------------------|-------------------------------| | 绑定时间 | 编译期 | 执行期 | | 实现方式 | 函数重载 / 模板 | 虚函数 | | 是否需要继承结构 | 不需要 | 需要 | --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值