现在的位置: 首页 > 综合 > 正文

C++中类型转换的解释

2014年02月17日 ⁄ 综合 ⁄ 共 11319字 ⁄ 字号 评论关闭

本文转自http://www.cppblog.com/guogangj/archive/2009/04/23/80870.html

下文中的“常规类型”指的是int、double、float、bool……这些非结构化类型,也就是不包括struct和class类型。“旧式转换”指的是C语言风格的“(NewType)(Val)”方式的转换。

1、指针=>常规类型

比如我们需要打印一个指针的值(它指向的地址)的时候,我们指针直接转换为整数,用printf输出个16进制的地址。我们可以用旧式转换,或者reinterpret_cast,其它转换都是不可以的。

    CIndepend oIndepend;
    CIndepend 
* pIndepend = &oIndepend;
    unsigned 
char cTest= (unsigned char )pIndepend;
    unsigned 
short sTest = (unsigned short )pIndepend;
    unsigned 
int iTest = (unsigned int )pIndepend;

在32位系统中,指针其实是个32位的无符号整型,要正常输出指针的值,正确做法是把它转换为一个无符号32位整形数输出(有符号的可能导致输出不正确),如果转换为一个16位的,或者8位的,那将丢失数据,当然了,前面这段代码不会出现任何error和warning,你知道你在干什么即可。刚说了,我们除了旧式转换还可以用reinterpret_cast,于是上面的代码可以这样写:

    unsigned char cTest= reinterpret_cast<unsigned char >(pIndepend);
    unsigned 
short sTest = reinterpret_cast<unsigned short>(pIndepend);
    unsigned 
int iTest = reinterpret_cast<unsigned int>(pIndepend);

也是没有任何问题的,运行效果一样,那我们能不能把指针转换为浮点型呢?这样:

    float fTest = reinterpret_cast<float>(pIndepend);
    
double dTest = reinterpret_cast<double>(pIndepend);

不行,你试试看就知道了,你会得到这样的编译错误:

error C2440: 'reinterpret_cast' : cannot convert from 'class CIndepend *' to 'float'
        There is no context in which this conversion is possible
error C2440: 'reinterpret_cast' : cannot convert from 'class CIndepend *' to 'double'
        There is no context in which this conversion is possible

其实将指针转换为浮点数这种做法就很怪异嘛,你不觉得吗?这样有什么用啊?不过你一定要这样干的话也不是没有办法,看代码:

    float fTest = reinterpret_cast<float &>(pIndepend);
    
double dTest = reinterpret_cast<double &>(pIndepend);

加个小小的“&”符号就可以了,C++会不顾一切地把pIndepend这个变量当作一个float和double,把指针的值“理解”为float和double,当然,这样获得的浮点数的值是没什么实际意义的,因为指针的值是32位无符号整型,而浮点数有它自己特定的格式,这么一转换就牛头不对马嘴。你还可以这样写:

      float fTest = (float &)pIndepend;
      
double dTest = (double &)pIndepend;

效果一样,得到的也是无意义的值。

2、常规类型=>指针

就是反过来罗,可能不说大家都能猜到结果了。把常规类型转换为指针有什么用呢?可能有点用,比如我们在某些特殊的场合要求一个指针指向一个特别的地址,这时候我们可以直接给指针赋个值,当然了,这个值应该是整型:

    CIndepend * pIndepend;

    char cVal = 1;
    
short sVal = 2;
    
int iVal = 3;

    pIndepend = (CIndepend *)cVal;
    pIndepend 
= (CIndepend *)sVal;
    pIndepend 
= (CIndepend *)iVal;

这样是没问题的,那浮点数呢?——又来了,浮点数转换为指针?这怎么算啊?显然这是不行的,如果真的需要“转换”,那就先把浮点数转换为整型,然后再赋值给指针吧。这个时候,dynamic_cast和static_cast都是不行的。

3、基本类型转换

比如int转换为float,char转变为short,很多时候我们都“默认”了这种转换,即使没有显式指定用旧式转换还是static_cast。在这种类型的转换中,旧式转换和static_cast的表现非常地类似:

    double dVal = 5.0;

    char cVal = static_cast<char>(dVal);
    
short sVal = static_cast<short>(dVal);
    
int iVal = static_cast<int>(dVal);

    cVal = (char)(dVal);
    sVal 
= (short)(dVal);
    iVal 
= (int)(dVal);

而dynamic_cast还是不可行,那……reinterpret_cast呢?不妨试试看:

    double dVal = 5.0;

    char cVal = reinterpret_cast<char>(dVal);
    
short sVal = reinterpret_cast<short>(dVal);
    
int iVal = reinterpret_cast<int>(dVal);

一编译,就出下面的错误:

error C2440: 'reinterpret_cast' : cannot convert from 'double' to 'char'
        Conversion is a valid standard conversion, which can be performed implicitly or by use of static_cast, C-style. cast or function-style. cast
error C2440: 'reinterpret_cast' : cannot convert from 'double' to 'short'
        Conversion is a valid standard conversion, which can be performed implicitly or by use of static_cast, C-style. cast or function-style. cast
error C2440: 'reinterpret_cast' : cannot convert from 'double' to 'int'
        Conversion is a valid standard conversion, which can be performed implicitly or by use of static_cast, C-style. cast or function-style. cast

说这是个有效标准转换,请使用static_cast或者C风格的旧式转换。reinterpret_cast不是号称最宽松的转换么?怎么不行了?你一定要它行也是没问题的,和前面的那样,加个“&”符号:

    char cVal = reinterpret_cast<char &>(dVal);
    
short sVal = reinterpret_cast<short &>(dVal);
    
int iVal = reinterpret_cast<int &>(dVal);

但结果并不是你想要的结果,因为这样reinterpret_cast会不管三七二十一,直接把dVal的东西当作是一个char,short和int,很明显,double是有一定的格式的,将double直接“理解”为char,short或者int一定会有问题。

4、class的转换

上一节说的是基本类型,那对于类呢?一个类直接转换为另一个类,这看起来确实有些荒谬,不过强大而灵活的C++却偏偏允许了这种行为。看代码:

class CBase
{
public:
    CBase(){};
    
int m_iBase;
};

class CIndepend
{
public:
    CIndepend(){};
    
int m_iIndepend;
};

int main(int argc, char* argv[])
{
    CBase oBase;
    CIndepend oIndepend 
= reinterpret_cast<CIndepend &>(oBase);
    
return 0;
}

居然编译过去了,运行貌似也没什么问题,当然转换过程和前面的差不多,就是把oBase理解为一个CIndepend对象,这个赋值运算执行“位拷贝”,这种方式的转换在实际中是碰不到的,起码我想不出有什么理由使用它。这种情况下,其它的转换方式都是不可行的。

5、class=>指针 or 指针=>class

这种行为更怪异,class直接理解为指针?这其实是不可行的,跟前面提到的浮点数转换为指针一样,如果实在需要,就把class转变为整型,然后整型转换为指针:

CIndepend * pIndepend = reinterpret_cast<CIndepend *>(reinterpret_cast<unsigned int &>(oBase));

可这……这啥意思呢?哈哈,别问我。反过来,指针转换为class恐怕也令人费解:

    CDerived oDerived;
    CDerived 
*pDerived = &oDerived;
    CIndepend oIndepend 
= reinterpret_cast<CIndepend &>(pDerived);

能担当起这种怪异的工作的,唯reinterpret_cast是也,这样会产生什么后果呢?指针是个32位无符号整型,将它强制理解为一个CIndepend,然后作位拷贝,理所当然,oIndepend的值会被改变,而且还有访问越界的风险,导致内容混乱甚至程序崩溃。

6、指针之间的转换

前面一直没提起的一种转换就是dynamic_cast,因为它是最为严格的一种转换,它只能完成指针到指针的转换,而且还有限制。看这个:

    CDerived oDerived;
    CDerived 
*pDerived = &oDerived;
    
CIndepend *pIndepend = dynamic_cast<CIndepend *>(pDerived);

编译出错了:

error C2683: dynamic_cast : 'CDerived' is not a polymorphic type
        D:/work/CastTest/CastTest.cpp(13) : see declaration of 'CDerived'

因为CDerived和CIndepend没有继承关系,把dynamic_cast换成static_cast还是不行的,会出另外一个错:

error C2440: 'static_cast' : cannot convert from 'class CDerived *' to 'class CIndepend *'
        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style. cast or function-style. cast

编译器说这是没有关系的两个指针,应该用reinterpret_cast或者C风格的旧式转换,再看:

    //CDerived是CBase的子类
    CBase oBase;
    CBase 
*pBase = &oBase;
    CDerived 
*pDerived = dynamic_cast<CDerived *>(pBase);

基类指针转换为子类指针,行不行呢?出错,错误跟刚才的一样,记住,dynamic_cast仅仅可以把子类指针转换为基类指针,别的都不行!上面这段代码如果不用dynamic_cast,而是用static_cast,就能编译通过,static_cast的要求来得比较宽松。

OK,到这里为止,大家都知道什么时候用什么转换是可以的了,问题是C++为什么搞出怎么多转换出来呢?我想很大程度上是兼顾了安全性和灵活性,要想安全,class指针的转换就使用dynamic_cast;一般情况下我们认为,static_cast也是安全的;C风格的旧式转换则灵活一些,它允许任意类型指针之间的转换;而reinterpret_cast就更加了,什么乱七八糟都可以。那从功能强弱上排个序,我想从强到弱应该是:reinterpret_cast,旧式转换,static_cast,dynamic_cast。

Oh,还有一种转换,差点忘了,就是const_cast,不过这种转换比较特别,可以独立开来,它的功能就是去除一个变量的const属性,也就是说,允许修改常量的值,哈哈,修改常量的值?既然要修改常量的值,那为什么还要声明它为常量?——这也是C++灵活的一个体现。不过const_cast其实有个问题,有时候它并不能真正改变一个常量的值,关于这个,在我的另一篇博文中有讲述,此文还在csdn.net,晚些时候移过来,我再给个链接。

另一篇:

条款二:使用C++风格的类型转换
C风格的类型转换并不总是按他们应该的方式做事。他们做事像粗鲁的野兽,帮你把任何一种类型转换成另外一种。但是如果能细致的指明每个转换的目的就更好了。举例来说,将一个指向常量对象的指针转换为指向非常量对象的指针同将一个指向基类的指针转换为指向继承类的指针是有明显的区别的,前者是改变对象的常量性,后者是完全改变了对象的类型。传统的C风格的类型转换是无法区分这些的。(这没什么可惊奇的,C风格的类型转换是为C语言设计的,而不是为C++)。
类型转换的另一个问题是它们不容易被找到。从句法上来说,类型转换由标识符和一对园括号以及一些其它东西构成,而C++中到处都在用标识符和园括号。这使得一些有关类型转换的基本问题也很难回答,比如“程序中有用到类型转换吗?”。这是因为读程序的人经常注意不到类型转换,而一些工具像grep没办法将类型转换同与它句法类似的非类型转换结构区分开来。
C++通过引进四个新的类型转换操作符来克服C的类型转换的缺点,它们是static_cast,const_cast,dynamic_cast和reinterpret_cast。这样做的目的是要你知道当你从前习惯于这么写时:
(type) expression
现在你要这样写
static_cast<type>(expression)
举例来说,假设你要把一个int型的数据转换为double型的数据以使一个表达式能吃进一个int型的数据而产生一个浮点型的输出值。用C的类型转换,你会这么写:
int firstNumber, secondNumber;

...

double result = ((double)firstNumber)/secondNumber;
而现在,你应该这么写:
double result = static_cast<double>(firstNumber)/secondNumber;
这样,不管对人还是对程序来说,这个转换都是很明显的。
static_cast的作用和含义基本上跟C的类型转换是一样的。它也有同样的限制。比如,像C的类型转换一样,你不能用static_cast把一个struct变为int或是把一个double型变为指针型。而且,static_cast不能把表达式的常量性去掉,因为另有一个类型转换const_cast来做这个工作。
另一个新的C++类型转换的使用更有限制性。const_cast被用来去除一个表达式的常量性(constness)或可变性(volatileness)。通过const_cast,你可以把转换的重点放在去除表达式的常量性或可变性上(对编程者和编译器都是如此)。这个含义由编译器强制执行。如果你的const_cast不是被用来去除表达式的常量性或可变性,你的转换请求会被拒绝。看以下例子:
class Widget { ... };
class SpecialWidget: public Widget { ... };
void update(SpecialWidget *psw);
SpecialWidget sw;            // sw is a non-const object,
const SpecialWidget& csw = sw;     // but csw is a reference to
                    // it as a const object

update(&csw);      // error! can't pass a const
             // SpecialWidget* to a function
             // taking a SpecialWidget*

update(const_cast<SpecialWidget*>(&csw));
             // fine, the constness of &csw is
             // explicitly cast away (and
             // csw — and sw — may now be
             // changed inside update)

update((SpecialWidget*)&csw);
             // same as above, but using a
             // harder-to-recognize C-style. cast

Widget *pw = new SpecialWidget;

update(pw);       // error! pw's type is Widget*, but
             // update takes a SpecialWidget*

update(const_cast<SpecialWidget*>(pw));
             // error! const_cast can be used only
             // to affect constness or volatileness,
             // never to cast down the inheritance
             // hierarch
到目前为止,const_cast最常见的用法是去除常量性。
dynamic_cast是第二个有特殊用途的类型转换,它被用来完成向继承类的安全转换的动作。这就是说,你可以用它把一个指向基类的指针或引用对象转换成继承类或是兄弟基类的对象,这样你可以判断转换是否成功。失败的类型转换返回空指针(如果转换的是指针)或是发生异常(如果转换的是引用)。
Widget *pw;
...
update(dynamic_cast<SpecialWidget*>(pw));
             // fine, passes to update a pointer
             // to the SpecialWidget pw points to
             // if pw really points to one,
             // otherwise passes the null pointer
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
             // fine, passes to updateViaRef the
             // SpecialWidget pw points to if pw
             // really points to one, otherwise
             // throws an exception
dynamic_cast帮助你操纵继承结构。它不能被用到没有虚函数的类型上,也不能去除常量性:
int firstNumber, secondNumber;
...
double result = dynamic_cast<double>(firstNumber)/secondNumber;
             // error! no inheritance is involved
const SpecialWidget sw;
...
update(dynamic_cast<SpecialWidget*>(&sw));
             // error! dynamic_cast can't cast
             // away constness
如果你要把类型转换用到不涉及到继承的类型上,最好用static_cast。如果要去除常量性,最好用const_cast。
四中类型转换中的最后一种是reinterpret_cast。它的转换结果通常是执行时决定的。所以,reinterpret_cast的可移植性不强。
reinterpret_cast最常见的用法是转换函数指针类型。比如,假设你有一个指向特定函数的指针的数组:
typedef void (*FuncPtr)();     // a FuncPtr is a pointer
                  // to a function taking no
                  // args and returning void
FuncPtr funcPtrArray[10];     // funcPtrArray is an array
                  // of 10 FuncPtrs
为了一些无法理解的原因,你要把一个指向下面这个函数的指针放到funcPtrArray中去:
int doSomething();
没有类型转换你不能这么做,因为doSomething跟funcPtrArray中的类型不一致。funcPtrArray中的函数返回值是void,而doSomething返回值是int:
funcPtrArray[0] = &doSomething;   // error! type mismatch
reinterpret_cast可以让编译器按你的思路来做:
funcPtrArray[0] =          // this compiles
 reinterpret_cast<FuncPtr>(&doSomething);
函数指针的类型转换是无法移植的(C++不保证所有的函数指针都以相同的方式来表现),而且有时这种转换会产生错误的结果,所以应该避免这种转换除非你走投无路。
如果你的编译器还没有提供对新类型的支持,你可以用传统的类型转换代替static_cast,const_cast和reinterpret_cast。而且,你还可以用宏来模仿这种语法:
#define static_cast(TYPE,EXPR)    ((TYPE)(EXPR))
#define const_cast(TYPE,EXPR)    ((TYPE)(EXPR))
#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))
你可以这么用:
double result = static_cast(double, firstNumber)/secondNumber;
update(const_cast(SpecialWidget*, &sw));
funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);
当然,这种近似不一定能像真实的一样工作,但当你的编译器支持新类型时,它可以减少你更新代码的工作量。
现在还没有方便的方法来仿真dynamic_cast,但是很多库都提供函数实现安全的基于继承的类型转换。如果你没有这些函数而你又必须使用这种转换,你可以用以前的C的类型转换,但它不能在转换失败时给你提示。看起来没必要再说了,你可以像前面做的一样定义一个类似dynamic_cast的宏:
#define dynamic_cast(TYPE,EXPR)   (TYPE)(EXPR)
记住这个无法像一个真实的dynamic_cast一样工作,因为它无法在转换失败时给你提示。
我承认,新的类型转换看起来很丑而且不容易书写。如果你实在不愿使用新的类型转换,那就用C的类型转换吧,他们还是有效的。然而,新类型之所以看起来不漂亮,时因为它们的含义更细致并且更容易认识。使用新的类型转换的程序对人对编译器来说都更容易解读,它还使编译器可以诊断出错误的类型转换,这在以前是无法实现的。现在有强烈的争论要不要抛弃C的类型转换,但也可能有第三中观点:或许把类型转换变得很难看并且很难书写是一件好的事情。

 

补充

reinterpret_cast:
    reinterpret_cast类型转换函数将一个类型的指针,转换为另一个类型的指针。这种转换不用修改指针变量值的数据格式(不改变指针变量值),值需在编译时重新解释指针的类型就可做到。reinterpret_cast可以将指针值转换为一个整数,但是不能用了非指针类型的转换。

const_cast:
    const_cast类型转换函数用于去除指针变量的常量属性,将它转换为一个对应指针类型的普通变量。反过来,也可以将一个非常量的指针变量转换为一个常指针变量。这种转换时在编译期间做出的类型更改。

static_cast:
    static_cast主要用于基本类型之间和具有继承关系的类型之间的转换,这种转换一般会更改变量的内部表示方式,因此static_cast应用用于指针类型的转换,没有太大的意义,即使允许指针类型转换,也不及reinterpret_cast效率高。继承类与基类指针可以进行相互转换,都能编译通过,但基类指针转换为继承类指针,具有一定的危害性。

dynamic_cast:
     与静态static_cast相对,是动态dynamic_cast转换。这种转换是在运行转换分析的,并非在编译时进行,明显区别于上面3个类型转换操作。
     dynamic_cast只能在继承类对象的指针之间或引用之间进行类型转换。进行转换时,会根据当前运行对象的运行时类型信息(RTTI),判断类型对象之间的转换是否合法。dynamic_cast的指针转换失败,可以通过NULL指针检测,引用转换失败,则抛出一个bad_cast异常

转化的实质:

reinterpret_cast

C旧式转换

static_cast

dynamic_cast

抱歉!评论已关闭.