c++中的多态

多态的概念,多种形态,分为编译时的静态多态和运行时的动态多态。

静态多态主要是函数重载和函数模板。通过传不同的参数达成多态。

运行时多态,完成函数行动时,不同对象会完成不同的行为,就像买票一样,有学生票和成人票

多态的定义和条件

1.继承关系:必须存在一个基类(父类)和一个或多个派生类(子类),这是多态的基础 。

2.虚函数重写:基类中必须有被 virtual 关键字修饰的虚函数,并且派生类要对其进行重写(Override) 。(重写:派生类中必须有与基类函数名相同,返回值类型相同,参数列表相同,派生类的函数可以不加virtual仍构成重写,父类不能不加virtual)(重写的本质是重写虚函数的实)

3.指针或引用调用:必须通过基类的指针或引用来指向派生类的对象,并调用这个虚函数 。

协变

要实现协变,必须同时满足以下所有条件:

继承关系:基类和派生类的返回类型必须是指向或引用某个类的指针或引用 。

类型关联:派生类返回类型所指向/引用的类,必须是基类返回类型所指向/引用的类的公有派生类 。

仅限指针/引用:协变只适用于指针和引用,不适用于按值返回的对象 。

在继承中 基类的析构建议是虚函数,因为创建父类指针指向子类对象析构时,才会调用子类的析构;

复制代码
#include<iostream>
using namespace std;
class a{
	public:
	virtual void pf(){
		cout<<"a"<<endl;
	}
	virtual ~a(){
		cout<<"A"<<endl;
	}
};
class b:public a{
	public:
	virtual void pf(){
		cout<<"b"<<endl;
	}
	virtual ~b(){
		cout<<"B"<<endl;
	}
};
void test(a* t){
	t->pf();
}
int main(){
	a* t1=new a;
	a* t2=new b;
	test(t1);
	test(t2);
	delete t1;
	delete t2;
}

结果

a

b

A

B

A

  1. 多态与动态绑定

    • main 函数中,t1 指向基类 a 的对象,t2 指向派生类 b 的对象。
    • 当调用 test(t1)test(t2) 时,由于基类 a 中的 pf() 函数被声明为 virtual(虚函数),程序会根据指针实际指向的对象类型来调用对应的函数(动态绑定)。
    • t1 实际指向 a,因此输出 at2 实际指向 b,因此输出 b
  2. 虚析构函数与对象销毁

    • 当使用 delete 释放对象时,如果基类的析构函数是虚函数(virtual ~a()),程序同样会根据实际对象的类型,先调用派生类的析构函数,再自动调用基类的析构函数。
    • delete t1t1 是基类 a 的对象,直接调用 ~a(),输出 A
    • delete t2t2 是派生类 b 的对象。因为基类析构函数是虚函数,会先调用派生类 b 的析构函数 ~b()(输出 B),紧接着自动调用基类 a 的析构函数 ~a()(输出 A)。

override和final

c++对虚函数的重写比较严格,因此c++11提供了override,可以帮助用户检测是否重写;

在函数名后加override;在编译时检查

cpp 复制代码
virtual void pf()override{
.....}

如果不想让派生类重写虚函数可以使用final;

cpp 复制代码
virtual void pf()final{
.....}

重载和重写和隐藏的区别

纯虚函数和抽象类

纯虚函数其实就是C++里定义接口的一种特殊方式。它最大的特点就是没有函数体,直接在声明后面加上 = 0。这种写法强制要求派生类必须给出自己的实现,否则派生类也会变成抽象类,无法实例化。

包含纯虚函数的类被称为抽象类

  • 不能实例化 :你不能创建抽象类的对象。例如,如果 Shape 是一个包含纯虚函数 draw() 的抽象类,写 Shape s; 会导致编译错误。
  • 只能作为基类:抽象类存在的意义就是被继承。它定义了一组接口规范。
cpp 复制代码
class Animal {
protected:
    string name;
public:
    Animal(string n) : name(n) {}
    // 纯虚函数:发出叫声 (对应图片:虚函数后面写 = 0)
    // 这里的逻辑是:动物都会叫,但每种动物叫法不同,基类无法提供统一实现
    virtual void makeSound() = 0; 
    // 普通虚析构函数(好习惯,防止内存泄漏)
    virtual ~Animal() {}
};
// 2. 定义派生类:狗
class Dog : public Animal {
public:
    Dog(string n) : Animal(n) {}
    // 重写纯虚函数 (对应图片:强制派生类重写虚函数)
    void makeSound() override {
        cout << name << " 说: 汪汪汪!" << endl;
    }
};
// 3. 定义派生类:猫
class Cat : public Animal {
public:
    Cat(string n) : Animal(n) {}
    // 重写纯虚函数
    void makeSound() override {
        cout << name << " 说: 喵喵喵!" << endl;
    }
};

动态绑定和静态绑定

满足多态条件的函数调用在运行时绑定,在运行时到指定对象的虚函数表中找到调用函数的地址叫动态绑定

对于不满足多态条件的函数调用是在编译时绑定,在编译时确定调用函数的地址叫静态绑定。

  1. 访问指针 :程序拿到 ptr 指针。
  2. 找到 vptr :通过 ptr 找到对象内存开头的那个隐藏指针(vptr)。
  3. 查找虚函数表 :顺着 vptr 找到 Dog 类的虚函数表
  4. 获取函数地址 :在表中查找 makeSound 对应的槽位,拿到 Dog::makeSound 的地址。
  5. 调用函数:跳转并执行该地址的代码。

同类型的虚函数表相同

相关推荐
炸膛坦客6 小时前
单片机/C/C++八股:(二十六)IIC 专题(I²C)---- 上集
c语言·c++·单片机
旖-旎6 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
Henry Zhu1236 小时前
C++中的特殊成员函数与智能指针
c++
_wyt0019 小时前
多重背包问题详解
c++·背包dp
Ljwuhe10 小时前
C++——多态
开发语言·c++
越甲八千10 小时前
STL stack为何没有迭代器
c++
从零开始的代码生活_12 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法
云小逸12 小时前
【C++ 第七阶段:模板、泛型编程与工程综合详解】
开发语言·c++
GIS阵地12 小时前
QgsSingleBandPseudoColorRenderer 完整详解(QGIS 3.40.13 C++)
开发语言·前端·c++·qt·qgis
王维同学13 小时前
[原创][Windows C++]LSA 认证、安全与通知包的注册表枚举
c++·windows·安全