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

多态的介绍

多态的定义

多态是指不同对象调用同一函数时,表现出不同的行为。在继承体系中,构成多态要满足两个关键条件:

1必须通过基类的指针或引用调用虚函数

只有通过基类的指针或引用,才能在运行时根据对象的实际类型来决定调用哪个版本的虚函数(这一过程称为动态绑定)

2被调用的函数必须是基类的虚函数,派生类必须重写基类的虚函数

虚函数:被virtual修饰的类成员函数

重写:派生类中存在与基类函数名,返回值,参数列表完全相同的基函数

class Person{public:virtual void BuyTicket(){cout<<"买票-全价"<<endl;}
}
class Student{public:virtual void BuyTicket(){cout<<"买票-半价"<<endl;}
}
class Soldier{public:virtual void ButTicket(){cout<<"买票-优先"<<endl;}
}
void Func(Person &p)
{p.BuyTicket();//通过基类引用调用虚函数,触发多态
}
int main()
{Person p;Student s;Soldier sol;Func(p);Func(s);Func(sol);return 0;
}

虚函数重写的两个例外

1 协变

当基类虚函数返回基类指针或引用,派生类函数返回派生类指针或引用,即使返回值的类型不同,仍构成重写,称为协变。

class A
{
};
class B:public A
{
};
class Person
{
public:virtual A* f(){return new A;//基类虚函数返回A*}
};
class Student:public Person
{public:virtual B* f(){return new B;//派生类虚函数返回B*}
};

说明协变的本质是返回值类型与继承关系一致。仅允许指针或引用的返回值协变,普通类型不支持。

2析构函数的重写

基类与派生类析构函数名称不同,但编译器会将所有的析构函数统一为destructor()

若基类析构函数是虚函数,派生类析构函数无论是否加virtual,都构成重写。

class Person
{public:virtual ~Person(){cout<<"virtual ~Person()"<<endl;}
}
class Student:public Person
{public:~Student(){cout<<"~Student()"<<endl;}
}
int main()
{Person* p1=new Person;// 调用 Person::~Person()Person* p2=new Student;//先调用Person::~Student(),再调用Person::~Person()delete p1;delete p2;return 0;
}

override和final

final:禁止虚函数被重写

修饰虚函数:表示该函数不能被派生类重写

修饰类:表示该类不能被继承

class Car
{public:virtual void Drive() final{}
};
class Benz:public Car
{public:virtual void Drive(){}//编译错误Drive()已被final修饰,不能重写
};

override

修饰派生类虚函数,检查该类是否严格重写基类的虚函数,若未重写则编译错误。

class Car()
{
public:virtual void Drive(){}
};
Class Benz:public Car()
{public:virtual void Drive() override{cout<<"Benz-舒适"<<endl;}
};
Class BMW:public Car()
{public:virtual void Drive(int speed) override{cout<<"BMW-操控"<<endl;//编译错误:参数列表与基类不同,未构成重写,override 检测报错}
};

重载,重写与隐藏的对比

重载:两个函数在同一个作用域,函数名/参数相同

重写:两个函数分别在派生类和基类的作用域,函数名/参数/返回值都相同(协变例外),两个函数必须是虚函数

隐藏(重定义):两个函数分别在派生类和基类的作用域,函数名相同,两个派生类和基类的函数不构成重写就构成重定义

抽象类

含有纯虚函数的类称为抽象类。纯虚函数是在虚函数后加上=0的函数。

class Car
{public:virtual void Drive=0;//纯虚函数,没有实现,强制派生类重写
};

抽象类不能实例化对象

派生类必须重写所有纯虚函数,否则仍为抽象类(无法实例化)

class Car
{
public:virtual void Drive()=0;//纯虚函数};
class Benz:public Car
{
public:void Drive() override{cout<<"Benz-舒适"<<endl;//重写纯虚函数}
};
class BMW : public Car 
{
public:void Drive() override { cout << "BMW-操控" << endl; } // 重写纯虚函数
};
int main()
{//Car c;错误,抽象类不能实例化Car* pBenz=new Benz;Car* BMW=new BMW;pBenz->Drive();BMW->Drive();return 0;
}

多态的底层逻辑

含有虚函数的类,其对象中会额外存储一个虚表指针(_vptr),该指针指向一张虚函数表。虚表的本质是一个存储虚函数指针的数组,数组末尾通常有一个nullptr作为标志。

基类的虚表指针:

class Base
{public:virtual void Func1(){cout<<"Base::Func1"<<endl;}virtual void Func2(){cout<<"Base::Func2"<<endl;}void Func3(){cout<<"Base::Func3"<<endl;//不是虚函数,不进虚表}
private:int _b=1;
};

Base对象的内存布局:

_vfptr虚表指针(4字节)->指向Base的虚表

_b成员变量(4字节)

Base的虚表结构:

Base虚表(存虚函数指针的数组)

&Base::Func1(第一个虚函数指针)

&Base::Func2(第二个虚函数指针)

nullptr(结束标志)

虚函数本身存放在代码段,和普通函数一样。虚表中只存储虚函数的指针,对象中存储的是虚表指针,而非虚表本身(虚表本身存储在代码段)。

派生类的虚表生成规则

派生类Drive继承Base并重写Func1,其虚表重写遵循三步:

class Derive:public Base
{public:virtual void Func1(){cout<<"Drive::Func1()"<<endl;}private:int _d=2;
}

1 拷贝基类虚表:先将基类Base的虚表完全拷贝到派生类Derive的虚表中

2覆盖重写的虚函数:若派生类重写了基类的虚函数,则用派生类的虚函数指针覆盖虚表中对应的基类虚函数指针。

3添加新增虚函数:若派生类有新增的虚函数,按声明顺序将其指针添加到虚表末尾。

Derive的对象内存布局:

继承自Base的部分:

_vptr(虚表指针,4个字节)->指向Base的虚表

_b成员变量

派生类新增部分:

_d

Derive的虚表结构:

Derive 虚表

├─ &Derive::Func1(覆盖后的虚函数指针)

├─ &Base::Func2(继承的虚函数指针,未重写)

└─ nullptr(结束标志)

多态的实现原理

多态的核心是运行时根据对象实际类型,通过虚表指针找到对应虚函数。

当基类指针指向派生类对象,指针p只能访问对象中基类部分的成员,包括虚表指针。

多态调用的三步流程

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

首先,通过引用p找到对象中的虚表指针_vptr,通过虚表指针找到对应的虚表,在虚表中找到BuyTicket函数的指针,调用对应的指针

根据虚表中存储的函数指针,调用对应的版本(若指向 Person 对象,调用 Person::BuyTicket;若指向 Student 对象,调用 Student::BuyTicket

虚表指针决定了虚表的地址,虚表的函数指针决定了调用的函数版本,而虚表指针由对象的实际类型决定,因此运行时能动态匹配函数。

单继承与多继承的虚函数表

单继承下,派生类虚函数表遵循“拷贝-覆盖-新增”三步,且仅含一张虚表。

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;} // 重写func1virtual void func3() {cout<<"Derive::func3" <<endl;} // 新增虚函数virtual void func4() {cout<<"Derive::func4" <<endl;} // 新增虚函数private:int b;
};

虚表结构:

Base虚表:

&Base::func1,&Base::func2,nullptr

Derive虚表;

&Derive::func1(覆盖),&Derive::func2(继承),&Derive::func3(重写),&Derive::func4(新增)

多继承的虚函数表:

多继承下派生类会生成多张虚表,未重写的基类函数存于对应基类的虚表,新增函数默认存于第一个基类的虚表。

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;} // 重写func1virtual void func3() {cout << "Derive::func3" << endl;} // 新增虚函数
private:int d1;
};

虚表结构:

Base1对应虚表:&Derive::func1(覆盖),&Derive::func2(继承),&Derive::func3(新增),nullptr

Base2对应虚表:&Derive::func1(覆盖),&Derive::func2(继承),nullptr

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

相关文章:

  • 有没有做美食的小视频网站wordpress如何备份 网站
  • (125页PPT)盐化行业数字化转型规划详细方案(附下载方式)
  • 全站仪为什么要建站彬县网站
  • 学字体网站企业建网站品牌
  • OC母语的Developer对Swift常见问题的整理
  • 网站新闻置顶怎么做做企业品牌网站的公司
  • 网站建设开发团队介绍国外网站问题
  • 大兴网站建设设计公司建设学院网站的通知
  • 西安市建设银行网站网站开发全流程
  • 网站开发项目企划书广州网站建设市场
  • 插件式微服务开发全解析
  • vs215开发python网站开发鲅鱼圈规划建设局网站
  • 厦门服装企业网站推广网站开发的前后端是什么
  • seo网站设计招聘做网站卖别人的软件可以吗
  • 黄金网站软件app大全下载网络技术工程师
  • 成都编程咨询如何突破AI搜索隐形墙?
  • 百度官网认证网站山东省住房和城乡建设厅厅长
  • php协会网站源码网站定制开发上海
  • 怎么用ps做网站图片网络销售面试问题有哪些
  • 谷歌有做网站建设为什么要做seo
  • photoshop网站设计成都保障房中心官方网站
  • 信号与槽(2),geometry/frameGeometry,控件的enabled属性,按钮的press与clicked(bool)信号
  • 网站建设百度贴吧网站备案包括空间内容吗
  • seo网站培训班介绍类网站建设策划书范文
  • 炫酷个人网站c net 做网站好吗
  • 【C++】洛谷P2165括号匹配
  • 拓吧网站自学做网站多长时间
  • 百度收录哪些网站吗专业网站开发公司
  • qq相册怎么制作网站专做西餐的网站
  • 做一个购物网站多少钱北京网页设计外包