C++中的强制类型转换
文章目录
C/C++是强类型语言,不同类型之间的相互转换是比较麻烦的.但是在编程实践中,不可避免的要用到类型转换.有2种类型转换:隐式类型转换和强制类型转换.
隐式类型转换
-
提升精度,此种是编译器自动完成的,安全的.所以编译的时候不会有任何错误或者警告信息提示.
short a=2000; int b; b=a;
short是两字节,int是四字节,由short型转成int型是宽化转换(bit位数增多),编译器没有warning.
宽化转换(如char到int,int到long long,int到float,float到double,int到double等)构成隐式转换,编译器允许直接转换。
-
降低精度,也是有编译器自动完成,会造成精度丢失,所以编译时得到一个警告信息提示.
double a=2000; short b; b=a;
此时,是从8字节的double型转成2字节的short型变量,是窄化转换,编译器就会有warning了,提醒程序员可能丢失数据。不过需要注意的是,有些隐式转换,编译器可能并不给出warning,比如int到short,但数据溢出却依然会发生。
-
多态的上行转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <iostream> using namespace std; class A { // ...... }; class B : public A { // ...... }; int main() { B *pB = new B; A *pA = pB; // Safe and will succeed }
``
-
任意类型指针到 void*, 都可以使用隐式类型转换,因为void*是通用指针。
-
非const对象转化为const对象,直接进行隐式转换。
注意:普通变量与指针不能隐式转换转换,比如int*和int不能发生隐式转换.
隐式转换失败后会出现编译错误.
显式类型转换
显式类型转换可以进行任何转换。
C风格的强制转换(包括旧式C++风格的强制转换)
格式:
类型(表达式); // 旧的C++风格
或者
(类型)表达式 // C风格
示例: int(dval) 或者 (int)dval
此种强制转换是比较粗暴直接的,有可能导致精度丢失(如从 double 转换为 int)或者一些莫名其妙的错误(如把 int 转换为 函数指针),一旦使用了强制转换,编译器将不提示任何警告.这也往往成为错误的源泉.而且这种错误非常难找.
C++强制转换操作符
C++增加了4个关键字用于强制类型转换:
static_cast, reinterpret_cast, const_cast 和 dynamic_cast.
static_cast(静态转换)
static_cast的转换格式:static_cast (expression)
将expression转换为type-id类型,比起强制类型转换提供了一定的安全性。在无关类的类指针之间转换上进行限制。主要用于非多态类型之间的转换,不提供运行时的检查来确保转换的安全性。
静态转换的功能如下:
-
用于相关数据类型之间的转换,如基本数据类型中把int转换成char,把int转换成enum等等
数值精度提高或者降低,包括把无符号型转换为带符号型(也是精度损失的一种),用 static_cast 可以消除编译器的警告信息。
-
静态转换能把void*指针转换成目标类型的指针,因为void*是通用指针,和其它指针有一定关系。
-
用于类层次结构中,基类和子类之间指针和引用的转换。但不推荐使用!
当进行上行转换,也就是把子类的指针或引用转换成父类表示,这种转换是安全的;
当进行下行转换,也就是把父类的指针或引用转换成子类表示,这种转换是不安全的,但编译可以通过,需要程序员来保证安全性;
注:static_cast不能转换掉expression的const、volatile和__unaligned属性。
dynamic_cast(动态转换)
dynamic_cast的转换格式:dynamic_cast (expression)
动态转换确保类指针的转换是合适完整的,它有两个重要的约束条件:
-
要求type为指针或引用
将expression转换为type-id类型,type-id必须是类的指针、类的引用或者是void *;如果type-id是指针类型,那么expression也必须是一个指针;如果type-id是一个引用,那么expression也必须是一个引用。
-
下行转换时要求基类是多态的(基类中包含至少一个虚函数)。
动态转换的功能如下:
-
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
-
在多重继承中的上行转换中,dynamic_cast可以实现同层基类的互相转换。
-
在类层次间进行上行转换时,隐式转换和dynamic_cast和static_cast的效果是一样的,不推荐使用!
-
将类的指针转换成void *指针,注意这也需要保证类A和B都有虚函数,不推荐使用!
下面我将分别在以下的几种场合下进行dynamic_cast的使用总结:
-
类之间的下行转换。
如果expression是type-id的基类,使用dynamic_cast进行转换时,在运行时就会检查expression是否真正的指向一个type-id类型的对象,如果是,则能进行正确的转换,获得对应的值;否则返回NULL,如果是引用,则在运行时就会抛出异常;例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class B { virtual void f(){}; }; class D : public B { virtual void f(){}; }; void main() { B* pb = new D; // unclear but ok B* pb2 = new B; D* pd = dynamic_cast<D*>(pb); // ok: pb actually points to a D D* pd2 = dynamic_cast<D*>(pb2); // pb2 points to a B not a D, now pd2 is NULL }
``
但是,在类B中必须包含虚函数,为什么呢?因为类中存在虚函数,就说明它有想让基类指针或引用指向派生类对象的情况,此时转换才有意义;由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。
-
可以将类的指针转换成void *指针,注意这也需要保证类A和B都有虚函数。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class A { public: virtual void f(){} // ...... }; class B { public: virtual void f(){} // ...... }; int main() { A *pA = new A; B *pB = new B; void *pV = dynamic_cast<void *>(pA); // pV points to an object of A pV = dynamic_cast<void *>(pB); // pV points to an object of B }
``
-
dynamic_cast可以实现同层基类的互相转换,这是static_cast无法实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class Base { public: virtual ~Base(){} //donnt forget : at least one virtual function..... }; class A : public Base {}; class B : public Base {}; class AB : public A, public B {}; int main() { AB ab; B *b = &ab; A * a = dynamic_cast<A*>(b); return 0; }
``
对于一些复杂的继承关系来说,使用dynamic_cast进行转换是存在一些陷阱的。
讨论上行转换,(此处所有动态转换都可以用隐式转换替代)有如下的一个结构:
D类型可以安全的转换成B和C类型,但是D类型要是直接转换成A类型呢?
|
|
如果进行上面的直接转,你将会得到一个NULL的pA指针;这是因为,B和C都继承了A,并且都实现了虚函数Func,导致在进行转换时,无法进行抉择应该向哪个A进行转换。正确的做法是:
|
|
讨论下行转换,有如下结构:
现在,你拥有一个A类型的指针,它指向E实例,如何获得B类型的指针,指向E实例呢?
如果直接进行转的话,就会出现编译器出现分歧,不知道是走E->C->B,还是走E->D->B。对于这种情况,我们就必须先将A类型的指针选择正确的路线进行下行转换,获得E类型的指针,然后,在指定一条正确的路线进行上行转换。
reinterpret_cast
reinterpret_cast的转换格式:reinterpret_cast (expression)
reinterpret_cast 常常被用作不同类型指针间的相互转换。
static_cast 运算符完成相关类型之间的转换. 而 reinterpret_cast 处理互不相关的类型之间的转换.互不相关的类型”指的是两种完全不同的类型,如从整型到指针类型,或者从一个指针到另一个毫不相干的指针.
对于static_cast操作符,如果需要截断,补齐或者指针偏移编译器都会自动完成.而对于reinterpret_cast,编译器不会做任何检查,截断,补齐的操作,只是把比特位拷贝过去.
示例:
int ival = 1;
double *dptr = reinterpret_cast<double*>(ival);
或者
int *iptr = NULL;
double *dptr = reinterpret_cast<double*>(iptr);
上面这个示例也说明了 reinterpret_cast 的意思:.
reinterpret_cast的功能如下:
-
不同类型指针间的相互转换,因为所有类型的指针的长度都是一致的(32位系统上都是4字节),按比特位拷贝后不会损失数据.
-
void 到任意类型指针的转换, 用 static_cast 和 reinterpret_cast 都可以,这是由 void 是通用指针这个语义决定的.
-
它还可用于将一种数据类型按比特位从一种类型转换为另一种类型。常用的是int 型和指针类型间的相互转换,但是这种转换在系统底层的操作,有极强的平台依赖性,移植性不好。
注意:考虑到C++对象模型的内存分布可能引起的指针偏移问题,绝对不能在多态情况下用 reinterpret_cast.
写代码的时候经常这样做: new 一个 struct,然后把指针返回给外部函数作为一个”句柄”,我不希望外部函数知道这是一个指针,只需要外部函数在调用相关函数时把这个”句柄”重新传回来.这时,就可以把指针转换为一个 int 型返回. 这是 reinterpret_cast 存在的绝佳理由.
|
|
const_cast
const_cast的转换格式:const_cast (expression)
该运算符用来修改类型的const属性。.有以下功能:
-
常量指针和非常量指针的相互转换,并且仍然指向原来的对象;要求type—id和expression都为指针
-
常量引用和非常量引用的相互转换,并且仍然指向原来的对象; 要求type-id为引用,expression可以是指向对象的引用,也可以是对象本身的名字。
-
常量对象和非常量对象的相互转换,两者之间毫无关系。要求type-id为引用。
type_id 必须为指针或引用,const_cast转换符不该用在对象数据上,因为这样的转换得到的两个变量/对象并没有相关性。只有用指针或者引用,让变量指向同一个地址才是解决方案.
const_cast通常用于常量转换为非常量,因为非常量转换为常量可以直接用隐式转换完成。
|
|
下面给出一些例子来进行解析,类B的结构如下:
|
|
-
指针的转换
1 2 3 4 5
const B *b1 = new B(); B *b2 = const_cast<B*>(b1);//指针之间的转换,两者指向同一地址 b2->m_iNum = 200; cout<<"b1: "<< b1->m_iNum <<endl; //200 cout<<"b2: "<< b2->m_iNum <<endl; //200
``
-
引用的转换
1 2 3 4 5 6
const B b5; // 左侧一定要用引用类型,否则b6和b5无关,不指向同一地址。 B &b6 = const_cast<B&>(b5); b6.m_iNum = 200; cout<<"b5: "<<b5.m_iNum <<endl; cout<<"b6: "<<b6.m_iNum <<endl;
-
对象的转换
1 2 3 4 5
const B b3; B b4 = const_cast<B&>(b3); //注意type-id是引用类型 b4.m_iNum = 200; cout<<"b3: "<<b3.m_iNum <<endl; //50 cout<<"b4: "<<b4.m_iNum <<endl; //200
``
const_cast与编译器优化
因为编译器的优化,会出现取消了const属性的const变量却不能修改其值的情况。
|
|
究其原因,在于const值初始化的内容:如果是以字面值初始化的话,编译器在编译的时候就把const的值用字面值取代了,而不是从const对应的地址进行取值。
解决方案就是不让const以字面值初始化,如下:
|
|
还有一种方法就是使用valatile关键字。
vs(Release)和g++都会执行编译器优化。
文章作者 Forz
上次更新 2017-06-23