【C++复习】第八章 多态性(2)(虚函数,纯虚函数)
虚函数是动态绑定的基础,必须是非静态的成员函数
1、一般虚函数
1.1 引例
程序 | 运行结果&解释 |
|
运行结果: Base1::display() 解释: 使用对象名,绑定发生在编译过程中。
根据类型兼容性规则,派生类对象的地址会被转换为指向基类的指针,fun中的p->display()会执行基类中的display() |
|
运行结果: Base1::display() 解释: fun()中的实参p绑定到了不同子类的指针,程序知道这一点,故执行各子类的display(),实现了多态(运行时) |
1.2 一般虚函数
1.2.1 什么是一般虚函数?
- 首先需要在基类中将这个同名函数声明为虚函数,这样通过基类类型的指针(引用)就可以使属于不同派生类的不同对象产生不同的行为,实现运行过程的多态。
- 虚函数声明只能出现在类定义中的函数原型声明中,而不能出现在成员函数
实现的时候!(一般不声明为内联函数) - 虚函数成员语法:
virtual 函数类型 函数名(形参表);
1.2.2 运行时多态需满足三个条件:
- 类之间满足兼容规则
- 声明虚函数
- 由成员函数来调用,或者通过指针、引用访问虚函数
1.2.3 系统如何判断派生类的函数成员是否为虚函数?
- 该函数是否与基类的虚函数有相同的名称?
- 该函数是否与基类的虚函数有相同的参数个数及相同的对应参数类型?(包括const)
- 该函数是否与基类的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型返回值?
1.2.4 一般虚函数的补充说明
- 只有虚函数是动态绑定的(如果派生类需要重写与基类函数同名的函数时,应该在基类中将相应的函数声明为虚函数)
- 基类中声明的非虚函数,通常代表那些不希望被派生类修改的函数,是不能实现多态的。
- 在重写继承来的虚函数时,如果函数有缺省值,不要重新定义不同的值(虚函数是动态绑定的,但缺省形参值是静态绑定的,缺省形参值只能来自基类的定义)
- 只有通过基类的指针或者引用调用虚函数时,才会发生动态绑定!(基类的指针可以指向派生类的对象,基类的引用可以作为派生类对象的别名,但基类的对象不能表示派生类的对象)
Derived d; Base *ptr=&d; //基类的指针ptr可以指向派生类的对象 Base &ref=d; //基类的引用ref可以作为派生类对象的别名 Base b=d; //调用Base1的复制构造函数用d构造b,b的类型是Base而非Derived
- 不能声明虚构造函数,但可以声明虚析构函数(保证使用基类类型的指针就能够调用适当的析构函数针对不同的对象进行清理工作)
2、虚析构函数
2.1 引例
程序 | 运行结果&解释 |
|
运行结果: Base destructor 解释: 此时通过基类指针删除派生类对象时调用的是基类的析构函数,派生类的析构函数没有被执行,因此派生类对象中动态分配的内存空间没有被释放,造成了内存泄露。 对于长期运行的程序来说,这是非常危险的! |
|
运行结果: Derived destructor 解释: 将基类析构析构函数声明为虚函数,在函数fun()中会执行派生类的析构函数,派生类中动态申请的内存空间被释放了。实现了多态。 |
2.2 虚析构函数的说明
- 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
- 派生类中如果定义了一个函数与基类中虚函数的名字相同但是形参列表不同,这仍然是合法的,但是该函数与虚函数是相互独立的,派生类中的函数并没有覆盖掉基类的版本。
2.3 override说明符
override与final都不是语言关键字(keyword),只有在特定的位置才有特别含意,其他地方仍旧可以作为一般标识符(identifier)使用。
- 用于声明显式函数覆盖。用于在编译期间发现未覆盖的错误。
- 运用显式覆盖,编译器会检查派生类中声明overrid的函数,在基类中是否存在可被覆盖的虚函数,若不存在,则会报错。
- 作用:
为了使派生类能覆盖基类的虚函数,但是容易弄错了参数列表,导致无法完成目标。
想通过调试发现此类错误十分困难,故使用override(C++11),在编译时编译器就会发现此类错误
例:
class Base
{
public:
virtual void f1(int) const;
virtual void f2();
void f3();
};
class Derived1 : public Base
{
public:
void f1(int) const override; //正确,f1与基类中的f1匹配
void f2(int) override; //错误,基类中没有形如f2(int)的函数
void f3() override; //错误,f3不是虚函数
void f4() override; //错误,基类中没有名为f4的函数
};
2.4 final说明符
- 用来避免类被继承,或是避免基类的函数被覆盖
class Base1 final
{
};
class Derived1 : Base1// 编译错误:Base1为final,不允许被继承
{
};
class Base2
{
virtual void f() final;
};
class Derived2 : Base2
{
void f(); // 编译错误:Base2::f 为final,不允许被覆盖
};
3、纯虚函数,抽象类
3.1 纯虚函数(了解概念即可)
纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本。
纯虚函数的声明格式为:
virtual 函数类型 函数名(参数表) = 0;
3.2 抽象类(不k)
带有纯虚函数的类称为抽象类
class 类名
{
virtual 函数类型 函数名(参数表) = 0;
//其他成员……
};
作用:
- 抽象类为抽象和设计的目的而声明
- 将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为
- 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现
注意:
- 抽象类只能作为基类来使用
- 不能定义抽象类的对象
- 可以定义抽象类的指针或者引用(从而运用虚函数多态)
3.3 例8-6
/*
8-6 P323
抽象类举例
*/
#include <iostream>
using namespace std;
class Base1 //基类Base1定义
{
public:
virtual void display() const = 0; //纯虚函数
};
class Base2 : public Base1 //公有派生类Base2定义
{
public:
void display() const; //覆盖基类纯虚函数
};
void Base2::display() const
{
cout << "Base2::display" << endl;
}
class Derived : public Base2 //公有派生类Derived定义
{
public:
void display() const;
};
void Derived::display() const
{
cout << "Derived::display" << endl;
}
void fun(Base1 *ptr)
{
ptr->display();
}
int main()
{
// Base1 b1;//错误,声明基类对象
Base1 *p; //正确,声明基类指针
Base2 b2; //声明派生类对象
Derived d1; //声明派生类对象
p = &b2;
fun(p); //调用派生类Base2函数成员
p = &d1;
fun(p); //调用派生类Derived函数成员
return 0;
}
运行结果:
Base2::display Derived::display |
参考:
C++语言程序设计(第5版),郑莉,清华大学
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程