C++:虚函数和类型转换
虚函数
类似于静态函数,类的成员函数也可以被定义为虚函数。1
2
3
4class A{
public:
virtual void function(){}
};
定义为虚函数之后,编译器会被显式告知这是一个多态函数,将会在派生类中被重写。
多态的意思是,这个函数在基类和派生类当中是不一样的。通过基类对象和派生类对象调用出的函数不一样。
那么,这种写法和直接在派生类中重写一个同名函数有什么区别呢?
区别在于,假如是重写一个同名函数,调用如下:
1 | class A{ |
假如使用虚函数:
编译器会检查虚函数有没有被重写,如果被重写了,即便通过基类指针调用,仍然会使用重写后的函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class A{
public:
virtual void function(){
cout<<"A\n";
}
};
class B:public A{
public:
void function(){
cout<<"B\n";
}
};
int main(){
A obj1;
B obj2;
obj2.function();//输出 B
A* p = &obj2;
p->function();//输出B
return 0;
}
输出:
B
B
为什么通过指针实现了隐式转换,还是调用了派生类中的function()呢?
实际上,对于包含虚函数的类,编译器会建立一个虚函数表。
在初始创建对象的时候,就会把这个对象的虚函数对应绑定虚函数表的位置,后续调用本质上是去查表调用。
因此即使隐式转换了,虚函数表的对应关系没有变,调用就不会改变。
多重继承的虚函数行为
在继承链里面,从第一个virtual开始都符合虚函数行为,无论后续派生类有没有加virtual关键字
1 |
|
类型转换
派生类隐式转换成基类
由于这是一个从“内容多”到“内容少”的过程,不需要编译器自主定义一些值,所以不会产生未定义行为,可以直接转换。
在以下代码示例中,A 为基类,B 为派生类。
1 | class A {}; |
基类显式转换成派生类
那么,假如我们想实现从“内容少”到“内容多”,行不行呢?
很遗憾,假如是严格的转换,是不可以的。
我们不可以把一个指向基类对象的指针转换成指向派生类对象。
但是我们可以把某个指针“拨乱反正”。
上面的代码有一些基类指针,指向的是派生类对象,我们知道这种隐式转换是可以的。
但是,既然指向的是派生类对象,那么完全可以把指向派生类的基类指针转换成派生类指针,这就是显式转换的过程。
1 | class A {}; |
我们可以通过几种转换符实现这个转换,它们分别是 dynamic_cast
、static_cast
、const_cast
、reinterpret_cast
dynamic_cast
- 用于多态类型之间的安全向下转换(基类 → 派生类)。
- 会在运行时检查类型是否合法。
- 如果转换失败,返回
nullptr
(指针)或抛出bad_cast
(引用)。
1 | class A { |
static_cast
- 编译时转换,无运行时检查。
- 用于已知类型关系的转换,例如基类指针转换为派生类指针(但必须确保实际对象类型正确,否则行为未定义)。
1 | class A {}; |
const_cast
- 用于去除
const
或volatile
修饰符。 - 常用于传递给需要非常量参数的函数。
- 注意:如果原对象本身是
const
定义的,那么修改它的值会导致未定义行为。
1 | const int a = 100; |
reinterpret_cast
- 最危险的转换,通常用于底层操作。
- 可将任意指针类型互转、指针转整数等。
- 没有类型安全保证,使用需极度谨慎。
1 | void func() { |
总结
转换方式 | 安全性 | 检查方式 | 使用场景 |
---|---|---|---|
dynamic_cast |
安全 | 运行时检查 | 多态类型安全转换 |
static_cast |
有风险 | 编译时检查 | 已知类型关系(如数值、类层次) |
const_cast |
安全(小心) | 编译时检查 | 去除 const/volatile 修饰符 |
reinterpret_cast |
极不安全 | 无检查 | 底层指针操作、类型重解释 |