c++中的四大类型强转
来源:互联网 发布:python try except e 编辑:程序博客网 时间:2024/04/29 05:03
C++的四种强制转换
C++中的四种转换,是一个老生常谈的话题。但是对于初学者来说,该如何选择哪种转换方式仍然会有点困惑。而且我总是觉得“纸上得来终觉浅”,于是便“绝知此事要躬行”。于是利用闲暇时光,整理一下reinterpret_cast、const_cast、static_cast和dynamic_cast这四种强制转换的相关知识。(转载请指明出于breaksoftware的csdn博客)
一般来说,我们需要类型转换的场景可以分为如下几种:
- 整型和浮点型相互转换。这种转换往往是在数学计算的场景下。比如一个库函数导出的是double型数据,而我们使用该数据的函数的参数要求是整型,于是我们就需要对其进行转换。反之亦然。
- 整型和指针相互转换。当我们试图根据某个成员变量的偏移位计算其在该对象内存空间位置时,就会需要将指针转换为整型进行计算。当计算出该变量的位置后(整型),就需要将其转换为指针类型。
- 整型和枚举类型相互转换。这种转换往往发生在数学计算的场景下。因为枚举一般只是用于表意,而实际参与运算的还是整型数据。
- 指针和无类型指针相互转换。一个典型的场景是,win32编程中,线程函数的入参要求是个LPVOID型数据。而我们往往将类对象的指针传递进去,以方便我们调用封装在类中的相关函数和变量。即CreateThread时将指针转为void*型,在线程函数中将void*转为指针。
- 无关系类指针的相互转换。这种场景并不多见。
- 存在继承关系的类指针相互转换。多发生在多态等场景下。
- 转换不同长度的数据。这是个转换截断的问题,在现实使用中,也不难见到。
在测试如上场景时,我们往往会遇到阻碍。这种阻碍来源于两个方面:
- 编译器出错。这是因为语法规定这种使用不合法。所以编译器在编译代码时,认为该行为违法,终止之后的流程。
- 运行时出错。这是因为在语法上是合法的,但是运行时是不合理的。
为了更好讨论如上场景,我们先预备一些辅助结构。
- class Parent {
- public:
- Parent() {m_strPointerFrom = "Parent Pointer";};
- public:
- void print() {printf("%s From Parent Func\n", m_strPointerFrom.c_str());};
- virtual void printv() {printf("%s From Parent Virutal Func\n", m_strPointerFrom.c_str());};
- protected:
- std::string m_strPointerFrom;
- };
- //#define USEERROR
- class Child : public Parent {
- #ifndef USEERROR
- public:
- Child() {m_strPointerFrom = "Child Pointer";};
- public:
- void print() {printf("%s From Child Func.\n", m_strPointerFrom.c_str());};
- virtual void printv() override {printf("%s From Child Virutal Func.\n", m_strPointerFrom.c_str());};
- #else
- public:
- Child() {m_strPointerFrom = "Child Pointer"; m_strOnlyChild = "OnlyInChild";};
- public:
- void print() {printf("%s From Child Func. %s\n", m_strPointerFrom.c_str(), m_strOnlyChild.c_str());};
- virtual void printv() override {printf("%s From Child Virutal Func. %s\n", m_strPointerFrom.c_str(), m_strOnlyChild.c_str());};
- private:
- std::string m_strOnlyChild;
- #endif
- };
- class Temp{};
Temp类是一个无继承关系的原始类。
Child类继承于Parent类。
Child类中print函数隐藏了Parent类中定义的print函数的实现。
Child类也实现了Parent类中的虚方法printv。
为了区分Parent和Child类的对象,我们设计了一个变量——m_strPointerFrom。这两个类的所有方法都将这个变量打印出来,以帮助我们在之后的测试中标识其来源。
我们使用预定义控制了Child类的实现。我们可以先关注没有定义USEERROR这个宏的版本。另外一个版本我们将在后面介绍其设计意图。
如果是编译期间出错,我将在给出的代码示例中,使用注释方法使该代码行失效。如果是运行期间出错,我们将任由之存在,但是会在最后点出其出错的地方。所以看本博文切不可“断章取义”。在引入C++四种转换之前,我们先看下最常见的一种转换——类C语言方式的转换。
类C类型的强制转换
类c类型的强制转换是我们最常见的一种转换,比如:
- int a = 0;
- double b = (double)a;
我们列出这种方式,是为了让其和我们即将讨论的四种C++强制转换进行对比。根据之前设计的方案,我们列出如下代码:
- void c_like_cast_test() {
- {
- Parent* pParent = new (std::nothrow) Parent();
- if (!pParent) {
- return;
- }
- int a = (int)(pParent); // 指针向整型转换
- Parent* pParent1 = (Parent*)(a);// 整型向指针转换
- double b = (double)(a); // 整型向浮点型转换
- int a1 = (int)b; // 浮点型向整型转换
- void* pv = (void*)pParent; // 指针向无类型指针转换
- Parent* pParent2 = (Parent*)pv; // 无类型指针向指针转换
- Temp* pTemp = (Temp*)pParent; // 无关系类型指针相互转换
- Parent* pParent3 = (Parent*)pTemp;// 无关系类型指针相互转换
- ETYPE e = (ETYPE)a; // 整型向枚举转换
- int a2 = (int)e; // 枚举向整型转换
- int a3 = reinterpret_cast<int>(pv); // 无类型指针转整型
- Temp* pTemp1 = reinterpret_cast<Temp*>(pv); // 无类型指针转其他指针
- delete pParent;}
我们再看存在父子关系的指针的转换及不同长度类型数据转换的例子:
- { // A
- Parent* pParent = new (std::nothrow) Parent();
- if (!pParent) {
- return;
- }
- pParent->print();
- pParent->printv();
- Child* pChild = (Child*)(pParent);
- pChild->print();
- pChild->printv();
- delete pParent;
- }
- { //B
- Child* pChild = new (std::nothrow) Child();
- if (!pChild) {
- return;
- }
- pChild->print();
- pChild->printv();
- Parent* pParent = (Parent*)(pChild);
- pParent->print();
- pParent->printv();
- delete pChild;
- }
- {
- Child* pChild = new (std::nothrow) Child();
- if (!pChild) {
- return;
- }
- Temp* pTemp = (Temp*)pChild;
- delete pChild;
- }
- {
- intptr_t p = 0xF000000000000001;
- Parent* pP = (Parent*)(p);
- printf("0x%x\n", pP);
- }
上图中A、B区域和代码中的A、B区域相对照。由上面可以得出如下结论:
- 调用的被隐藏函数实体(函数入口)是由其调用时的指针类型决定的——print函数是调用Child版(From Child Func)的还是Parent版(From Parent Func)?这是由其左侧指针类型决定的。如果左侧指针是Child指针类型,则调用的是Child的print。如果左侧指针是Parent指针类型,则调用的Parent的print。
- 调用的虚函数实体(函数入口)是由对象的实际类型决定的——printv函数是调用Child版(From Child Virtual Func)还是Parent版(From Parent Virtual Func)?这是由调用者最初被new出来时的类型决定的。如果是调用new (std::nothrow) Child,则调用的是Child的printv。如果是调用new (std::nothrow) Parent,则调用的是Parent的printv。
- 无论指针在被创建后如何转换,其指向的依旧是初始时new出来的对象——可以见得A区域中的指针都指向了Parent类对象(Parent Pointer),而B区域中的指针都指向了Child类的对象(Child Pointer)。
说到这个问题,可能就要扯一点C++对象的内存模型。这儿我并不详细介绍其模型,只是想引出几个原理:
- 类成员函数的实现,在内存中是有一个唯一入口和唯一代码片的。可以想象下,这段代码片和类数据是“分离”的,它们只是在编译期间由编译器保证其相关性。
- 驱动类函数执行的是类的this指针所指向的数据区。其实类的非静态函数的第一个参数——也是隐藏的参数是这个类的this指针。通过该this指针,该函数才能访问到对象的成员数据。于是在多线程环境下,一个对象的函数在被多个线程执行时,它们会可能会修改同一个this指针的同一个数据。
如果能正确理解如上两点,则上例中的结果便可以得到理解了。
再回到类型转换上来。可以说类C的强制转换的能力是非常强大的,使用这种方法就意味着“通吃”。这也是大家非常喜欢使用它的一个原因。为什么它这么强大,我们看下汇编,以其中几段为例:
- Parent* pParent = (Parent*)(pChild);
- 01077A5D mov eax,dword ptr [pChild]
- 01077A60 mov dword ptr [pParent],eax
- int a = (int)(pParent); // 指针向int型转换
- 01077896 mov eax,dword ptr [pParent]
- 01077899 mov dword ptr [a],eax
reinterpret_cast
reinterpret_cast是四种C++强制转换中和类C强制转换最接近的了。我们看一段来自cplusplus网站上对该转换的说明:
- /*
- reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes.
- The operation result is a simple binary copy of the value from one pointer to the other.
- All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.
- It can also cast pointers to or from integer types.
- The format in which this integer value represents a pointer is platform-specific.
- The only guarantee is that a pointer cast to an integer type large enough to fully contain it (such as intptr_t),
- is guaranteed to be able to be cast back to a valid pointer.
- reinterpret可用于任何指针向任何指针的转换。它只是简单的进行二进制拷贝。
- 它还可以用于将指针类型和整型类型相互转换(注意整型类型和指针类型的长度不一致)。
- 它不进行类型检查。
- */
- void reinterpret_cast_test() {
- {
- Parent* pParent = new (std::nothrow) Parent();
- if (!pParent) {
- return;
- }
- int a = reinterpret_cast<int>(pParent); // 指针向整型转换
- Parent* pParent1 = reinterpret_cast<Parent*>(a); // 整型向指针转换
- // double b = reinterpret_cast<double>(a); // 整型向浮点型转换
- // int a1 = reinterpret_cast<int>(b); // 浮点型向整型转换
- void* pv = reinterpret_cast<void*>(pParent); // 指针向无类型指针转换
- Parent* pParent2 = reinterpret_cast<Parent*>(pv); // 无类型指针向指针转换
- Temp* pTemp = reinterpret_cast<Temp*>(pParent); // 无关系类型指针相互转换
- Parent* pParent3 = reinterpret_cast<Parent*>(pTemp);// 无关系类型指针相互转换
- // ETYPE e = reinterpret_cast<ETYPE>(a); // 整型向枚举转换
- // int a2 = reinterpret_cast<int>(e); // 枚举向整型转换
- int a1 = reinterpret_cast<int>(pv); // 无类型指针转整型
- Temp* pTemp1 = reinterpret_cast<Temp*>(pv); // 无类型指针转其他指针
- delete pParent;
- }
- Temp* pTemp = reinterpret_cast<Temp*>(pParent); // 无关系类型指针相互转换
- 013021DE mov eax,dword ptr [pParent]
- 013021E1 mov dword ptr [pTemp],eax
- Parent* pParent3 = reinterpret_cast<Parent*>(pTemp);// 无关系类型指针相互转换
- 013021E4 mov eax,dword ptr [pTemp]
- 013021E7 mov dword ptr [pParent3],eax
由上我们可以总结出:reinterpret_cast转换是在类C转换的基础上,在编译期间
- 约束了整型、浮点型和枚举类型的相互转换。
那么C++中有没有提供整型、浮点和枚举类型的相互转换方法呢?有的!见static_cast。
static_cast
static_cast也是使用非常多的一种强制转换。我们看一段来自cplusplus网站上对该转换的说明:
- /*
- static_cast can perform conversions between pointers to related classes,
- not only upcasts (from pointer-to-derived to pointer-to-base),
- but also downcasts (from pointer-to-base to pointer-to-derived).
- No checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type.
- Therefore, it is up to the programmer to ensure that the conversion is safe.
- On the other side, it does not incur the overhead of the type-safety checks of dynamic_cast.
- 它用于在存在继承关系的类指针之间转换。可以从派生类指针转为基类指针,也可以从基类指针转为派生类指针。
- static_cast is also able to perform all conversions allowed implicitly
- (not only those with pointers to classes), and is also able to perform the opposite of these.
- It can:
- Convert from void* to any pointer type. In this case,
- it guarantees that if the void* value was obtained by converting from that same pointer type, the resulting pointer value is the same.
- Convert integers, floating-point values and enum types to enum types.
- 它可以将void*型向任意指针类型转换。还可以在整型、浮点型和枚举型将相互转换。
- */
- void static_cast_test() {
- {
- Parent* pParent = new (std::nothrow) Parent();
- if (!pParent) {
- return;
- }
- // int a = static_cast<int>(pParent); // 指针向整型转换
- // Parent* pParent1 = static_cast<Parent*>(a); // 整型向指针转换
- int a = 1;
- double b = static_cast<double>(a); // 整型向浮点型转换
- int a1 = static_cast<int>(b); // 浮点型向整型转换
- void* pv = static_cast<void*>(pParent); // 指针向无类型指针转换
- Parent* pParent2 = static_cast<Parent*>(pv); // 无类型指针向指针转换
- // Temp* pTemp = static_cast<Temp*>(pParent); // 无关系类型指针相互转换
- // Parent* pParent3 = static_cast<Parent*>(pTemp); // 无关系类型指针相互转换
- ETYPE e = static_cast<ETYPE>(a); // 整型向枚举转换
- int a2 = static_cast<int>(e); // 枚举向整型转换
- // int a3 = static_cast<int>(pv); // 无类型指针转整型
- Temp* pTemp = static_cast<Temp*>(pv); // 无类型指针转其他指针
- delete pParent;
- }
可以见得static_cast
- 约束了指针和整型的相互转换。
- 约束了无关系类型的指针的相互转换。(无类型指针除外)
其他继承关系类指针相互转换也不列出了。其代码同类C相似,只是修改了操作方式。而且static_cast在汇编级的代码和类C强制转换是一致的。
- double b = (double)(a); // 整型向浮点型转换
- 01291872 fild dword ptr [a]
- 01291875 fstp qword ptr [b]
- int a1 = (int)b; // 浮点型向整型转换
- 01291878 fld qword ptr [b]
- 0129187B call @ILT+420(__ftol2_sse) (12911A9h)
- 01291880 mov dword ptr [a1],eax
- void* pv = (void*)pParent; // 指针向无符号指针转换
- 01291883 mov eax,dword ptr [pParent]
- 01291886 mov dword ptr [pv],eax
- Parent* pParent2 = (Parent*)pv; // 无符号指针向指针转换
- 01291889 mov eax,dword ptr [pv]
- 0129188C mov dword ptr [pParent2],eax
- double b = static_cast<double>(a); // 整型向浮点型转换
- 012928CD fild dword ptr [a]
- 012928D0 fstp qword ptr [b]
- int a1 = static_cast<int>(b); // 浮点型向整型转换
- 012928D3 fld qword ptr [b]
- 012928D6 call @ILT+420(__ftol2_sse) (12911A9h)
- 012928DB mov dword ptr [a1],eax
- void* pv = static_cast<void*>(pParent); // 指针向无符号指针转换
- 012928DE mov eax,dword ptr [pParent]
- 012928E1 mov dword ptr [pv],eax
- Parent* pParent2 = static_cast<Parent*>(pv); // 无符号指针向指针转换
- 012928E4 mov eax,dword ptr [pv]
- 012928E7 mov dword ptr [pParent2],eax
说完上述两种转换,我们可以发现:在reinterpret_cast上,没有任何面向对象的痕迹。而static_cast出现了对继承的关系的约束。之后我们将介绍C++特性更强的转换——dynamic_cast。
dynamic_cast
在讨论dynamic_cast之前,我们要先回到最前面定义的两个辅助类——Parent和Child上。之前为了保证这两个类指针在相互转换后,调用相关函数不会出现运行时错误,我们没有定义USEERROR宏。现在我们要开启USERROR宏,使得Child类比Parent类多一个成员变量——m_strOnlyChild。并在Child类重写函数print和继承的虚函数printv中使用到该变量。于是我们之前的类C强制转换、reinterpret_cast和static_cast对父子类指针转换后函数调用,将出现运行时出错。我们以static_cast为例:
- {
- Parent* pParent = new (std::nothrow) Parent();
- if (!pParent) {
- return;
- }
- pParent->print();
- pParent->printv();
- Child* pChild = static_cast<Child*>(pParent);
- pChild->print();
- pChild->printv();
- delete pParent;
- }
在知道之前我们父类对象向子类指针转换的过程存在如此不安全的行为时,我们就要介绍dynamic_cast了。它有着很强烈的C++特性。我们先看下说明:
- /*
- dynamic_cast can only be used with pointers and references to classes (or with void*).
- Its purpose is to ensure that the result of the type conversion points to a valid complete object of the destination pointer type.
- This naturally includes pointer upcast (converting from pointer-to-derived to pointer-to-base), in the same way as allowed as an implicit conversion.
- But dynamic_cast can also downcast (convert from pointer-to-base to pointer-to-derived) polymorphic classes (those with virtual members)
- if -and only if- the pointed object is a valid complete object of the target type.
- dynamic_cast can also perform the other implicit casts allowed on pointers: casting null pointers between pointers types (even between unrelated classes),
- and casting any pointer of any type to a void* pointer.
- dynamic_cast只可以用于指针之间的转换,它还可以将任何类型指针转为无类型指针,甚至可以在两个无关系的类指针之间转换。
- */
- class TmpBase{ public: virtual void fun(){};};
- class TmpDerived : public TmpBase{public: virtual void fun() override {};};
- void dynamic_cast_test() {
- {
- Parent* pParent = new (std::nothrow) Parent();
- if (!pParent) {
- return;
- }
- // int a = dynamic_cast<int>(pParent); // 指针向整型转换
- // Parent* pParent1 = dynamic_cast<Parent*>(a); // 整型向指针转换
- // double b = dynamic_cast<double>(a); // 整型向浮点型转换
- // int a1 = dynamic_cast<int>(b); // 浮点型向整型转换
- void* pv = dynamic_cast<void*>(pParent); // 指针向无符号指针转换
- // Parent* pParent2 = dynamic_cast<Parent*>(pv); // 无符号指针向指针转换
- Temp* pTemp = dynamic_cast<Temp*>(pParent); // 无关系类型指针相互转换
- // Parent* pParent3 = dynamic_cast<Parent*>(pTemp);// 无关系类型指针相互转换
- // ETYPE e = dynamic_cast<ETYPE>(a); // 整型向枚举转换
- // int a2 = dynamic_cast<int>(e); // 枚举向整型转换
- // int a2 = dynamic_cast<int>(pv); // 无符号指针转整型
- // Temp* pTemp1 = dynamic_cast<Temp*>(pv); // 无符号指针转其他指针
- TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);
- Child* pChild1 = dynamic_cast<Child*>(pTmp2);
- delete pParent;
- }
- TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);
- Child* pChild1 = dynamic_cast<Child*>(pTmp2);
- TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);
- 00D52D16 push 0
- 00D52D18 push offset TmpDerived `RTTI Type Descriptor' (0D5E034h)
- 00D52D1D push offset Parent `RTTI Type Descriptor' (0D5E000h)
- 00D52D22 push 0
- 00D52D24 mov eax,dword ptr [pParent]
- 00D52D27 push eax
- 00D52D28 call @ILT+885(___RTDynamicCast) (0D5137Ah)
- 00D52D2D add esp,14h
- 00D52D30 mov dword ptr [pTmp2],eax
我们看段使用dynamic_cast规避掉运行时出错的代码
- {
- Parent* pParent = new (std::nothrow) Parent();
- if (!pParent) {
- return;
- }
- pParent->print();
- pParent->printv();
- Child* pChild = dynamic_cast<Child*>(pParent);
- if (pChild) { // Bad
- pChild->print();
- pChild->printv();
- }
- else {
- printf("dynamic_cast error\n"); // Hit
- }
- try {
- Child& c = dynamic_cast<Child&>(*pParent); // Bad
- }
- catch (std::bad_cast& e) {
- printf("dynamic_cast error : %s\n", e.what()); // Hit
- }
- delete pParent;
- }
下面两段代码是安全的
- {
- Child* pChild = new (std::nothrow) Child();
- if (!pChild) {
- return;
- }
- pChild->print();
- pChild->printv();
- Parent* pParent = dynamic_cast<Parent*>(pChild);
- if (pParent) { // Well
- pParent->print();
- pParent->printv();
- }
- else {
- printf("dynamic_cast error\n");
- }
- try {
- Parent& p = dynamic_cast<Parent&>(*pChild); // Well
- p.print();
- p.printv();
- }
- catch (std::bad_cast& e) {
- printf("dynamic_cast error : %s\n", e.what()); // No Hit
- }
- delete pChild;
- }
- {
- Parent* pChild = new (std::nothrow) Child();
- if (!pChild) {
- return;
- }
- pChild->print();
- pChild->printv();
- Parent* pParent = dynamic_cast<Parent*>(pChild);
- if (pParent) { // Well
- pParent->print();
- pParent->printv();
- }
- else {
- printf("dynamic_cast error\n");
- }
- try {
- Parent& p = dynamic_cast<Parent&>(*pChild); // Well
- p.print();
- p.printv();
- }
- catch (std::bad_cast& e) {
- printf("dynamic_cast error : %s\n", e.what()); // No Hit
- }
- delete pChild;
- }
- }
const_cast
const_cast与之前介绍的各种转换都不同,它只是负责给变量增加或者删除一些属性,以保证编译器可以编过。我们直接看例子- void printf_without_const(char* pbuffer){
- if (!pbuffer || 0 == strlen(pbuffer)) {
- return;
- }
- *(pbuffer) = '$'; // can modify
- printf(pbuffer);
- };
- void const_cast_test() {
- {
- const char* pbuffer = "const_cast_test";
- char szbuf[64] = {0};
- strncat(szbuf, pbuffer, strlen(pbuffer));
- const char* psz = const_cast<const char*>(szbuf);
- //printf_without_const(psz); // error
- char* p = const_cast<char*>(psz);
- printf_without_const(p);
- }
- }
- 类C强制转换十分强大。因为它是二进制级别内存拷贝操作,所以可以大部分场景不会出现编译错误。但是如果用它去转换指针,可能会出现运行时错误。
- reinterpret_cast、static_cast和dynamic_cast主要用于指针的转换。
- reinterpret_cast的能力仅次于类C转换。虽然它约束了整型、浮点和枚举类型的相互转换,但是还是支持指针和整型的转换。它也存在转换后运行时出错的隐患。
- static_cast弥补了reinterpret_case对整型、浮点和枚举类型的相互转换的功能。除了这些转换外,它要求操作的参数是指针。且已经出现C++特性限制,要求指针转换时的类存在继承关系(void*除外)。它也存在转换后运行时出错的隐患。
- dynamic_cast已经是纯的C++特性转换,使用到了RTTI技术。于是它要求操作的指针类型具有多态特性。它解决了指针转换后使用出现运行时出错的问题,但是使用该方法要付出运行时计算的代价。如果能明确转换是安全的,建议使用static_cast方法(不使用reinterpret_cast是因为它还没体现出C++的特性)。
- const_cast用于增加和去掉一些类型描述标志,如const等。
- c++中的四大类型强转
- LUA中的类型强转
- LUA中的类型强转
- [C#]强类型
- 四大类型的语言,动态,静态,强类型,弱类型
- 四大类型的语言,动态,静态,强类型,弱类型
- C 语言四大存储类型。。。
- C语言中强类型&弱类型
- C++类型强转
- null类型强转
- C#类型强转
- scala 类型强转
- 类型强转和地址强转
- VS 2005中的强类型名SN
- ADO.NET中的数据访问--强类型
- Modern C++(六)强类型枚举enum
- C# float类型强转
- python基础 强转类型
- 数据库设计三大范式
- hdu 2022海选女主角 绝对值排序
- Android SurfaceView初次进入有黑屏解决方案
- Sencha Touch 数据层篇 Proxy(上)
- IOS控件 Tableview 下拉刷新,加载数据
- c++中的四大类型强转
- 码流、单码流、双码流、多码流
- [体系结构]设计模式(一)
- Android通过HttpURLConnection和HttpClient接口实现网络编程
- epoll使用详解(精髓)
- 对于java线程的想法
- Js获取当前日期时间及其它操作
- 又开始了
- XML基础知识