在旧式转型(cast)下面隐藏着一些见不得人的、鬼鬼祟祟的东西。他们的语法形式使其在一段代码中通常很难引起人们的注意,但它们可能会搞一些可怕的破坏活动,就好比你冷不丁被一个恶棍猛击一拳似的。让我们阐明旧式转换的含义。显然,在最初的C语法中,在表达式中将类型加括号就是旧式转型:

  char * hopeItWorks = (char *)0x00ff000;//旧式转型

  C++引入了另一种转型,即采用函数形式的转型语法来表达同样的意思:

  typedef char * pChar;

  hopeItWorks = pChar( 0x00ff0000 ); //函数形式/旧式转型

  函数形式的转型也许看上去比它那可怕的老祖先斯文一些,但实际上同样龌龊,应该像躲避瘟疫一样躲避他们。

  诚实可靠的程序员使用新式转型操作符,因为它们能够更精确地表达意思。有四个新式转型操作符,每一个都有这特定的用途。

  const_cast操作符允许添加和移除表达式中类型的const 或 volatile 修饰符:

  const Person * getEmployee(){...};

  Person * anEmployee = const_cast< Person * >( getEmployee());

  在以上代码中,使用const_cast来剥除getEmployee返回类型中的const修饰符。可以使用旧式转型获得相同的效果:

  anEmployee = (Person *)getEmployee();

  但使用const_cast的做法更好,这有几个方面的原因。首先,它看上去丑陋、醒目,就像一个受伤的拇指一样从代码中伸出来。这是件好事,因为任何形式的转型都存在危险。所以,它们写起来应该很痛苦,只有当不得已时才键入它们。它们还应该易于发现,因为无论何时当代码中出现bug时,人们应该首先检查转型这个嫌犯。其次,const_cast要比旧式转型的威力小,因为它只影响类型修饰符。这个限制同样是件好事,因为它允许我们更精确地表达意图。使用旧式转型等于告诉编译器,“你给我闭嘴,因为我希望将getEmployee的返回类型转换为Person * ”。而使用const_cast则等于告诉编译器,“我只希望将getEmployee的返回类型的const去掉”。就目前来看,这两种语句没有多大的差别(尽管实际上他们都很无礼),但是对getEmployee函数进行了一些维护性的修改后,情况就不同了:

  const Employee * getEmployee();//大修改

  旧式转型所强加的规则现在任然有效,编译器将不会对从const Employee* 到Person * 这个不正确的转换做出反应,但如果使用const_cast,编译器将会发出抱怨,因为这么剧烈的转换已经超出了它的能力范围。简而言之,const_cast由于旧式转型,因为它更丑陋、更难用,并且威力更小。

  static_cast操作符用于相对而言可跨平台移植的转型。最常见的情况是,它用于将一个继承层次结构的基类的指针或引用,向下转型为一个派生类的指针或引用:

  Shape * sp = new Circle;

  Circle * cp = static_cast<Circle *>( sp );向下转型

  在这个例子中,使用static_cast将会产生正确的代码,因为sp确实指向一个Circle对象。然而,如果sp指向其他类型的Shape( Shape的其它派生类),那么当时使用cp时,很可能会得到某种运行期间错误。因此,重申一遍,虽然这种新式转型操作符比旧式转型安全一些,但还不够安全。

  注意,static_cast无法像const_cast那样改变类型的修饰符。这意味着,有时需要使用由两个新式转型操作符构成的转型序列,来获得单个旧式转型所能表达的效果:

  const Shape * getNextShape(){...}

  //...

  Circle * cp = static_cast< Circle * > ( const_cast< Shape * >( getNextShape()));

  标准没有对reinterpre_cast的行为提供太多的保证,不过它通常的行为可以顾名思义。它从位( bit )的角度看待一个对象,从而允许将一个东西看作另一个完全不相同的东西:

  hopeItWorks = reinterpre_cast< char * >( 0x00ff0000); //把int假装成指针

  int * hopeless = reinterpre_cast< int * >( hopeItWorks );//把char* 假装成 int *

  这类东西在底层编码里偶尔非用不可,但它可能不具移植性。你要谨慎行事。注意区分当分别使用reinterpre_cast 和static_cast将指向基类的指针向下转型为指向派生类的指针时的行为。reinterpre_cast通常只是将基类指针假装成一个派生类指针而不改变其值,而static_cast(以及旧式转型---从这个角度来说) 则将执行正确的地址操作。

  在类层次结构的范畴谈转型,就会涉及到dynamic_cast。dynamic_cast通常用于执行从指向基类的指针安全地向下转型为指向派生类的指针。不同于static_cast的是,dynamic_cast仅用于对多态类型进行向下转型(也就是说,被转型的表达式的类型,必须是一个指向带有虚函数的类类型的指针),并且执行运行期间检查工作,来判定转型的正确性。当然,这种安全性的获得是要付出代价的。使用static_cast通常无需付出(或极少付出)运行时代价,而使用dynamic_cast则意味着要付出显著的运行时开销。

  const Circle *cp = dynamic_cast< const Circle *>( getNextShape());

  if ( cp ) {...}

  如果getNextShape返回一个指向Circle的指针(或者从Circle共有派生的东西,换句话说,一些和Circle之间存在着 is-a 关系的东西。), 那么转型是成功的,并且 cp 将会指向一个Circle。注意,我们可以将生命和测试结合与同一个表达式中:

  if( const Circle *cp = dynamic_cast<const Circle *>( getNextShape())){...}

  这样做是有好处的,因为它将变量cp的作用于限制在if语句之内,因此,当不再使用它时,cp将会离开作用域(并被销毁)。

  有关dynamic_cast_一个不太常见的用法是对引用类型执行向下转型:

  const Circle & rc = dynamic_cast<const Circle &>( *getNextshape() );

  这个操作类似于对指针类型的dynamic_cast操作,不过如果转型失败,操作法将抛出一个std::bad_cast异常而不是仅仅返回一个空指针(记住,不存在空引用)。习惯上,对一个指针进行dynamic_cast等于在说:“这个Shape指针真的指向一个Circle吗?如果不是,我可以处理这种情况”。而对一个引用执行dynamic_cast则等于声明一个不变式(invariant):“这个Shape应该是一个Circle,否则,肯定是哪儿出了严重的错误”。

  与其它新式转型操作符相比,dynamic_cast只是偶尔需要使用,但因为背上了“安全”的名声,所以常常被滥用。

第一个代码例子:(Visual C++ 6.0)

#include<iostream>

class Shape{
public:
virtual ~Shape(){};
virtual void draw()=;
};
class Rollable{
public:
virtual ~Rollable(){};
virtual void roll()=;
};
class Circle:public Shape,public Rollable{
public:
void draw(){std::cout<<"\n:画圆\n";}
void roll(){std::cout<<"\n:滚圆\n";}
};
class Square:public Shape{
public:
void draw(){std::cout<<"\n:画矩形\n";}
};
class Ball:public Shape,public Rollable{
public:
void draw(){std::cout<<"\n:画球\n";}
void roll(){std::cout<<"\n:滚球\n";}
}; int main(int argc, char* argv[])
{
Shape * spCircle=new Circle;
Shape * spSquare=new Square;
Shape * spBall = new Ball; //Circle * rollcircle=spCircle; //error C2440:'initializing':cannot convert from 'class Shape *' to 'class Circle *'
Circle * rollcircle = static_cast<Circle*>(spCircle);
rollcircle->roll(); //输出:滚圆 //Circle * rollcircle=new Circle;
Ball * rollball=new Ball; if(Rollable * roller=dynamic_cast<Rollable *>(rollcircle)) //可以
roller->roll();//输出:滚圆
if(Rollable * roller=dynamic_cast<Rollable *>(rollball)) //可以
roller->roll();//输出:滚球
if(Shape * shape=dynamic_cast<Shape*>(rollcircle)) //可以
shape->draw();//输出:画圆 return ;
}

第二个代码例子(C++Builder6.0):

 class A{
public:
virtual A* func(){};
};
class B:public A{
public:
B* func(){} //重写 A A::func() ,重写后的返回类型为B ,因为 B继承于A (B is a A) ,这叫:协变返回类型
};
class C{
public:
virtual void func(){}
};
class D{}; A *a=new A;
B *b=new B;
C *c=new C;
D *d=new D;
A *a_1tmp=dynamic_cast<A*>(b); //编译通过
A *a_2tmp=dynamic_cast<A*>(c); //编译通过
//A *a_3tmp=dynamic_cast<A*>(d); //E2307Type'D'is not a defined class with virtual functions A *a_4tmp=static_cast<A*>(b);//编译通过
//A *a_5tmp=static_cast<A*>(c); //E2031 Cannot cast from 'C*' to 'A*'
//A *a_6tmp=static_cast<A*>(d); //E2031 Cannot cast from 'D*' to 'A*'

新式转型操作符[条款9] --《C++必知必会》的更多相关文章

  1. 《C++必知必会》学习笔记

    转载:http://dsqiu.iteye.com/blog/1734640 条款一 数据抽象 抽象数据设计遵循步骤:(1)为类型取一个描述性的名字.(2)列出类型所能执行的操作,不要忘了初始化(构造 ...

  2. C++必知必会(1)

    条款1数据抽象 抽象数据类型的用途在于将变成语言扩展到一个特定的问题领域.一般对抽象数据类型的定义须要准训下面步骤: 1.     为类型取一个描写叙述性的名字 2.     列出类型所能运行的操作 ...

  3. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

  4. 《MySQL 必知必会》读书总结

    这是 <MySQL 必知必会> 的读书总结.也是自己整理的常用操作的参考手册. 使用 MySQL 连接到 MySQL shell>mysql -u root -p Enter pas ...

  5. 《SQL必知必会》学习笔记(一)

    这两天看了<SQL必知必会>第四版这本书,并照着书上做了不少实验,也对以前的概念有得新的认识,也发现以前自己有得地方理解错了.我采用的数据库是SQL Server2012.数据库中有一张比 ...

  6. SQL 必知必会

    本文介绍基本的 SQL 语句,包括查询.过滤.排序.分组.联结.视图.插入数据.创建操纵表等.入门系列,不足颇多,望诸君指点. 注意本文某些例子只能在特定的DBMS中实现(有的已标明,有的未标明),不 ...

  7. 0005 《SQL必知必会》笔记01-SELECT语句

    1.SELECT基本语句: SELECT 字段名1,···,字段名n FROM 表名 2.检索所有字段,用"*"替换字段名,这会导致效率低下 SELECT * FROM 表名; 3 ...

  8. 《MySQL必知必会》[01] 基本查询

    <MySQL必知必会>(点击查看详情) 1.写在前面的话 这本书是一本MySQL的经典入门书籍,小小的一本,也受到众多网友推荐.之前自己学习的时候是啃的清华大学出版社的计算机系列教材< ...

  9. mysql必知必会系列(一)

    mysql必知必会系列是本人在读<mysql必知必会>中的笔记,方便自己以后查看. MySQL. Oracle以及Microsoft SQL Server等数据库是基于客户机-服务器的数据 ...

随机推荐

  1. 透過 bc 計算 pi

    echo "scale=${num}; 4*a(1)" | bc -lq例如: echo "scale=5000; 4*a(1)" | bc -lq 4*a(1 ...

  2. 多个return和一个return

    //一个returnnamespace CleanCSharp.Methods.Dirty { class MethodExitPoints { public string GenerateAgeAp ...

  3. ubuntu 16.04安装 navicat

    原文地址:http://www.cnblogs.com/wbJson/p/5655537.html 下载地址:http://download2.navicat.com/download/navicat ...

  4. docker实用命名

    删除tag/镜像: #删除tag docker rmi index-dev.qiniu.io/cs-kirk/nginx:latest docker rmi index-dev.qiniu.io/cs ...

  5. iOS开发之--最简单的导航按钮更换方法/导航颜色的改变

    有很多时候,我们需要用到导航,那么更换导航的时候,是在那用那修改,还是自定义一个导航,或者是声明一个代理方法,经过查资料和对导航属性的一些了解,用一种方法最为简单,就是在入口类里面添加一个方法,调用偏 ...

  6. css+jq写的小小的移动端按钮的动画改变(三个很闲变成一个叉号)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. vue2.0非父子间进行通讯

    在vue中,父组件向之组件通讯使用的是props,子组件向父组件通讯使用的是$emit+事件,那非父子间的通讯呢,在官方文档上只有寥寥数笔, 概念很模糊,这个空的vue实例应该放在哪里呢,光放文档并没 ...

  8. HDU 3450 Counting Sequences(线段树)

    Counting Sequences Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/65536 K (Java/Other ...

  9. 污染Bootstrap modal 通过 css选择器 避免

    w 对框架的掌握.改进. 0-存在重复代码,需要改正,js timepicker框架传入类名: 1-大量的点击块,怎样避免对每个块重复写modal? <style> .w > td ...

  10. Java 之设计模式(总述)

    1. 面向对象设计原则 单一职责原则: 一个类只负责一个功能领域中的相应职责 开闭原则: 软件实体应对扩展开放,而对修改关闭; 里氏代换原则: 所有引用基类对象的地方能够透明地使用其子类的对象; 依赖 ...