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

目录
  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_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++强制类型转换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈C++的语句语法与强制数据类型转换

    一个程序包含一个或多个程序单位(每个程序单位构成一个程序文件).每一个程序单位由以下几个部分组成: 预处理命令.如#include命令和#define命令. 声明部分.例如对数据类型和函数的声明,以及对变量的定义. 函数.包括函数首部和函数体,在函数体中可以包含若干声明语句和执行语句. 如下面是一个完整的C++程序: #include <iostream>//预处理命令 using namespace std; //在函数之外的声明部分 int a=3; //在函数之外的声明部分 int ma

  • C++超详细讲解强制类型转换

    目录 1 C 强制类型转换 2 C++ 强制类型转转 1 C 强制类型转换 C 方式的强制类型转换的用法如下代码所示: (Type)(Expression) Type:需要转换成的类型 Expression:对其进行转换 e.g. int v = 0x12345; // 将 int 类型的变量转换成 char 类型 char c = char(v); C 方式的强制类型转换存在如下问题: 过于粗暴:任意类型之间都可以进行转换,编译器很难判断其正确性 typedef void(PF)(int);

  • C++强制类型转换的四种方式

    目录 1 C++类型转换本质 1.1 自动类型转换(隐式) 1.2 强制类型转换(显式) 1.3 类型转换的本质 1.4 类型转换的安全性 2 四种类型转换运算符 2.1 C语言的强制类型转换与C++的区别 3 static_cast 4 reinterpret_cast 5 const_cast 6 dynamic_cast 6.1 向上转型(Upcasting) 6.2 向下转型(Downcasting) 1 C++类型转换本质 1.1 自动类型转换(隐式) 利用编译器内置的转换规则,或者用

  • C++中4种强制类型转换的区别详析

    前言 C++即支持C风格的类型转换,又有自己风格的类型转换.C风格的转换格式很简单,但是有不少缺点的: 1.转换太过随意,可以在任意类型之间转换.你可以把一个指向const对象的指针转换成指向非const对象的指针,把一个指向基类对象的指针转换成一个派生类对象的指针,这些转换之间的差距是非常巨大的,但是传统的C语言风格的类型转换没有区分这些. 2.C风格的转换没有统一的关键字和标示符.对于大型系统,做代码排查时容易遗漏和忽略. C++风格完美的解决了上面两个问题.1.对类型转换做了细分,提供了四

  • 关于C++的强制类型转换浅析

    前言 一说起强制类型转换大家都很熟悉,相信很多学习完C++的朋友还在使用C语言的强制类型的方式 (类型)变量. C++其实也具有自己的一套强制类型转换它们分明是:static_cast  reinterpret_cast  const_cast  dynamic_cast四种类型. 那么肯定会有人好奇C++是不是闲,C语言的强制类型用的舒舒服服的,为什么要新推出来这几个? 新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换.C++中风格是static_cast<typ

  • C++中4种强制类型转换的区别总结

    前言 使用标准C++的类型转换符:static_cast.dynamic_cast.reinterpret_cast和const_cast. const_cast,字面上理解就是去const属性. static_cast,命名上理解是静态类型转换.如int转换成char. dynamic_cast,命名上理解是动态类型转换.如子类和父类之间的多态类型转换. reinterpreter_cast,仅仅重新解释类型,但没有进行二进制的转换. 一.static_cast 用法:static_cast

  • C++ 强制类型转换详解

    目录 一.C强制转换 二.C++强制转换 1.static_cast 静态转换(编译时检查) 2.const_cast 常量转换 3.reinterpret_cast 重新解释转换 4.dynamic_cast 动态转换(运行时检查) 三.要点总结 一.C强制转换 C语言中的强制转换主要用于普通数据类型.指针的强制转换,没有类型检查,转换不安全, 语法为: (type-id)expression//转换格式1 type-id(expression)//转换格式2(基本已经不用了) 二.C++强制

  • 一文搞懂C++中的四种强制类型转换

    在了解c++的强制类形转换的时候,先看看在c语言中是怎么进行强制类形转换的. C语言中的强制类形转换分为两种 隐式类型转换 显示类型转换 int main() { int a = 97; char ch = a; // 隐式类型转换 int b = (int)ch; // 显示类型转换 cout << "a = " << a << endl; cout << "ch = " << ch << e

  • C++强制类型转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)

    目录 1. c强制转换与c++强制转换 2. static_cast.dynamic_cast.const_cast.reinterpret_cast dynamic_cast const_cast reinterpret_cast 3. c++强制转换注意事项 1. c强制转换与c++强制转换 c语言强制类型转换主要用于基础的数据类型间的转换,语法为: (type-id)expression//转换格式1 type-id(expression)//转换格式2 c++除了能使用c语言的强制类型转

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

    目录 static_cast dynamic_cast const_cast reinterpret_cast static_cast static_cast<type-id>(expression) 将 expression 转换为 type-id 类型.static_cast 是静态类型转换,发生在编译期.这种转换不会进行运行时的动态检查(RTTI),因而这种转换可能是不安全的.static_cast 典型应用场景如下: 1. 类的层级结构中,基类和子类之间指针或者引用的转换. 上行转换(

  • C++超详细讲解构造函数与析构函数的用法及实现

    目录 写在前面 构造函数和析构函数 语法 作用 代码实现 两大分类方式 三种调用方式 括号法 显示法 隐式转换法 正确调用拷贝构造函数 正常调用 值传递的方式给函数参数传值 值传递方式返回局部对象 构造函数的调用规则 总结 写在前面 上一节解决了类与对象封装的问题,这一节就是对象的初始化和清理的构造函数与析构函数的内容了:对象的初始化和清理也是两个非常重要的安全问题:一个对象或者变量没有初始状态,对其使用后果是未知,同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题:c++利用了构

  • MySql超详细讲解表的用法

    目录 1. 建表的语法 2. mysql中的数据类型 3. 模拟表 4. 创建一个学生表 1. 创建表(create-DDL) 2. 插入数据(insert-DML) 3. 插入日期 4. date和datetime的区别 5. 更新(update-DML) 6. 删除(delete-DML) 5. 快速创建表(复制表) 6. 快速删除表中数据 1. 建表的语法 建表属于 DDL 语句,DDL 语句包括:create.drop.alter… create table 表名(字段1 数据类型, 字

  • Java超详细讲解接口的实现与用法

    目录 1.接口的定义 2.接口的实现 3.接口的引用 4.接口的继承 5.利用接口实现多重继承 1.接口的定义 接口是一种特殊的抽象类,是Java提供的一个重要的功能,与抽象类不同的是: 接口的所有数据成员都是静态的且必须初始化. 接口中的所有方法必须都是抽象方法,不能有一般的方法. [public] interface 接口名称 [extends  父接口名列表]{    [public] [static] [final]数据类型 成员变量名 = 常量;    ...    [public][

  • 超详细讲解SpringCloud Commons公共抽象的用法

    目录 Spring Cloud Commons公共抽象 @EnableDiscoveryClient 服务注册ServiceRegistry RestTemplate的负载均衡 RestTemplate的失败重试 本期主角——Spring Cloud Commons:公共抽象 Spring Cloud Commons公共抽象 Spring Cloud将服务发现.负载均衡和断路器等通用模型封装在一个公共抽象中,可以被所有的Spring Cloud客户端使用,不依赖于具体的实现(例如服务发现就有Eu

  • Java超详细讲解ArrayList与顺序表的用法

    目录 简要介绍 Arraylist容器类的使用 Arraylist容器类的构造 ArrayList的常见方法 ArrayList的遍历 ArrayList中的扩容机制 简要介绍 顺序表是一段物理地址连续的储存空间,一般情况下用数组储存,并在数组上完成增删查改.而在java中我们有ArrayList这个容器类封装了顺序表的方法. 在集合框架中,ArrayList是一个普通的类,其实现了list接口.其源码类定义如图 可见,其实现了RandomAccess, Cloneable, 以及Seriali

  • Java超详细讲解抽象类的原理与用法

    目录 1. 抽象类是什么 2 抽象类的语法 3 抽象类都有什么特性 4 抽象类是干什么的 1. 抽象类是什么 ️给大家上一篇小作文,看完这个,你就理解了什么叫做抽象类 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的, 如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. ️思考一下抽象类和类在功能上什么区别 因为抽象类是没有足够的信息去描绘一个具体的对象的,所以抽象类也就不能实例化对象 除此之外,抽象类的其它功能都是存在的,成员

  • React useState超详细讲解用法

    目录 前言 基本用法 initData为非函数的情况 initData为函数的情况 state变化监听 过时状态问题 更新引用数据类型 useState 实现原理 前言 React-hooks 正式发布以后, useState 可以使函数组件像类组件一样拥有 state,也就说明函数组件可以通过 useState 改变 UI 视图.那么 useState 到底应该如何使用,底层又是怎么运作的呢,首先一起看一下 useState . 基本用法 [ state , dispatch ] = useS

  • 超详细讲解Java异常

    目录 一.Java异常架构与异常关键字 Java异常简介 Java异常架构 1.Throwable 2.Error(错误) 3.Exception(异常) 4.受检异常与非受检异常 Java异常关键字 二.Java异常处理 声明异常 抛出异常 捕获异常 如何选择异常类型 常见异常处理方式 1.直接抛出异常 2.封装异常再抛出 3.捕获异常 4.自定义异常 5.try-catch-finally 6.try-with-resource 三.Java异常常见面试题 1.Error 和 Excepti

随机推荐