[c++] 继承和多态整理二

1 虚函数和纯虚函数

虚函数,之所以说是虚的,说的是在派生类中,可以覆盖基类中的虚函数;相对于虚函数来说,没有 virtual 修饰的函数可以叫做实函数,实函数就不能被覆盖。虚函数是实现多态的核心。虚函数和纯虚函数比较的话,虚函数可以在派生类中被覆盖,但是虚函数也是有自己的实现的,可以被直接调用;纯虚函数没有自己的实现,在派生类中可以被覆盖,并且必须实现。包含纯虚函数的类是抽象类,不能创建对象,如果抽象类的派生类中没有实现纯虚函数 ,那么派生类也是抽象类,不能创建对象。

虚函数和纯虚函数并没有严格的优劣之分。

从实际使用中,纯虚函数有一个优点,假如一个基类中的函数,派生类中必须实现自己的逻辑,而不能使用基类中的逻辑,那么就可以使用纯虚函数,这样在派生类中如果忘记实现了,那么编译器就会提示错误,起到了一个约束的作用;如果用虚函数实现,那么派生类中忘记实现的话,编译器也不会报错,起不到约束提醒的作用。

纯虚函数有以下两点:

(1)纯虚函数的声明方式

函数声明之后加 = 0,而不是花括号

(2)抽象类不能创建对象,抽象类的派生类如果没有实现所有的纯虚函数,派生类也是抽象类

(3)抽象类可以定义指针,并且可以使用派生类的指针给它赋值

如下是使用抽象类的一个例子:

#include <iostream>
#include <string>class Phone {
public:Phone() {std::cout << "Phone()" << std::endl;}~Phone() {std::cout << "~Phone()" << std::endl;}virtual void Call() = 0;virtual void SendMessage(std::string msg) = 0;
};class Apple : public Phone {
public:Apple() {std::cout << "Apple()" << std::endl;}~Apple() {std::cout << "~Apple()" << std::endl;}virtual void Call() {std::cout << "Apple Call()" << std::endl;}virtual void SendMessage(std::string msg) {std::cout << "apple send msg: " << msg << std::endl;}
};class Oppo : public Phone {
public:Oppo() {std::cout << "Oppo()" << std::endl;}~Oppo() {std::cout << "~Oppo()" << std::endl;}virtual void Call() {std::cout << "Oppo Call()" << std::endl;}
};class Vivo : public Phone {
public:Vivo() {std::cout << "Vivo()" << std::endl;}~Vivo() {std::cout << "~Vivo()" << std::endl;}virtual void Call() {std::cout << "Vivo Call()" << std::endl;}virtual void SendMessage(std::string msg) {std::cout << "vivo send msg: " << msg << std::endl;}
};int main() {// 不能创建 Phone 对象,因为 Phone 是抽象类// Phone phone;// 不能创建 Oppo 对象,因为 Oppo 没有实现 Phone 中的 SendMessage 函数// 所以 Oppo 也是抽象类// Oppo oppo;std::cout << "sizeof(Phone) = " << sizeof(Phone) << std::endl;std::cout << "sizeof(Apple) = " << sizeof(Apple) << std::endl;std::cout << "sizeof(Oppo) = " << sizeof(Oppo) << std::endl;std::cout << "sizeof(Vivo) = " << sizeof(Vivo) << std::endl;Phone *phone;Apple apple;Vivo vivo;phone = &apple;phone->Call();phone->SendMessage("this is apple");phone = &vivo;phone->Call();phone->SendMessage("this is vivo");return 0;
}

运行结果如下:

抽象类中也有虚表,从上边的打印来看,sizeof(Phone) 计算出来的结果是 8。

2 构造函数调用虚函数

2.1 基类构造函数调用虚函数,调用的是基类的虚函数

在构造函数中调用虚函数,基类构造函数调用虚函数调用的是基类的虚函数还是调用的子类的虚函数;派生类的构造函数中调用虚函数,调用的是基类的虚函数还是调用的自己的虚函数。

使用下边的代码来做一下实验,基类是 Base,有一个虚函数 VDo(),派生类是 Derived1,覆盖了基类的虚函数 VDo()。在 Base 的构造函数中调用了虚函数 VDo(),在 Derived1 的构造函数中也调用了虚函数 VDo()。在 main 函数中创建一个 Derived1 对象。

#include <iostream>
#include <string>class Base {
public:Base() {std::cout << "Base()" << std::endl;VDo();}~Base() {std::cout << "~Base()" << std::endl;}void Do() {std::cout << "Base() Do()" << std::endl;}virtual void VDo() {std::cout << "Base() VDo()" << std::endl;}
};class Derived1 : public Base {
public:Derived1() {std::cout << "Derived1()" << std::endl;VDo();}~Derived1() {std::cout << "~Derived1()" << std::endl;}virtual void VDo() {std::cout << "Derived1() VDo()" << std::endl;}
};int main() {Derived1 d1;return 0;
}

如下是打印的日志,从日志可以看出来,在构造 Derived1 的时候首先要构造 Base。在 Base 构造函数中调用的 VDo() 是 Base 中的,在 Derived1 的构造函数中调用的 VDo 是 Derived1 中的。

当派生类构造的时候,首先构造基类,然后再构造派生类。在调用基类构造函数的时候,派生类还没构造,还没有初始化,所以在基类构造函数中调用的虚函数是基类中的虚函数。

派生类构造函数中调用的虚函数是派生类的虚函数。

2.1 基类和派生类之间的引用传递,指针传递,值传递

引用传递和指针传递都能体现多态,值传递无法体现多态。

#include <iostream>
#include <string>class Base {
public:Base() {std::cout << "Base()" << std::endl;VDo();}~Base() {std::cout << "~Base()" << std::endl;}void Do() {std::cout << "Base() Do()" << std::endl;}virtual void VDo() {std::cout << "Base() VDo()" << std::endl;}
};class Derived1 : public Base {
public:Derived1() {std::cout << "Derived1()" << std::endl;VDo();}~Derived1() {std::cout << "~Derived1()" << std::endl;}virtual void VDo() {std::cout << "Derived1() VDo()" << std::endl;}
};void CallDo(Base &b) {std::cout << "CallDo(Base &b)" << std::endl;b.VDo();
}void CallDo(Base *b) {std::cout << "CallDo(Base *b)" << std::endl;b->VDo();
}void CallDoValue(Base b) {std::cout << "CallDoValue(Base b)" << std::endl;b.VDo();
}int main() {Derived1 d1;std::cout << std::endl;CallDo(d1);std::cout << std::endl;CallDo(&d1);std::cout << std::endl;CallDoValue(d1);std::cout << std::endl;return 0;
}

程序运行结果,使用值传递的时候,调用的函数还是 Base 中的 VDo()。

2.2 虚函数重载

如下代码 Base 是基类,Derived 是派生类。Base 中有两个虚函数 func1() 和 func2(),Derived 中也有两个虚函数 func1() 和 func2()。Derived 中的 func1() 和 Base 中的 func1() 的形参列表是不一样的,所以 Derived 中的 func1 不会覆盖 Base 中的 func1。

#include <iostream>
#include <string>class Base {
public:Base() {std::cout << "Base()" << std::endl;}~Base() {std::cout << "~Base()" << std::endl;}virtual void func1(int a) {std::cout << "Base()::func1(int a), a = " << a << std::endl;}virtual void func2(int a = 100) {std::cout << "Base()::func2(int a = 100), a = " << a << std::endl;}
};class Derived : public Base {
public:Derived() {std::cout << "Derived()" << std::endl;}~Derived() {std::cout << "~Derived()" << std::endl;}virtual void func1(double a) {std::cout << "Derived()::func1(double a), a = " << a << std::endl;}virtual void func2(int a = 200) {std::cout << "Derived()::func2(int a = 200), a = " << a << std::endl;}
};typedef void (*PF)(int);
typedef void (*PF1)(double);
int main() {Base *b = new Derived;Derived *d = new Derived;std::cout << "----------------" << std::endl;b->func1(10);b->func1(1.123);b->func2();std::cout << "----------------" << std::endl;d->func1(10);d->func1(1.123);d->func2();return 0;
}

运行结果如下:



(1)派生类中的虚函数表如下

第一个表项是 Base 中的 func1。

第二个表项在基类中是 Base 中的 func2,在派生类中,Derived 中的 func2 对 Base 中的 func2 进行了覆盖。

第三个表象是派生类中的 func1,因为派生类中的 func1 和 Base 中的 func1 形参不一样,所以不会对 Base 中的 func1 进行覆盖。

(2)运行结果分析

① 使用 Base 类型的指针或者 Base 类型的引用,当指针指向 Derived 对象的时候,调用 func1(),不管传参是 int 类型还是 double 类型,都是调用的 Base 中的 func1()。当传参是 double 类型的时候也不是调用的 Derived 中的 func1,也就是派生类和基类形不成重载。

② 使用 Base 类型的指针或者引用,当指针指向 Derived 对象的时候,调用 func2,调用的函数是 Derived 中的 func2,但是默认参数还是 Base 中初始化的。这个现象让人看起来有点奇怪,函数和默认参数不是配套的。

③ 使用 Derived 指针或者引用,调用的 func1() 都是 Derived 中的函数,不管入参是 int 还是 double。

④ 使用 Derived 指针或者引用,调用 func2() 调用的是 Derived 中的 func2(),并且形参默认是 200。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/505542.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pdf如何压缩文件大小?pdf文件在线压缩方法介绍

在日常工作中&#xff0c;我们经常使用PDF文件进行传输和保存&#xff0c;然而&#xff0c;有时候我们会遇到过大的PDF文件&#xff0c;这不仅会导致传输困难&#xff0c;还会占用过多的设备空间&#xff0c;因此&#xff0c;我们需要对PDF压缩一下以便更轻松地传输和保存&…

重学Springboot3-@ConditionalOnXxx条件注解

重学Springboot3-ConditionalOnXxx条件注解 引言常见的条件注解常见的条件注解示例扩展条件注解1. ConditionalOnJndi2. ConditionalOnJava3. ConditionalOnCloudPlatform4. ConditionalOnEnabledResourceChain5. 自定义条件注解 总结 引言 Spring Boot 提供了一组强大的条件注…

对程序、进程、线程、并发、并行、高并发概念的讲解

一、概述 程序、进程、线程、并发、并行和高并发是计算机科学领域中非常重要的概念。 了解进程、线程、并发和并行的概念&#xff0c;可以更好地利用计算机的多核处理器和并行计算能力&#xff0c;提高计算机性能。 了解进程和线程为操作系统中的资源管理提供了基础&#xff…

每日一类:QLabel深入解析

QLabel是Qt中用于显示文本或图像的控件&#xff0c;属于Qt Widgets模块。它是展示静态内容的理想选择&#xff0c;支持富文本格式&#xff0c;使得文本可以包含不同的字体、颜色和链接。QLabel也可以用来显示图像&#xff0c;包括动态图像。此外&#xff0c;它还支持文本和图像…

P4715 【深基16.例1】淘汰赛题解

题目 有&#xff08;n≤7&#xff09;个国家参加世界杯决赛圈且进入淘汰赛环节。已经知道各个国家的能力值&#xff0c;且都不相等。能力值高的国家和能力值低的国家踢比赛时高者获胜。1号国家和2号国家踢一场比赛&#xff0c;胜者晋级。3号国家和4号国家也踢一场&#xff0c;…

实战 | 使用YOLOv8图像分割实现路面坑洞检测(步骤 + 代码)

导 读 本文主要介绍使用YOLOv8图像分割实现路面坑洞检测&#xff08;步骤 代码&#xff09;。 背 景 如上图所示&#xff0c;现实生活中路面坑洞对车辆和驾驶员安全来说存在巨大隐患&#xff0c;本文将介绍如何使用YoloV8图像分割技术来检测路面坑洞&#xff0c;从而提示驾…

平台工程: 用Backstage构建开发者门户 - 1

本文介绍了如何使用开源Backstage构建自己的开发者门户&#xff0c;并基于此实践平台工程。本系列共两篇文章&#xff0c;这是第一篇。原文: Platform Engineering: Building Your Developer Portal with Backstage — Part 1 在上一篇文章(平台工程与安全)中&#xff0c;我们介…

2023人机交互期末复习

考试题型及分值分布 1、选择题&#xff08;10题、20分&#xff09; 2、填空题&#xff08;10题、20分&#xff09; 3、判断题&#xff08;可选、5题、10分&#xff09; 4、解答题&#xff08;5~6题、30分&#xff09; 5、分析计算题&#xff08;1~2题、20分&#xff09; 注意&…

EI顶刊复现:基于氨储能技术的电转氨耦合风–光–火综合能源系统双层优化调度程序代码!

适用平台&#xff1a;MatlabYalmipCplex 程序首先提出电转氨耦合综合能源系统构型&#xff0c;并为燃煤机组出力、风光消纳和电转氨运行的经济性和稳定性的综合评价定义风–光–火–氨系统协调运行指标&#xff1b;进而构建以协调运行指标最大为上层目标、电转氨耦合风–光–火…

【每日一题】3.2 求逆序对

题目描述 给定一个长度为 n的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i个和第 j个元素&#xff0c;如果满足 i<j 且 a[i]>a[j]&#xff0c;则其为一个逆序对&#xff1b;否则不是。 输入格式 第一行包含整数 n…

基于 LLaMA 和 LangChain 实践本地 AI 知识库

有时候,我难免不由地感慨,真实的人类世界,本就是一个巨大的娱乐圈,即使是在英雄辈出的 IT 行业。数日前,Google 正式对外发布了 Gemini 1.5 Pro,一个建立在 Transformer 和 MoE 架构上的多模态模型。可惜,这个被 Google 寄予厚望的产品并未激起多少水花,因为就在同一天…

腾讯云优惠券领取入口_先领取再下单_2024腾讯云优惠攻略

腾讯云优惠代金券领取入口共三个渠道&#xff0c;腾讯云新用户和老用户均可领取8888元代金券&#xff0c;可用于云服务器等产品购买、续费和升级使用&#xff0c;阿腾云atengyun.com整理腾讯云优惠券&#xff08;代金券&#xff09;领取入口、代金券查询、优惠券兑换码使用方法…