前言
最近有时间就会看一看C++ primer,快速过一遍C++,因为感觉自己都是零零碎碎地学C++,没有系统地看过。今天刚好看到第四章类型转换,发现之前一直没有认真了解过C++的类型转换,只会用万能的括号强制转换法。所以今天这篇博客就当一个学习记录。
新的类型转换有四种,const_cast,static_cast,reinterpret_cast,dynamic_cast。今天先讲三种,dynamic_cast还没有看到,先留个坑。
旧的类型转换
旧的类型转换就是直接在变量的前面加括号和需要转换的目标类型,就像C语言中的(NewType) Expression
强转,例如:
double d_a = 1.2;
int i_a = (int) d_a;
cout << d_a << endl;
// 1.2
cout << i_a << endl;
// 1
static_cast
它的格式是static_cast<type>(expression)
。这是最常见的一种,没有什么特殊的处理,用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换。static_cast 不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。例如:
// 案例1
double d_a = 1.2;
int i_a = static_cast<int>(d_a); // 将double转换成int
cout << d_a << endl; // 1.2
cout << i_a << endl; // 1
// 案例2,错误
double* d_ptr_a = &d_a;
int* i_ptr_a = static_cast<int*>(d_ptr_a); // 错误,不能将double* 转换成 int*
// 案例3,成功,因为void*是万能指针,可以被转换成指向任意类型的指针,也可以被指向任意类型的指针转换
void* d_ptr_a = &d_a;
int* i_ptr_a = static_cast<int*>(d_ptr_a); // 正确,将void* 转换成 int*
cout << d_ptr_a << endl; // 0x61fe00
cout << i_ptr_a << endl; // 0x61fe00
以上案例说明static_cast可以转换普通类型值,但是不能转换不同类型的指针(指的是double* 转换成 int*这种指向不同类型的指针的转换)。转换不同类型的指针可以用 reinterpret_cast实现,我们接着往下看。
reinterpret_cast
它的格式是reinterpret_cast<type>(expression)
,它可以对运算对象的位模式提供较低层次上的重新解释,是特意用于底层的强制转型。它在进行类型转换时不会对两者的类型进行检查和判断,只是简单地拷贝二进制比特。
reinterpret_cast一般用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。
reinterpret_cast十分灵活,所以使用时必须十分谨慎。
例子如下:
double d_a = 1.0;
int i_a = 0x10;
int * i_a_address = (int*) i_a; // 正确,实际上i_a_address = i_a = 0x10
i_a_address = reinterpret_cast<int* >(i_a); // 正确,将int的值转换成int*,这就是reinterpret_cast的过人之处。
// i_a_address = i_a = 0x10
i_a_address = reinterpret_cast<int* >(&d_a); // 正确,将double* 转换成 int*
// i_a_address = &d_a
d_p = static_cast<int* >(i_a); // 错误,static_cast不能将int的值转换成int*
以上案例说明,reinterpret_cast确实十分强大,得益于底层简单地对二进制拷贝,对转换的类型不做检查。也就是底层的二进制是啥就复制啥,不看转换的类型是什么。它可以执行不同类型指针之间转换,干了static_cast不能干的事。但是它也十分危险,例如如果用指向int的指针去访问存放double的地址的值,会出现异常。
const_cast
它的格式是const_cast<type>(expression)
。这个转换的功能比较单一,但是它的功能也很强大:可以用于去除const属性的转换。它是四个转换中唯一一个可以去除 const 属性的。
一般来说要使用指针指向一个const变量,则这个指针必须也是const,也就是不能通过这个指针来修改指向的const变量的值。而使用const_cast可以将 const 引用转换为同类型的非const引用,将const指针转换为同类型的非const指针,接着就可以使用非const的指针或者引用来修改指向的const变量的值。例如:
const string s = "hhh";
string* p_s = const_cast<string*>(&s); // 使用非const指针指向const变量,正常情况下是不允许的
string& r_s = const_cast<string&>(s); // 使用非const引用指向const变量,正常情况下是不允许的
// 可以看出p_s确实指向s的地址
cout << &s << endl; // 0x61fd00
cout << p_s << endl; // 0x61fd00
// 通过指向s的指针来修改字符串s
p_s->append("eee");
// 从结果可以看出,确实可以使用非const指针或引用来修改const变量的值
cout << s << endl; // hhheee
cout << *p_s << endl; // hhheee
cout << r_s << endl; // hhheee
当然,在测试过程中我还遇到了一个神奇的事情,具体看代码:
const int i_a = 1;
int* i_ptr_a = const_cast<int* >(&i_a); // 使用非const指针指向const变量
*i_ptr_a = 2; // 修改const变量的值
// 输出两者的地址,可以看出i_ptr_a确实指向i_a
cout << &i_a << endl; // 0x61fcfc
cout << i_ptr_a << endl; // 0x61fcfc
// 然而,输出两者保存的值,发现不一致
cout << i_a << endl; // 1
cout << *i_ptr_a << endl; // 2
从代码中可以看出,非const指针i_ptr_a确实指向const变量i_a,但是输出两者的结果时,发现不一致。于是我找了网上的博客,发现这应该是编译器做了优化。编译器默认const变量不改变,于是将const变量保存在符号表中,然后在读取变量的时候直接读取符号表中的值,提高读取变量的速度。如果使用volatile强迫编译器不做优化,从内存中读取i_a,而不是从符号表中读取,则可以看到const变量的值确实是被改变了。代码如下:
const volatile int i_a = 1; // 这里加了volatile,强迫编译器不做优化,从内存中取值
int* i_ptr_a = const_cast<int* >(&i_a);
*i_ptr_a = 2;
cout << i_a << endl; // 2
cout << *i_ptr_a << endl; // 2
结果证明i_a的值确实是被改变了。
当然这里还有一个疑问,为什么前一个例子中的string变量,不需要加volatile也可以读取到修改后的值,而int变量不行。我猜应该是因为int是基本类型,所占用的存储空间是一定的(4Byte),所以可以存入到寄存器中。而string不是基本类型,大小并不固定,因此无法将其存储到寄存器中,所以每次读取string变量只能去内存中读取,也就一定能够读到修改后的值。
dynamic_cast
它的格式是dynamic_cast<type>(expression)
。这个因为我还没有看到,所以我直接借用一个网站上的解释(来源于C语言中文网):
用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但是这种转换不检查安全性,即不检查转换后的指针是否确实指向一个派生类对象。dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针。
dynamic_cast 是通过“运行时类型检查”来保证安全性的。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性,只好用 reinterpret_cast 来完成。
例子如下:
#include <iostream>
#include <string>
using namespace std;
class Base
{ //有虚函数,因此是多态基类
public:
virtual ~Base() {}
};
class Derived : public Base { };
int main()
{
Base b;
Derived d;
Derived* pd;
pd = reinterpret_cast <Derived*> (&b);
if (pd == NULL)
//此处pd不会为 NULL。reinterpret_cast不检查安全性,总是进行转换
cout << "unsafe reinterpret_cast" << endl; //不会执行
pd = dynamic_cast <Derived*> (&b);
if (pd == NULL) //结果会是NULL,因为 &b 不指向派生类对象,此转换不安全
cout << "unsafe dynamic_cast1" << endl; //会执行
pd = dynamic_cast <Derived*> (&d); //安全的转换
if (pd == NULL) //此处 pd 不会为 NULL
cout << "unsafe dynamic_cast2" << endl; //不会执行
return 0;
}
输出结果为:
unsafe dynamic_cast1
实际上,可以通过判断pd是否为NULL就可以知道类型转换是否是安全的。
总结
这四种类型转换,分别对应不同的功能:
static_cast<type>(expression)
主要用于普通变量之间的类型转换。dynamic_cast<type>(expression)
主要用于将多态基类的指针或引用强制转换为派生类的指针或引用。const_cast<type>(expression)
主要用使用非const指针或者非const引用指向const变量,主要是在函数参数传递上会用到。reinterpret_cast<type>(expression)
主要用于指向不同类型的指针之间的转换。这个转换的功能最强,适用性最广,但是也最危险。
以上就是关于C++类型转换的简单记录。欢迎大家阅读我的博客,如文中有问题的话可以在评论中提出。谢谢!