C++四种强制类型转换

转自:https://blog.csdn.net/Bob__yuan/article/details/88044361

C语言中的强制类型转换(Type Cast)有显式和隐式两种,显式一般就是直接用小括号强制转换,TYPE b = (TYPE)a; 隐式就是直接 float b = 0.5; int a = b; 这样隐式截断(by the way 这样隐式的截断是向 0 取整的,我喜欢这么叫因为 0.9 会变成 0,1.9 变成 1,-0.9 变成 0,-1.9 变成 -1)。
  C++对C兼容,所以上述方式的类型转换是可以的,但是有时候会有问题,所以推荐使用C++中的四个强制类型转换的关键字:
  1、static_cast,2、const_cast,3、reinterpret_cast,4、dynamic_cast

1)static_cast

这应该四种中是最常见的。用法为 static_cast<type-id> (expression)
  这应该四种中是最常见的。用法为 static_cast<type-id> (expression)。
  该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。
  主要用法如下:
    (1)用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
        进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
        进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
    (2)用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
    (3)把空指针转换成目标类型的空指针。
    (4)把任何类型的表达式转换成void类型。
  最常用的应该还是基本数据类型之间的转换,如下:

    const auto a1 = 11; // int
    const auto a2 = 4;  // int

// C style
    double res1 = (double)(a1) / (double)(a2);  // 其实写一个 (double) 就行
    cout << "res1 = " << res1 << endl;          // res1 = 2.75

// C++ style
    auto res2 = static_cast<double>(a1) / static_cast<double>(a2);
    cout << "res2 = " << res2 << endl;          // res2 = 2.75
    cout << typeid(res2).name() << endl;        // double

如果在 Visual Studio 中编写,且安装了 Resharper C++ 的话,如果按照 C 风格写的话,会提示你,并帮助你自动修改为 C++ 风格,如下图所示。

image

需要注意的是,static_cast 不能转换掉 expression 的 const、volitale 或者 __unaligned 属性,如下图所示。

image

2)const_cast

上边的 static_cast 不能将 const int* 转成 int*,const_cast 就可以,用法为 const_cast<type-i> (expression)。如下面代码

    const int a = 10;
    const int * p = &a;
    *p = 20;                         // Compile error: Cannot assign readonly type 'int const'
    int res1 = const_cast<int>(a);   // Compile error: Cannot cast from 'int' to 'int' via const_cast
                                     // only conversions to reference or pointer types are allowed
    int* res2 = const_cast<int*>(p); // ok

也就是说,const_cast<>里边的内容必须是引用或者指针,就连把 int 转成 int 都不行。
  对于 const_cast 的使用,其实还有很多不明白的(C++未定义的行为),比如下面三张图。

image
image
image

更神奇的是,在 VS2017 中,debug 模式下,执行完 *p = 20; 这一步后,a 的值就变成了 20,但是输出 a 和 *p 还都是 11。我理解的就是 const int 类型的变量,在编译器就优化了,里边所有单独的 a 都已经变成了 11,所以怎么修改都不影响了,这种情况包括:1、直接用 11 这种常量来初始化 a,2、用同样为 const int 类型的 c 来初始化 a。如果是用 int c = 11; 这样的 c 来初始化 a,那 a 就是可变的??

image

总结来说,const_cast 通常是无奈之举,只是 C++ 提供了一种修改 const 变量的方式,但这种方式并没有什么实质性的用处,还是不用的好。const 的变量不要让它变。

3)reinterpret_cast

reinterpret_cast 主要有三种强制转换用途:
  1、改变指针或引用的类型
  2、将指针或引用转换为一个足够长度的整形
  3、将整型转换为指针或引用类型

  用法为 reinterpret_cast <type-id> (expression)
  type-id 必须是一个指针、引用、算术类型、函数针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
  我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)。因此, 你需要谨慎使用 reinterpret_cast。

4)dynamic_cast

用法为 dynamic_cast<type-id> (expression)
  几个特点如下:
  1)其他三种都是编译时完成的,dynamic_cast 是运行时处理的,运行时要进行类型检查。
  (2)不能用于内置的基本数据类型的强制转换
  (3)dynamic_cast 要求 <> 内所描述的目标类型必须为指针或引用。dynamic_cast 转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回 nullptr
  (4)在类的转换时,在类层次间进行上行转换(子类指针指向父类指针)时,dynamic_cast 和 static_cast 的效果是一样的。在进行下行转换(父类指针转化为子类指针)时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。
  (5)使用 dynamic_cast 进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表(C++中的虚函数基本原理这篇文章写得不错,https://blog.csdn.net/xiejingfa/article/details/50454819)。

class base {
public:
    void print1() { cout << "in class base" << endl; }
};

class derived : public base {
public:
    void print2() { cout << "in class derived" << endl; }
};

int main() {
    derived *p, *q;
    // p = new base;    //  Compilr Error: 无法从 "base * " 转换为 "derived * "

    // Compile Error: Cannot cast from 'base*' to 'derived*' via dynamic_cast: expression type is not polymorphic(多态的)
    // p = dynamic_cast<derived *>(new base);

    q = static_cast<derived*>(new base);    // ok, but not recommended

    q->print1();    // in class base
    q->print2();    // in class derived
}

从上边的代码可以看出用一个派生类的指针是不能直接指向一个基类的对象的,会出现编译错误。用 dynamic_cast 的话也会编译错误,提示我们基类不是多态的,也就是基类中没有虚函数。可以看到 static_cast 是可以编译通过的,且输出结果看起来都是对的,但是 VS 还是会提示说 Do not use static_cast to downcast from a base to a derived class,如下图所示。

image

static_cast 强制类型转换时并不具有保证类型安全的功能,而 C++ 提供的 dynamic_cast 却能解决这一问题,dynamic_cast 可以在程序运行时检测类型转换是否类型安全。当然 dynamic_cast 使用起来也是有条件的,它要求所转换的 expression 必须包含多态类类型(即至少包含一个虚函数的类)。

class A {
public:
    virtual void print(){
        cout << "in class A" << endl;
    };
};

class B :public A {
public:
    void print(){
        cout << "in class B" << endl;
    };
};

class C {
    void pp(){
        return;
    }
};

int main() {
    A *a1 = new B; // a1是A类型的指针指向一个B类型的对象
    A *a2 = new A; // a2是A类型的指针指向一个A类型的对象
    B *b1, *b2, *b3, *b4;
    C *c1, c2;
    b1 = dynamic_cast<B*>(a1);  // not null,向下转换成功,a1 之前指向的就是 B 类型的对象,所以可以转换成 B 类型的指针。
    if (b1 == nullptr) cout << "b1 is null" << endl;
    else               cout << "b1 is not null" << endl;

    b2 = dynamic_cast<B*>(a2);  // null,向下转换失败
    if (b2 == nullptr) cout << "b2 is null" << endl;
    else               cout << "b2 is not null" << endl;

    // 用 static_cast,Resharper C++ 会提示修改为 dynamic_cast
    b3 = static_cast<B*>(a1);   // not null
    if (b3 == nullptr) cout << "b3 is null" << endl;
    else               cout << "b3 is not null" << endl;

    b4 = static_cast<B*>(a2);   // not null
    if (b4 == nullptr) cout << "b4 is null" << endl;
    else               cout << "b4 is not null" << endl;

    a1->print();    // in class B
    a2->print();    // in class A

    b1->print();    // in class B
    // b2->print(); // null 引发异常
    b3->print();    // in class B
    b4->print();    // in class A

    c1 = dynamic_cast<C*>(a1);  // 结果为null,向下转换失败
    if (c1 == nullptr) cout << "c1 is null" << endl;
    else               cout << "c1 is not null" << endl;

    // c2 = static_cast<C*>(a1);    // 类型转换无效, Cannot cast from 'A*' to 'C*' via static_cast
    // delete 省略
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348