c++ 虚函数使用笔记
1. 虚函数基础
定义:虚函数是实现 运行时多态(动态绑定) 的核心机制,允许通过基类指针或引用调用派生类的函数。
语法:使用 virtual 关键字声明,非静态成员函数和析构函数可以声明为虚函数,构造函数不能是虚函数。
class Base {
public:
virtual void func() {
std::cout << "Base::func()" << std::endl;
}
virtual ~Base() {} // 虚析构函数(关键!)
};
2. 动态绑定(运行时多态)
原理:通过基类指针或引用调用虚函数时,实际执行的是 对象实际类型 的虚函数。
示例:
class Derived : public Base {
public:
void func() override {
std::cout << "Derived::func()" << std::endl;
}
};
int main() {
Base* ptr = new Derived();
ptr->func(); // 输出 "Derived::func()"
delete ptr;
}
关键点:
- 动态绑定仅通过指针或引用生效(直接对象调用不触发多态)。
- 虚函数调用依赖 虚函数表(vtable) ,每个多态类有一个 vtable,存储虚函数地址。
3. 虚函数表(vtable)
结构:
- 每个多态类(含虚函数)隐式生成一个 vtable。
- 对象内存布局中有一个指向 vtable 的指针(vptr)。
- vtable 中按声明顺序存储虚函数地址。
内存模型示例:
+---------------------+
| Derived 对象 |
| +-----------------+ |
| | vptr | --> [Derived::func()地址, ...]
| | 其他成员变量 | |
| +-----------------+ |
+---------------------+
4. 纯虚函数与抽象基类
纯虚函数:用 = 0 标记,强制派生类实现。含纯虚函数的类为 抽象基类,不能实例化。
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "绘制圆形" << std::endl;
}
};
5. 虚函数重写规则
- 函数签名一致:函数名、参数列表、const 修饰符必须相同。
- 返回类型协变:允许返回基类虚函数返回类型的派生类型(仅限指针或引用)。
- 使用
override关键字(C++11+):显式标记重写,避免签名错误。
class Derived : public Base {
public:
void func() override {
// 显式标记重写,若签名不匹配则编译报错
}
};
6. final 关键字
- 标记类:禁止继承。
- 标记虚函数:禁止派生类重写。
class Base final {}; // 类不可继承
class Base {
public:
virtual void func() final; // 函数不可重写
};
7. 关键注意事项
7.1 构造函数中调用虚函数
- 问题:在基类构造函数中调用虚函数,实际调用的是基类的版本(派生类尚未构造完成)。
class Base {
public:
Base() { func(); } // 调用 Base::func()
virtual void func() { std::cout << "Base"; }
};
class Derived : public Base {
public:
void func() override { std::cout << "Derived"; }
};
int main() {
Derived d; // 输出 "Base"
}
7.2 虚析构函数
- 必要性:若通过基类指针删除派生类对象,基类析构函数必须为虚函数,否则导致资源泄漏。
Base* ptr = new Derived();
delete ptr; // 若 ~Base() 非虚,仅调用 Base 的析构函数!
7.3 虚函数的缺省实参
- 静态绑定:缺省实参的值由调用者的静态类型决定,而非动态类型。
class Base {
public:
virtual void func(int x = 1) {
std::cout << x;
}
};
class Derived : public Base {
public:
void func(int x = 2) override {
std::cout << x;
}
};
int main() {
Base* ptr = new Derived();
ptr->func(); // 输出 "1"(缺省实参由 Base 决定)
}
7.4 修改派生类虚函数的访问权限
- 允许但危险:派生类可以修改虚函数的访问权限(如从
public改为private),但会破坏设计逻辑。
class Base {
public:
virtual void func() {}
};
class Derived : public Base {
private:
void func() override {} // 合法但违反直觉
};
8. dynamic_cast 与虚函数
- 关联性:
dynamic_cast依赖虚函数表(vtable)实现运行时类型检查,因此只能用于多态类型。
Base* ptr = new Derived();
Derived* d = dynamic_cast<Derived*>(ptr); // 成功
if (d) { /* 安全使用 d */ }
9. 性能与设计建议
- 虚函数调用成本:比非虚函数多一次间接寻址(访问 vtable)。
- 何时使用虚函数:
- 需要运行时多态时。
- 基类定义通用接口,派生类实现具体逻辑。
- 何时避免虚函数:
- 性能敏感代码(如高频循环)。
- 简单对象无需多态时。
总结
虚函数是 C++ 多态的核心机制,通过动态绑定实现灵活的运行时行为。关键要点:
- 基类析构函数必须为虚函数。
- 使用
override和final增强代码安全性。 - 避免在构造函数中调用虚函数。
- 理解虚函数表的内存模型及其性能影响。
示例代码整合:
#include <iostream>
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};
class Cat : public Animal {
public:
void speak() override {
std::cout << "Meow!" << std::endl;
}
};
int main() {
Animal* animals[] = {new Dog(), new Cat()};
for (auto* animal : animals) {
animal->speak(); // 动态绑定
delete animal;
}
return 0;
}
输出:
Woof!
Meow!
2451

被折叠的 条评论
为什么被折叠?



