当前位置: 首页 > news >正文

了解和使用多态

多态

  • 多态的概念
  • 多态的定义
    • 1. 虚函数
    • 2.虚函数的重写和覆盖
  • 协变
  • 多态的一道选择题
  • override和final
  • 纯虚函数和抽象类
  • 多态的原理
    • 1.虚函数表指针
    • 2.多态的原理
    • 3. 动态绑定和静态绑定
    • 4.虚函数表
  • 今日的题:

多态的概念

多态(polymorphism)的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态),这⾥我们重点讲运⾏时多态,编译时多态(静态多态)和运⾏时多态(动态多态)。编译时
多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态,运⾏时归为动态。

运行时多态指的是,传不同的对象就会调用不同的方法,遇到多种形态。

多态的定义


#include<iostream>class person {
public:virtual void talk() {//被重写的函数需要virtual,构成虚函数。std::cout << "person talk" << std::endl;}
protected:int _num;};class student :public person {
public:virtual void talk() {//重写的函数,名字相同,参数相同,返回类型相同std::cout << "student talk" << std::endl;}
protected:int _id;};void fun(person*x) {//必须为基类的指针或者引用x->talk();
}
int main() {person d1;fun(&d1);student d2;fun(&d2);
}

从代码中的多态,我们可以知道构成多态的条件如下:

  • 必须是基类的指针或者引⽤调⽤虚函数
  • 被调⽤的函数必须是虚函数,并且完成了虚函数重写/覆盖

说明要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向基类对象⼜指向派⽣类对象;
第⼆派⽣类必须对基类的虚函数完成重写/覆盖,重写或者覆盖了,基类和派⽣类之间才能有不同的函数,多态的不同形态效果才能达到。

实现多态要使用public 继承.

在这里插入图片描述
当基类不是虚函数的时候,不构成多态,调用只会调用person,如果不是指针,和引用也是如此.
在这里插入图片描述

1. 虚函数

虚函数就是在成员函数前面加virtual,一般在需要重写的函数前面加,非成员函数不能加。

2.虚函数的重写和覆盖

对于派生类函数重写或者覆盖基类函数(就是返回类型,函数名,参数要相同)。
派生类重写基类的函数前面可以不加virtual,因为它重写了,声明在基类,定义就派生类的这个函数。依旧保持了虚函数的特性。

协变

使用很少,了解一下

//协变
class A {};
class B :public A {};class person {
public:virtual A* talk() {//被重写的函数需要virtual,构成虚函数。std::cout << "person talk" << std::endl;return nullptr;}
protected:int _num;};class student :public person {
public:virtual B* talk() {//重写的函数,名字相同,参数相同,返回类型相同std::cout << "student talk" << std::endl;return nullptr;}
protected:int _id;};void fun(person* x) {//必须为基类的指针或者引用x->talk();
}int main() {person d1;fun(&d1);student d2;fun(&d2);
}

基类和子类重写的虚函数返回类型可以不一样,基类的虚函数返回类型必须是基类的指针或者引用,也可以是perosn派生类的虚函数返回类型必须是派生类的指针或者引用,也可以是student

多态的一道选择题

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;
}

在这里插入图片描述

分析:首先要判断,构不构成多态,B对象赋值给B类指针。对于继承,首先先出派生类找有没有test,再从基类找test().而基类的this->fun().恰好是基类的指针。fun函数是虚函数的重写或者覆盖。所以构成多态。因为对象传的是B的指针,所以调用B的函数。
注意:看到这里有人会选D,但是这个函数的重写,上面提过基类的虚函数构成声明,子类的虚函数构成定义,所以这个val 应该是1答案所以是B。

在这里插入图片描述
如果是注意,那么就不构成多态,因为没有基类的指针调用虚函数。答案就是D了

override和final

对于编译器编译的时候,如果函数没有重写,并不会检查出错误,协变也算重写
为了避免要忘记重写,所以c++11提供了override.
在这里插入图片描述

这个函数我们加上override必须重写,但是函数名不同而报错,如果没有就不会报错
在这里插入图片描述
final就与之相反
如果在基类虚函数后面加上final,就不能重写,重写了就会报错
在这里插入图片描述

纯虚函数和抽象类

在虚函数的后⾯写上 =0 ,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被
派⽣类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例
化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。纯虚函数某种程度上强制了
派⽣类重写虚函数,因为不重写实例化不出对象。

在这里插入图片描述
纯虚函数不能实列化出对象。


class car {
public:virtual void run() = 0;};
class ben :public car {
public:virtual void run() {std::cout << "品牌";}};int main() {//car e;ben de;
}

继承抽象类,如果不重写函数,就不能实列化出对象,必须要重写函数,才能实列化出对象。

多态的原理

1.虚函数表指针

在这里插入图片描述

class Base{public:virtual void Func1(){cout << "Func1()" << endl;}protected:int _b = 1;char _ch = 'x';};int main(){Base b;cout << sizeof(b) << endl;return 0;}

在这里插入图片描述

所以选D,因为为了实现多态的调用,虚函数要用虚函数表储存,然后虚函数表的指针存储类对象的内存里面,这里主要考察了内存对齐和虚函数表。

在这里插入图片描述
里面有个虚函数表,,只有虚函数才会放到这个虚表里面,普通函数不会,虚函数在里面构成指针数组,就是数组里面存储虚函数的指针。

2.多态的原理

多态调用函数的原理并不是在编译时,调用对象确定函数的地址,而是在运行时,通过基类的指针指向的对象,调用虚函数表指针,找到虚函数,从而在里面确实虚函数的地址。这样就实现了指向基类的指针指向基类的对象就调用基类的虚函数,指向派生类就调用派生类的虚函数。

在这里插入图片描述
即使继承下来,,虚函数表的地址不一样。重写的虚函数的地址也不一样
在这里插入图片描述

class Person {public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
private:string _name;};class Student : public Person {public:virtual void BuyTicket() { cout << "买票-打折" << endl; }private:string _id;};class Soldier : public Person {public:virtual void BuyTicket() { cout << "买票-优先" << endl; }private:string _codename;};void Func(Person * ptr){// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。ptr->BuyTicket();}int main() {{// 其次多态不仅仅发⽣在派⽣类对象之间,多个派⽣类继承基类,重写虚函数后// 多态也会发⽣在多个派⽣类之间。Person ps;Student st;Soldier sr;Func(&ps);Func(&st);Func(&sr);return 0;}}

3. 动态绑定和静态绑定

不满足多态的条件就是静态绑定。静态绑定就是在编译时,就确定调用函数的地址。而动态绑定是在运行时,通过指针或者引用找到虚函数表,从而确定虚函数的地址。

4.虚函数表

  • 基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共⽤同⼀张虚表,不同类型的对象各⾃有独⽴的虚表,所以基类和派⽣类有各⾃独⽴的虚表。
class Person {public:virtual void BuyTicket() { cout << "买票-全价" << endl; }virtual void run() {cout << "跑";}
private:string _name;};class Student : public Person {public:virtual void BuyTicket() { cout << "买票-打折" << endl; }private:string _id;};

我们在基类多实现了一个虚函数,两个都是同一个类,所以总用一个虚函数表,而派生类的虚函数表和基类的虚函数表地址不一样,说明不是同一个表。由于继承下来,没有重写run,所以派生类的run地址跟基类一样。
在这里插入图片描述

  • 派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表 指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基 类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴ 的。

class Person {public:virtual void BuyTicket() { cout << "买票-全价" << endl; }virtual void run() {cout << "跑";}
private:string _name;};class Student : public Person {private:string _id;};

即使派生类没有 虚函数,也会继承虚函数表的指针,自己不用生成,但是两个虚函数表地址不同,说明不是同一个。
在这里插入图片描述

  • 派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函 数地址。派⽣类的虚函数表中包含,(1)基类的虚函数地址,(2)派⽣类重写的虚函数地址完成覆盖,派⽣类 ⾃⼰的虚函数地址三个部分。

意思是重写基类的虚函数,派生类的虚函数表里面的虚函数地址就会变,如果重写,就是基类虚函数一样地址。自己的虚函数也会写入虚函数表。

vs下一般虚函数表存放在常量区。

今日的题:

在这里插入图片描述

对于static静态成员,只是属于类本身,不属于类对象,类对象不包括静态成员,所以A基类不包括静态成员。
B选项也是如此
C.子类对象继承了基类的私有成员,只是不能直接访问。
D,注意关键词,基类的静态成员一定不包含在子类对象中

2.由于粗心又错了
在这里插入图片描述

A子类的对象可以直接赋值给基类的指针。
B.不能父类赋值给子类
C跟B差不多,父类不能给子类。只要当父类的引用或者指针指向子类,才能将父类的指针或者引用给子类的引用或者指针
D对的,这是规则之一。

3.这是一道好题,注意是地址
在这里插入图片描述

这要结合继承基类与派生类的图在这里插入图片描述
在这里插入图片描述
由于先继承的放在内存最前面,自己的成员放在最后面,这也是初始化的规则,所以p1,p3的地址一样,p2的地址不一样。

在这里插入图片描述

A被修饰的成员函数才叫虚函数
C定义可以不加virtual
D静态成员确实没有this指针,因为不属于具体的类对象。stactic 和virtual不能同时出现。

在这里插入图片描述
在这里插入图片描述
6
在这里插入图片描述

A 必须是父类的成员hanshu
B 通过父类的指针或者引用
C 在运行,编译时检查语法,这就是为什么叫运行时多态,运行时才调用.

重定义就是隐藏,重写和重定义是两码事.

7
在这里插入图片描述

重定义就是隐藏.
主要C选项,这里后半句出现矛盾,通过基类对象调用,这里就是不是多态了,多态是通过基类的引用或者指针调用虚函数.

在这里插入图片描述

http://www.dtcms.com/a/578622.html

相关文章:

  • 企业网站开发的文献综述网站开发的中期工作
  • 广州市门户网站建设宝应建设局网站
  • 做视频网站要什么格式好网站建设与管理指什么
  • 基于防伪标签的吊牌防伪:品牌核心防护环节
  • 国产的编程语言
  • 条款36:如果异步是必需的,请指定为std::launch::async
  • 建网站广州中国建行官网登录首页
  • 连云港网站建设方案西安网站建设多少钱
  • 网络管理(NM)
  • 【第1章>第4节】基于FPGA的图像腐蚀处理算法的测试以及MATLAB辅助验证
  • 脉冲在克尔效应下的频谱展宽仿真:原理与 MATLAB 实现
  • PPP工作法:贝索斯做事的方法
  • 古县网站建设如何让移动网站更优秀
  • 杭州网站建设出名24小时通过网站备案
  • CSS卡片淡出效果
  • 洛阳做网站价格wordpress视频插件
  • 经典网站设计作品软件定制开发多少钱
  • STM32项目分享:水质检测系统(升级版)
  • 外语教学网站开发广州计算机软件公司排名
  • 主流开源视觉语言模型(VLM)的视觉编码器架构解析
  • SGV3D:面向基于视觉的路边3D目标检测的场景泛化
  • 实现 json path 来评估函数式解析器的损耗
  • 微网站分销linux做网站哪个版本好
  • 解决Git 冲突后本地提交丢失/未推送问题
  • 企业做网站建设遇到的问题合肥长丰路网站建设
  • 【剑斩OFFER】算法的暴力美学——最小覆盖字串
  • 全屏网站模板制作教程国外网站需要备案吗
  • 免费做网站有哪些家SaaS网站可以做seo嘛
  • 14:C++:二叉搜索树
  • 「日拱一码」142 Lasso调参注意事项与技巧