深入理解C++中五种强制类型转换的使用场景

您所在的位置:网站首页 c语言强制转换语句是什么 深入理解C++中五种强制类型转换的使用场景

深入理解C++中五种强制类型转换的使用场景

2024-06-12 06:40| 来源: 网络整理| 查看: 265

深入理解C++中五种强制类型转换的使用场景 1、C风格的强制类型转换2、C++风格的强制类型转换2.1、static_cast2.1.1、用于基本内置数据类型之间的转换2.1.2、用于指针之间的转换2.1.3、不能转换掉expression的const或volitale属性2.1.4、用于类实例的之间转换2.1.5、用于没有多态的类实例指针或引用之间的转换2.1.6、用于具有多态的类实例指针或引用之间的转换 2.2、const_cast2.3、reinterpret_cast2.4、dynamic_cast 3、总结

1、C风格的强制类型转换

C风格的强制类型转换很容易理解,不管什么类型都可以直接进行转换,使用格式如下: Type b = (Type)a; 当然,C++也是支持C风格的强制类型转换的,但是C风格的强制类型转换可能会带来一些隐患,出现一些难以察觉的问题,所以C++又推出了四种新的强制类型转换来替代C风格的强制类型转换,降低使用风险。

2、C++风格的强制类型转换

在C++中新增了四个关键字static_cast、const_cast、reinterpret_cast和dynamic_cast,用于支持C++风格的强制类型转换。这几个看起来有点像类模板,但事实上他们是正儿八经的关键字。C++风格的强制类型转换的好处是它们能更清晰的表明它们要干什么,程序员只要扫一眼这样的代码,就能立即知道一个强制转换的目的,并且在多态场景也只能使用C++风格的强制类型转换。

看本节之前建议看一下一下两篇文章,因为往下的内容会涉及一些关于多态的知识:

一文读懂C++虚函数的内存模型一文读懂C++虚继承的内存模型 2.1、static_cast

static_cast是最常用的C++风格的强制类型转换,主要是为了执行那些较为合理的强制类型转换,使用格式如下: static_cast(expression);

因为static_cast的使用范围比较广,而且限制也比较多,所以下面分小节对各个场景进行分析

2.1.1、用于基本内置数据类型之间的转换

static_cast可以用于基本内置数据类型之间的转换,比如把char转成float、double转换成long等,这种内置类型之间的转换往往可以由隐式转换自动执行,而不需要人工特地去执行强制类型转换。由于转换结果可能存在截断性,这种转换的安全性要由开发人员来保证(一般不会出现严重程序运行错误,但是可能会出现逻辑错误),编译器可能会适当打印警告信息。示例如下:

#include int main(int argc, char* argv[]) { char type_char = 'A'; float type_float = type_char; // 隐式转换也可以 float type_float_cast = static_cast(type_char); // 显式地使用static_cast进行强制类型转换 double type_double = 1.23; long type_long = type_double; // 隐式转换也可以 long type_long_cast = static_cast(type_double); // 显式地使用static_cast进行强制类型转换 } 2.1.2、用于指针之间的转换

static_cast可以用于指针之间的转换,这种转换类型检查非常严格,不同类型的指针是直接不给转的,除非使用void*作为中间参数,我们知道隐式转换下void*类型是无法直接转换为其它类型指针的,这时候就需要借助static_cast来转换了。示例如下:

#include int main(int argc, char* argv[]) { int type_int = 10; float* float_ptr1 = &type_int; // int* -> float* 隐式转换无效 float* float_ptr2 = static_cast(&type_int); // int* -> float* 使用static_cast转换无效 char* char_ptr1 = &type_int; // int* -> char* 隐式转换无效 char* char_ptr2 = static_cast(&type_int); // int* -> char* 使用static_cast转换无效 void* void_ptr = &type_int; // 任何指针都可以隐式转换为void* float* float_ptr3 = void_ptr; // void* -> float* 隐式转换无效 float* float_ptr4 = static_cast(void_ptr); // void* -> float* 使用static_cast转换成功 char* char_ptr3 = void_ptr; // void* -> char* 隐式转换无效 char* char_ptr4 = static_cast(void_ptr); // void* -> char* 使用static_cast转换成功 }

补充说明:static_cast是直接不允许不同类型的引用进行转换的,因为没有void类型引用可以作为中间介质,这点和指针是有相当大区别的

2.1.3、不能转换掉expression的const或volitale属性

static_cast不能转换掉expression的const或volitale属性。示例如下:

#include int main(int argc, char* argv[]) { int temp = 10; const int* a_const_ptr = &temp; int* b_const_ptr = static_cast(a_const_ptr); // const int* -> int* 无效 const int a_const_ref = 10; int& b_const_ref = static_cast(a_const_ref); // const int& -> int& 无效 volatile int* a_vol_ptr = &temp; int* b_vol_ptr = static_cast(a_vol_ptr); // volatile int* -> int* 无效 volatile int a_vol_ref = 10; int& b_vol_ref = static_cast(a_vol_ref); // volatile int& -> int& 无效 } 2.1.4、用于类实例的之间转换

测试程序如下:

#include class A { public: int a; }; class B { public: int b; }; class C : public A, public B { public: int c; }; int main(int argc, char* argv[]) { C c; A a = static_cast(c); // 上行转换正常 B b = static_cast(c); // 上行转换正常 C c_a = static_cast(a); // 下行转换无效 C c_b = static_cast(b); // 下行转换无效 }

从测试程序中可以看到如果对类实例使用static_cast进行转换,static_cast是会进行类型判断的,对于上行转换来说这个过程就是正常的(其实任何合法的上行转换都可以直接由隐式转换来完成,而不需要手工去强制类型转换),但是下行转换则不行,static_cast认为下行转换等同于两个无关联的类进行转换,会报错。但是这个错误是有解决方法的,我们从报错信息中可以看到当static_cast转换失败时,会使用expression作为传入参数来调用type_id的构造函数,所以我们可以把类C改成以下形式,上面的示例即可编译通过

class C : public A, public B { public: C() { } C(const A& v) { a = v.a; } C(const B& v) { b = v.b; } int c; };

综上,我们可以得出使用static_cast对类实例进行强制类型转换时有以下特点:

进行上行转换是完全安全合法的,当然这个过程由隐式转换来完成也是合法的进行下行转换时,static_cast会认为两个类无关联,这种转换不合法。如果此时硬要转换的话,比如类A->类B(这两个类可以无任何关系,因为实例下行转换static_cast就是认为他们没关联),可以在B中添加一个使用类A进行构造的构造函数,比如B(const A&),这样就可以正常使用static_cast来进行类A->类B的操作了 2.1.5、用于没有多态的类实例指针或引用之间的转换

进行上行转换的示例如下:

#include class A { public: int a; }; class B { public: int b; }; class C : public A, public B { public: int c; }; int main(int argc, char* argv[]) { C c; A* a_ptr = static_cast(&c); // 上行指针转换正常 B* b_ptr = static_cast(&c); // 上行指针转换正常 A& a_ref = static_cast(c); // 上行引用转换正常 B& b_ref = static_cast(c); // 上行引用转换正常 }

可以看到上行转换都是正常的,转换过程中不会出现任何显性和隐性错误,下面来看一下下行转换的示例:

int main(int argc, char* argv[]) { C c; A* a_ptr = static_cast(&c); B* b_ptr = static_cast(&c); A& a_ref = static_cast(c); B& b_ref = static_cast(c); C* c_ptra = static_cast(a_ptr); // 下行指针转换正常 C* c_ptrb = static_cast(b_ptr); // 下行指针转换正常 C& c_refa = static_cast(a_ref); // 下行引用转换正常 C& c_refb = static_cast(b_ref); // 下行引用转换正常 A* a_ptr_fail = static_cast(b_ptr); // B* -> A*,无关联的两个类型,无效 }

从上面的例子可以看到,下行转换也是正常的,并且static_cast也会拒绝掉两个无关联类之间的转换???这和书中说的不一样啊,不是说static_cast下行转换不安全吗?别急,上面的例子是片面的,各位看一下下面的代码就知道了

int main(int argc, char* argv[]) { A a; B b; // 以下都能转换成功,说明static_cast根本就没有安全检查,只看到有继承关系就给转换了 C* c_ptra = static_cast(&a); C* c_ptrb = static_cast(&b); C& c_refa = static_cast(a); C& c_refb = static_cast(b); }

综上,我们可以得出使用static_cast对没有多态的类实例指针或引用进行强制类型转换时有以下特点:

进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,要尽量避免这种用法如果两个类无继承关系,则使用static_cast进行转换时会失败,但是这种情况下static_cast会显性地展示出错误信息,是安全的 2.1.6、用于具有多态的类实例指针或引用之间的转换

进行上行转换的示例如下:

#include class A { public: virtual void print() { std::cout std::cout std::cout C c; A* a_ptr = static_cast(&c); B* b_ptr = static_cast(&c); A& a_ref = static_cast(c); B& b_ref = static_cast(c); C* c_ptra = static_cast(a_ptr); // 下行指针转换正常 C* c_ptrb = static_cast(b_ptr); // 下行指针转换正常 c_ptra->print(); // 输出C,符合多态的要求 c_ptrb->print(); // 输出C,符合多态的要求 C& c_refa = static_cast(a_ref); // 下行引用转换正常 C& c_refb = static_cast(b_ref); // 下行引用转换正常 c_refa.print(); // 输出C,符合多态的要求 c_refb.print(); // 输出C,符合多态的要求 }

可以看到这个也是正常的,和前面那个没有多态的差不多,接下来看一下不正常的下行转换例子:

int main(int argc, char* argv[]) { A a; B b; C* c_ptra = static_cast(&a); C* c_ptrb = static_cast(&b); c_ptra->print(); // 正常输出A c_ptrb->print(); // 段错误 C& c_refa = static_cast(a); C& c_refb = static_cast(b); c_refa.print(); // 正常输出A c_refb.print(); // 段错误 }

上面这个例子中的下行转换是错误的,但是通过c_ptra可以正常调用类A的print()方法打印出字母A来,使用c_ptrb就直接段错误了,原因是类A是第一个被继承的,类B是第二个被继承的,也就是在类C中,第一个虚表指针指向的就是类A的虚表,第二个虚表指针指向的就是类B的虚表。在上面的例子那样进行错误地转换时,由于类A被继承之后它位置的特殊性导致可以使用c_ptra正确地调用类A的print()方法,而类B则不行,可能这有点难理解,下面给大家看张图就明白了,如图2-1所示:

在这里插入图片描述

图2-1

从图2-1中可以看出,对于类C来说,它始终调用着_vptr.A指向的print()方法,当我们使用纯类B类型进行下行转换时,根本就没有这一块的数据(这个转换是不完整的、不安全的),所以就会出现段错误了。当然,使用纯类A类型进行下行转换也是不完整、不安全的,只不过位置刚好才不会出现段错误而已。综合分析上面的代码是完全错误的,一定要杜绝写出这种垃圾代码。

综上,我们可以得出使用static_cast对具有多态的类实例指针或引用进行强制类型转换时有以下特点:

进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,并且因为具有多态的类往往具有特殊的用法,所以在这种情况下产生的后果比前面没有多态情况下的要更严重,要尽量避免这种用法

小结:通过上面的介绍,我们可以很直观地看到static_cast相比C风格的强制类型转换要安全很多,有很大程度上的类型安全检查。本节我们所有的例子都可以使用C风格的强制类型转换去做,但是转出来的结果有可能会错到天际去,并且编译器不会给你任何报错信息。。。同时我们也要认识到static_cast也是有明显缺点的,那就是无法消除const和volatile属性、无法直接对两个不同类型的指针或引用进行转换和下行转换无类型安全检查等,不过没关系,其它三个强制类型转换的关键字刚好能弥补static_cast的这些缺点。

2.2、const_cast

const_cast的作用是去除掉const或volitale属性,前面介绍static_cast的时候我们知道static_cast是不具备这种功能的。使用格式如下: const_cast(expression);

注意事项:const_cast不是用于去除变量的常量性,而是去除指向常量对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用,并且const_cast不支持不同类型指针或引用之间的转换,比如说float*转换成int*是不允许的,直白一点说就是type_id和expression要基本类型保持一致,相差的话只能差const或volatile属性。

先来看一个错误的使用示例:

#include int main(int argc, char* argv[]) { int type_int = 100; float type_float = const_cast(type_int); // 错误,const_cast只能转换引用或者指针 float* type_float_ptr = const_cast(&type_int); // 错误,从int* -> float* 无效 float& type_float_ref = const_cast(type_int); // 错误,从int& -> float& 无效 }

再来看一个不太正确的使用示例:

#include int main(int argc, char* argv[]) { const int type_const_int = 100; int* type_const_int_ptr = const_cast(&type_const_int); // 转换正确 int& type_const_int_ref = const_cast(type_const_int); // 转换正确 *type_const_int_ptr = 10; std::cout int type_int = 100; fun(type_int); std::cout public: int b; }; int main(int argc, char* argv[]) { float type_float = 10.1; int type_int = reinterpret_cast(type_float); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点) char type_char = reinterpret_cast(&type_float); // 出错,我的是64位系统,这里type-id只能是long类型(注意事项第3点) double* type_double_ptr = reinterpret_cast(type_float); // 出错,这里expression只能是整型(注意事项第4点) A a; B b; long type_long = reinterpret_cast(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点) B b1 = reinterpret_cast(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点) A a1 = reinterpret_cast(&b); // 出错,B* -> A不允许,我的是64位系统,type-id只能是long(注意事项第3点) A* a_ptr = reinterpret_cast(b); // 出错,这里expression只能是整型(注意事项第4点) }

下面再来看正确的使用示例:

#include class A { public: int a; }; class B { public: int b; }; int main(int argc, char* argv[]) { float type_float = 10.1; long type_long = reinterpret_cast(&type_float); // 正确,float* -> long(注意事项第3点) float* type_float_ptr = reinterpret_cast(type_long); // 正确,long -> float*(注意事项第4点) std::cout A*(注意事项第4点) A* a_ptr2 = reinterpret_cast(&b); // 正确,B* -> A*(注意事项第1点) }

程序中写的比较清楚了,大家配合前面的注意事项去看就行了,很容易就可以看懂了。reinterpret_cast的一个典型使用也是标准库的std::addressof,具体大家可以看一下这篇文章《C++11的std::addressof源码解析》。

2.4、dynamic_cast

dynamic_cast是本文讲的最后一个C++风格强制类型转换了,也是最特殊的一个,前面三种都是编译时完成的,而dynamic_cast是运行时处理的,使用格式如下: dynamic_cast(expression);

注意事项如下:

dynamic_cast是运行时处理的,运行时会进行类型检查(这点和static_cast差异较大)dynamic_cast不能用于内置基本数据类型的强制转换,并且dynamic_cast只能对指针或引用进行强制转换dynamic_cast如果转换成功的话返回的是指向类的指针或引用,转换失败的话则会返回nullptr使用dynamic_cast进行上行转换时,与static_cast的效果是完全一样的使用dynamic_cast进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。并且这种情况下dynamic_cast会要求进行转换的类必须具有多态性(即具有虚表,直白来说就是有虚函数或虚继承的类),否则编译不通过 需要有虚表的原因:类中存在虚表,就说明它有想要让基类指针或引用指向派生类对象的情况,dynamic_cast认为此时转换才有意义(事实也确实如此)。而且dynamic_cast运行时的类型检查需要有运行时类型信息,这个信息是存储在类的虚表中的在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。dynamic_cast则可以在运行期对可能产生问题的类型转换进行测试

先来看一个错误的使用示例:

#include class A { public: void print() { std::cout std::cout std::cout public: virtual void print() { std::cout std::cout std::cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3