本篇围绕 C++ 运行时多态机制(虚函数、虚表、类型识别等)展开。虚函数是类设计的灵魂,但也隐藏许多坑:对象切割、析构不虚、类型转换失败……本篇精选 10 个实际常见的问题,解释 + 示例 + 延伸练习,助你培基托底,深入内核。
矩点 | 记忆要点 |
---|---|
构造函数类型 | 默认、有参、拷贝、移动 |
explicit | 禁止隐式类型转换 |
Rule of Five | 自编辑资源管理类必须遵守 |
静态成员变量 | 类共用,非对象的成员 |
对象大小 | 受成员顺序、对齐和虚表影响 |
typeid
和 dynamic_cast
概念:
虚函数用 virtual
关键字声明,支持运行时多态
代码示例:
class Animal {
public:
virtual void speak() { std::cout << "Animal\n"; }
};
class Dog : public Animal {
public:
void speak() override { std::cout << "Dog\n"; }
};
区别:
类型 | 绑定时机 | 是否支持多态 |
---|---|---|
普通函数 | 编译期 | 不支持 |
虚函数 | 运行期 | 支持 |
原理:
C++ 通过“虚函数表 vtable ” 和 “虚表指针 vptr ” 实现多态
代码示意:
调用虚函数 = 通过 vptr 查表调用
有效多态的 3 大条件:
错误示例:
Dog d;
d.speak(); // 非多态,编译期绑定
正确示例:
Animal* a = new Dog;
a->speak(); // 多态,运行期确定调用
代码示例:
void print(Animal a) {
a.speak(); // 只有 Animal 部分,切割了
}
Cat c;
print(c); // 调用 Animal::speak()
改进:
给入参改为引用/指针,防止切割
问题: 如果某类有 virtual 函数,应该把析构函数也声明为 virtual
否则后果: 通过基类指针 delete 子类对象时,只调用基类析构,子类资源泄露
解決:
virtual ~Animal(); // 完全析构
RTTI = Run-Time Type Information
在运行期获取对象实际类型,仅适用于含虚函数的类
两个工具操作符:
typeid(expr)
:获取类型对象dynamic_cast
:安全地转换指针Base* p = new Derived;
if (typeid(*p) == typeid(Derived)) {
std::cout << "It's Derived\n";
}
前提: 类中有虚函数,否则 typeid(*p) 只看指针类型
Base* b = new Derived;
if (Derived* d = dynamic_cast<Derived*>(b)) {
d->draw(); // 转换成功
} else {
std::cout << "Conversion failed\n";
}
特性:
代码比较:
class A { int x; };
class B { virtual void f(); int x; };
std::cout << sizeof(A) << " " << sizeof(B);
结果: B 比 A 大,因为多了 vptr
错误示例:
Base b = Derived(); // 切割只保留基类
b.virtualFunc(); // 调用的是 Base 版本
正确使用:
使用基类指针或引用,不用值传递
矩点 | 记忆讲解 |
---|---|
虚函数 | 运行时绑 |