当前课程知识点:C++语言程序设计进阶 >  第八章 多态性 >  虚函数 >  虚函数

返回《C++语言程序设计进阶》慕课在线视频课程列表

虚函数在线视频

虚函数

虚函数

问题:还记得第7章的例子吗?

例7-3 类型转换规则举例

#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
    void display() const {
        cout << "Base1::display()" << endl;
    }
};
class Base2: public Base1 { //公有派生类Base2定义
public:
    void display() const {
        cout << "Base2::display()" << endl;
    }
};
class Derived: public Base2 { //公有派生类Derived定义
public:
    void display() const {
        cout << "Derived::display()" << endl;
    }
};

void fun(Base1 *ptr) {  //参数为指向基类对象的指针
    ptr->display();     //"对象指针->成员名"
}
int main() {    //主函数
    Base1 base1;    //声明Base1类对象
    Base2 base2;    //声明Base2类对象
    Derived derived;    //声明Derived类对象

    fun(&base1);    //用Base1对象的指针调用fun函数
    fun(&base2);    //用Base2对象的指针调用fun函数
    fun(&derived);     //用Derived对象的指针调用fun函数

    return 0;
}

例8-4通过虚函数实现运行时多态

现在我们来改进一下第7章的程序

#include <iostream>
using namespace std;

class Base1 {
public:
    virtual void display() const;  //虚函数
};
void Base1::display() const {
    cout << "Base1::display()" << endl;
}

class Base2::public Base1 { 
public:
     virtual void display() const;
};
void Base2::display() const {
    cout << "Base2::display()" << endl;
}
class Derived: public Base2 {
public:
     virtual void display() const; 
};
void Derived::display() const {
    cout << "Derived::display()" << endl;
}

void fun(Base1 *ptr) { 
    ptr->display(); 
}

int main() {    
    Base1 base1;
    Base2 base2;
    Derived derived;    
    fun(&base1);
    fun(&base2);
    fun(&derived);
    return 0;
}

初识虚函数

一般虚函数成员

virtual 关键字



下一节:虚析构函数

返回《C++语言程序设计进阶》慕课在线视频列表

虚函数课程教案、知识点、字幕

大家好

欢迎回来继续学习

C++语言程序设计

这一节我们来学习虚函数

虚函数是实现动态绑定的函数

什么是动态绑定呢

为什么需要动态绑定呢

大家还记得第七章

那个不成功的例子吧

对了

在第七章那个例子中呢

我们没能实现期望中的

那个通用的显示函数

现在我们来回顾一下

第七章那个不成功的例子

那么在第七章这个例子中呢

我们是定义了基类1 基类2

还有派生类

这个Base2继承Base1

Derived继承Base2

每一个类中都有一个

原型完全相同的display函数

当时呢我们是期望

能够写这么一个通用的函数

这个函数接收基类的指针

作为参数

然后每次运行的时候

这个指针所指向的对象

很可能是不同的

比如说

它曾经指向基类Base1对象

指向Base2对象

指向derived对象

我们把这三个地址

都传给过这个fun函数

然后我们希望这个fun函数

每一次能够根据它

指针所指向的实际对象

去调用每个类

自己的display函数

但是运行结果呢

我们看到没有达到这个效果

所以当时

我给大家的建议是

不要重新定义

继承而来的非虚函数

当时我们还没有学虚函数

也就是这样的函数

达不到这个效果

你就不要这样写

写了以后呢

我们读程序的时候

预期的效果

和程序真正的效果

它不一样

会造成这种程序的可读性有问题

大家有没有深入去想一想

为什么不成功呢

不成功的原因就是

在编译阶段

编译器根据指针

无法去判断在运行时

它会指向一个什么类型的对象

所以呢

它只能说指针是什么类型的

它就调用那个类

定义的display函数

那这种情况下

我们特别希望告诉编译器

对了

在编译阶段

你没法正确地决定

那怎么办呢

你推迟这个决定

在编译的时候先别确定

这个display函数调用表达式

跟函数体

到底哪个函数体跟它对应

先别对应

把它留着

留到运行时再确定

那么运行时

当然就能够知道

指针在某个时刻指向的实际对象

是什么了

怎么告诉编译器这件事呢

就用一个virtual关键字

就这么简单

现在呢

我们把第七章

那个不成功的例题呢

再改编一下

现在我们在第七章

这个例题的基础之上呢

只做一点点修改

在这个display函数

前面加一个virtual关键字来修饰

那这个virtual的意思

就是告诉编译器

凡是你遇到对这样原型的

这个函数的调用

你都不要在编译的时候

马上做决定

决定它

该去调用哪个函数的函数体

先把这个置后

这是一个指示编译器

不要在编译阶段做静态绑定

要为运行阶段做动态绑定

做好准备

是这么一个意思

好 那我们再来看

现在呢

我们就不能把这个

display函数的实现

写在类体里面

作为内联函数了

因为

既然不要它在编译阶段处理

要求它在运行阶段

再去决定

对display的调用

该执行哪个函数体

而内联函数呢

是在编译阶段处理的

就要把它嵌入到程序代码中去

所以这两者显然矛盾

所以这样的虚函数

加了virtual的函数

都要在类外去实现函数体

不能写成内联的了

再看同样Base2也有display函数

同样的原型也写成虚函数

Derived也是

同原型的函数写成虚函数

都是在类外

有各自不同的实现

那现在我们再进行同样地测试

这一回呢

我们希望的效果就达到了

每次分别送给它

不同的基类派生类对象的地址

送到这个形参里面去

这个指针呢

在运行的时候

不同的时刻

它可能就指向了

不同的类型的对象

虽然都是这个基类指针

来调用display

但是运行时

能够正确地找到

每个对象自己的display去运行

为什么呢

因为我们说了它是虚函数

所以编译器在编译的时候

它不做决定

不确定该调用哪个函数体

而是到运行的时候

再确定该调用哪个函数体

现在大家看到

用了一个virtual关键字

一切问题就都迎刃而解了

用virtual关键字说明的函数呢

就是虚函数

虚函数是实现运行时多态的基础

那么C++中

你用virtual指定的函数呢

就是要实现动态绑定的函数

但是虚函数

必须是非静态的成员函数

也就是说

虚函数应该是属于对象的

不是属于整个类的

它是需要在运行的时候

用指针去定位到

它指向的对象是谁

然后决定调用哪个函数体

所以呢虚函数

当然得是属于对象的

而不是属于类的函数

那么虚函数经过派生以后呢

就可以实现这种运行中的多态

这个我们在刚才例子中呢

也已经看到过了

我们知道类的成员函数呢

有这样几种

最大多数是一般的

实现类的功能的成员函数

还有呢

两种特殊的函数

一类是构造函数

一个是析构函数

那么一般的成员函数呢

可以是虚函数

构造函数是不能是虚函数

析构函数可以是虚函数

首先呢我们来看一下

怎么样声明一般的虚成员函数

这个大家在刚才例子中

已经都看到了

特别简单

就加一个virtual关键字

在最前面就可以了

其他的没有什么差别

但是呢要注意

虚函数的声明

它只能出现在

类定义中的函数原型声明中

不能在成员函数实现的时候

去加这个virtual关键字

那么有了这个

virtual关键字的声明以后呢

在派生类中

就可以对基类中的成员函数

进行覆盖了

在第七章那个例子的最后

我给大家一个建议说

千万不要重写

继承而来的非虚函数

对吧 为什么呢

因为它如果不是虚函数

在第七章中我们看到了

它就达不到我们想达到的目的

那你何必去重写一个

一样原型的函数呢

在阅读的时候

还会引起一种误会

现在我们有了这个virtual关键字

用virtual关键字
去把函数声明为虚函数以后

在派生类中

就可以对它进行覆盖

写一个相同原型的函数

就叫做覆盖

那么经过这样的覆盖呢

我们就可以实现运行时的多态性

还有呢

虚函数一般不要声明为内联函数

因为对虚函数的调用

需要进行动态绑定

也就是在运行时才绑定

而对内联函数的处理呢

它是在编译时进行的

是静态的

现在呢

我们对virtual关键字

做一个简单的小结

在基类中只要我们用virtual

去声明了一个虚函数

那么派生类继承了这个函数以后

它自动就是虚函数了

那么这个时候派生类

可以写一个相同原型的函数

来覆盖这个虚函数

这个相同原型的函数

写virtual关键字

或者不写virtual

它都是虚函数

既然在派生类中

你有些函数写不写virtual

它都是虚函数

那么编译器是通过什么来判定的

一个函数到底是不是虚函数呢

当然你自己写了virtual了

它肯定是虚函数

对于没有写virtual的

那编译器就会去判断

这个函数

它的函数名 参数表

还有它的返回值类型

以及

它的按照兼容规则规定的

这种指针引用的返回类型

是不是跟基类是一致的

也就是说

它的函数原型

跟基类的虚函数的函数原型

是不是一致

如果是一致的

就判定它是个虚函数

就要对它进行动态绑定

派生类中的虚函数呢

还会隐藏

基类中的同名函数的所有

其他的重载形式

那么这一点呢

大家也需要注意

如果你需要调用基类

同名函数的其他的重载形式的话

那你还要用类名

虽然说在派生类中

我们可以对继承而来的虚函数

进行覆盖

并且不写virtual关键字

但是一般呢

还是习惯于在派生类中

也用这个virtual关键字

这样程序的可读性会更好一些

C++语言程序设计进阶课程列表:

第七章 继承与派生

-导学

--导学

-继承的基本概念和语法

--继承的基本概念和语法

-第七章 继承与派生--继承的基本概念和语法习题

-继承方式

--继承方式简介及公有继承

--私有继承和保护继承

-第七章 继承与派生--继承方式

-基类与派生类类型转换

--基类与派生类类型转换

-第七章 继承与派生--基类与派生类类型转换

-派生类的构造和析构

--派生类的构造函数

--派生类的构造函数举例

--派生类的复制构造函数

--派生类的析构函数

--第七章 继承与派生--派生类的构造和析构

-派生类成员的标识与访问

--访问从基类继承的成员

--虚基类

-第七章 继承与派生--派生类成员的标识与访问

-小结

--小结

-综合实例

--第七章综合实例

-实验七

--实验七

-第七章讲义

第八章 多态性

-导学

--导学

-第八章 多态性--导学

-运算符重载

--运算符重载的规则

--双目运算符重载为成员函数

--单目运算符重载为成员函数

--运算符重载为非成员函数

-第八章 多态性--运算符重载

-虚函数

--虚函数

--虚析构函数

--虚表与动态绑定

-第八章 多态性--虚函数

-抽象类

--抽象类

--第八章 多态性--抽象类

-override与final

--override与final

-第八章 多态性--override与final

-小结

--第八章小结

-综合实例

--第八章综合实例

-实验八

--实验八

- 第八章讲义

第九章 模板与群体数据

-导学

--导学

-模板

--函数模板

--类模板

-第九章 模板与群体数据--模板

-线性群体

--线性群体的概念

-第九章 模板与群体数据--线性群体

-数组

--数组类模板

--例9-4数组类应用举例

-链表

--链表的概念与结点类模板

--链表类模板

-第九章 模板与群体数据--链表

-栈

--栈类模板

--栈类模板课后习题

--例9-9 栈的应用

--例9-9 栈的应用课后习题

-队列

--队列类模板

-第九章 模板与群体数据--队列

-排序

--排序概述

--插入排序

--选择排序

--交换排序

-第九章 模板与群体数据--排序

-查找

--查找

--查找课后习题

-小结

--小结

-综合实例

--综合实例

-实验九

--实验九

- 第九章讲义

第十章 泛型程序设计与C++标准模板库

-导学

--导学

-泛型程序设计及STL的结构

--泛型程序设计的基本概念

--STL简介

-第十章 泛型程序设计与C++标准模板库--泛型程序设计及STL的结构

-迭代器

--迭代器

-第十章 泛型程序设计与C++标准模板库--迭代器

-容器的基本功能与分类

--容器的基本功能与分类

-第十章 泛型程序设计与C++标准模板库--容器的基本功能与分类

-顺序容器

--顺序容器的基本功能

--顺序容器的特征

--顺序容器的插入迭代器与适配器

--第十章 泛型程序设计与C++标准模板库--顺序容器

-关联容器

--关联容器分类和基本功能

--集合

--映射

--多重集合和多重映射

-第十章 泛型程序设计与C++标准模板库--关联容器

-函数对象

--函数对象

--函数适配器

-算法

--算法

-小结

--第十章小结

-综合实例

--综合实例

-实验十

--实验十

- 第十章讲义

第十一章 流类库与输入/输出

-导学

--导学

-I/O流的概念及流类库结构

--I/O流的概念及流类库结构

-第十一章 流类库与输入/输出--I/O流的概念及流类库结构

-输出流

--输出流概述

--向文本文件输出

--向二进制文件输出

--向字符串输出

-第十一章 流类库与输入/输出--输出流

-输入流

--输入流概述

--输入流应用举例

--从字符串输入

-第十一章 流类库与输入/输出--输入流

-输入/输出流

--输入/输出流

-第十一章 流类库与输入/输出--输入/输出流

-小结

--小结

-综合实例

--综合实例

-实验十一

--实验十一

- 第十一章讲义

第十二章 异常处理

-导学

--第12章导学

-异常处理的思想与程序实现

--异常处理的思想与程序实现

-第十二章 异常处理--异常处理的思想与程序实现

-异常处理中的构造与析构

--异常处理中的构造与析构

-第十二章 异常处理--异常处理中的构造与析构

-标准程序库异常处理

--标准程序库异常处理

-第十二章 异常处理--标准程序库异常处理

-小结

--第12章小结

-综合实例

--综合实例

-实验十二

--实验十二

- 第十二章讲义

虚函数笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。