本文已参与「新人创作礼」活动,一起开启掘金创作之路。
引入
类型转换这个概念在许多编程语言中都有涉及,比如最经典的编程语言:C语言,它对类型转换的处理可视性比较差,所有的转换形式都是以同一种相同形式书写:在变量前声明转换类型,这样的方式难以跟踪错误的转换。
举例:
int main(){
int i = 1;
double d = i; // 隐式类型转换
printf("%d, %.2f\n", i, d);
int* p = &i;
int address = (int)p; // 显式的强制类型转换
printf("%x, %d\n", p, address);
}
输出结果:
1, 1.00
28fb78, 268516
代码虽然会有警告格式字符串“%x”需要类型“unsigned int”的参数,但可变参数 1 拥有了类型“int *”,但是编译没有问题,还是可以正常运行。
下面引入C++中的强制类型转换方式:
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_castreinterpret_castconst_castdynamic_cast
static_cast
static_cast用于非多态类型的转换(静态转换)
编译器隐式执行的任何类型转换都可static_cast,但它只能用于两个相关类型的转换,不相关类型不能转换。
(何为不相关?例如int与int*,编译器对于二者的理解没有强相关性,一个为整型数据,一个为地址,所以不可以使用static_cast进行强制转换)。
用法:
int main(){
double d = 88.48;
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}
输出结果:
88
很明显static_cast将同为实数类型的整型与浮点数进行了强制转换。
reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。
reinterpret_cast将数据的二进制形式重新解释,但是不改变其值:
int main() {
int i;
const char* ptr = "1";
i = reinterpret_cast<char>(ptr);
std::cout << i << std::endl;
system("pause");
return 0;
}
输出结果:
64
警告:“reinterpret_cast”: 从“const char *”到“char”截断。
const_cast
const_cast的用途就是删除变量的const属性,方便赋值,例如把const类型的指针变为非const类型的指针。
int main() {
const int a = 21;
int* modifier = const_cast<int*>(&a);
*modifier = 7;
cout << *modifier << endl;
return 0;
}
输出结果:
7
这里要提一个没有使用过的关键字:
volatile:保持内存可见性,防止编译器过度优化。
volatile const int a = 21;
- 使用了
volatile关键字之后发生了什么? - 因为
const关键字使得编译器默认a变量是const类型,所以编译读取一次a的值之后就默认这个值不会改变了,对a的读取操作也会直接从寄存器上取出使用这个值,等效替代读取a。如果是修改就没有影响。
这个例子可能没有很好的体现问题,但是我们确实进行了修改,所以为了更加科学,就要让编译器时刻保持与内存的数据同步(可见性),不要出现类似于数据库相关的“幻读”或者“脏读”现象(扯远了。。),所以底层执行输出时进行了一次同步而不是使用寄存器值,读取了a中的7,进行输出。
dynamic_cast
- 该操作符用于运行时检查该转换是否类型安全,但只在多态类型时合法,即被转换的类至少具有一个虚拟方法。
dynamic_cast用于将一个父类对象的指针转换为子类对象的指针或引用(也称动态转换)
- 向上转型:如果子类对象指针转换为父类指针或引用,此时不需要转换,因为赋值兼容规则)。
- 向下转型:如果父类对象指针转换为子类指针或引用,用
dynamic_cast转型是安全的。
注意:
dynamic_cast只能用于含有虚函数的类。dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。
class A{
public:
virtual void f() {}
};
class B : public A
{};
void fun(A* pa){
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
B* pb1 = static_cast<B*>(pa);
B* pb2 = dynamic_cast<B*>(pa);
cout << "pb1:" << pb1 << endl;
cout << "pb2:" << pb2 << endl;
}
int main(){
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
输出结果:
pb1:0019F8F0
pb2:00000000 //未转换成功
pb1:0019F8E4
pb2:0019F8E4
其实 dynamic_cast是一个泛型:
模拟实现
template<class PDirve, class Base>
PDirve my_dynamic_cast(Base* p){
if (p->is_base()){
return nullptr;
}
else{
return (PDirve)p;
}
}
- 小结: 强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序管理者应该仔细考虑是否还有其他不同的方法达到同一目的,如果非要使用强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会。
既然容易出问题,那么还是建议防患于未然:避免使用强制类型转换。
explicit关键字
explicit关键字阻止经过转换构造函数进行的隐式转换的发生。
class A{
public:
explicit A(int a){
cout << "A(int a)" << endl;
}
A(const A& a){
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
int main(){
A a1(1); // 隐式转换-> A tmp(1); A a2(tmp);
A a2 = 1;
}
程序会报错:没有int到A的适当的构造函数,因为我们通过explicit禁止了它的单参数构造。
强转的必要性
虽然C风格的转换格式很简单,但是有不少缺点的:
- 隐式类型转化有些情况下可能会出问题。
- 显式类型转换将所有情况混合在一起,代码不够清晰。
所以C++的强制类型转换还是有用武之地的。
RTTI:运行时类型识别
RTTI:(Run-time Type identification)即:运行时类型识别。
C++通过以下方式来支持RTTI:
typeid运算符。dynamic_cast运算符。