文章目录
01、C风格类型转换
在学习C语言的时候,很多场合,我们会用到强制类型转换,那个时候,我们是很少考虑这样做是否有问题的,因为这就是C语言不好的地方,什么都可以交给我们处理,因此,错误千奇百怪。
C风格的强制转换(Type Cast)容易理解,不管什么类型的转换都可以使用使用下面的方式.
int nCarNum = 10;
double fCarWeight = 22.3;
int tmp = (int)fCarWeight; //潜在问题:精度丢失
int a;
char b = 'b';
a = b; //隐式转换
//亦或是
const char* buf = "ABCD";
char* tp = buf; //报错:不存在由const char* 到 char* 的转换,所以就有了下面的表达
char* tp = (char*)buf; //OK,当然,这个没有大问题,是可以这样的,但是如果你要去修改这个buf的值就会出现问题了
C风格类似此等式: template A = (template)B;
(显示转换)
02、C++ 四种强制转换类型函数
2.1、 static_cast
一般来说,我们称此方法为静态转换,形如: static_cast<type-id> (expression)
,也是这四种转换中最常见的,但是他有一个很bug的问题,不会在运行时进行运行时类型检查来保证转换的安全性该运算符把 expression 转换为 type-id 类型
主要用法如下:
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
-
用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
-
把空指针转换成目标类型的空指针
-
把任何类型的表达式转换成void类型
举例如下:
//第一种:类层次中的转换 Class B: public A { //略 } A* p1 = new A(); B* p2 = new B(); p1 = static_cast<A*>(p2) //类型安全,反过来就不一定了,使用时,一定要考虑到 //第二种:基础数据类型转换 typedef enum DATATYPE { DATATYPE_ONE = 0, DATATYPE_TWO = 1, }TYPE; int a; TYPE type; a = static_cast<int>(type); //OK //第三种:空指针转目标类型的空指针 int* p1 = NULL; DataPos* b = NULL; //自定义类型 //将int* 转 DataPos* b = static_cast<DataPos*>(p1); //OK //第四种:任意类型表达式转void类型 DataPos* dp = new DataPos(); void* buf = static_cast<void*>(dp); //OK
可能有很多朋友会问,在C++中可以使用C的方式嘛?
如果在 Visual Studio 中编写,且安装了 Resharper C++ 的话,如果按照 C 风格写的话,会提示你,并帮助你自动修改为 C++ 风格,如下提示:
C-style cast used instead of a C++ cast
需要注意的是,static_cast 不能转换掉 expression 的 const、volitale 或者 __unaligned 属性,如下图所示:
2.2、 const_cast
上边的 static_cast 不能将 const int* 转成 int*,const_cast 就可以,用法为 const_cast<type-i> (expression)
。如下面代码 :
const int a = 10;
const int * p = &a;
*p = 20; // Compile error: Cannot assign readonly type 'int const'
int res1 = const_cast<int>(a); // Compile error: Cannot cast from 'int' to 'int' via const_cast
// only conversions to reference or pointer types are allowed
int* res2 = const_cast<int*>(p); // OK
也就是说,const_cast<>里边的内容必须是引用或者指针,就连把 int 转成 int 都不行。
对于 const_cast 的使用,其实还有很多不明白的(C++未定义的行为),如下:
const int a = 11;
const int *p1 = &a;
int* p2 = const_cast<int*>(p1);
*p2 = 20; //OK,修改成功
cout << "a: " << a << " &a :" << &a << endl; // a: 11 &a: ********
cout << "*p1: " << *p1 << " p1 :" << p1 << endl; //*p1:20 p1: *********
cout << "*p2: " << *p2 << " p2 :" << p2 << endl; //*p2: 11 p2: *********
//其中,***********代表地址,地址是相同的,重点看值
这里涉及一个编译器的优化步骤,就是常量编译器会自动优化掉。
在 VS2017 中,debug 模式下,执行完 *p = 20; 这一步后,a 的值就变成了 20,但是输出 a 和 *p 还都是 11。我理解的就是 const int 类型的变量,在编译器就优化了,里边所有单独的 a 都已经变成了 11,所以怎么修改都不影响了,这种情况包括:1、直接用 11 这种常量来初始化 a,2、用同样为 const int 类型的 c 来初始化 a。如果是用 int c = 11; 这样的 c 来初始化 a,那 a 就是可变的。
2.3、 reinterpret_cast
reinterpret_cast 主要有三种强制转换用途:
- 改变指针或引用的类型
- 将指针或引用转换为一个足够长度的整形
- 将整型转换为指针或引用类型
使用方法: reinterpret_cast <type-id> (expression)
type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)。因此, 你需要谨慎使用 reinterpret_cast。
示例如下(示例来自:MSDN 的一个哈希函数辅助):
//expre_reinterpret_cast_Operator.cpp
//compile with: /EHsc
#include <iostream>
//Returns a hash code based on an address
unsigned short Hash(void* p){
unsigned int val = reinterpret_cast<unsigned int>(p);
return (unsigned short)(val ^ (val >> 16));
}
using namespace std;
int main()
{
int a[20];
for(int i = 0; i < 20; i++)
cout << Hash(a+i) << endl;
}
上面示例中,通过reinterpret_cast
将void* 转化为 unsigned int 类型数据。
2.4、dynamic_cast
用法为:dynamic_cast<type-id> (expression)
几个特点如下:
- 其他三种都是编译时完成的,dynamic_cast 是运行时处理的,运行时要进行类型检查
- 不能用于内置的基本数据类型的强制转换
- dynamic_cast 要求 <> 内所描述的目标类型必须为指针或引用,dynamic_cast 转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回 nullptr。
- 在类的转换时,在类层次间进行上行转换(子类指针指向父类指针)时,dynamic_cast 和 static_cast 的效果是一样的。在进行下行转换(父类指针转化为子类指针)时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。
- 使用 dynamic_cast 进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表(C++中的虚函数基本原理这篇文章写得不错,https://blog.csdn.net/xiejingfa/article/details/50454819 )
class base {
public:
void print1() { cout << "in class base" << endl; }
};
class derived : public base {
public:
void print2() { cout << "in class derived" << endl; }
};
int main() {
derived *p, *q;
// p = new base; // Compilr Error: 无法从 "base * " 转换为 "derived * "
// Compile Error: Cannot cast from 'base*' to 'derived*' via dynamic_cast: expression type is not polymorphic(多态的)
// p = dynamic_cast<derived *>(new base);
q = static_cast<derived*>(new base); // ok, but not recommended
q->print1(); // in class base
q->print2(); // in class derived
}
从上边的代码可以看出用一个派生类的指针是不能直接指向一个基类的对象
的,会出现编译错误。用 dynamic_cast 的话也会编译错误,提示我们基类不是多态的,也就是基类中没有虚函数
。可以看到 static_cast 是可以编译通过的,且输出结果看起来都是对的,但是 VS 还是会提示说 Do not use static_cast to downcast from a base to a derived class
static_cast 强制类型转换时并不具有保证类型安全的功能,而 C++ 提供的 dynamic_cast 却能解决这一问题,dynamic_cast 可以在程序运行时检测类型转换是否类型安全。当然 dynamic_cast 使用起来也是有条件的,它要求所转换的 expression 必须包含多态类类型(即至少包含一个虚函数的类)
class A {
public:
virtual void print(){
cout << "in class A" << endl;
};
};
class B :public A {
public:
void print(){
cout << "in class B" << endl;
};
};
class C {
void pp(){
return;
}
};
int main() {
A *a1 = new B; // a1是A类型的指针指向一个B类型的对象
A *a2 = new A; // a2是A类型的指针指向一个A类型的对象
B *b1, *b2, *b3, *b4;
C *c1, c2;
b1 = dynamic_cast<B*>(a1); // not null,向下转换成功,a1 之前指向的就是 B 类型的对象,所以可以转换成 B 类型的指针。
if (b1 == nullptr) cout << "b1 is null" << endl;
else cout << "b1 is not null" << endl;
b2 = dynamic_cast<B*>(a2); // null,向下转换失败
if (b2 == nullptr) cout << "b2 is null" << endl;
else cout << "b2 is not null" << endl;
// 用 static_cast,Resharper C++ 会提示修改为 dynamic_cast
b3 = static_cast<B*>(a1); // not null
if (b3 == nullptr) cout << "b3 is null" << endl;
else cout << "b3 is not null" << endl;
b4 = static_cast<B*>(a2); // not null
if (b4 == nullptr) cout << "b4 is null" << endl;
else cout << "b4 is not null" << endl;
a1->print(); // in class B
a2->print(); // in class A
b1->print(); // in class B
// b2->print(); // null 引发异常
b3->print(); // in class B
b4->print(); // in class A
c1 = dynamic_cast<C*>(a1); // 结果为null,向下转换失败
if (c1 == nullptr) cout << "c1 is null" << endl;
else cout << "c1 is not null" << endl;
// c2 = static_cast<C*>(a1); // 类型转换无效, Cannot cast from 'A*' to 'C*' via static_cast
// delete 省略
}
03、总结
关于类型转换,C++提供了四种方式,这四种方式根据不同的场景,使用的函数也不尽相同,小结如下:
1、static_cast:类型转换不检查安全性,但是基本上都允许你转换,但常量对象或常量成员是不允许的。
2、const_cast:弥补了上述不能转换常量对象和常量成员,但是const一般是不建议转换去修改的,总结来说,const_cast 通常是无奈之举,只是 C++ 提供了一种修改 const 变量的方式,但这种方式并没有什么实质性的用处,还是不用的好。const 的变量不要让它变。
3、reinterpret_cast:type-id 必须是一个指针、引用、算术类型、函数针或者成员指针,且转换途中,映射很容易出错,很危险。
4、dynamic_cast:动态类型转换,编译期间会检查安全性,但是有个前提条件是必须有虚函数的类方可转换,取决于虚函数表的实现。