详解c++多态---上

news2024/9/25 17:20:54

virtual关键字

1.可以修饰原函数,为了完成虚函数的重写,满足多态的条件之一。

class Person {
public:
 virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
 
class Student : public Person {
public:
 virtual void BuyTicket() { cout << "买票-半价" << endl; }
 /*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后
基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用*/
 /*void BuyTicket() { cout << "买票-半价" << endl; }*/
};
 
void Func(Person& p)
{ p.BuyTicket(); }
 
int main()
{
 Person ps;
 Student st;
 
 Func(ps);
 Func(st);
 
 return 0;
}

2.可以在菱形继承中,去完成虚继承,解决菱形继承数据冗余和二义性。

多态的两个条件

1.虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类 型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

2.父类对象的指针或引用(父类可以接受子类->切割/切片,子类不一定能接受父类)去调用虚函数

void Func(Person& p)
{ p.BuyTicket(); }

将&去掉后就无法满足多态。 

满足多态:跟指向的对象有关,指向那个对象就调用其虚函数。

不满足多态:跟调用对象的类型有关,类型决定其调用的虚函数。

虚函数重写的两个例外:

协变(基类与派生类虚函数返回值类型不同)

class A{};
class B : public A {};
 
class Person {
public:
 virtual A* f() {return new A;}
};
 
class Student : public Person {
public:
 virtual B* f() {return new B;}
}

父类写了virtual子类可以不写->但是这样的话不太标准。~~~~~~~~~~

析构函数的重写(基类与派生类析构函数的名字不同)

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的 析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规 则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处 理成destructor。

class Person{
public:
    virtual ~Person() {cout<<"~Person()"<<endl;}
};
class Student : public Person {
public:
 virtual ~Student() { cout << "~Student()" << endl; }
};
 
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成
多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
 Person* p1 = new Person;
 Person* p2 = new Student;
 
 delete p1;
 delete p2;
 
 return 0;
}
 virtual ~Student() { cout << "~Student()" << endl; }
};

 子类和父类的析构函数的函数名均会被处理为destructor。

这样使用没有问题。

 delete p2;不构成多态时,调用的指针(Person)类型决定了要调用(Person)的析构函数。

构成多态时,调用的指针指向谁,就调用谁的析构函数。

C++11 override 和 final

final

final:修饰虚函数,表示该虚函数不能再被重写

final:修饰类,表示该类不能被继承<----->构造函数私有化

override

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

class Car{
public:
 virtual void Drive(){}
};
 
class Benz :public Car {
public:
 virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

重载、覆盖(重写)、隐藏(重定义)的对比

 重写时为了重写函数实现。

重写比重定义要求更严格。

抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
 virtual void Drive() = 0;
};
 
class Benz :public Car
{
public:
 virtual void Drive()
 {
 cout << "Benz-舒适" << endl;
 }
};
 
class BMW :public Car
{
public:
 virtual void Drive()
 {
 cout << "BMW-操控" << endl;
 }
};

 纯虚函数

virtual void Drive() = 0;

1.强制子类去完成重写。 

2.表示抽象(在现实中没有对应的实体)的类型。

接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的 继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所 以如果不实现多态,不要把函数定义成虚函数。

vfptr

虚函数表指针(简称虚表指针)。

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}

private:
	int _b = 1;
};

 虚函数表其实就是一个函数指针数组。通常以0x00000000 结束。

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	int _p = 1;
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	int _s = 2;
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person Mike;
	Func(Mike);

	Student Johnson;
	Func(Johnson);

	return 0;
}

 

 

 满足多态时:

 运行时 到指向对象的虚函数表中查找对应的虚函数的地址

不满足多态时:

编译时 直接通过p的类型决定要调用函数的地址。

重写之后子类的虚函数会将父类的虚函数(virtual void BuyTicket())覆盖掉。

虚函数存在那?

普通函数和虚函数最终都会编译为指针,存在代码段。

注:虚函数不是存在虚表,虚表中存的是虚函数的指针。

虚函数表存在那?

代码段(常量区)同类型的对象共用一张虚表。

代码证明

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}

private:
	int _b = 1;
};
void func()
{
	//取出对象的前4个字节
	Base b1;
	printf("vfptr虚表地址: %p\n", *(int*)&b1);

	int i = 0;
	int* p1 = &i;
	int* p2 = new int;
	const char* p3 = "hello";
	printf("栈变量: %p\n", p1);
	printf("堆变量: %p\n", p2);
	printf("代码段常量: %p\n", p3);
	printf("虚函数地址: %p\n", &Base::Func1);
	printf("普通函数地址: %p\n", func);

}

动态绑定与静态绑定

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数 重载

2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用 具体的函数,也称为动态多态。 (运行时到虚表中找虚函数地址)

单继承和多继承关系的虚函数表

单继承中的虚函数表

函数指针的定义

void(*p)();
class Base{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b;
};

 打印虚函数表

typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* pTable)
{
	for (size_t i = 0; pTable[i] != 0; ++i)
	{
		printf("vfTable[%d] : %p-> ", i, pTable[i]);
		VF_PTR f = pTable[i];
		f();
	}
	cout << endl;
}

多继承中的虚函数表

class Base1 {
public:
 virtual void func1() {cout << "Base1::func1" << endl;}
 virtual void func2() {cout << "Base1::func2" << endl;}
private:
 int b1;
};
 
class Base2 {
public:
 virtual void func1() {cout << "Base2::func1" << endl;}
 virtual void func2() {cout << "Base2::func2" << endl;}
private:
 int b2;
};
 
class Derive : public Base1, public Base2 {
public:
 virtual void func1() {cout << "Derive::func1" << endl;}
 virtual void func3() {cout << "Derive::func3" << endl;}
private:
 int d1;
};
 

}

sizeof(Derive)=20(8+8+4)

子类继承父类,会继承父类的虚表,子类直接覆盖父类的虚表即可。

char*+1---->+1

int*+1----->+4

	Derive d;
	PrintVFTable((VF_PTR*)(*(int*)&d));
	PrintVFTable((VF_PTR*)(*(int*)((char*)&d + sizeof(Base1))));

多继承时func3()往Base1的虚表里放。

多态练习题

1.子类缺省参数不起作用/虚继承

class A
{
public:
 virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
 virtual void test(){ func();}
};
 
class B : public A
{
public:
 void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};
 
int main(int argc ,char* argv[])
{
 B*p = new B;
 p->test();
 return 0;
}

A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

virtual void test(){//A* this
 func();
}
p->test();//p->test(p);
class B : public A
{
public:
 void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};

子类缺省参数不起作用。

虚函数继承的是接口(函数名,参数,返回值),唯独不继承函数体。

重写指的是对其实现进行重写。

{ std::cout<<"B->"<< val <<std::endl; }

1. 下面哪种面向对象的方法可以让你变得富有( )

A: 继承 B: 封装 C: 多态 D: 抽象

2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的 调用则可以关联于具体的对象。

A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定(多态)

3. 面向对象设计中的继承和组合,下面说法错误的是?()

A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为 白盒复用

B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也 称为黑盒复用

C:优先使用继承,而不是组合,是面向对象设计的第二原则

D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现

4. 以下关于纯虚函数的说法,正确的是( )

A:声明纯虚函数的类不能实例化对象

B:声明纯虚函数的类是虚基类

C:子类必须实现基类的纯虚函数

D:纯虚函数必须是空函数

5. 关于虚函数的描述正确的是( )

A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型

B:内联函数不能是虚函数

C:派生类必须重新定义基类的虚函数

D:虚函数可以是一个static型的函数

6. 关于虚表说法正确的是( )

A:一个类只能有一张虚表-》多重继承

B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表

C:虚表是在运行期间动态生成的-》编译时生成,运行时初始化

D:一个类的不同对象共享该类的虚表

7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )

A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址

B:A类对象和B类对象前4个字节存储的都是虚基表的地址

C:A类对象和B类对象前4个字节存储的虚表地址相同

D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表

多继承中指针偏移问题

下面说法正确的是( )

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
 
int main(){
 Derive d;
 Base1* p1 = &d;
 Base2* p2 = &d;
 Derive* p3 = &d;
 return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2138041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NarratoAI利用AI大模型,一键解说并剪辑视频

测试视频: 字幕/配乐后期添加的,视频由NarratoAI自动生成的 雪迷宫-NarratoAI利用AI大模型剪辑解说视频测试 WIN整合包 下载链接&#xff1a;https://pan.quark.cn/s/8f54ef99e3fb 使用前先更新&#xff0c;运行update.bat Gemini API Key 访问 https://aistudio.google.c…

性能测试-jmeter的控制器(十六)

一、if控制器 需求&#xff1a;使用“用户自定义变量”定义name变量&#xff0c;值可以是“baidu”或“itcast”,使用变量值&#xff0c;控制是否访问对应网站。 1、步骤&#xff1a; 在测试计划中添加用户定义的变量name,取值可为baidu或itcast添加两个http请求&#xff1a…

Docker突然宣布:涨价80%

从11月15日起&#xff0c;Docker的付费订阅中Pro和Team的价格都将大幅上调&#xff1a;Pro从原来的5美元每月激增到9美元每月&#xff0c;直接涨了80%&#xff1b;而Team也从之前的9美元每月来到15美元每月&#xff0c;涨价66.7%。只有Business保持此前的24美元每月不变。 同时…

S32K3 工具篇6:如何将RTD EB工程导入到S32DS

S32K3 工具篇6&#xff1a;如何将RTD EB工程导入到S32DS 1. MCAL_Plugins->Link Source Resource Filters2. Includes3. Preprocessor4. Linker5. optimization6. main.c 这个主题实际上&#xff0c;之前已经有多人写过&#xff0c;并且写的很好&#xff0c;只是实际操作中&…

基础物理-直线运动2

2-1 位置、位移和平均速度 位置与位移 为了确定物体的位置&#xff0c;通常需要相对于某个参考点来测量&#xff0c;这个参考点通常是某个坐标轴的原点&#xff08;或零点&#xff09;&#xff0c;如图 2-1 中的 x 轴。坐标轴的正方向是坐标增大的方向&#xff0c;在图 2-1 中…

微信h5跳转小程序wx-open-launch-weapp开放标签不显示(已解决)

前言&#xff1a;  前几天成功对接了跳转第三方小程序的功能&#xff0c;今天有个页面有需要对接。但是奇怪的是用的和上次一模一样的配置&#xff0c;但就是死活不显示wx-open-launch-weapp这个开放标签的按钮&#xff0c;看不到任何效果&#xff08;这个问题真的是让人欲哭无…

Docker基础命令汇总,小白必备

1、docker信息概览 docker info容器的数量 在运行的容器 暂停状态的容器 停止状态的容器 容器的镜像数量 系统的内核版本 操作系统centos 7 操作系统类型 linux 系统架构为64位 系统的cpu核心2个 总内存1.777G docker镜像仓库地址 南京大学 中国科技大 网易 百度云 腾讯云 …

C++入门基础知识69(高级)——【关于C++ 动态内存】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C 异常处理的相关内容&#xff01; 目录…

【AWDP】 AWDP 赛制详解应对方法赛题实践 量大管饱

文章首发于【先知社区】&#xff1a;https://xz.aliyun.com/t/15535 一、AWDP概述 AWDP是什么 AWDP是一种综合考核参赛团队攻击、防御技术能力、即时策略的攻防兼备比赛模式。每个参赛队互为攻击方和防守方&#xff0c;充分体现比赛的实战性、实时性和对抗性&#xff0c;对参…

恢弘集团SRM采购数字化项目成功上线,企企通助推新材料企业发展新质生产力

近日&#xff0c;企企通携手恢弘集团有限公司&#xff08;以下简称“恢弘集团”&#xff09;打造的一站式数字化采购管理平台正式上线。基于该平台&#xff0c;恢弘集团全流程全周期的数字化采购管理体系进一步升级&#xff0c;在推动企业提高效率的同时&#xff0c;也将形成新…

工作流activiti笔记(四)审批人设置

单人 方式一&#xff1a;写死Assignee 画流程图时填写Assignee&#xff0c;启动流程自动会为每个环节分配好审批人。 方式二&#xff1a;写死变量 ${xx}&#xff0c;然后在启动流程时设置变量。 与方式一一样&#xff0c;启动流程时分配好&#xff0c;只不过它是以变量的形式…

Java抽象类和接口的学习了解

目录 1. 抽象类 1.1 抽象类概念 1.2例子 1.3 抽象类语法 1.被 abstract 修饰的类--抽象类 2.抽象类中被 abstract 修饰的方法--抽象方法&#xff0c;该方法不用给出具体的实现体 3.当一个类中含有抽象方法时&#xff0c;该类必须要abstract修饰 4.抽象类也是类&#xff…

超链接/表格/表单的复习(课后作业)

1.作业1 提示&#xff1a; 标题在title中修改 百度logo是图片链接(img) 新闻&#xff0c;贴吧是超链接&#xff0c;直接上官网cv 还有文本呢输入框 完成前端HTML代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&q…

4.qml单例模式

这里写目录标题 js文件单例模式qml文件单例模式 js文件单例模式 直接添加一个js文件到qml中 修改内容 TestA.qml import QtQuick 2.0 import QtQuick.Controls 2.12 import "./MyWork.js" as MWItem {Row{TextField {onEditingFinished: {MW.setA(text)}}Button…

基于Arduino Uno的简易可视化操作界面设计

Arduino UNO是基于ATmega328P的Arduino开发板。它有14个数字输入/输出引脚&#xff08;其中6个可用于PWM输出&#xff09;、6个模拟输入引脚&#xff0c;一个16 MHz的晶体振荡器&#xff0c;一个USB接口&#xff0c;一个DC接口&#xff0c;一个ICSP接口&#xff0c;一个复位按钮…

完整gpt应用(自用)

qrc.py 把gpt_qrc.qrc转化成gpt_qrc.py pyrcc5 -o icons_rc.py icons.qrc <RCC><qresource prefix"img"><file>img/53.png</file><file>img/ai.png</file><file>img/关闭.png</file><file>img/最小化.png&l…

9.15 BFS中等 133 Clone Graph review 138 随机链表的复制

133 Clone Graph //错误代码class Solution { public:Node* cloneGraph(Node* node) {//邻接表、BFS---》类似于二叉树的层次遍历if(!node || !node->val) return node;//构造队列queue<Node*> prev;prev.push(node);//构造新的图结点列表vector<Node*> adjList…

ESP8266_MicroPython——ADC_PWM

MicroPython 文章目录 MicroPython前言一、ADC二、PWM 前言 这一节简单学习一下ACD和PWM 一、ADC ADC(analog to digital conversion) 模拟数字转换。意思就是将模拟信号转化成数字信号&#xff0c;由于单片机只能识别二级进制数字&#xff0c;所以外界模拟信号常常会通过 A…

OpenCV结构分析与形状描述符(22)计算图像中某个轮廓或区域的矩函数moments()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算一个多边形或光栅化形状直到三阶的所有矩。 该函数计算一个向量形状或光栅化形状直到三阶的矩。结果返回在 cv::Moments 结构中。 函数原型…

数据结构————二叉树基础知识(零基础包会的!)

今天带来数据结构二叉树的知识&#xff0c;保证大家不会离散数学或者没有数据结构基础&#xff0c;也能明明白白的。 一&#xff0c;树 1&#xff0c;树的结构 我们在了解什么是二叉树之前我们先了解下什么是树&#xff0c;树是一种非线性的数据结构&#xff0c;它是由n个节点…