C++ 类型转换
原文:http://www.wuzesheng.com/?p=1931
在C中,类型转换相对比较简单,直接加括号强制转即可,不过这样做的后果,就是很难保证类型安全,所以,在C++中,虽然还允许C方式的类型转换,但是已经成为deprecated的方式了,良好的C++编程习惯都不推荐使用C方式的类型转换了。C++提供了类型相对安全的转换方式,本文的主要内容,就是介绍C++中的各种类型转换方式,以及各自用在什么样的场合下,有什么需要注意的地方,帮助大家更好的掌握类型转换,更好的驾驭自己的程序。
dynamic_cast,static_cast, const_cast和reinterpret_cast在C++中被统称为显式类型转换,在介绍它们之前,有必要介绍一下C++的隐式类型转换。隐式转换,就是不需要什么特殊运算符的转换,它是在变量赋值过程中自动发生的。在C++中,允许的隐式转换,主要包括以下两种方式:
- 1. 基本类型之间的隐式转换
1 2 |
short small_num = 1000; int big_num = small_num; |
上面的例子中,把一个16bit的整数,赋值给一个32bit的整数,在它们赋值的过程,就发生了隐式的类型转换。基本类型之间大都可以进行这样的转换,不过需要注意的是,把“小”的类型,赋值给“大”的类型,通常是OK的,反之,编译器可能会有warning, 也会有可能损失一些精度,比如把double(8bytes)赋给float(4bytes),即有warning, 又有精度损失。如果确实是想这样转换,尽量使用后面会介绍的显式的转换方式。
- 2. 一个参数的构造函数
1 2 3 4 5 6 7 |
class Integer { public: Integer(int num); }; int a = 10; Integer num_a = a; |
看上面的例子,Integer类有一个int类型参数的构造函数,所以可以直接把一个int的value赋值给Integer对象,这在C++中是安全的。不过这样的转换要用得小心,如果用得不当,会发生很诡异的结果。请看下面的例子:
1 2 3 4 5 6 |
class Server { public: Server(const char * name); }; Server svr = "hello world!"; |
上面的程序,在语法上是完全OK的,编译也没有任何问题。不过在逻辑上,会让人觉得十会诡异,把一个字符串赋值一个Server对象,两个风马牛不相及的东西之间发生了转换。在这里,C++提供了一个explicit的关键字,告诉使用者,必须显式的调用构造函数来构造,不能用隐式转换的方式,请看改进后的程序:
1 2 3 4 5 6 7 |
class Server { public: explicit Server(const char * name); }; Server svr1 = "hello world!"; ///< compile-error!! Server svr2("time sever"); ///< OK |
上面的程序中,直接赋值的方式编译器会告诉你错误的,这样就很好的避免了前面的问题。所以,关于一个参数的构造函数,使用着必须时刻保持清醒的头脑,不允许隐式转换的一定要加上explicit!
通过上面的介绍,相信大家对隐式转换已经有了大致的了解,下面介绍一下C++中显式类型转换的几种方式:
- 1. dynamic_cast
dynamic_cast只能用在指针和引用类型的转换中,它是唯一进行运行期(runtime)检查的类型转换符,它的主要目的就是保证转换后的类型是一个完整类型(Complete type)。dynamic_cast在转换指针类型时,如果结果不是一个Complete Type, 它会返回NULL; dynamic_cast在转换引用类型时,如果结果不是一个Complete Type,它会抛出bad_cast的异常。看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class Car { /// ... }; class BMW : public Car { /// ... }; class Benz : public Car { /// ... }; bool Repair(Car * car) { BMW * bmw = dynamic_cast< BMW *>(car); if (bmw != NULL) ///< a BMW car { return RepairBMW(bmw); } Benz * benz = dynamic_cast< Benz *>(car); if (benz != NULL) ///< a Benz car { return RepairBenz(benz); } return false; ///< not BMW and Benz } |
在上面的例子中,BMW和Benz是从Car继承过来的子类型,Repair函数接受一个Car指针类型参数,然后根据具体的类型,调用相应的Repair函数,BMW调用RepairBMW, Benz调用RepairBenz。在C++中,多态类型转为实际的类型,尽量使用dynamic_cast,这样可以利用dynamic_cast的运行期检查,更好地保证程序的正确性。
- 2. static_cast
static_cast是最为常用的cast,它所做的工作,就是进行“逻辑上”正确的类型转换。它可以用在各种类型的转换中,包括指针类型、引用类型和普通类型。static_cast没有运行期的类型检查,因此也没有因运行期类型检查带来的额外开销,但是需要使用者自己保证转换后结果的完整性,否则可能会出现runtime error。下面是一些static_cast的例子:
1 2 3 4 5 |
double d = 3.14 int n = static_cast< int>(d); Car * car = new BMW(); Benz * benz = static_cast< Benz *>(car); |
上面两种转换,逻辑上都是正确的。但是,需要使用者保证,第一个中精度的损失是允许的,第二个中,BMW向Benz之间是否可以转换。
- 3. const_cast
const_cast的用法相对比较简单,它主要用来给指定类型增加、或者移除const属性。另外,const_cast也可以用于各种类型,包括指针类型、引用类型和普通类型。看下面的例子,移除/增加char *类型的const属性。
1 2 3 |
const char * msg1 = "hello world"; char * msg2 = const_cast< char *>(msg1); const char * msg3 = const_cast< const char *>(msg2); |
- 4. reinterpret_cast
reinterpret_cast的行为是implementation-defined,它的实现可能是按bits重新interpret,但是不能总是保证这一点,所以用interpret_cast的程序,移植性不是很好,尽量少用。reinterpret_cast也可以用来转换各种类型,包括指针类型、引用类型和普通类型。看下面的例子,把一个32bit的整数,reinterpret成一个32bit的对象。
1 2 3 4 5 6 |
int a = 10; struct Num { int m_a; }; Num num = reinterpret_cast< Num>(a); |