类型转换

  rel="File-List" href="file:///C:%5CUsers%5CChendx%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CUsers%5CChendx%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CUsers%5CChendx%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

隐式类型转换

Boolean Conversions

       译为bool转换。当指针、ints和浮点数被赋给一个bool变量时,非0值会隐式转换为true0值会隐式转换为false

Integral Conversions

       译为整型转换。当不同类型的ints之间相互赋值时,要遵循如下三条规则:

1.       如果目的数据类型是signed,那么当源数据超过了目的数据类型的取值范围,其结果依赖于实现;

2.       如果目的数据类型是unsigned,那么当源数据超过了目的数据类型的取值范围,则截取源数据的低位给目的数据;

3.       boolenum可以隐式转换成能够容纳其最大值的最小位数的ints

Floating Point Conversions

译为浮点转换。是一种当不同类型的浮点数之间相互赋值时进行的转换——主要是单精度与双精度间的转换。转换的核心是如果源数据在目的数据类型的取值范围中,则可以转换;否则转换的行为就是未定义的。但是由于浮点数的精度问题,可能找不到与源数据相等的目的数据,那么与源数据毗邻的两个目的数据都有可能被取到,这依赖于具体的实现。

Floating-Integral Conversions

译为浮点-整型转换。这是浮点数与ints数相互赋值时发生的转换。主要分为整型转到浮点型和浮点型转到整型两个部分:

1.       浮点转整型。浮点型的小数部分被舍去。如果浮点数大于目的数据类型的取值范围时,转换的行为是未定义的。

2.       整型转浮点。增加精度就是了。不过,当一个整型的数据是浮点数无法精确表示的话,会损失精度。例如:

float f = 1234567890;

int i = 1234567890;

f = i;

中,f不等于1234567890,而是等于1234567936

Integral promotion

译为整型提升。设计整型提升的目的是为了让算术计算中的ints(charshort int)型的操作数占据一个合适的长度(nature size),从而提高计算的效率。了解内存对齐的人都知道,在32位机器上CPU一次从内存读取的数据是4个字节,这时4个字节(int)就是合适的长度了(nature size)。也就是说CPU直接读出数据就可以无需经过任何处理的使用,无疑就提高了计算的效率了。从另一个角度来说,如果将charshort int提升到int,在扩大表示范围的同时,自然也减少了计算时溢出的可能性。

可以看出,一般而言整型提升是在算术计算中使用,我会在接下来的算术转换中指出整型提升的使用的时机。不过整型提升还会用在可变参数函数的实现中:因为函数原型的缺乏,函数参数的类型和数目是事先未知的,所有的参数(除了固定的参数)都是按照整型提升的规则存储在函数所在的frame中——在变参函数中,C++为了保持与C语言的兼容性,同时会将float提升为double

现在我们明确了整型提升的设计目的和适用范围了,接下来讨论整型提升的规则:

1.       signedunsignedcharshortshort int在算式中将会被转换为int:如果算式中的某个值超过了int的取值范围,则都会转换为unsigned int

2.       在有wchar_t类型和enum类型的算式中,这两种类型的值会依次测试intunsigned intlongunsigned long四种类型。一旦遇到能容纳其最大值的类型就结束测试,转换成该类型。

3.       位域在算式中的转换遵循规则1。但如果某位域的值超过了unsigned int的取值范围,则不会使用整型提升。(这从另一个角度证明,位域并非局限于一个字节以内)

4.       bool类型的值会转化为inttrue转换为1false转换为0.

从这一节我们了解到整型提升是一般算术转换中的一部分,而且还会在可变参数函数实现中的宏var_arg内使用:进一步的,在var_arg中会额外地将float提升为double

Arithmetic Conversions

       译为算术转换。以下是摘抄自C++ Primer Plus的原文。模拟了编译器对一个表达式进行转换的流程。注意,整个处理主要分为两个部分:首先,如果表达式中有浮点数的存在,则将所有数据转化为表达式中取值范围最大的那个浮点数据对应的浮点类型再计算;然后,如果表达式中没有浮点数,则首先对表达式使用整型提升规则。然后就像处理有浮点数的表达式一样,将所有数据转化为表达式中取值范围最大的那个数据对应的ints类型,最后再计算;

1. If either operand is type long double, the other operand is converted to long double.

2. Otherwise, if either operand is double, the other operand is converted to double.

3. Otherwise, if either operand is float, the other operand is converted to float.

4. Otherwise, the operands are integer types and the integral promotions are made.

5. In that case, if either operand is unsigned long, the other operand is converted to

unsigned long.

6. Otherwise, if one operand is long int and the other is unsigned int, the conversion

depends on the relative sizes of the two types. If long can represent possible unsigned

int values, unsigned int is converted to long.

7. Otherwise, both operands are converted to unsigned long.

8. Otherwise, if either operand is long, the other is converted to long.

9. Otherwise, if either operand is unsigned int, the other is converted to unsigned int.

10. If the compiler reaches this point in the list, both operands should be int.

小结

       总的来说,类型转换应该遵循自下转向上的原则,这样才能保证精度不损失(注意,intfloat不一定);但凡是自上而下的转换,一旦源数据大小超过了目标数据类型取值范围,其行为是未被定义的——除了bool转换和整型转换中目标数据类型是unsigned的转换。整型提升是一个比较陌生的概念,需要特别注意它在算术转换中使用的时机和在变参函数实现中使用的位置。


 

显式类型转换

C-Style

       这是很熟悉的方式,格式如下:

Type des = (Type)Ori

需要注意的是,(Type)Ori并没有修改Ori值,而是产生了一个Type类型的新值(这是一个rvalue),并将其赋值给des。而在此过程中,内建数据类型和类的处理是不一样的:如果Type是内建的数据类型(ints和浮点数),那么取址操作符&是绝对不能在表达式的括号前使用的——有人用反汇编跟踪过,发现当Type是内建类型时,新产生的值是在寄存器而非内存中,自然是无法用&取地址的;而如果Type是类,那么新产生的值会在内存中,所以取址操作符&就可以在表达式的括号前使用,例程如下:

#include <iostream>

using namespace std;

class tTmpObject{

public:

       tTmpObject(){

              cout<<"I'm constructor of father!/n";

       }    

       tTmpObject(const tTmpObject& rn){

              cout<<"I'm Copy con of father!/n";

       }

       ~tTmpObject(){

              cout<<"I'm de of father!/n";

 

       }

       void myprint(){ cout<<"father/n";}

};

class tSub: public tTmpObject{

public:

       tSub(){

              cout<<"I'm constructor of Son!/n";

       };

       tSub(const tSub& rn){

              cout<<"I'm Copy con of son!/n";

       }

       ~tSub(){

              cout<<"I'm de of son!/n";

       }

};

int main()

{    

       tSub s;

       tTmpObject *k ;

       k = &(tTmpObject)s; //新产生了一个tTmpObject对象,调用了tTmpObject的复制构造函数

       tSub *p = &s;

       cout<<&p<<endl<<&k;   //k!=p

    float *fp = &(float)i;  //错误

}

程序中,k指向的地址和p指向的地址是不一样的,也就是说程序通过k = &(tTmpObject)s语句新产生了一个tTmpObject的对象。有点奇怪吧,用指针k通过对象转换获取一个地址,结果产生了新的对象,呵呵。

C++-Style

       上面这种C-Style的转换方式在Bjarne Stroustrup看来是不严谨的,因此C++通过引进四个新的类型转换操作符企图克服C风格类型转换的缺点,这四个操作符如下所示:

static_cast:执行上面所提到的隐式转换罢了,它并不能实现C风格中某些任意不相干类型间的转换。它是唯一一个可以处理对象(非指针非引用)的转换操作符。但是如果在进行up-casting的时候,最好用指针转换。还有一点,static_cast也不能进行暴力转换,如,将某个类的指针指向另一个不相干的类,要做这样的事情,就必须用C-style的转换了。

const_cast:仅仅适用于指向constvolatile对象指针转换;也就是说,只是可以把一个指向constvolatile的指针的constvolatile特性去除,但是一样不能修改constvolatile对象的值。我想这个最大的用处就在函数的参数传递中吧。

dynamic_cast:这个操作符的作用是使父类的指针(引用)能够转化为子类的指针(引用),也就是常说的向下映射(Downcasting)。不过,由于该操作符检测匹配性(源指针指向的真实Object必须是目标指针的父类或是自身)用到了RTTI,所以会降低运行效率,在能肯定转化正确的情况下,可以用static_cast替代。dynamic_cast还有几个需要注意的地方:第一,该操作符只用于指针和引用;第二,如果不符合转换条件,对指针赋NULL,对引用则抛出一个bad_cast异常。

reinterpret_cast:其转换结果几乎都是执行期定义(implementation-defined)。它也是仅仅适用于指针的。这是一种底层的转换,它将一切都视为纯粹的内存地址,也就是说可以用一个int指针直接指向一个类。因此,使用reinterpret_casts的代码很难移植,也很不安全。最普通的用途就是在函数指针类型之间进行转换。

以上几个操作符具体在使用中的正确语法为:

static_cast<type>(expression)

自定义类型转换

       这是牵涉到非继承性类之间、类和C内建类型之间的特殊的类型转换。主要有两种:构造函数转换法和conversion operator转换法。

构造函数转换法

对于某一特定类MyClass,定义两个构造函数如下:

MyClass(int i){…}

MyClass(OtherClass o){…}

再定义全局函数如下:

{…}

那么,可以如下使用MyClass

OtherClass o;

MyClass t1=2;

MyClass t2=o;

func(t1);; func(o);

可以看出,这看上去是完成了从intMyClass,从OtherClassMyClass的转化,只不过使用的是MyClass不同的构造函数。不过这实际上是一种伪转换,因为在函数func中使用的还是MyClass类,而非OtherClass或是int变量。很奇妙,不是么?需要特别指出的一点就是,如果还定义了一个全局函数为

{…}

那么func(2)调用的不是func(MyClass)而是func(int)!这涉及到函数重载时的优先选择问题,见附录。

例程如下:

#include <iostream>

using namespace std;

class Otherclass

{

 

};

class MyClass

{

public:

       MyClass(){cout<<"default constructor!/n";}

       MyClass(int i){cout<<"int constructor!/n";}

       MyClass(Otherclass o){cout<<"Otherclass constructor!/n";}

};

void func(MyClass test){}

int main()

{

       MyClass t;

       MyClass t1 = 1;

       Otherclass o;

       MyClass t2 = o;

       func(t);

       func(1);

       func(o);

       return 0;

}

//输出为:

//default constructor!

//int constructor!

//Otherclass constructor!

//int constructor!

//Otherclass constructor!

//Press any key to continue . . .

总的来说,如果想使用这种方法,只需要在指定类中定义拥有不同类型参数的构造函数,即可将那些参数类型的对象在需要指定类的情况下转为指定类的对象。实际上,在操作符重载的时候就会用到这个机制,例程如下:

#include <iostream>

using namespace std;

 

class tOperator

{

public:

    int i;

    tOperator(int ii):i(ii){}

    const tOperator operator+(const tOperator& cr) {

       cout<<"I'm tOperator's member function!/n";

       return tOperator(this->i+cr.i);

    }

};

 

const tOperator operator+(const tOperator& crl,const tOperator& crr) {

    cout<<"I'm a global function!/n";

    return tOperator(crl.i+crr.i);

}

 

int main()

{

    tOperator to(1);

    tOperator to1(2);

    tOperator to2 = 5+to;  //用到了该特性,将转化为tOperator对象

    cout<<to2.i;

}

       不过,在某些情况下,我们并不需要进行这种转换,但是又需要保留所有的构造函数。这时,我们可以在构造函数的前面加上explicit关键字即可禁止这种转换

conversion operator转换法

       这个是更加有意思的转换。如果说构造函数转换法是一种伪转换的话,那么通过conversion operator的转换就是真正的转换了。不多说,例程如下(这是一个从《ANSI-ISO C++ Professional Programmer's Handbook》拿来的例程,只是修改了一下)

#include <iostream>

using namespace std;

struct DateRep //legacy C code

{

       char day;

       char month;

       short year;

};

class Date // object-oriented wrapper

{

private:

       DateRep dr;

public:

       operator DateRep () const { return dr;} // automatic conversion to DateRep

       operator int () const {return 1;}

};

void func(DateRep d){cout<<"funcDateRep/n";}

void funci(int i){cout<<"funci/n";}

int main()

{

       Date d;

       func(d);

       funci(d);      

       return 0;

}

//输出为

//funcDateRep

//funci

//Press any key to continue . . .

看,Date类可以作为funcfunci的参数!这都是因为Date中两个conversion操作符(大红色标出)的作用。现在,在funcfunci中使用的就必须是DateRep类和int型了。其实,可以自己实现一个将string类用在strcmp等接受const char*的函数中的封装类。

       conversion操作符的定义规则如下:

1.       以关键字operator开头;

2.       后面接需要转换为的数据类型,事实上这就是返回值——如上面程序中的intDateRep

3.       函数的()中不许有参数;

4.       在函数体中返回前面指定的数据类型。

附录

重载函数的选择规则

       这里暂时不考虑类继承树中的问题,假设考虑的重载函数们都位于同一个名词空间中,那么选择的规则如下:

1.       首先编译器进行精确的匹配。也就是说,不对参数进行任何形式的convert(除了数组名转为指针,函数名转为指针,Type转为const Type),直接寻找能匹配的函数。

2.       使用整型提升进行匹配。嗯,是C风格的整型提升,包括float提升为doubledouble提升为long double

3.       使用隐式转换进行匹配。

4.       使用自定义类型转换进行匹配。

开始处理可变参数的类型匹配。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值