c++ 多态与虚函数

news/2024/7/24 10:02:20

c++中多态分为静态多态和动态多态,静态多态是函数重载,在编译阶段就能确定调用哪个函数。动态多态是由继承产生的,指不同的对象根据所接收的消息(成员函数)做出不同的反应。例如,动物都能发出叫声,但不同的动物能发出不同的叫声,这就是多态。

虚函数声明格式:

class A{
权限控制符:
    virtual 函数返回值类型 函数名 (参数列表);
};

c++多态满足条件

(1)基类声明虚函数

class Animal {
public:
	virtual void Sound();
};

(2)派生类重写基类的虚函数

class Dog:public Animal {
public:
	virtual void Sound();
	//void Sound();等价于virtual void Sound();
};

注意:派生类中重写的虚函数前是否添加virtual,均被视为虚函数

(3)将派生类对象赋值给基类指针或引用,通过基类指针或引用访问虚函数

    Dog dog;
	Animal* animal =  &dog;//通过指针
	animal->Sound();
	Animal& animal_1 = dog;//通过引用
	dog.Sound();

声明虚函数注意以下几点:

(1)构造函数不能声明为虚函数,因为构造函数执行时,对象还没有创建,但析构函数可以声明为虚函数

(2)虚函数不能是静态成员函数。因为静态成员函数是对象共享的

(3)友元函数不能声明为虚函数,但虚函数可以作为另一个类的友元函数


 c++11 final关键字

(1)修饰类,表示该类不可以被继承

class A final{};

(2)修饰虚函数,表示该虚函数不能在派生类中重写

virtual void f() final;

c++虚函数实现多态的原理

虚函数是通过动态绑定实现多态的,当编译器在编译过程中遇到virtual关键字时,他不会对函数进行绑定,而是为包含虚函数的类建立一张虚函数表Vftable.编译器按照虚函数的声明依次保存虚函数地址。同时,编译器会在类中添加一个隐藏的虚函数指针Vfptr,指向虚函数表。在创建对象时,将虚函数指针Vfptr放置在对象的起始位置,为其分配内存空间,而虚函数表不占用对象内存空间。

派生类继承基类时,也继承了基类的虚函数指针。当创建了派生类对象时,派生类对象的虚函数指向自己的虚函数表。如果,派生类重写了基类的虚函数,则派生类虚函数会覆盖基类的同名函数。当通过基类指针操作派生类对象时,已派生类对象内存为准,从对象中获取Vfptr,通过Vfptr找到Vftable,从而调用相应的虚函数,实现了动态绑定。

示例介绍:

class Cattle {
public:
	virtual void walk();
	virtual void sound1();
	virtual void eat1();
};
class Horse {
public:
	virtual void walk();
	virtual void sound2();
	virtual void eat2();
};
class CattleHorse :public Cattle,public Horse {
public:
	virtual void walk();
	virtual void sound1();
	virtual void eat2();
};

其中声明了Cattle类,Horse类,CattleHorse类。CattleHorse重写了walk()方法,Cattle的sound1()方法,Horse的eat2()方法

我们来看一下vftable和vfptr

当创建派生类对象ch赋值给Cattle类时

    CattleHorse ch;
	Cattle* cattle = &ch;

 基类指针cattle从对象ch中获得虚函数指针Vfptr从而获得虚函数表

  调用虚函数时

	cattle->walk();
	cattle->sound1();
	cattle->eat1();

 我们要理解虚函数被重写之后,派生类虚函数会覆盖基类的同名虚函数的原理


c++纯虚函数和抽象类

有时基类并不需要实现函数,只需声明即可,实现交由派生类即可,这样的函数成为纯虚函数

声明格式:

virtual 返回值类型 函数名(参数列表) = 0;

注意:纯虚函数后面"=0",并不是函数的返回值为0,它只是告诉编译器这是一个纯虚函数

纯虚函数的几个说明

(1)如果一个类中包含了纯虚函数,这样的类称为抽象类。抽象类特点是:不能实例化对象,但可以定义抽象类的指针或引用。

如声明了抽象类Animal

class Animal {
public:
	virtual void walk() = 0;
	virtual void eat();
};
	//Animal animal;不允许
    //Animal* animal = new Animal;不允许
	Animal* animal;

其中不能写Animal animal,也即不能实例化对象,但可以定义抽象类的指针Animal *animal;这时如果不用派生类对象为其赋值,尽管实现了eat()方法,也是不可以调用eat()方法的

	animal->eat();//错误,未初始化animal局部变量

 (2)派生类都应该实现基类的纯虚函数,如果不实现,则该函数在派生类中仍然是纯虚函数,该派生类也是抽象类,也不能实例化对象。


c++虚析构函数与纯虚析构函数

若派生类中有开辟到堆区的数据,而基类没有声明虚析构函数,在析构派生类对象时,编译器只会调用基类的析构函数,不会调用派生类析构函数,导致派生类对象申请的资源不能正确的释放。所以需要声明虚析构函数

虚析构函数声明格式:

virtual ~析构函数();

纯虚析构函数声明格式:

virtual ~析构函数() = 0;

简单示例:

class Animal {
public:
	Animal();
	virtual ~Animal();
	virtual void sound()=0;
};
class Dog :public Animal {
public:
	string *name;
public:
	Dog(string name);
	virtual void sound();
	virtual ~Dog();
};
Dog::Dog(string name) {
	cout << "调用Dog子类的构造函数:" << endl;
	 this->name=new string(name);
}
Dog::~Dog() {
	if (this->name != NULL) {
		cout << "调用派生类Dog析构函数" << endl;
		delete name;
		this->name = NULL;
	}
}
int main() {
	Animal* animal = new Dog("小黄");
	animal->sound();
	delete animal;
	return 0;
}

 上图部分代码声明了基类Animal以及构造函数和析构函数,派生类Dog以及构造函数与析构函数,其中派生类为小狗起名声明了name堆区数据,如果不声明基类析构函数为虚析构函数,则派生类堆区数据name就无法释放而造成内存泄漏。

关于虚析构函数的注意事项

(1)在基类声明虚析构函数之后,基类的所有派生类析构函数都自动成为虚析构函数

(2)在析构派生类对象时,先调用派生类析构函数,在调用基类析构函数(栈)

(3)虚析构函数和纯虚析构函数都要在基类中实现,因为假若基类中有堆区开辟的数据,也是需要实现虚析构函数释放资源的

(4)虚析构函数和纯虚析构函数区别:声明了纯虚析构函数后,该类为抽象类(只要该类中有虚函数,该类就是抽象类),不能实例化对象。而声明虚析构函数不会成为抽象类。


http://www.niftyadmin.cn/n/318255.html

相关文章

vite跨域问题,你可能需要看这篇文章

最近在学习项目的时候&#xff0c;使用了vite工具进行构建&#xff0c;然后出现了跨域的问题&#xff0c;中间的曲折不过多叙述&#xff0c;直接进入正题。 前端成功启动后的界面&#xff1a; 然后在后端进行的Controller上使用了如下的配置 然后浏览器就会出现跨域的问题 为什…

Godot4 C++ 嵌入Opencv

前言 使用GDExtension&#xff0c;可以很轻松的写godot4的c插件&#xff0c;并且不需要编译引擎 强烈建议先观看官方文档&#xff1a;GDExtension C example — Godot Engine (4.0) documentation in English 跟着做可以让你实现最基础的GDExtension工程 下载OpenCV 首先&a…

图像超分辨率调研

1、基于包 基础环境安装 conda create --name myppocr python3.8 conda activate myppocr pip install --upgrade pip# 安装paddlepaddle2.4.1版本 python -m pip install paddlepaddle-gpu2.4.1.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html …

Redis的日常使用小结

一、数据类型 五大数据类型String型&#xff1a;String 是redis中最基本的数据类型,二进制安全的,即它可以包含任何数据,如序列化的对象、jpg图片,大小上限是512M。Hash型(存储消耗高于字符串): 键值对集合,适合存储对象&#xff0c;类似 Java的Map<String,Object>。Lis…

11.SpringSecurity PasswordEncoder详解与实战

这节课我们开始讲PasswordEncoder,如果大家还有印象的话,我们前面有提到过PasswordEncoder: 为什么密码使用{noop}开头呢?我们也做出了相应的解释,这节课开始带大家真正的了解PasswordEncoder, PassworderEncoder 详解 主要方法 String encode(CharSequence rawPassword)…

ChatGPT系列学习(1)transformer基本原理讲解

文章目录 1. 简介1.1. 发展史 2. Transformer 整体结构3. 名词解释3.1. token 4. transformer输入4.1. 单词 Embedding4.2. 位置Embedding4.3. Transformer Embedding层实现 5. Attention结构5.1. 简介5.2. Self Attention&#xff08;自注意力机制&#xff09;5.2.1. 简介5.2.…

JVM垃圾收集器(一)

目录 1、如何考虑 GC 2、如何确定一个对象“死去” 3、分代收集理论 4、垃圾回收算法 5、HotSpot的算法实现细节 1、如何考虑 GC 垃圾收集&#xff08;Garbage Collection&#xff0c;GC&#xff09;的历史比Java更久远&#xff0c;1960年诞生于MIT。 GC 需要考虑的三件事…

newbing狗头军师给出的不完全成功的wsl windows linux 子系统 移动位置解决方法以及后续解决

你好&#xff0c;这是Bing。如果你想修改WSL安装的位置&#xff0c;你需要区分以下几个组件https://learn.microsoft.com/en-us/windows/wsl/wsl-config&#xff1a; • wsl.exe命令&#xff0c;这是Windows内置的&#xff0c;你不能安装或卸载它&#xff0c;它默认在C:\Window…