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. 调用函数:跳转并执行该地址的代码。

同类型的虚函数表相同

相关推荐
汉克老师1 小时前
GESP2025年6月认证C++五级( 第三部分编程题(1、奖品兑换))
c++·二分算法·gesp5级·gesp五级
Lhan.zzZ1 小时前
笔记_2026.4.28_003
c++·笔记·qt·opencv
stolentime1 小时前
我常常追忆过去
c++·里程碑纪念
fengenrong2 小时前
20260429
c++·算法
千寻girling2 小时前
滑动窗口刷了快一个月(26天)了 , 还没有刷完. | 含(操作系统学什么的Java 后端)
java·开发语言·javascript·c++·人工智能·后端·python
故事还在继续吗2 小时前
C++多线程与多进程编程
开发语言·c++
晓py2 小时前
highpool测试报告
c++
liuyao_xianhui2 小时前
进程概念与进程状态_Linux
linux·运维·服务器·数据结构·c++·哈希算法·宽度优先
迷途之人不知返2 小时前
List的模拟实现
数据结构·c++·学习·list