今晚翻了一下 todo list
,发现这个条目堆积在未完成列表的末尾,于是来学习一下 C++
中的四种类型转化方式,而 C++
的复习也告一段落。我知道我还差得很远,等某天 C++
功底足够深厚,来写一下 C++
的内存模型。
static_cast
static_cast
是一个 c++
运算符,功能是把一个表达式转换为某种类型,依赖编译时得到的类型信息,没有运行时类型检查来保证转换的安全性,用于非多态类型的转换。所以为了安全起见,通常用于转换数值数据类型,如 int
到 double
。
1 | int a = 5, b = 2; |
如果涉及到类层次的类型转换,父到子不安全(向下转换),子到父安全(向上转换,也是隐式转换)。由于不检查转换的安全性,因此向下转换为子类类型时,可能有基类不存在的成员变量和函数,会导致未定义行为,直接报错。
1 |
|
const_cast
const_cast
能给变量添加 const
特性或移除变量的 const
特性,其他的任何转换不能移除 const
修饰。因为可以直接将非 const
变量赋值给 const
变量,因此 const_cast
增加 const
修饰很少用到。此外,如果修饰的引用或指针指向的是 const
修饰的变量,将指针和引用修改为 non-const
会导致未定义行为;相反,如果修饰的变量不是 const
,那么转换是安全的。
至于未定义行为是否能编译通过,取决于使用的编译器,不能一概而论。是 UB 不过这样写的话编译器不会报错, 然后一般来说会产生指向一个局部变量的指针, 所以也不会 Segmentation fault
。
但是如果是字符串常量, 那实现上会存放在 rodata, 运行时加载到了一个只读的页, 如果 const_cast
掉 const
然后写入那么就会 Segmentation fault
。
1 | int a = 5; // NOTE: non-const object |
此外,const_cast
不能修改到其他的数据类型:
1 | int* a = nullptr; |
reinterpret_cast
这个是最危险的转换,如果使用这个转换,相当于告诉了编译器,我知道我在做什么。假如从 A
类型转换为 B
类型,那么就是基于 bit 模式,用 B
类型重新解释 A
类型之前指向的地址,由于是重新解释地址,因此这个转换不会编译为任何 CPU 指令。滥用 reinterpret_cast
运算符可能很容易带来风险,除非所需转换本身是低级别的,比如 int 到 double,否则建议使用其他强制转换运算符之一。
这个转换允许将任何指针转换为任何其他指针类型(如 char*
到 int*
或 One_class*
到 Unrelated_class*
之类的转换,但其本身并不安全),也允许将任何整数类型转换为任何指针类型以及反向转换。这些转换中唯一正确的是 reinterpret_cast
将变量转换为原类型, 会得到相同的值,但是如果中间变量所占内存小的话就不一定了。听着就危险,此外,reinterpret_cast
运算符不能丢掉 const
修饰。
1 | class A { /* ... */ }; |
dynamic_cast
这个和 RTTI
还有些关系,所以多说一些吧。这个转换应用于用于多态类型的转换,如果被转换的类型不具备多态性,那么向上转换可以在编译时期分析继承关系,确定类型,而且向下转换会直接报错:
1 | class Base { }; |
假设此时基类具备了多态性,那么这个转换能够去检验具有继承关系的父子类型的指针、引用的转换是否安全。那么可以将一个指针类型或引用转换为其他多态类型,也可以用来向下转换。由于只适用于指针或引用,那么只能进行运行时类型检查,dynamic_cast
将寻找所需的对象并在可能的情况下返回它,对不明确的指针的转换将失败(返回 nullptr
),但不引发异常。来看个例子:
第二个转换失败,因为子类不具有多态性,
1 | class Base { virtual void dummy() {} }; // polymorphic class |
dynamic_cast
在转换之前,就要确定基类究竟指向的是什么类型的对象。由于基类可能指向任何的自身类型对象、子类类型对象。所以,编译期基类指针是无法确定其指向的对象类型的,只能等到运行时。
typeid
除 dynamic_cast
外,也能通过 typeid
运算符使用运行时堕胎,获得变量的类型。此时,需要重点注意的是,typeid
可以在编译期将获得变量的类型,也可以在运行期获得变量的类型。
1 |
|
而对于无法在编译时期确认的类型,只能等到运行时期。如下所示的程序:
1 | class base{ |
但是重点是要把方法声明为虚方法:
- 如果一个方法不是虚方法,那么将根据引用类型或指针类型选择执行的方法,静态联编
- 如果一个方法是虚方法,将根据指针或引用指向对象的类型选择执行的方法,动态联编