【C++进阶学习】C++中的多态(2)

简介: 【C++进阶学习】C++中的多态(2)

3、C++11 override 和 final


  • 引入:


C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的(编译器会按需实例化,只有实例化才会进行检查)


为此C++11提供了override和final两个关键字,可以帮助用户检测是否重写


final

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


示例:


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



  1. override


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

  • 示例:


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


4、重载/重写/重定义对比


  • 对比示图:


三、抽象类


  • 概念:


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


  • 示例:


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;
  }
};
void Test()
{
  Car* pBenz = new Benz;
  pBenz->Drive();
  Car* pBMW = new BMW;
  pBMW->Drive();
}


接口继承和实现继承:

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现


虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口(如果不实现多态,不要把函数定义成虚函数)


注意:

虽然函数重写需要接口一样,但是对于参数的缺省值没有检查,如果基类虚函数和派生类重写函数的缺省值不同,按照基类虚函数的接口来走,也就是说不用管派生类重写函数的接口


四、多态的原理


1、虚函数表


  • 例题:


// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
  virtual void Func1()
  {
    cout << "Func1()" << endl;
  }
private:
  int _b = 1;
};


解释:

b对象是8bytes,除了_b成员,还多一个 _vfptr放在对象的前面


注意:

_vfptr一般存放在变量前(存放位置与平台有关)


对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)


一个含有虚函数的类中都至少都有一个虚函数表指针(因为虚函数的地址要被放到虚函数表中,虚函数表也称虚表)


示例:


// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Func3
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;
};
class Derive : public Base
{
public:
  virtual void Func1()
  {
    cout << "Derive::Func1()" << endl;
  }
private:
  int _d = 2;
};
int main()
{
  Base b;
  Derive d;
  return 0;
}


说明:

d对象由两部分构成,一部分是父类继承下来的成员(虚表指针也就),存在部分的另一部分是自己的成员


对于派生类d对象,因为Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1(将继承的虚函数进行重写,而对应的在虚函数表上进行覆盖成自己的虚函数地址也叫作覆盖)(重写是语法的叫法,覆盖是原理层的叫法)


Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表


虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr(用来表示结束)(可能根据编译器而定)


派生类的虚表生成总结:

先将基类中的虚表内容拷贝一份到派生类虚表中


如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数


派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后


注意:

虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是函数的地址存到了虚表中,另外对象中存的是虚表指针,而虚表存在代码段中的(在vs平台下)(可以打印对象的地址与常量,变量进行比较)



相关文章
|
1天前
|
编译器 开发工具 C语言
配置C++的学习环境
这篇教程介绍了学习C++语言所需的环境配置和软件选择。首先,你需要一个文本编辑器(如Visual Studio Code、Visual Studio、Vim、Emacs或Eclipse)和一个C++编译器(如GCC)。在不同操作系统上安装GCC的方法包括:在Linux或UNIX上使用命令行检查或安装GCC,在Mac OS X上通过Apple的Xcode,而在Windows上则需要安装MinGW。教程还提供了使用Visual Studio创建和编译C++程序的步骤。最后,文章简述了g++编译器的使用及其常用命令选项。
7 0
|
1天前
|
C++
c++多态
c++多态
9 0
|
7天前
|
存储 设计模式 编译器
【C++】继承和多态常见的问题
【C++】继承和多态常见的问题
|
7天前
|
编译器 C++
【C++】多态 -- 详解(下)
【C++】多态 -- 详解(下)
|
7天前
|
编译器 Linux C++
【C++】多态 -- 详解(上)
【C++】多态 -- 详解(上)
|
7天前
|
算法 编译器 C语言
从C语言到C++⑩(第四章_模板初阶+STL简介)如何学习STL(下)
从C语言到C++⑩(第四章_模板初阶+STL简介)如何学习STL
13 0
|
7天前
|
编译器 C语言 C++
从C语言到C++⑩(第四章_模板初阶+STL简介)如何学习STL(上)
从C语言到C++⑩(第四章_模板初阶+STL简介)如何学习STL
9 0
|
7天前
|
编译器 C语言 C++
从C语言到C++①(第一章_C++入门_上篇)C++学习介绍(命名空间和C++输入输出流)(下)
从C语言到C++①(第一章_C++入门_上篇)C++学习介绍(命名空间和C++输入输出流)
12 0
|
7天前
|
Java 编译器 C#
从C语言到C++①(第一章_C++入门_上篇)C++学习介绍(命名空间和C++输入输出流)(上)
从C语言到C++①(第一章_C++入门_上篇)C++学习介绍(命名空间和C++输入输出流)
24 0
|
13天前
|
C++ 编译器 存储