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

C++之多态

1.多态的概念

    多态简而言之就是多种形态的意思。多态分为编译时多态(静态多态)和运行时多态(动态多态)。

     静态多态:主要就是函数重载和函数模版两类,因为实参传给形参是在编译时完成的,所以又称为编译时多态。

    动态多态:具体点来说就是去完成某个行为,可以传给不同的对象从而打到完成不同的行为。

     以买票为例:成人买成人票,学生则会买优惠的学生票。这属于动态多态    

2.多态的定义及实现

      2.1多态的构成条件

多态是一个继承关系的下的类对象,去调用同一函数,产生了不同的行为。

       2.1.1实现多态还有两个必须重要条件

      1.必须指针或者引用调用虚函数

      2.被调用的函数必须是虚函数

说明:要实现多态效果,第一必须是基类的指针或者引用,因为只有基类的指针或引用才能指向派生类。第二派生类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派生类才能有不同的函数,多态的不同形态效果才能达到

       2.1.2虚函数 

类函数前面加virtual修饰,那么这个成员函数被称为虚函数。(注意非成员函数不能加virtual修饰)

      2.1.3虚函数的重写/覆盖

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

注意:在写派生类的虚函数也可以不用带上virtual因为派生类把基类的虚函数属性继承下来了,但是这种写法不规范,尽量不要这样写

     2.1.4 多态场景的一个选择题(重要

    以下程序输出结果是什么()

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

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,原因: 重写的本质是重写函数实现,函数声明是不可替换的,所以还是用的父类的int val = 1;

     2.1.5 虚函数重写的一些其他问题

1.协变

派生类重写基类函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用,称为协变,该知识点意义不大稍作了解即可。

2.析构函数的重写

基类函数一定要虚化,并且派生类的析构函数一定要重写,这样可以避免内存泄漏问题。

下面的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调用的A的析构函数,没有调用B的析构函数,就会导致内存泄漏问题,因为B的资源没有释放。

注意:根据下面例子可以很好解释基类中的析构函数为什么建议设计为虚函数。

class A
{
public:
virtual ~A()
{
   cout << "~A()" << endl;
}
};
class B : public A {
public:
~B()
{
  cout << "~B()->delete:"<<_p<< endl;
  delete _p;
}
protected:
  int* _p = new int[10];
};
// 只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能
构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。
int main()
{
  A* p1 = new A;
  A* p2 = new B;
 
  delete p1;
  delete p2;
 
  return 0;
}

   2.1.6 override 和 final关键字 

C++对函数的重写比较严格,但是有些情况下由于疏忽(写错参数名之类),而这种错误在编译时不会报出,但程序无法正常运行,因此C++提供了override,可以帮助用户检测是否重写,如果我们不想让派生类重写这个虚函数,那么可以用final去修饰。

// error C3668: “Benz::Drive”: 包含重写说明符 “override” 的⽅法没有重写任何基类⽅法

class Car

{

public:

           virtual void Dirve()

            {}

};

class Benz :public Car

{

 public:

           virtual void Drive() override { cout << " Benz-舒服" <<  endl;}

};

int main()

{

   return 0;

}

 // error C3248: “Car::Drive”: 声明为“final”的函数⽆法被“Benz::Drive”重写

class Car

{

public:

            virtual void Drive()  final{}

};

class Benz :public Car

{

public:

           virtual void Drive()    {cout << " Benz-舒服 " << endl;}

};

int main()

{

  return 0;

}

 2.1.7 重载/重写/隐藏的对比 

注意:这个概念不要死记硬背,要多加理解

3.纯虚函数和抽象类 

在虚函数的后面写上 = 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;
}
};
int main()
{
// 编译报错:error C2259: “Car”: ⽆法实例化抽象类
 Car car;
 Car* pBenz = new Benz;
 pBenz->Drive();
 Car* pBMW = new BMW;
 pBMW->Drive();
 return 0;
}

4.多态的原理 

     4.1多态是如何实现的

满足多态条件后,底层不再是编译时通过调用对象确定函数的地址,而是运行时指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引用指向基类就调用基类的虚函数,指向派生类就调用派生类对应的虚函数。

     4.2动态绑定与静态绑定

1.对不满足多态条件的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定。

2.满⾜多态条件的函数调⽤是在运⾏时绑定,也就是在运⾏时到指向对象的虚函数表中找到调⽤函数的地址,也就做动态绑定。

     4.3 虚函数表

1.基类对象的虚函数表中存放基类所有虚函数的地址

2.派生类有两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。

注意:这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同一个。

3.派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。

4.派生类的虚函数表中包含:基类的虚函数地址,派生类重写的虚函数地址,派生类自己的虚函数地址三个部分。

5.虚函数表本质是一个存虚函数指针的指针数组。

6.虚函数和普通函数一样的,编译好后是一段指针,都是存在代码段的,只是虚函数的地址又存到了虚表中。

相关文章:

  • Vue 响应式原理
  • pycharm 使用 translation 插件通过openai进行翻译
  • 使用Maven创建一个Java项目并在repository中使用
  • 【机器学习(七)】分类和回归任务-K-近邻 (KNN)算法-Sentosa_DSML社区版
  • JDBC 编程
  • Postman cURL命令导入导出
  • 5.内容创作的未来:ChatGPT如何辅助写作(5/10)
  • 数据预处理方法—数据标准化和数据归一化
  • 大话C++:第11篇 类的定义与封装
  • 【AI视频】Runway:Gen-2 运镜详解
  • 关于安卓App自动化测试的一些想法
  • 7、论等保的必要性
  • LabVIEW提高开发效率技巧----采用并行任务提高性能
  • 使用Kong开源API网关的保姆级教程
  • MATLAB窗口操作常用命令
  • Th:1.1 建立连接
  • 2024华为杯研究生数学建模竞赛(研赛)选题建议+初步分析
  • Apache Iceberg 试用
  • Python学习——【4.2】数据容器:tuple元组
  • 前端web端项目运行的时候没有ip访问地址
  • 美国明尼苏达州发生山火,过火面积超80平方公里
  • 著名连环画家庞邦本逝世
  • 真人秀《幸存者》百万美元奖金,25年间“缩水”近一半
  • 退休10年后,70岁成都高新区管委会原巡视员王晋成被查
  • 马上评丨为护士减负,不妨破除论文“硬指标”
  • 石家庄推动城市能级与民生福祉并进