【C++】一文简练总结【多态】及其底层原理&具体应用(21)

简介: 【C++】一文简练总结【多态】及其底层原理&具体应用(21)

一.多态的概念

  • 多态是在不同继承关系的类对象,去调用 同一 函数,产生了 不同 的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。
  • 例:iphone和安卓手机用户打车同程不同价

二.多态的实现

1)虚函数&虚函数表

  • 虚函数:即被 virtual 修饰的类成员函数称为虚函数。
class Person {
public:
 virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
  • 虚函数表本质是一个存虚函数指针 指针数组,一般情况这个数组最后面放了一个nullptr。
  • 虚函数表:虚函数表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚函数表中。
  • 一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表

2)虚函数的重写(覆盖)

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

3)多态的构成条件

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

4)虚函数重写的两种特殊情况:

【1】协变:(基类与派生类虚函数返回值类型不同)
  • 派生类重写基类虚函数时 ,与基类虚函数 返回值类型不同 。即如下代码所示:【基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时】,称为协变
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;}
};
【2】析构函数的重写:(基类与派生类析构函数的名字不同)
  • 如果 基类的析构函数为虚函数 ,此时派生类析构函数只要定义, 无论是否加virtual关键字
    都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。 虽然函数名不相同【~Person() 】 【~Student() 】,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成 destructor
class Person {
public:                  //基类的析构函数为虚函数
 virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
  //~Student() { cout << "~Student()" << endl; 不加virtual也行
 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;
}

三.【override】【final】关键字——帮助用户检测是否重写(C++11)

  • 从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
    名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
    得到预期结果才来debug会得不偿失,因此:C++11从两个角度提供了 override final 两个关键字,可以帮
    助用户检测是否重写。
  • final:表示虚函数不能被重写,被重写即报错
  • override:检查虚函数是否重写了别的虚函数,重写了即报错

【1】 final:表示虚函数不能被重写,被重写即报错

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

【2】override:检查虚函数是否重写了别的虚函数,重写了即报错

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

四. 多态的具体应用:抽象类(接口类)(纯虚函数类)

1)利用 [ 只有重写纯虚函数 派生类才能实例化出对象 ] 性质

  • 在虚函数的后面写上 =0 ,则这个函数为 纯虚函数 。 包含纯虚函数的类 叫做 抽象类(也叫接口类) , [ 抽象类不能实例化出对象 ]&[ 派生类继承后也不能实例化出对象 ] 。只有 [ 重写纯虚函数 ] ,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
class Car
{
public:
virtual void Drive() = 0;         //在虚函数的后面写上 =0,这个函数为 纯虚函数
};
class Benz :public Car
{
public:
 virtual void Drive()
 {
 cout << "Benz-舒适" << endl;  //只有 [ 重写纯虚函数 ] ,派生类才能实例化出对象
 }
};
class BMW :public Car
{
public:
 virtual void Drive()
 {
 cout << "BMW-操控" << endl;
 }
};
void Test()
{
Car* pBenz = new Benz;
 pBenz->Drive();
 Car* pBMW = new BMW;
 pBMW->Drive();
}

2)实现继承与接口继承

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


相关文章
|
2月前
|
C++
【C++】深入解析C/C++内存管理:new与delete的使用及原理(二)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
2月前
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
2月前
|
存储 C语言 C++
【C++】深入解析C/C++内存管理:new与delete的使用及原理(一)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
22天前
|
存储 编译器 数据安全/隐私保护
【C++】多态
多态是面向对象编程中的重要特性,允许通过基类引用调用派生类的具体方法,实现代码的灵活性和扩展性。其核心机制包括虚函数、动态绑定及继承。通过声明虚函数并让派生类重写这些函数,可以在运行时决定具体调用哪个版本的方法。此外,多态还涉及虚函数表(vtable)的使用,其中存储了虚函数的指针,确保调用正确的实现。为了防止资源泄露,基类的析构函数应声明为虚函数。多态的底层实现涉及对象内部的虚函数表指针,指向特定于类的虚函数表,支持动态方法解析。
29 1
|
2月前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
46 2
C++入门12——详解多态1
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
86 1
|
2月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
42 1
|
2月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
60 2
|
3月前
|
编译器 C++
【C++核心】函数的应用和提高详解
这篇文章详细讲解了C++函数的定义、调用、值传递、常见样式、声明、分文件编写以及函数提高的内容,包括函数默认参数、占位参数、重载等高级用法。
28 3