C++超详细讲解强制类型转换的用法

 更新时间:2022年06月20日 08:37:22   作者:loongknown  
在C++语言中新增了四个关键字static_cast、const_cast、reinterpret_cast和dynamic_cast。这四个关键字都是用于类型转换的,类型转换(type cast),是高级语言的一个基本语法。它被实现为一个特殊的运算符,以小括号内加上类型名来表示,接下来让我们一起来详细了解

static_cast

static_cast<type-id>(expression)

expression 转换为 type-id 类型。static_cast 是静态类型转换,发生在编译期。这种转换不会进行运行时的动态检查(RTTI),因而这种转换可能是不安全的。static_cast 典型应用场景如下:

1. 类的层级结构中,基类和子类之间指针或者引用的转换。

上行转换(Upcasting),也即子类像基类方向转换,是安全的。

下行转换(Downcasting),也即基类向子类方向转换,是不安全的,需要程序员自行保证安全转换。

下面举例说明:

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

对于上行转换,肯定是安全的。

  B* pb = new B();
  A* pa = static_cast<A*>(pa);
  pa->func();

对于下行转换:

  A* pa = new B();
  B* pb = static_cast<B*>(pa);
  pb->print();

这里,A* pa = new B();,由于 C++ 的多态的支持,可以使用基类指针指向子类。这里的转换是安全的,因为 pa 初始化就指向的就是 B。而下面的转换则是不安全的:

  A* pa = new A();
  B* pb = static_cast<B*>(pa);
  pb->print();

此外,对于两个不存在继承关系的两个类之间转换,总是失败的,编译器报错:

#include <iostream>
class A {
    virtual void func(){}
};
class B {
    virtual void func(){}
};
int main(){
    A* pa = new A();
    B* pb = static_cast<B*>(pa); 
    return 0;
}

2. 基本数据类型间的转换。这种转换也是不安全的,需要程序员自行保证安全转换。 例如 intshort,直接高位截断;而 shortint 则高位根据符号位填充。两种不同类型指针间相互转换是相当危险的,例如 int*float*。将 int 转换为指针类型也是危险的转换,例如 float* p = static_cast<float*>(0X2edf);

3. 将 void 类型转换为其他类型的指针。 显然这种转换也是不安全的,需要程序员自行保证安全转换。

4. 把其他类型转换为 void 类型。

有转换构造函数或者类型转换函数的类与其它类型之间的转换。例如:

#include <iostream>
class Point{
public:
    Point(double x, double y): m_x(x), m_y(y){ }
    Point(double& x): m_x(x), m_y(1.1){ }
public:
    operator double() const { return m_x; }  //类型转换函数
    void print() {
        std::cout << "m_x: " << m_x << "  m_y: " << m_y << std::endl;
    }
private:
    double m_x;
    double m_y;
};
int main() {
    Point p1(12.5, 23.8);
    double x= static_cast<double>(p1);
    // std::cout << x << std::endl;
    Point p2 = static_cast<Point>(x);
    // p2.print();
    return 0;
}

dynamic_cast

dynamic_cast<type-id>(expression)

expression 转换为 type-id 类型,type-id 必须是类的指针、类的引用或者是 void *;如果 type-id 是指针类型,那么 expression 也必须是一个指针;如果 type-id 是一个引用,那么 expression 也必须是一个引用。

dynamic_cast 提供了运行时的检查。对于指针类型,在运行时会检查 expression 是否真正的指向一个 type-id 类型的对象,如果是,则能进行正确的转换;否则返回 nullptr。对于引用类型,若是无效转换,则在运行时会抛出异常 std::bad_cast

T1 obj;
T2* pObj = dynamic_cast<T2*>(&obj);    // 无效转换返回 nullptr
T2& refObj = dynamic_cast<T2&>(obj);   // 无效转换抛出 bad_cast 异常

上行转换:其实和 static_cast 是一样的,一般肯定能成功。例如前面用到的例子:

// A->B
B* pb = new B();
A* pa = static_cast<A*>(pa);  

但是,下面这种继承关系会转换失败:

#include <iostream>
/*    A
     / \
    V  V
    B  C
     \/
     v
     D   */
class A {
    virtual void func(){}
};
class B : public A {
    void func(){}
};
class C : public A {
    void func(){}
};
class D : public B, public C {
    void func(){}
};
int main(){
    D* pd = new D();
    A* pa = dynamic_cast<A*>(pd);
    return 0;
}

上面这个例子,虽然也是上行转换,但是存在两条路径,在 B 和 C 都继承于 A,并且有虚函数实现,上行转换不知道从哪条路径进行转换。下面的写法则没问题:

  D* pd = new D();
  B* pb = dynamic_cast<B*>(pd);
  A* pa = dynamic_cast<A*>(pb);

下行转换:看个例子。

#include <iostream>
class A {
    virtual void func(){}
};
class B : public A {
    void func(){}
};
int main(){
    A* pa1 = new B();
    A* pa2 = new A();
    B *pb1 = dynamic_cast<B*>(pa1); // ok
    B *pb2 = dynamic_cast<B*>(pa2); // pb2 is a nullptr!
    return 0;
}

其实 dynamic_cast 本质只支持上行转换,只会沿着继承链向上遍历,找到目标类型则转换成功,否则失败。dynamic_cast 看似支持下行转换,这都是多态的缘故。上面的例子,pa1 虽然类型是 A,但实际指向 B,沿着 B 向上可以找到 B,因为第一个转换可以成功。而 pa2 指向 A,沿着 A 向上找不到 B 类型,因而转换失败。

因而在有继承关系的类的转换时候, static_cast 转换总是成功的, dynamic_cast 显然比 static_cast 更加安全。

const_cast

const_cast 用来去掉表达式的 const 修饰或 volatile 修饰,也就是将 constvolatile 类型转换为非 const 或 非 volatile 类型。

#include <iostream>
int main(){
    const int n = 111;
    int *p = const_cast<int*>(&n);
    *p = 222;
    std::cout<< "n = " << n << std::endl;
    std::cout<< "*p = " << *p << std::endl;
    return 0;
}

这里需要注意:按照正常理解,n 的打印值应该是 222。但是,由于编译器的常量传播优化,std::cout<< "n = " << n << std::endl; 会被编译器替换成类似 std::cout<< "n = " << 111 << std::endl; 的语义。

reinterpret_cast

reinterpret_cast 转换直接对二进制位按照目标类型重新解释,非常粗暴,所以风险很高,慎重使用。

#include <iostream>
int main(){
    char str[]="hello world!";
    float *p = reinterpret_cast<float*>(str);
    std::cout << *p << std::endl;  // 1.14314e+27
    return 0;
}

到此这篇关于C++超详细讲解强制类型转换的用法的文章就介绍到这了,更多相关C++强制类型转换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++消息队列(定义,结构,如何创建,发送与接收)

    C++消息队列(定义,结构,如何创建,发送与接收)

    这篇文章主要介绍了C++消息队列(定义,结构,如何创建,发送与接收),消息队列是一种先进先出的队列型数据结构,实际上是系统内核中的一个内部链表
    2022-08-08
  • 史上最强C语言分支和循环教程详解

    史上最强C语言分支和循环教程详解

    这篇文章主要介绍了史上最强C语言分支和循环教程详解,本文通过代码演示给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-11-11
  • C语言共用体union作用使用示例教程

    C语言共用体union作用使用示例教程

    这篇文章主要为大家介绍了C语言共用体union作用的使用示例教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-02-02
  • VSCode搭建STM32开发环境的方法步骤

    VSCode搭建STM32开发环境的方法步骤

    当我们的工程文件比较大的时候,编译一次代码需要很久可能会花费到四五分钟,但是我们用vscode编写和编译的话时间就会大大缩减,本文就介绍一下VSCode搭建STM32开发环境,感兴趣的可以了解一下
    2021-07-07
  • C语言中memcpy 函数的用法详解

    C语言中memcpy 函数的用法详解

    这篇文章主要介绍了C语言中memcpy 函数的用法详解的相关资料,需要的朋友可以参考下
    2017-07-07
  • VC++ 使用 _access函数判断文件或文件夹是否存在

    VC++ 使用 _access函数判断文件或文件夹是否存在

    这篇文章主要介绍了VC++ 使用 _access函数判断文件或文件夹是否存在的相关资料,需要的朋友可以参考下
    2015-10-10
  • 用c语言实现HUP信号重启进程的方法

    用c语言实现HUP信号重启进程的方法

    本篇文章是对使用c语言实现HUP信号重启进程的方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C++深入分析讲解智能指针

    C++深入分析讲解智能指针

    为了解决内存泄漏的问题,C++中提出了智能指针。内存泄漏的产生原因有很多,即使我们正确的使用malloc和free关键字也有可能产生内存泄漏,如在malloc和free之间如果存在抛异常,那也会产生内存泄漏。这种问题被称为异常安全
    2022-05-05
  • C语言之实现控制台光标随意移动的实例代码

    C语言之实现控制台光标随意移动的实例代码

    下面小编就为大家带来一篇C语言之实现控制台光标随意移动的实例代码。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-07-07
  • C语言实现酒店预订管理系统

    C语言实现酒店预订管理系统

    这篇文章主要为大家详细介绍了C语言实现酒店预订管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06

最新评论