C++的4种类型转换

前言

这篇文章是我从网上一些讲的比较好的文章上整理出来的。
关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的《C++的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_castdynamic_castreinterpret_cast、和 const_cast。下面对它们一一进行介绍。

C-Style类型转换

旧的类型转换就是直接在变量的前面加括号和需要转换的目标类型,就像C语言中的(NewType) Expression强转,例如:

double d_a = 1.2;
int i_a = (int) d_a;
cout << d_a << endl;
// 1.2
cout << i_a << endl;
// 1

static_cast

用法:static_cast < type-id > ( expression )
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

  1. 用于类层次结构中基类和子类之间指针或引用的转换。
    进行向上转型(把子类的指针或引用转换成基类表示)是安全的;
    进行向下转型(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。(#add,因此类层次的转换不用static_cast,而用dynamic_cast
  2. 用于基本数据类型之间的转换。如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
  3. 把空指针转换成目标类型的空指针。
  4. 把任何类型的表达式转换成void类型。
    注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

这是最常见的一种,没有什么特殊的处理,用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换。static_cast 不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。例如:

// 案例1
double d_a = 1.2;
int i_a = static_cast<int>(d_a); // 将double转换成int
cout << d_a << endl;    // 1.2
cout << i_a << endl;    // 1

// 案例2,错误
double* d_ptr_a = &d_a;
int* i_ptr_a = static_cast<int*>(d_ptr_a);  // 错误,不能将double* 转换成 int*

// 案例3,成功,因为void*是万能指针,可以被转换成指向任意类型的指针,也可以被指向任意类型的指针转换
void* d_ptr_a = &d_a;
int* i_ptr_a = static_cast<int*>(d_ptr_a);  // 正确,将void* 转换成 int*
cout << d_ptr_a << endl;  // 0x61fe00
cout << i_ptr_a << endl;  // 0x61fe00

以上案例说明static_cast可以转换普通类型值,但是不能转换不同类型的指针(指的是double* 转换成 int*这种指向不同类型的指针的转换)。转换不同类型的指针可以用 reinterpret_cast实现,我们接着往下看。

reinterpret_cast

用法:reinterpret_cast< type_id > (expression)
它可以对运算对象的位模式提供较低层次上的重新解释,是特意用于底层的强制转型。它在进行类型转换时不会对两者的类型进行检查和判断,只是简单地拷贝二进制比特。
reinterpret_cast一般用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。
reinterpret_cast十分灵活,所以使用时必须十分谨慎。

double d_a = 1.0;
int i_a = 0x10;
int * i_a_address = (int*) i_a;  // 正确,实际上i_a_address = i_a = 0x10
i_a_address = reinterpret_cast<int* >(i_a);  // 正确,将int的值转换成int*,这就是reinterpret_cast的过人之处。
// i_a_address = i_a = 0x10
i_a_address = reinterpret_cast<int* >(&d_a);  // 正确,将double* 转换成 int*
// i_a_address = &d_a
d_p = static_cast<int* >(i_a);  // 错误,static_cast不能将int的值转换成int*

以上案例说明,reinterpret_cast确实十分强大,得益于底层简单地对二进制拷贝,对转换的类型不做检查。也就是底层的二进制是啥就复制啥,不看转换的类型是什么。它可以执行不同类型指针之间转换,干了static_cast不能干的事。但是它也十分危险,例如如果用指向int的指针去访问存放double的地址的值,会出现异常。

const_cast

用法:const_cast< type-id > (expression)
这个转换的功能比较单一,但是它的功能也很强大:可以用于去除const属性的转换。它是四个转换中唯一一个可以去除 const 属性的。
一般来说要使用指针指向一个const变量,则这个指针必须也是const,也就是不能通过这个指针来修改指向的const变量的值。而使用const_cast可以将 const 引用转换为同类型的非const引用,将const指针转换为同类型的非const指针,接着就可以使用非const的指针或者引用来修改指向的const变量的值。例如:

const string s = "hhh";
string* p_s = const_cast<string*>(&s);  // 使用非const指针指向const变量,正常情况下是不允许的
string& r_s = const_cast<string&>(s);  // 使用非const引用指向const变量,正常情况下是不允许的

// 可以看出p_s确实指向s的地址
cout << &s << endl;  // 0x61fd00
cout << p_s << endl;  // 0x61fd00

// 通过指向s的指针来修改字符串s
p_s->append("eee");

// 从结果可以看出,确实可以使用非const指针或引用来修改const变量的值
cout << s << endl;  // hhheee
cout << *p_s << endl;  // hhheee
cout << r_s << endl;  // hhheee

常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

Voiatile和const类试。举如下一例:

class B{
    
    

public:

int m_iNum;

}

 
void foo(){
    
    

const B b1;

b1.m_iNum = 100; //comile error

B b2 = const_cast<B>(b1);

b2. m_iNum = 200; //fine

}

上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。

dynamic_cast

用法:dynamic_cast< type-id >(expression)

用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但是这种转换不检查安全性,即不检查转换后的指针是否确实指向一个派生类对象。dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针。 dynamic_cast 是通过“运行时类型检查”来保证安全性的。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性,只好用 reinterpret_cast 来完成。

#include <iostream>
#include <string>
using namespace std;
class Base
{
    
      //有虚函数,因此是多态基类
public:
    virtual ~Base() {
    
    }
};
class Derived : public Base {
    
     };
int main()
{
    
    
    Base b;
    Derived d;
    Derived* pd;
    pd = reinterpret_cast <Derived*> (&b);
    if (pd == NULL)
        //此处pd不会为 NULL。reinterpret_cast不检查安全性,总是进行转换
        cout << "unsafe reinterpret_cast" << endl; //不会执行
    pd = dynamic_cast <Derived*> (&b);
    if (pd == NULL)  //结果会是NULL,因为 &b 不指向派生类对象,此转换不安全
        cout << "unsafe dynamic_cast1" << endl;  //会执行
    pd = dynamic_cast <Derived*> (&d);  //安全的转换
    if (pd == NULL)  //此处 pd 不会为 NULL
        cout << "unsafe dynamic_cast2" << endl;  //不会执行
    return 0;
}

输出结果为:
unsafe dynamic_cast1

实际上,可以通过判断pd是否为NULL就可以知道类型转换是否是安全的。

dynamic_cast主要用于类层次间的上行转换下行转换,还可以用于类之间的交叉转换
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

//交叉转换
class A

{
    
    

public:

int m_iNum;

virtual void f(){
    
    }

};

 

class B:public A

{
    
    

};

 

class D:public A

{
    
    

};

 

void foo(){
    
    

B *pb = new B;

pb->m_iNum = 100;

D *pd1 = static_cast<D *>(pb); //copile error

D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL

delete pb;

}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的(允许,但不安全,不安全的情况下返回空指针),结果是空指针。

总结

这四种类型转换,分别对应不同的功能:

  1. static_cast< type >(expression)主要用于普通变量之间的类型转换。
  2. dynamic_cast< type >(expression)主要用于将多态基类的指针或引用强制转换为派生类的指针或引用。
  3. const_cast< type >(expression)主要用使用非const指针或者非const引用指向const变量,主要是在函数参数传递上会用到。
  4. `reinterpret_cast< type >(expression)主要用于指向不同类型的指针之间的转换。这个转换的功能最强,适用性最广,但是也最危险。
    不好意思忘记放转载链接了
    https://www.cnblogs.com/weiqubo/archive/2011/01/26/1945210.html
    https://blog.csdn.net/Johnsonjjj/article/details/103793522?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-8.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-8.channel_param

猜你喜欢

转载自blog.csdn.net/J_avaSmallWhite/article/details/109251553
今日推荐