C++ 构造函数、析构函数、虚函数、虚析构

一、构造函数 & 析构函数 基础

1. 构造函数

语法规则
  • 名字和类名完全一样
  • 无返回值、不用写 void/int
  • 对象创建瞬间自动调用
  • 作用:初始化成员、申请资源、配置初始化
示例
cpp 复制代码
#include <iostream>
using namespace std;

class Person {
public:
    // 构造函数:和类名同名
    Person() {
        cout << "构造函数:对象创建了,做初始化\n";
    }
};

int main() {
    Person p; // 自动调用构造函数
    return 0;
}

2. 析构函数

语法规则
  • 固定标准写法:~类名()
  • ~ 是语法规定,代表「构造的反向操作」
  • 无返回值、无参数、不能重载
  • 对象销毁 / delete自动调用
  • 作用:释放内存、关文件、清理资源
示例
cpp 复制代码
class Person {
public:
    Person() {
        cout << "构造:对象出生\n";
    }
    // 标准析构函数写法
    ~Person() {
        cout << "析构:对象销毁,清理资源\n";
    }
};

int main() {
    Person p; // 构造执行
    return 0;  // 函数结束,p 销毁,自动执行析构
}

3. 为什么 C++ 要有构造/析构?对比 C / Java / Python

  1. C 语言
    没有类、没有对象生命周期,只能手动写 init()free(),没有语法级别的构造析构。
c 复制代码
// C 只能手动初始化、手动释放
typedef struct { int age; } Person;
void PersonInit(Person* p) { p->age = 18; }
void PersonFree(Person* p) { /* 手动清理 */ }
  1. Java

    有构造方法,没有析构 ;靠 GC 垃圾回收 自动清理内存,不用程序员手动释放。

  2. Python

    构造 __init__,析构 __del__ 几乎不用;靠引用计数+GC自动管内存。

  3. C++ 独有原因

    手动 new/delete 管内存,没有自动GC;

    必须用构造初始化、析构自动清理,绑定对象生命周期,防内存泄漏。


二、虚函数 核心概念 + 示例

1. 什么是虚函数

成员函数前面加 virtual虚函数
虚析构本质也是虚函数,共用同一套虚表机制。

2. 虚表机制 通俗理解

  • 有虚函数的类,编译器自动生成一张虚表 vtable(存所有虚函数地址)
  • 每个对象暗藏一个 vptr 虚表指针,指向自己真实类型的虚表
  • 非虚函数:编译静态绑定,只看指针类型
  • 虚函数:运行动态绑定,看对象真实类型

3. 虚函数经典示例(多态+重写)

父类形状,子类圆形、方形:

cpp 复制代码
class Shape {
public:
    // 虚函数
    virtual void draw() {
        cout << "绘制通用形状\n";
    }
};

// 子类:圆形
class Circle : public Shape {
public:
    // 重写虚函数
    void draw() override {
        cout << "绘制圆形\n";
    }
};

// 子类:方形
class Square : public Shape {
public:
    void draw() override {
        cout << "绘制方形\n";
    }
};
测试 1:父类指针指向子类对象
cpp 复制代码
int main() {
    // 父类指针 装 子类对象
    Shape* s1 = new Circle();
    Shape* s2 = new Square();

    s1->draw(); // 绘制圆形
    s2->draw(); // 绘制方形

    delete s1;
    delete s2;
    return 0;
}

👉 虚函数作用:父类指针调用,自动执行子类重写的逻辑

测试 2:去掉 virtual 会怎样?

virtual 删掉,输出变成:

复制代码
绘制通用形状
绘制通用形状

只认父类指针类型,不认对象真身。


4 为什么不用 Circle* 非要用 Shape*

单个子类:直接用子类指针没问题
cpp 复制代码
Circle* c = new Circle();
c->draw();
多个子类:父类指针统一批量管理(核心价值)
cpp 复制代码
int main() {
    // 一个父类数组,装所有子类
    Shape* arr[] = {
        new Circle(),
        new Square()
    };

    // 统一循环调用,不用逐个写逻辑
    for (auto s : arr) {
        s->draw();
    }

    return 0;
}

好处:新增三角形、星星,不用改循环调度代码,符合开闭原则。

5. 虚函数两大内部关联功能

  1. 允许子类重写父类虚函数,实现自己业务
  2. 配合父类指针,实现多态统一调度
    二者靠同一张虚表机制实现,不是两个独立功能。

三、虚析构函数 原理 + 完整示例

1. 不加虚析构的坑(内存泄漏演示)

cpp 复制代码
class Base {
public:
    ~Base() { // 普通析构,不是虚析构
        cout << "父类析构\n";
    }
};

class Son : public Base {
public:
    ~Son() {
        cout << "子类析构\n";
    }
};

int main() {
    Base* p = new Son();
    delete p; // 只执行父类析构,子类析构没执行!
    return 0;
}

输出:

复制代码
父类析构

👉 子类资源完全没清理,内存泄漏

2. 改成虚析构,问题解决

cpp 复制代码
class Base {
public:
    // 虚析构函数
    virtual ~Base() {
        cout << "父类析构\n";
    }
};

class Son : public Base {
public:
    ~Son() {
        cout << "子类析构\n";
    }
};

int main() {
    Base* p = new Son();
    delete p; 
    return 0;
}

输出:

复制代码
子类析构
父类析构

👉 先走子类析构,再走父类析构,清理干净。

3. 底层原理总结

  • 析构加 virtual → 进入虚表
  • delete 父类指针 时,走虚表动态绑定
  • 找到真实子类析构执行,再自动向上执行父类析构

4. 最佳实践铁律 + 规范写法

  1. 只要类会被继承当基类,析构必须写 virtual
  2. 不被继承的工具类,不用虚析构,省虚表开销
  3. 现代 C++ 标准写法:
cpp 复制代码
virtual ~Base() = default;

四、全篇终极总结

  1. 构造函数 :和类名同名,对象出生初始化;不能是虚函数
  2. 析构函数 :固定 ~类名() 写法,对象销毁自动清理;~ 代表构造逆操作。
  3. C++ 独有构造析构:因为手动管内存无GC,C/Java/Python 不需要这套机制。
  4. 虚函数 :依靠虚表+虚表指针 ,实现子类重写 + 父类指针多态调用
  5. 父类指针装子类:为了批量统一管理多子类,不用重复写调度代码。
  6. 虚析构 :本质就是虚函数,解决父类指针delete子类对象时内存泄漏
  7. 口诀
    非虚看指针,虚函数看真身;
    可继承基类,析构必虚。
相关推荐
Dovis(誓平步青云)1 小时前
《QT学习第四篇:常见事件与UDP、TCP、文件系统、(锁、信号量、条件变量》
c语言·开发语言·汇编·qt
code monkey.1 小时前
【Linux之旅】Linux 应用层自定义协议与序列化:从粘包问题到网络计算器
linux·网络·c++
草莓熊Lotso1 小时前
【Linux网络】深入理解 HTTP 协议(二):从协议格式到手写工业级 HTTP 服务器
linux·运维·服务器·网络·c++·http
isyangli_blog9 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008119 小时前
FastAPI APIRouter
开发语言·python
Benszen9 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆9 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木9 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
MC皮蛋侠客10 小时前
C++17 多线程系列(五):C++17 并行算法——从串行到并行的零成本迁移
c++·多线程
杨充10 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法