隐式转换(implicit conversion)

short a=;
int b;
b=a;

short是两字节,int是四字节,由short型转成int型是宽化转换(bit位数增多),编译器没有warning,如下图所示。宽化转换(如char到int,int到long long,int到float,float到double,int到double等)构成隐式转换,编译器允许直接转换。

但若反过来

double a=;
short b;
b=a;

此时,是从8字节的double型转成2字节的short型变量,是窄化转换,编译器就会有warning了,如下所示,提醒程序员可能丢失数据。不过需要注意的是,有些隐式转换,编译器可能并不给出warning,比如int到short,但数据溢出却依然会发生。

C风格显式转换(C style explicit conversion)

要去掉上述waring很简单,熟悉C语言的程序员知道,有两种简单的写法(C风格转换与函数风格转换):

double a=2000.3;
short b;
b = (short) a; // c-like cast notation
b = short (a); // functional notation

如下图所示,此时warning就没了

这种显式转换方式简单直观,但并不安全,举一个父类和子类的例子如下:

// class type-casting
#include <iostream>
using namespace std; class CDummy {
float i,j;
CDummy():i(),j(){}
}; class CAddition:public CDummy
{
int *x,y;
public:
CAddition (int a, int b) { x=&a; y=b; }
int result() { return *x+y;}
}; int main () {
CDummy d;
CAddition * padd;
padd = (CAddition*) &d;
cout << padd->result();
return ;
}

编译器不报任何错,但运行结果出错,如下图所示:

究其原因,注意这一句:padd = (CAddition*) &d;

  此时父类的指针&d被C风格转换方式强制转成了子类的指针了,后面调用了子类的方法result,需要访问*x,但指针指向的对象本质还是父类的,所以x相当于父类中的i,y相当于父类中的j,*x相当于*i,但i是float型变量(初始化为100),不是地址,所以出错,如果程序员正是鲁莽地对这个地址指向的内存进行写入操作,那将可能会破坏系统程序,导致操作系统崩溃!

  这里有一个重要概念,CAddition*是子类的指针,它的变量padd可以调用子类的方法,但是它指向的是父类的对象,也就是说padd指向的内存空间里存放的是父类的成员变量。深入地说,数据在内存中是没有“类型”一说的,比如0x3F可能是字符型,也可能是整型的一部分,还可能是地址的一部分。我们定义的变量类型,其实就是定义了数据应该“被看成什么”的方式。

  因此padd类指针实质是定义了取值的方式,如padd->x就是一并取出内存空间里的0号单元至3号单元的值(共4个字节),将其拼成32位并当作指针,padd->y则取出内存空间里的4号单元至7号单元(共4个字节),将其拼成32位并当作int型变量。但实际上padd指向的是父类的对象,也就是前4个字节是float型变量,后4个字节也是float型变量。

从这里可以看出,程序员的这种转换使编译器“理解”出错,把牛当成马了。从上可见,用C风格的转换其实是不安全的,编译器无法看到转换的不安全。

上行转换(up-casting)与下行转换(down-casting)

  看到这个,读者可能会问,哪些转换不安全?根据前面所举的例子,可以看到,不安全来源于两个方面:其一是类型的窄化转化,会导致数据位数的丢失;其二是在类继承链中,将父类对象的地址(指针)强制转化成子类的地址(指针),这就是所谓的下行转换。“下”表示沿着继承链向下走(向子类的方向走)。

  类似地,上行转换的“上”表示沿继承链向上走(向父类的方向走)。

  我们给出结论,上行转换一般是安全的,下行转换很可能是不安全的。

  为什么呢?因为子类中包含父类,所以上行转换(只能调用父类的方法,引用父类的成员变量)一般是安全的。但父类中却没有子类的任何信息,而下行转换会调用到子类的方法、引用子类的成员变量,这些父类都没有,所以很容易“指鹿为马”或者干脆指向不存在的内存空间。

  值得一说的是,不安全的转换不一定会导致程序出错,比如一些窄化转换在很多场合都会被频繁地使用,前提是程序员足够小心以防止数据溢出;下行转换关键看其“本质”是什么,比如一个父类指针指向子类,再将这个父类指针转成子类指针,这种下行转换就不会有问题。

针对类指针的问题,C++特别设计了更加细致的转换方法,分别有:

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

可以提升转换的安全性。

static_cast <new_type> (expression) 静态转换

静态转换是最接近于C风格转换,很多时候都需要程序员自身去判断转换是否安全。比如:

double d=3.14159265;
int i = static_cast<int>(d);

但static_cast已经有安全性的考虑了,比如对于不相关类指针之间的转换。参见下面的例子:

// class type-casting
#include <iostream>
using namespace std; class CDummy {
float i,j;
}; class CAddition {
int x,y;
public:
CAddition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
}; int main () {
CDummy d;
CAddition * padd;
padd = (CAddition*) &d;
cout << padd->result();
return ;
}

这个例子与之前举的例子很像,只是CAddition与CDummy类没有任何关系了,但main()中C风格的转换仍是允许的padd = (CAddition*) &d,这样的转换没有安全性可言。

如果在main()中使用static_cast,像这样:

int main () {
CDummy d;
CAddition * padd;
padd = static_cast<CAddition*> (&d);
cout << padd->result();
return ;
}

编译器就能看到这种不相关类指针转换的不安全,报出如下图所示的错误:

注意这时不是以warning形式给出的,而直接是不可通过编译的error。从提示信息里可以看到,编译器说如果需要这种强制转换,要使用reinterpret_cast(稍候会说)或者C风格的两种转换。

总结一下:static_cast最接近于C风格转换了,但在无关类的类指针之间转换上,有安全性的提升。

dynamic_cast <new_type> (expression) 动态转换

动态转换确保类指针的转换是合适完整的,它有两个重要的约束条件,其一是要求new_type为指针或引用,其二是下行转换时要求基类是多态的(基类中包含至少一个虚函数)。

看一下下面的例子:

#include <iostream>
using namespace std;
class CBase { };
class CDerived: public CBase { }; int main()
{
CBase b; CBase* pb;
CDerived d; CDerived* pd; pb = dynamic_cast<CBase*>(&d); // ok: derived-to-base
pd = dynamic_cast<CDerived*>(&b); // wrong: base-to-derived
}

在最后一行代码有问题,编译器给的错误提示如下图所示:

把类的定义改成:

class CBase { virtual void dummy() {} };

class CDerived: public CBase {};

再编译,结果如下图所示:

编译都可以顺利通过了。这里我们在main函数的最后添加两句话:

cout << pb << endl;

cout << pd << endl;

输出pb和pd的指针值,结果如下:

我们看到一个奇怪的现象,将父类经过dynamic_cast转成子类的指针竟然是空指针!这正是dynamic_cast提升安全性的功能,dynamic_cast可以识别出不安全的下行转换,但并不抛出异常,而是将转换的结果设置成null(空指针)。

再举一个例子:

#include <iostream>
#include <exception>
using namespace std; class CBase { virtual void dummy() {} };
class CDerived: public CBase { int a; }; int main () {
try {
CBase * pba = new CDerived;
CBase * pbb = new CBase;
CDerived * pd; pd = dynamic_cast<CDerived*>(pba);
if (pd==) cout << "Null pointer on first type-cast" << endl; pd = dynamic_cast<CDerived*>(pbb);
if (pd==) cout << "Null pointer on second type-cast" << endl; } catch (exception& e) {cout << "Exception: " << e.what();}
return ;
}

输出结果是:Null pointer on second type-cast

两个dynamic_cast都是下行转换,第一个转换是安全的,因为指向对象的本质是子类,转换的结果使子类指针指向子类,天经地义;第二个转换是不安全的,因为指向对象的本质是父类,“指鹿为马”或指向不存在的空间很可能发生!

最后补充一个特殊情况,当待转换指针是void*或者转换目标指针是void*时,dynamic_cast总是认为是安全的,举个例子:

#include <iostream>
using namespace std;
class A {virtual void f(){}};
class B {virtual void f(){}}; int main() {
A* pa = new A;
B* pb = new B;
void* pv = dynamic_cast<void*>(pa);
cout << pv << endl;
// pv now points to an object of type A pv = dynamic_cast<void*>(pb);
cout << pv << endl;
// pv now points to an object of type B
}

运行结果如下:

可见dynamic_cast认为空指针的转换安全的,但这里类A和类B必须是多态的,包含虚函数,若不是,则会编译报错。

reinterpret_cast <new_type> (expression) 重解释转换

这个转换是最“不安全”的,两个没有任何关系的类指针之间转换都可以用这个转换实现,举个例子:

class A {};

class B {};

A * a = new A;

B * b = reinterpret_cast<B*>(a);//correct!

更厉害的是,reinterpret_cast可以把整型数转换成地址(指针),这种转换在系统底层的操作,有极强的平台依赖性,移植性不好。

它同样要求new_type是指针或引用,下面的例子是通不过编译的:

double a=2000.3;

short b;

b = reinterpret_cast<short> (a); //compile error! 

const_cast <new_type> (expression) 常量向非常量转换

这个转换好理解,可以将常量转成非常量。

// const_cast
#include <iostream>
using namespace std; void print (char * str)
{
cout << str << endl;
} int main () {
const char * c = "sample text";
char *cc = const_cast<char *> (c) ;
Print(cc);
return ;
}

从char *cc = const_cast<char *>(c)可以看出了这个转换的作用了,但切记,这个转换并不转换原常量本身,即c还是常量,只是它返回的结果cc是非常量了。

总结

  (1)C风格转换是“万能的转换”,但需要程序员把握转换的安全性,编译器无能为力;

  (2)static_cast最接近于C风格转换,但在无关类指针转换时,编译器会报错,提升了安全性;

  (3)dynamic_cast要求转换类型必须是指针或引用,且在下行转换时要求基类是多态的,如果发现下行转换不安全,dynamic_cast返回一个null指针,dynamic_cast总是认为void*之间的转换是安全的;

  (4)reinterpret_cast可以对无关类指针进行转换,甚至可以直接将整型值转成指针,这种转换是底层的,有较强的平台依赖性,可移植性差

  (5)const_cast可以将常量转成非常量,但不会破坏原常量的const属性,只是返回一个去掉const的变量

static_cast, dynamic_cast, reinterpret_cast, const_cast区别比较的更多相关文章

  1. C++雾中风景11:厘清C++类型转换(static_cast,dynamic_cast,reinterpret_cast,const_cast)

    C++是一门弱类型的语言,提供了许多复杂和灵巧类型转换的方式.笔者之前写的Python与Go都是强类型的语言,对这种弱类型的设计实在是接受无力啊~~ ( 生活所迫,工作还得写C++啊~~)C++语言提 ...

  2. c++ 数据类型转换: static_cast dynamic_cast reinterpret_cast const_cast

    c++ 数据类型转换: static_cast dynamic_cast reinterpret_cast const_cast  [版权声明]转载请注明出处 http://www.cnblogs.c ...

  3. static_cast, dynamic_cast, reinterpret_cast, const_cast的区别

    static_cast最像C风格的强制转换,很多时候都需要程序员自身去判断转换是否安全.但是相对C风格的强制转换,在无关类的类指针之间转换上,有安全性的提升. dynamic_cast是运行时的转换吧 ...

  4. 你也许还不知道const_cast,static_cast,dynamic_cast,reinterpret_cast的区别吧?

    [QQ群: 189191838,对算法和C++感兴趣可以进来]       开篇立意: C++中各种转换令人眼花缭乱,看似差不多,实际差很多,而且在当今时间,做一个"差不多先生"其 ...

  5. C++ static_cast dynamic_cast reinterpret_cast const_cast转换

    static_cast <type-id> ( expression ) 和C风格的类型转换相似,可以转换一个指针到基类,或者派生类.不做Run-time类型检查,这样转换并不总是安全的. ...

  6. C++里的强制类型转换符reinterpret_cast、static_cast 、dynamic_cast、const_cast 区别

    C 风格(C-style)强制转型如下: (T) exdivssion // cast exdivssion to be of type T 函数风格(Function-style)强制转型使用这样的 ...

  7. reinterpret_cast,static_cast, dynamic_cast,const_cast的运用分析

    reinterpret_cast(重新解释类型转换) reinterpret_cast 最famous的特性就是什么都可以,转换任意的类型,包括C++所有通用类型,所以也最不安全 应用 整形和指针之间 ...

  8. Qt 中C++ static_cast 和 reinterpret_cast的区别

    1.C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作.因此,被做为隐式类型转换使用.比如: int i; float f = 166.7f; i = static_cast ...

  9. Qt 中C++ static_cast 和 reinterpret_cast的区别(static_cast是隐式类型转换,会有数据损失,reinterpret_cast是底层二进制转换,没有数据损失)

    1.C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作.因此,被做为隐式类型转换使用.比如: int i; float f = 166.7f; i = static_cast ...

随机推荐

  1. 完美版js金钱正则表达式校验

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

  2. 【STL源码学习】std::list类的类型别名分析

    有了点模板元编程的traits基础,看STL源码清晰多了,以前看源码的时候总被各种各样的typedef给折腾得看不下去, 将<list>头文件的类继承结构简化如下 #include < ...

  3. Java基础--NIO

    NIO库在JDK1.4中引入,它以标准Java代码提供了高速的,面向块的IO,弥补了之前同步IO的不足. 缓冲区Buffer Buffers是一个对象,包含了一些要写入或读出的数据.在面向流的IO模型 ...

  4. linux下echo命令详解

    linux的echo命令, 在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的, 因此有必要了解下echo的用法 例如: echo $JAVA_HOME /export/se ...

  5. Hadoop单机环境搭建整体流程

    1.   Ubuntu环境安装和基本配置 本例程中在MAC上安装使用的虚拟机Ubuntu系统(64位,desktop): 基本配置 考虑到以后涉及到hadoop的应用便于权限的管理,特别地创建一个ha ...

  6. 学生党如何拿到阿里技术offer: 《2016阿里巴巴校招内推offer之Java研发工程师(成功)》

    摘要: 这篇文章字字珠玑,这位面试的学长并非计算机相关专业,但是其技术功底足以使很多计算机专业的学生汗颜,这篇文章值得我们仔细品读,其逻辑条理清晰,问题把握透彻,语言表达精炼,为我们提供了宝贵的学习经 ...

  7. Linux tar命令高级用法——备份数据

    Linux tar命令高级用法——备份数据 2015-12-31 Linux学习 Linux上有功能强大的tar命令,tar最初是为了制作磁带备份(tape archive)而设计的,它的作用是把文件 ...

  8. Django xadmin的使用 (三)

    关于报错: 1.在页面添加好新的用户后登录报错 解决方法: 新建一个app,名叫app D:\myfiles\study\python\django\datas>python manage.py ...

  9. django -- url (模版语言 {% url 'test1' param1=5 param2=6 %})

    如果想让form表单提交的url是类似 action="/index-5-6.html" 这样的,可以在html模版语言中使用{% url 'test1' param1=5 par ...

  10. chrom中安装elastic search head插件

    1. 访问https://chrome.google.com/webstore/detail/elasticsearch-head/ffmkiejjmecolpfloofpjologoblkegm/ ...