当前位置: 首页 > news >正文

13 【C++】C++的类型转换

文章目录

  • 前言
  • 1.为什么C++需要四种类型转换
  • 2.C++的类型转换
    • 2.1 static_cast
      • **static_cast的安全检查:**
        • **允许相关的/安全的类型转换**
        • **阻止不相关的/不安全的类型转换**
    • 2.2 reinterpret_cast
    • 2.3 const_cast
      • const_cast的安全检查:
        • ​只允许修改 const/volatile属性,且必须是指针/引用类型
        • ​不允许其他的类型转换和非指针引用的转换
    • 2.4 dynamic_cast
      • dynamic_cast的运行时类型转换检查 (以指针向下转型为例)​​
  • 总结


前言

C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。

  1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
  2. 显式类型转化:需要用户自己处理

到目前为止我们的所有代码,使用的也都是C语言的类型转换,

void Test()
{int i = 1;// 隐式类型转换double d = i;printf("%d, %.2f\n", i, d);int* p = &i;// 显示的强制类型转换int address = (int)p;printf("%p, %d\n", p, address);
}

缺陷:
转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换


1.为什么C++需要四种类型转换

​C 风格转换过于“强大”且危险:​​ (type)expression这种写法可以执行多种本质上不同的转换操作(如数值转换、指针类型转换、去除 const、多态类型转换等),编译器不会检查转换是否合理或安全。程序员需要自己承担风险。

如以下类型转换的坑:
场景1

在这里插入图片描述在while比较时,如果pos是0,end就算减到了负数,整个程序也不会停下,因为运算符比较的时候发生了隐式类型转换,编译器用end创造了一个临时的size_t类的变量,而我们的size_t变量的是没有负数的,所以这个end(实际是负的,但是转换成的size_t类临时是极大的)永远会>=0,所以这个循环永远不会停止.

场景2:
以下代码不可以:

int n = 10;
int arr[n];  //表达式的计算结果不是常数

这种定义叫变长数组,是C99中的一个特性之一,但是我们的vs系列中一直都没有支持.

下面代码可以吗?

const int n = 10;
int arr[n];

可以,但是其实它被替换成了常量,但是n它其实是一个常变量,只是经过了编译器的隐式类型转换。
它不能直接修改,但是可以间接修改:

int main()
{const int n = 10;int* p = (int*)&n;*p++;cout << n << endl;cout << *p << endl;return 0;
}

以上代码有几个问题:

  1. 编译器可能将n直接替换为字面量10,当读取n时,实际读取的是编译器缓存的常量值(而非内存中的值),导致cout << n输出10,而*p实际指向的内存值已被改为11。

    对此可以使用volatile关键字修饰变量:

    volatile const int n = 10; 
    

    这样可以让编译器强制去内存取,禁用了宏替换,于是打印出来的就只能是改过的值。

  2. ​内存模型欺骗​:

    const int n = 10;    // 编译器可能将n放入只读内存段
    int* p = (int*)&n;   // 通过指针强制访问
    *p = 20;             // 试图修改只读内存 -> 程序崩溃!
    

    可能触发段错误

  3. 修改声明为const的对象是未定义行为(UB)

    • 具体表现取决于编译器实现:
      • 可能正常修改(如您的示例)
      • 可能输出旧值(编译器优化)
      • 可能程序崩溃(内存保护)
      • 可能引发其他任意行为

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast

​C++的新操作符限制转换范围,读者(包括未来的你和其他开发者)能立即理解程序员进行该转换的目的是什么。
static_cast: “我知道这个数值转换/相关类层次转换应该是安全的,或者是我需要的标准转换。”
dynamic_cast: “我想在运行时安全地检查这个对象是否属于某个派生类并转换。”
const_cast: “我需要临时移除这个对象的 const或 volatile属性(通常用于与旧 API 交互,需谨慎)。”
reinterpret_cast: “我需要把这块内存当作完全不同的类型来解释(例如处理硬件寄存器、序列化),我知道这很危险且不可移植。”

注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。

2.C++的类型转换

类型之间的转换是需要我们的数据之间有一点关联的(相近类型的),正如一般自定义类型是不能转换的,除了两类在单参数的构造函数有关联,如:
在这里插入图片描述
如果想禁用这个转换,我们可以在构造函数这里加上explicit。

2.1 static_cast

static_cast是最常用的类型转换,它是静态转换,用于两个相关类型之间的装换,不可用于多态类型的转换(比C风格的转换更安全),编译器隐式执行的任何类型转换都可用static_cast。

int main()
{double d = 12.34;int a = static_cast<int>(d);cout<<a<<endl;return 0;
}
  1. 静态类型转换,即static_cast的所有类型检查、转换规则和最终生成的转换代码,都是在编译阶段由编译器确定和完成的。
    因为它是静态类型转换,所以在相关类型转换中特殊转换情况(继承体系向下转换)下,它不进行运行时检查,依赖程序员保证正确性。所以我们在做继承体系下的类型转换时,还是使用dynamic_cast,因为安全的继承体系向下转型是 dynamic_cast的工作。

  2. 相比与C风格的转换它更安全,因为编译器会根据源代码中提供的类型信息(源类型和目标类型),应用 C++ 标准中定义的规则来判断这个转换是否合法。

  3. 它用于编译器已知且允许的转换,所以编译器隐式执行的任何类型转换都可用static_cast。

static_cast的安全检查:

允许相关的/安全的类型转换

static_cast是用于相关类型之间的转换的,编译器会在编译阶段检查使用static_cast转换的合法性和安全性,然后才生成对应的转换

  1. ​基本类型转换(数值类型)​​:
int main()
{int a = 42;double b = static_cast<double>(i); // 安全:int转double(无精度损失)double c = 42.01; int d = static_cast<double>(c); //waring: 从“double”转换到“int”,可能丢失数据return 0;
}

​安全检查​:编译器允许数值类型间的转换(如int→double),但会警告可能丢失精度的反向转换(如double→int)。

  1. 继承体系中的向上转换(派生类→基类)
    我们知道继承体系,子类和父类是密切相关的(相关类型),且子类本来就有父类的成员(安全的转换),所以,向上转换,是安全的:
class Base {};
class Derived : public Base {};int main()
{	Derived d;Base* b = static_cast<Base*>(&d); // 安全:向上转换始终合法
}

​安全检查​:编译器验证继承关系,若Derived不是Base的子类,则报错。

  1. 枚举与整数类型转换​:
enum Color { RED, GREEN };
int main()
{Color c = static_cast<Color>(1); // 安全:整数转枚举(GREEN)
}

​安全检查​:编译器确保整数在枚举定义的取值范围内(此处1是有效的)。

阻止不相关的/不安全的类型转换
  1. ​不相关的指针类型转换​:
int main()
{int x = 10;double* p = static_cast<double*>(&x); // 错误:int*与double*无关
}

安全检查​:编译器直接报错,因为两种指针类型无继承关系。

  1. 去除const限定符​:
int main()
{const int ci = 100;int* p = static_cast<int*>(&ci); // 错误:static_cast不能移除const
}

安全检查​:必须使用const_cast移除const,static_cast会拒绝。

  1. ​向下转换(基类→派生类)无检查​:
class Base {};
class Derived : public Base {};
int main()
{Base base;Derived* d = static_cast<Derived*>(&base); // 编译通过,但运行时行为未定义!
}

​安全漏洞​:static_cast允许向下转换,但不验证对象实际类型 ​(若base非Derived对象,会引发未定义行为)。此时应使用dynamic_cast(需多态类)。

其实面对继承体系,不应该使用static_cast,上面例子我们可以得知,继承体系的向上、向下转换,static_cast就像是我们C式的类型转换一样,并没有安全检查,所以我们在面对继承体系的类型转换的时候,应该使用专门的dynamic_cast,static_cast只是对类型之间的相关性检查,对精度损失做告警。

2.2 reinterpret_cast

reinterpret_cast是 C++ 中最强大但也最危险的类型转换运算符,它允许在任意类型之间进行低级别的重新解释(reinterpretation)。它不进行任何编译时类型检查或运行时安全检查,直接操作底层比特模式,所以它可以进行任意不相关类型之间的转换。​仅在完全理解后果且无其他替代方案时使用。

int main()
{double d = 12.34;int a = static_cast<int>(d); //相关类型的转换用:static_castcout << a << endl;// 这里使用static_cast会报错,应该使用reinterpret_cast//int *p = static_cast<int*>(a);int *p = reinterpret_cast<int*>(a);    //这时不相关类型的转换就要用:reinterpret_castreturn 0;  
}

注意: reinterpret_cast也是静态转换,也是在编译阶段就完成的。

reinterpret_cast类型转换,其实和我们C式类型转换在生成的机器码层面通常是完全相同的,之所以要引入它,是因为它相比 C 风格的(SomeType*)0xDEADBEEF,C++ 版本的reinterpret_cast像危险警示标签​。

​reinterpret_cast本质是 C++ 为 C 风格转换中危险的那部分操作设计的“语法隔离区”。
在 C++ 中永远优先使用 static_cast/const_cast,把 reinterpret_cast当作和 C 风格强制转换一样的“最后手段”。

2.3 const_cast

用于对const常变量的类型转换,它也是静态转换,用于添加或者删除变量的const或volatile属性,但是只对最初非 const 定义的对象使用 const_cast移除const属性。

void main()
{int a = 2;const int* b = const_cast<const int*>(&a);int* p = const_cast<int*>(b);*p = 3;cout << a << endl;
}
  1. 它也是静态转换,也是在编译阶段就完成的。

  2. 不改变底层类型​
    const_cast​不能用于不同类型之间的转换(如 int转 char)。如需此类转换,请用 reinterpret_cast。

  3. 仅限指针/引用​
    它只适用于指针、引用或成员指针类型。

  4. 只对最初非 const 定义的对象使用 const_cast移除const属性,否则是未定义的行为。

const_cast的安全检查:

​只允许修改 const/volatile属性,且必须是指针/引用类型

const_cast​不能改变底层数据类型。它只允许添加或移除 const和 volatile限定符。

//合法:
const int*int*          // 移除 const
int*const int*          // 添加 const
volatile char*char*     // 移除 volatile
//合法:
int*const int*          // 指针类型一致
const int&int&          // 引用类型一致
​不允许其他的类型转换和非指针引用的转换

源类型和目标类型必须是相同的底层类型,仅 const/volatile限定符不同。

//非法:
const int*char*         // 错误:改变了底层类型 (int → char)
double*const float*     // 错误:改变了底层类型 (double → float)//非法:
int**const int**        // 错误:指向指针的指针(多级指针需逐级修改)
int[10]const int*       // 错误:数组与指针类型不同

2.4 dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:

  1. dynamic_cast只能用于父类含有虚函数的类
  2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回,如果是指针转换失败返回nullptr,引用转换失败抛出 std::bad_cast异常(后面讲)。
class A
{
public:virtual void f() {}
};
class B : public A
{
};
void fun(A* pa)
{// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回B* pb1 = static_cast<B*>(pa);B* pb2 = dynamic_cast<B*>(pa);cout << "pb1:" << pb1 << endl;cout << "pb2:" << pb2 << endl;
}
int main()
{A a;B b;fun(&a);fun(&b);return 0;
}

dynamic_cast的运行时类型转换检查 (以指针向下转型为例)​​

假设我们有 Base* basePtr指向一个实际类型可能是 Derived的对象。我们执行 Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);。

  1. 检查源类型是否多态:​​
  • 编译器首先检查 Base是否是多态类型(是否有虚函数)。如果不是,dynamic_cast要么编译失败(如果目标类型与源类型无关),要么行为未定义(如果编译器允许编译通过但转换无效)。​dynamic_cast要求源类型是多态的!​​
  1. ​获取对象的实际类型信息:​​
  • 通过 basePtr找到它指向的对象。
  • 通过该对象的 vptr找到其实际类型对应的 vtable。
  • 从 vtable 中预先设定的位置,获取指向该对象实际类型​(比如 ActualDerived)的 std::type_info对象的指针。
  1. 检查转换是否合法:​​
  • 编译器生成的代码会利用 std::type_info对象中存储的继承关系信息。
  • 它需要检查两个条件:
    • Derived是否是 ActualDerived的基类?​​ (或者 ActualDerived就是 Derived,或者 ActualDerived继承自 Derived)
    • ActualDerived的继承路径中是否包含唯一的、可访问的 Derived?​​ (避免歧义转换)
  • 这个检查本质上是在运行时遍历 ActualDerived的继承树,看 Derived是否在其中。
  1. ​执行转换 (如果合法):​​
  • ​如果转换合法:​​
    • 如果 Derived是 ActualDerived的一个非虚基类(且是单继承或非第一个基类等情况),对象内存中 Derived子对象的地址可能与 ActualDerived对象的起始地址不同(存在偏移)。
    • dynamic_cast不仅检查类型,​还需要计算正确的地址偏移量。这个偏移量信息也存储在 RTTI 数据中(通常与 std::type_info关联或在 vtable 的附加结构中)。
    • dynamic_cast返回一个指针,该指针是 basePtr的值加上计算出的偏移量(如果需要调整)。这个指针现在正确地指向了对象内部的 Derived子对象部分。
  • 如果转换不合法:​​
    • 返回 nullptr。

疑问:dynamic_cast执行转换是在运行时的,此时我们的运行代码已经固定,如何去执行我们类型的转换呢?

依赖存储在对象虚函数表 (vtable) 中的 ​运行时类型信息 (RTTI)​。编译器不是在运行时生成的转换代码,而是一段由编译器预先生成的、通用的“查询逻辑代码”(查询 RTTI 并执行条件逻辑)。

总结

  1. static_cast,静态类型转换,检查相关类型之间的转换。
  2. reinterpret_cast,静态类型转换,不做类型安全检查,直接操作底层比特模式,和我们C式类型转换在生成的机器码层面通常是完全相同。
  3. constcast,静态类型转换,只用于指针和引用,且只用于添加或者消除变量最初非 const 定义的对象的const或者volatile属性,注意:对最终为const定义的对象消除const属性是未定义的行为。
  4. dynamiccast,动态转换,用于多态体系的父子类指针或引用之间的安全类型转换,会在代码运行时:做是否有虚函数(虚表)的检查、通过虚表做 实际类型 和 转换目标类型 之间的类型继承关系检查(检查其是否满足向上转换,或者一开始就指向子类)、然后根据检查的结果,返回指定的结果(转换成功的指针或者是转换失败的空指针、异常)。

本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点 😃。

http://www.dtcms.com/a/404317.html

相关文章:

  • STM32定时器(寄存器与HAL库实现)
  • 教学资源库网站建设立项申报书网络维护员岗位职责
  • 第10节-CTE公用表表达式-Recursive-CTE
  • 广东省备案网站建设方案书iis发布网站的教程
  • git基础操作
  • 茂名专业网站建设外贸出口流程12步骤图
  • 响应式网站 模版58同城企业网站怎么做的
  • ARM——中断(按键)
  • 【conda配环境】导出环境配置与安装
  • 上饶市建设厅网站快速达建网站
  • 深圳做h5网站公司wordpress 入口
  • 网站建设一般多钱高职院校优质校建设专栏网站
  • 网站建设属于哪种公司科技公司logo
  • 个人网站备案入口注册网站建设公司
  • 网站建设要求报告网上做网站网站代理赚钱吗
  • 网站的角色设置如何做北京建站免费模板
  • 代理分佣后台网站开发安徽住房和城乡建设厅官网
  • 微网站用什么做天津工程建设信息网站
  • 网站制造做网站流量
  • 南昌网站开发培训学校全媒体运营师报考条件
  • 站长工具下载app智能云建站
  • 做网站应该会什么问题建设一个网站需要哪些方面的开支
  • 宁德住房和城乡建设部网站东莞创建网站
  • 网站做微信链接怎么做的建设网站的知识竞赛
  • 上海好的高端网站建设服务公司网站建设一站式
  • 网站百度秒收自助建站管理平台
  • 网站建设的机构2022年企业所得税政策
  • 手机网站建设维护网络叶子 网站推广
  • 公司网站建设的方案网站建设网站
  • 北京通州个人网站建设桐柏网站