C++虚函数,纯虚函数,抽象类以及虚基类的区别

Part1.C++中的虚函数

  • 什么是虚函数:

    • 直观表达就是,如果一个函数的声明中有 virtual 关键字,那么这个函数就是虚函数。

       
  • 虚函数的作用:
    • 虚函数的最大作用就是实现面向对象程序设计的一大特点,多态性,多态性表达的是一种动态的概念,是在函数调用期间,进行动态绑定,以达到什么样的对象就实现什么样的功能的效果。

       
  • 虚函数的一般声明语法:
    • virtual 函数类型 函数名 (形参表)

       
  • 注意:
    • 虚函数的声明只能出现在类的定义中,不能出现在成员函数实现的时候
    • 虚函数一般不声明为内联函数,但是声明为内联函数也不会引起错误

       
  • 在运行过程中要实现多态的三个条件:
    • 类之间满足赋值兼容关系(也就是类之间有继承关系)
    • 要声明为虚函数
    • 调用虚函数时,要由成员函数或者是指针和引用来访问

 

  • 代码举例
	#include <iostream>
using namespace std; class Base1 {
public:
public:
virtual void play();
}; void Base1::play()
{
cout << "Base1::play()" << endl;
} class Base2:
public Base1
{
virtual void play();
}; void Base2::play() {
cout << "Base2::play()" << endl;
} class Derived :
public Base2
{
virtual void play();
}; void Derived::play() {
cout << "Derived:: play()" << endl;
} void fun(Base1* ba) { //声明一个基类的指针
ba->play();
} int main()
{
Base1 ba1;
Base2 ba2;
Derived de;
//分别用不同的对象指针来调用 fun 函数
fun(&ba1);
fun(&ba2);
fun(&de);
return 0;
}

这代码含义就充分体现了虚函数作为实现多态条件的原因,由于 Base1 是 Base2 和 Derived 的父类,所以,Base1 是可以兼容 Base2 和 Derived 的,所以在 fun 函数这里是用的 Base1 的指针来作为形参,不同的是当我传入参数不同时,fun 函数执行的是不同的结果,这就体现了多态的效果,我需要那个类型的实例,他就执行那个实例对应的方法。

需要注意的是:

基类的指针可以指向派生类的对象,基类的引用可以作为派生类的别名,但是基类的对象不能表示派生类的对象,所以在 fun 函数中我使用的是 Base1 的指针(换成引用也可以),而不是 Base1 的对象。

Part2.纯虚函数和抽象类

纯虚函数一般是和抽象类一起使用的,因为抽象类的定义就是:拥有纯虚函数的类是抽象类,所以需要先了解一下纯虚函数。

  • 什么是纯虚函数

    • 在虚函数的声明基础上,在函数声明后面加上 =0,的函数为纯虚函数

       
  • 一般定义语法:
    • virtual 函数类型 函数名(参数表)=0;

       
  • 什么时候使用纯虚函数:
    • 当一个方法不用实例化
    • 方法的实现必须在派生类中

       
  • 注意:
    • 纯虚函数不用被实现
    • 区分纯虚函数和函数体为空的函数,这是两中不同的函数

抽象类:

  • 抽象类的主要作用:

    • 为一个类族建立起一个公共的接口,使他们能够完整发挥多态的特性

       
  • 注意:
    • 抽象类不能实例化

       

代码举例

	 #include <iostream>
using namespace std; class Base1 {
public:
public:
virtual void play()=0;
}; void Base1::play()
{
cout << "Base1::play()" << endl;
} class Base2:
public Base1
{
virtual void play();
}; void Base2::play() {
cout << "Base2::play()" << endl;
} class Derived :
public Base2
{
virtual void play();
}; void Derived::play() {
cout << "Derived:: play()" << endl;
} void fun(Base1* ba) { //声明一个基类的指针
ba->play();
} int main()
{
Base2 ba2;
Derived de;
//分别用不同的对象指针来调用 fun 函数
fun(&ba2);
fun(&de);
return 0;
}

这段代码和上面的代码区别不大,唯一的区别在于,将 Base1 的 play方法声明为了纯虚函数,所以 base1 成了一个抽象类,就不能在直接声明一个 Base1 类型的对象,如果在声明一个纯虚函数,编译器就会报错,因为抽象类不能被实例化。

Part3.虚基类

之所以把虚基类放到这里为了做一个比较,因为学习了虚函数,抽象类后容易把这几个概念弄混,所以在这里对比记忆。

  • 为什么要使用虚基类
	 #include <iostream>
using namespace std; class A {
public :
int varA;
}; class B:
public A
{
public:
int varB;
}; class C:
public A
{
public:
int varC;
}; class D :
public C, public B
{
public:
int varC;
}; int main()
{
D d;
d.varA;
return 0;
}

解析上面的代码,由于对象 D 是继承与 B 和 C的,然而 B 和 C 又共同继承与 A,所以 B 和 C都具有从 A 中继承下来的属性 varA,所以我可以用 d 对象来调用 varA ,但是正是因为 B 和 C 中都具有 varA,所以我用

d 对象调用 varA 时,编译器会产生疑问,到底是调用的 B 的 varA 还是 C 的 varA,这样就会产生错误,当然我们可以使用作用域分辨符来区分到底是谁的 varA,但是这样就不能很好的体现继承的概念了,所以这里使用虚基类来解决这个问题。

  • 虚基类的作用

    • 当一个派生类中有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接基类的数据成员的多份同名成员,就会造成二义性,将这个共同基类设置为虚基类时,同名的数据成员就会只存在一个副本,同一个函数名只有一个映射,就解决了二义性。

也就是说,当声明为虚基类时,在程序中只保留了一个共同基类的数据副本,对象 B 和对象 C 的 varA 都指向同一个 varA(本来也应该是这样),这样再调用 varA 时,在程序中只有一个 varA,编译器就不会产生疑问了。

  • 基本语法
   class A {
public :
int varA;
}; class B:
virtual public A
{
public:
int varB;
}; class C:
virtual public A
{
public:
int varC;
}; class D :
public A, public B
{
public:
int varC;
};

Part4.总结

虚函数,是在类中中的概念,他的重要作用就是实现面向对象的多态的特性,

抽象类,是类的概念,他必须和纯虚函数函数搭配使用

虚基类,是继承中的概念,是继承过程中用来解决多个间接基类同一个副本产生的二义性的方法。

c++第五次博客作业的更多相关文章

  1. 第五周博客作业 <西北师范大学| 周安伟>

    第五周博客作业 一,助教博客链接https://home.cnblogs.com/u/zaw-315/ 二,本周工作查阅项目汇报,班级微信群.对同学们的中期项目汇报进行查看,解决上周留言问题,对及时出 ...

  2. 第十五周博客作业 <西北师范大学| 周安伟>

    第十五周作业 助教博客链接https://home.cnblogs.com/u/zaw-315/ 作业要求链接https://www.cnblogs.com/nwnu-daizh/p/10946673 ...

  3. 第五周博客作业<西北师范大学|李晓婷>

    1.助教博客链接:https://home.cnblogs.com/u/lxt-/ 2.作业要求链接:https://www.cnblogs.com/nwnu-daizh/p/10527959.htm ...

  4. 第五次博客作业——Alpha2项目的测试

    格式描述: 这个作业属于哪个课程 <课程的链接> 这个作业要求在哪里 <作业要求的链接> 团队名称 你的代码我的发 这个作业的目标 选取非自己所在团队的3个项目进行测试,并写出 ...

  5. C语言第五次博客作业--函数

    一.PTA实验作业 题目1:使用函数判断完全平方数 1. 本题PTA提交列表 2. 设计思路 3.本题调试过程碰到问题及PTA提交列表情况说明. 部分正确 :将else的情况放入for循环内,导致循环 ...

  6. C语言第五次博客作业

    一.PTA实验作业 题目1:6-6 使用函数输出水仙花数 1. 本题PTA提交列表 2. 设计思路 (1) 首先先定义narcissistic函数. (2)定义四个整形变量n,a,d,cnt,sum, ...

  7. java课程设计——博客作业教学数据分析系统(201521123083 戴志斌)

    目录 一.团队课程设计博客链接 二.个人负责模块或任务说明 三.自己的代码提交记录截图 四.自己负责模块或任务详细说明 五.课程设计感想 (题外话,终于可以用markdown建目录) 一.团队课程设计 ...

  8. Java课程设计——博客作业教学数据分析系统(201521123082 黄华林)

    Java课程设计--博客作业教学数据分析系统(201521123082 黄华林) 一.团队课程设计博客链接 博客作业教学数据分析系统(From:网络五条狗) 二.个人负责模块或任务说明 1.网络爬虫 ...

  9. OO第一次博客作业

    OO第一次博客作业 一.三次作业的bug反省 1.自己发现别人的问题 (1)输入处理的问题,比如第一次作业,主要就是处理输入的字符串,然后有同学的正则表达式有问题,则对于一些错误输入就不能正确判断. ...

随机推荐

  1. CEIWEI USBMonitor USB监控精灵 v2.3.2 USB过滤驱动 USB监控

    CEIWEI USBMonitor USB监控精灵 是一款监控USB端口协议分析软件,用于监控和分析USB设备协议,可以拦截.记录USB软件程序操作USB设备的In.Out数据包.支持监控分析USB票 ...

  2. 【网络开发】winsock组播

    https://my.oschina.net/lopo/blog/260685 //客户端 #include <winsock2.h> #include <stdio.h> # ...

  3. C++用于类型转换的4个操作符

    Dynamic_cast,   const_cast,  static_cast,  reinterpret_cast. (1)reinterpret_cast 用于基本的类型转换.如 in *ip; ...

  4. 使用java类加载器,报异常java.nio.file.InvalidPathException

    String path = Label.class.getClassLoader().getResource("").getPath(); /F:/idea-Java/ImageD ...

  5. ding

    Import "shanhai.lua"Dim currHour,currMinute,currSecondDim mmRnd = 0Dim sumFor=Int(ReadUICo ...

  6. LeetCode 108. 将有序数组转换为二叉搜索树(Convert Sorted Array to Binary Search Tree) 14

    108. 将有序数组转换为二叉搜索树 108. Convert Sorted Array to Binary Search Tree 题目描述 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索 ...

  7. PAT(B) 1078 字符串压缩与解压(Java)

    题目链接:1078 字符串压缩与解压 (20 point(s)) 题目描述 文本压缩有很多种方法,这里我们只考虑最简单的一种:把由相同字符组成的一个连续的片段用这个字符和片段中含有这个字符的个数来表示 ...

  8. Scala 面向对象编程之Trait

    将trait作为接口使用 // Scala中的Triat是一种特殊的概念 // 首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似 // 在triat中可以定义抽象方 ...

  9. vue的就地复用--- v-for与:key

    v-for遵循的是vue的就地复用原则.文本与数据是绑定的,所以当文本被重新渲染的时候,列表也会被重新渲染. 就地复用只适用于不依赖子组件状态或临时DOM状态的列表渲染输出.[比如表单输入值的列表渲染 ...

  10. jdk8新特性--使用lambda表达式的延迟执行特性优化性能

    使用lambda表达式的延迟加载特性对代码进行优化: