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

C++笔记(面向对象)虚析构函数 纯虚函数 抽象类 final、override关键字

为什么构造函数不可以是虚函数呢?

虚函数调用只需要 “部分的” 信息,即只需要知道函数接口,而不需要对对象的具体类型。但是构建一个对象,却必须知道具体的类型信息。如果你调用一个虚构造函数,编译器怎么知道你想构建是继承树上的哪种类型呢?所以构造函数不能为虚。

  1. 构造函数的用途:1)创建对象,2)初始化对象中的属性,3)类型转换。
  2. 在类中定义了虚函数就会有一个虚函数表(vftable),对象模型中就含有一个指向虚表的指针(_vfptr)。在定义对象时构造函数设置虚表指针指向虚函数表。
  3. 使用指针和引用调用虚函数,在编译只需要知道函数接口,运行时指向具体对象,才能关联具体对象的虚方法(通过虚函数指针查虚函数表得到具体对象中的虚方法)
  4. 构造函数是类的一个特殊的成员函数:1)定义对象由系统自动调用构造函数,对象自己是不可以调用构造函数;2)构造函数的调用属于静态联编,在编译时必须知道具体的类型信息。
  5. 如果构造函数可以定义为虚构造函数,使用指针调用虚构造函数,如果编译器采用静态联编,构造函数就不能为虚函数。如果采用动态联编,运行时指针指向具体对象,使用指针调用构造函数,相当于已经实例化的对象在调用构造函数,这是不容许的调用,对象的构造函数只执行一次。
  6. 如果指针可以调用虚构造函数,通过查虚函数表,调动虚构造函数,那么,当指针为 nullptr,如何查虚函数表呢?
  7. 构造函数的调用是在编译时确定,如果是虚构造函数,编译器怎么知道你想构建是继承树上的哪种类型呢?总结:构造函数不允许是虚函数。

就是为每一个要构建的类型再创建一个对应的 factory,把问题放到 factory 的 make 方法中去解决。这也是 C++ 中的通用解决方案

虚析构函数:析构函数是类的一个特殊的成员函数:

1)当一个对象的生命周期结束时,系统会自动调用析构函数注销该对象并进行善后工作,对象自身也可以调用析构函数;

2)析构函数的善后工作是:释放对象在生命期内获得的资源(如动态分配的内存,内核资源);3)析构函数也用来执行对象即将被撤销之前的任何操作。

根据赋值兼容规则,可以用基类的指针指向派生类对象,如果使用基类型指针指向动态创建的派生类对象,由该基类指针撤销派生类对象,则必须将析构函数定义为虚函数,实现多态性,自动调用派生类析构函数,否则可能存在内存泄漏问题。总结:在实现运行时的多态,无论其他程序员怎样调用析构函数都必须保证不出错,所以必须把析构函数定义为虚函数。

注意:类中没有虚函数,就不要把析构函数定义为虚

1. 虚析构函数 (Virtual Destructor)

为什么要用虚析构函数?

问题场景:

cpp

Base* ptr = new Derived();
delete ptr;  // 如果析构函数不是虚的,只会调用Base的析构函数!

后果:

  • 派生类的析构函数不被调用

  • 派生类中分配的资源无法释放 → 内存泄漏

  • 其他清理工作无法执行

解决方案:

cpp

class Base {
public:virtual ~Base() = default;  // 虚析构函数
};

工作原理:

  • 通过虚函数表(vtable)实现多态析构

  • delete ptr时,通过vptr找到实际的析构函数地址

  • 先调用派生类析构函数,再自动调用基类析构函数

重要规则:

  1. 任何可能被继承的类都应该有虚析构函数

  2. 如果类有虚函数,它必须有虚析构函数

  3. STL容器存储多态对象时,必须用虚析构函数


2. 纯虚函数 (Pure Virtual Function)

定义和语法:

cpp

virtual 返回类型 函数名(参数) = 0;

特点:

  • 只有声明,没有实现(在基类中不提供函数体)

  • 使类成为抽象类

  • 派生类必须实现所有纯虚函数才能实例化

示例:

cpp

class Drawable {
public:virtual void draw() const = 0;  // 纯虚函数virtual ~Drawable() = default;
};

特殊 case:纯虚析构函数

cpp

class AbstractBase {
public:virtual ~AbstractBase() = 0;  // 纯虚析构函数
};
// 但必须提供实现!
AbstractBase::~AbstractBase() {}

3. 抽象类 (Abstract Class)

定义:

包含至少一个纯虚函数的类

特性:

  • ❌ 不能实例化AbstractClass obj; // 编译错误

  • ✅ 可以定义指针和引用

  • ✅ 可以有构造函数和析构函数

  • ✅ 可以包含数据成员和普通成员函数

  • ✅ 派生类必须实现所有纯虚函数才能实例化

使用场景:

  1. 定义接口规范

  2. 提供部分实现(模板方法模式)

  3. 阻止实例化

示例:

cpp

class Animal {  // 抽象类
public:virtual void speak() const = 0;  // 纯虚函数virtual void eat() = 0;          // 纯虚函数// 普通函数void sleep() { cout << "Sleeping..." << endl; }virtual ~Animal() = default;
};

4. final 关键字 (C++11)

两种用法:

1. 用于类:禁止继承

cpp

class Base final {  // 这个类不能被继承// ...
};
// class Derived : public Base { };  // 错误!编译失败

使用场景:

  • 工具类、工具函数集合

  • 性能敏感,避免虚函数开销

  • 设计上不应该被扩展的类

2. 用于虚函数:禁止重写

cpp

class Base {
public:virtual void func() final {  // 派生类不能重写这个函数// 实现}
};class Derived : public Base {// void func() override { }  // 错误!编译失败
};

使用场景:

  • 基类中的关键算法,不允许修改

  • 模板方法模式中的固定步骤


5. override 关键字 (C++11)

为什么要用 override?

问题: 意外的函数隐藏

cpp

class Base {
public:virtual void func(int x) { }
};class Derived : public Base {
public:virtual void func(double x) { }  // 本想重写,实际是隐藏!
};

解决方案:

cpp

class Derived : public Base {
public:void func(int x) override { }  // 明确表示要重写
};

override 的好处:

  1. 编译器检查:确保函数签名与基类完全匹配

  2. 代码清晰:明确表达设计意图

  3. 防止错误:避免拼写错误、参数类型错误等

  4. 便于维护:让阅读者立即知道这是重写函数

使用规则:

  • 只能用于虚函数重写

  • 函数签名必须与基类完全一致(包括const、引用限定符等)


6. 完整设计示例

良好的类层次设计:

cpp

// 抽象基类:定义接口
class Shape {
public:// 纯虚函数:接口规范virtual double area() const = 0;virtual double perimeter() const = 0;virtual void draw() const = 0;// 普通虚函数:有默认实现virtual void scale(double factor) {// 默认实现}// 虚析构函数:必须!virtual ~Shape() = default;// 普通成员函数void printInfo() const {cout << "Area: " << area() << ", Perimeter: " << perimeter() << endl;}
};// 具体实现类
class Circle final : public Shape {  // final:不允许进一步继承
private:double radius;
public:// 必须实现所有纯虚函数double area() const override {return 3.14159 * radius * radius;}double perimeter() const override {return 2 * 3.14159 * radius;}void draw() const override {cout << "Drawing Circle" << endl;}// 重写普通虚函数void scale(double factor) override {radius *= factor;}
};

7. 综合对比表格

特性虚析构函数纯虚函数抽象类finaloverride
作用安全的多态删除定义接口不能实例化的基类禁止继承/重写明确重写意图
语法virtual ~Class()virtual func() = 0含纯虚函数class A final 或 virtual func() finalvoid func() override
强制性强烈推荐派生类必须实现不能实例化编译时强制编译时检查
使用场景所有基类接口定义接口、部分实现工具类、关键方法所有虚函数重写

8. 最佳实践总结

设计原则:

  1. 基类法则:公有继承的基类必须有虚析构函数

  2. 接口清晰:使用纯虚函数明确接口契约

  3. 意图明确:总是使用 override 标识重写

  4. 限制合理:适当使用 final 防止误用

  5. 资源安全:结合RAII和虚析构函数确保资源释放

代码检查清单:

  • 基类有虚析构函数吗?

  • 重写虚函数用了override吗?

  • 不该被继承的类用final了吗?

  • 接口类用纯虚函数定义清楚了吗?

  • 抽象类确实不需要实例化吗?

http://www.dtcms.com/a/558213.html

相关文章:

  • Kanass V1.3.4 版本发布,支持项目内集成sward文档、集成GitPuk代码及Arbess流水线
  • 正规的网站建设公司扬中营销网站建设
  • VSCode美化之修改新窗口首页/启动页logo
  • 做磨砂卡贴的网站网站建设是虚拟行业吗
  • 网站单页面制作wordpress会员页面
  • 电商网站设计工作内容厦门做外贸网站
  • c 网站开发简单实例wordpress的文章title在哪里
  • 选择排序优化
  • 想把公司的外部网站替换dede网站地图不显示文章列表
  • 最简单做网站亚马逊雨林的危险之处
  • 全源最短路(Johnson)
  • 网站的性能特点如何增加网站关键词
  • deepseek封装结合websocket实现与ai对话
  • 电子商务微网站制作佛山网站建设冯哥
  • 基本魔法语言数组 (二) (C语言)
  • 五金喷漆东莞网站建设公司网站制作设计联系方式
  • python进阶教程13:多线程、GIL、锁和线程隔离
  • 深圳网站建设平台厦门专业网站设计代理
  • 网站备案 法规网站建设一般涉及后台功能
  • STM32 单片机 ESP8266 联网 和 MQTT协议
  • 铜仁住房和城乡建设局网站域名主机基地
  • 主流 AI IDE 之一的 Trae IDE 介绍
  • 南京市建设工程档案馆网站网站上传的图片不显示
  • 河北省建设执业资格中心网站网站有哪些类型和它的成功案例
  • 免注册、免激活,可永久使用!
  • PySide6 Win10记事本从零到一——第六章(上) 编辑菜单界面与部分功能实现
  • Android和JAVA面试题相关资料
  • 自己开发网站需要多少钱asp.net网站开发 pdf
  • (N_087)基于java拼图游戏
  • 免费的企业建站系统app页面设计软件