首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

[深入了解C++(一)]类型转换(Type Casting)

2012-11-03 
[深入理解C++(一)]类型转换(Type Casting)[深入理解C(一)]类型转换(Type Casting)罗朝辉 (http://blog.csd

[深入理解C++(一)]类型转换(Type Casting)

[深入理解C++(一)]类型转换(Type Casting)

罗朝辉 (http://blog.csdn.net/kesalin)CC许可,转载请注明出处

类型转换就是将给定类型的表达式转换为另一种类型。C++中的转型可分为两种:隐式类型转换和显式类型转换。下面将一一介绍。


一,隐式类型转换

隐式类型转换是C中的遗留物,在C++中并不推荐使用(C++有专门的转型操作符,见下文的显式转型)。将某种类型的对象拷贝到另一种不同类型的对象中时就会发生隐式转型。比如异型赋值,返回值(函数声明的返回值与代码块实际返回值不同的情况下),按值传递异型参数等情况均会发生隐式类型转换。

short a = 128;int b;b = a;

如上所示,short 类型的对象被赋值给 int 型的对象,这是C++语言内建支持的标准转换。

情形一:标准转换支持数值类型,bool以及某些指针之间相互转换。注意:某些转换可能会导致精度丢失,比如从 long 转换到 int。

情形二:可被单参调用(只有一个参数或多个参数但至少从第二个参数起均带有缺省值)的构造函数或隐式类型转换操作符也会引起隐式类型转换。比如:

class A {};class B
{
    public: B (A a) {}
    public: B (int c, int d = 0);
    public: operator double() const; 
};A a;
B b1 = a;
B b2 = 10;
B b3;
double d;
d = 10 + b3;

上面的代码里就存在只带有一个参数的构造函数,多个参数但至少从第二个参数起均带有缺省值以及用户自定义类型转换操作符这三种情况。

隐式类型转换是件麻烦事,它们很可能导致错误或非预期的函数被调用(参看ME 条款5);此外 C++ 也不能在一个转换过程中连续进行多余一次的用户自定义转换操作(即情形二中的转换),如下所示:接上面的代码,A (类型的对象,后略)可被隐式转换为 B,B 可被隐式转换为 C,但 A 却非常不合逻辑地不可被隐式转换为 C。

class C
{
    public: C(B b) {};
}
A a;
C c;C = a; // 错误!

因此应该尽量避免隐式类型转换,为此 C++ 提供了关键字 explicit 来规避可被单参调用的构造函数引起的隐式类型转换。但标准转换以及隐式类型转换操作符引起的转换只能交由那些追求至善至美的程序员来小心处理了。当然 C++ 语言还是提供了必要的工具来辅助那些追求至善至美的程序员,这些工具就是下面要讲的显式类型转换关键字:static_cast, const_cast, dynamic_cast 以及 reinterpret_cast。


二,显式类型转换

C++ 是一门强类型转换,因此不同自定义类型之间的转换必须进行显式转换,当然基础数据类型也可以进行显式转换。

short a = 10;int b;b = (int) a;    // c-like cast notationb = int (a);    // functional notation

以上是基础数据类型之间进行传统的强制类型转换。这种强制类型转换可以在两种指向不同类型对象的指针之间进行,这很可能是相当危险的事情。所以 C++ 提供四种转换操作符来细分显式类型转换:

static_cast <new_type> (expression)
const_cast <new_type> (expression)
dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)


static_cast

static_cast 很像 C 语言中的旧式类型转换。它能进行基础类型之间的转换,也能将带有可被单参调用的构造函数或用户自定义类型转换操作符的类型转换,还能在存有继承关系的类之间进行转换(即可将基类转换为子类,也可将子类转换为基类),还能将 non-const对象转换为 const对象(注意:反之则不行,那是const_cast的职责。)。

double d = 3.14159265;int i = static_cast<int>(d);
class A {};class B{public:    B (A a) {};};
A a;B b = static_cast<B>(a);
class CBase {};class CDerived: public CBase {};CBase * a = new CBase;CDerived * b = static_cast<CDerived *>(a);

注意:static_cast 转换时并不进行运行时安全检查,所以是非安全的,很容易出问题。因此 C++ 引入 dynamic_cast 来处理安全转型。


dynamic_cast 

dynamic_cast 主要用来在继承体系中的安全向下转型。它能安全地将指向基类的指针转型为指向子类的指针或引用,并获知转型动作成功是否。如果转型失败会返回null(转型对象为指针时)或抛出异常(转型对象为引用时)。dynamic_cast 会动用运行时信息(RTTI)来进行类型安全检查,因此 dynamic_cast 存在一定的效率损失。(我曾见过属于优化代码80/20法则中的20那一部分的一段游戏代码,起初使用的是 dynamic_cast,后来被换成 static_cast 以提升效率,当然这仅是权宜之策,并非好的设计。)

class CBase { };class CDerived: public CBase { };CBase b; CBase* pb;CDerived d; CDerived* pd;pb = dynamic_cast<CBase*>(&d);     // ok: derived-to-basepd = dynamic_cast<CDerived*>(&b);  // error: base-to-derived

上面的代码中最后一行 VS2010 会报如下错误:

error C2683: 'dynamic_cast' : 'CBase' is not a polymorphic type
IntelliSense: the operand of a runtime dynamic_cast must have a polymorphic class type

这是因为 dynamic_cast 只有在基类带有虚函数的情况下才允许将基类转换为子类

class CBase { virtual void dummy() {} };class CDerived: public CBase { int a; };int main ()
{
    CBase * pba = new CDerived;    CBase * pbb = new CBase;    CDerived * pd1, * pd2;    pd1 = dynamic_cast<CDerived*>(pba);
    pd2 = dynamic_cast<CDerived*>(pbb);    return 0;}

结果是:上面代码中的 pd1 不为 null,而 pd2 为 null。


dynamic_cast 也可在 null 指针和指向其他类型的指针之间进行转换,也可以将指向类型的指针转换为 void 指针(基于此,我们可以获取一个对象的内存起始地址const void * rawAddress = dynamic_cast<const void *> (this);)。


const_cast

前面提到 const_cast 可去除对象的常量性(const),它还可以去除对象的易变性(volatile)。const_cast 的唯一职责就在于此,若将 const_cast 用于其他转型将会报错。

void print (char * str){  cout << str << endl;}int main () {  const char * c = "sample text";  print ( const_cast<char *> (c) );  return 0;}

reinterpret_cast

reinterpret_cast 用来执行低级转型,如将执行一个 int 的指针强转为 int。其转换结果与编译平台息息相关,不具有可移植性,因此在一般的代码中不常见到它。reinterpret_cast 常用的一个用途是转换函数指针类型,即可以将一种类型的函数指针转换为另一种类型的函数指针,但这种转换可能会导致不正确的结果。总之,reinterpret_cast 只用于底层代码,一般我们都用不到它,如果你的代码中使用到这种转型,务必明白自己在干什么。


三,typeid:获取表达式的类型

typeid 定义在标准头文件<typeinfo>中,用于获取表达式的类型,它返回一个数据类型或类名字的字符串。当 typeid 用于自定义类型时,它使用 RTTI 信息来获取对象的动态类型。基于 typeid,我们可以构建出比较对象(动态)类型的操作。


四,使用原则:尽量避免类型转换操作;优先使用 C++ 风格的转型

1,鉴于类型转换的隐蔽,不安全,易引起非预期的函数调用,对象切割等等诸多问题,应该尽量避免类型转换操作。如使用 explicit 声明可被单参调用的构造函数,按引用传递参数或返回值,使用虚函数机制等等可避免类型转换;

2,若类型转换不可避免,优先使用 C++ 风格的新式类型转换。C++ 风格的类型转换一则易于辨识,二则有着其特有惯用手法,遵循这些惯用手法好处多多。


引用:

1,<Effective C++> 条款 27

2,<More Effective C++> 条款 02


追加测验:

class A {
public :
virtual ~A () {}
}

class B : private virtual A { }
class C : public A {}
class D : public B, public C {}

对于上述类,定义如下对象与引用:
A a1, B b1, C c1; D d1;
const A a2;
const A& ca1 = a1;
const A& ca2 = a2;

下面四个赋值表达式都正确么?如果正确又分别对应哪一种 C++ 风格的转换?
A * pa; B * pb; C * pc;
(一)pa = (A*) & ca1;
(二)pa = (A*) & ca2;
(三)pb = (B*) & c1;
(四)pc = (C*) & d1;


7楼garrettyou昨天 23:06
学习中,谢谢分享。。。
6楼zj510昨天 20:54
就发表一个意见,是有关于dynamic_cast的,确实我们有时可以使用dynamic_cast在运行时将一个基类指针转换成子类指针。但是个人不推荐这么使用。有关这个内容,C++ PRIMER里面专门有一个章节来介绍,可以参考。而且我发现google code convention里面特别强调不要使用dynamic_cast,如果真的避免不了使用这个,通常是设计上面出问题了。以下内容来自google code convention:nDecision: nnDo not use C-style casts. Instead, use these C++-style casts. nnUse static_cast as the equivalent of a C-style cast that does value conversion, or when you need to explicitly up-cast a pointer from a class to its superclass. nUse const_cast to remove the const qualifier (see const). nUse reinterpret_cast to do unsafe conversions of pointer types to and from integer and other pointer types. Use this only if you know what you are doing and you understand the aliasing issues. nDo not use dynamic_cast except in test code. If you need to know type information at runtime in this way outside of a unittest, you probably have a design flaw.
Re: zdarkalone昨天 22:25
回复zj510ndynamic_cast主要用于rtti。用dynamic_cast不能完全说是设计错误。n特别是使用面向对象的类库时(比如Qt),一旦类型信息被抹掉了,除了dynamic_cast外别无他法。n这是因为static_cast要求编译期类型信息已知,而第三方库编译时不可能获知由用户派生的类的信息,所以static_cast用不了。n一定要用static_cast,解决方法是建立一个平行的rtti框架,和原有库整合在一起。但很多时候不划算,没dynamic_cast方便。nrtti的终极解决方案是全部用模板实现,完全不用二进制复用,这样可以做到所有类型信息均不丢失。
Re: kesalin昨天 13:34
回复zj510nn嗯,是这样的,文中第四节也说了尽量避免类型转型操作。
5楼eternalwish昨天 17:58
写得真好,学习了
4楼liu_anan昨天 16:26
确实很深入啊nhttp://www.docvv.com/c-00001.html看看这个论文代写。
3楼songpengyu521昨天 10:44
把点点滴滴记录下来,分享他人也是很好的想法~支持楼主!
2楼cyblueboy83昨天 10:24
同意2楼的意见!
1楼bigwater101前天 08:38
抢沙发咯,不过话说深入,我看了之后,好像也不是什么太高深的内容。都知道的。
Re: kesalin前天 09:22
回复bigwater101nn可别小看类型转型,有时候还真不容易看出来。小小检验下:nnclass A {npublic :nvirtual ~A () {}n}nnclass B : private virtual A { }nclass C : public A {}nclass D : public B, public C {}nn对于上述类,定义如下对象与引用:nA a1, B b1, C c1; D d1;nconst A a2;nconst A& ca1 = a1;nconst A& ca2 = a2;nn下面四个赋值表达式都正确么?如果正确又对应哪一个C++ 风格的转换?nA * pa; B * pb; C * pc;n(一)pa = (A*) & ca1;n(二)pa = (A*) & ca2;n(三)pb = (B*) & c1;n(四)pc = (C*) & d1;

热点排行