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

【C++】C++风格的类型转换

C++风格的类型转换

  • C++风格的类型转换
  • github地址
  • 0. 前言
  • 1. C语言中的类型转换
      • 内置类型
      • 自定义类型
        • C++98:单参数的构造函数支持隐式类型转换
        • explicit禁止单参数构造函数进行隐式类型转换
        • 有关联之间的类的转换
        • 无关联的自定义类型不能互相转化
      • const类型转换
  • 2. 为什么C++需要四种类型转换
  • 3. C++的4种类型转换
    • 1. static_cast
    • 2. reinterpret_cast
    • 3. const_cast
    • 4. dynamic_cast
      • 父子指针/引用的转换
      • `dynamic_cast` 的真正价值
      • 总结一句话
    • 四者不能互换的典型对比(精要)
  • 4. RTTI
  • 5. C++中类型转换的价值
    • 1. 明确的语义与意图
    • 2. 更强的类型检查
    • 3. 更安全的运行时行为
    • 4. 限制滥用(逼迫开发者思考)
    • 5. 更好的可维护性和团队协作
    • 6. 与现代 C++ 特性的兼容
  • 6. 结语

C++风格的类型转换

github地址

有梦想的电信狗

0. 前言

类型转换是 C++ 中极具代表性的机制之一。

  • 从 C 语言的 (T)value 到 C++ 的 static_castreinterpret_castconst_castdynamic_cast, 语言的演进让类型转换变得更安全、明确、可控

C 语言的强制转换虽灵活,却模糊而危险;

C++ 通过四种显式转换,让编译器与开发者都能清楚地知道“在做什么样的转换”。

本文将系统讲解:

  • C 与 C++ 类型转换的差异;
  • 四种 C++ 类型转换的语义与用法;
  • 以及 RTTI 的运行时类型识别机制。

1. C语言中的类型转换

C/C++ 语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转换

C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换

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

内置类型

// C语言中的类型转换int i = 1;
// 整型家族基本都能 互相  隐式转换
double d = i;	// 隐式类型转换  意义相似的类型 可以进行隐式类型转换   int、char、double、size_t
printf("%d, %.2f\n", i, d);int* p = &i;
// 显式的强制类型转换     指针和整型也能互相转换
int address = (int)p;
printf("%p, %d\n", p, address);

在这里插入图片描述

自定义类型

C++98:单参数的构造函数支持隐式类型转换
class A {
public:A(int a):_a(a){ cout << "单参数的构造函数支持隐式类型转换\n" << endl; }
private:int _a;
};
// 单参数的构造函数支持隐式类型转换
int x = 10;
A a1 = x;	// 两次构造
A a2(x);

在这里插入图片描述

explicit禁止单参数构造函数进行隐式类型转换
  • 添加explicit后无法进行隐式类型转换
class A {
public:explicit A(int a):_a(a){ cout << "禁止 单参数的构造函数的隐式类型转换\n" << endl; }
private:int _a;
};
有关联之间的类的转换
  • 自定义类型间有某种关联时,也可以互相转换,取决于类的设计
class B {
public:B(const A& a){ cout << "自定义类型间有某种关联时,也可以互相转换,取决于类的设计\n" << endl; }
private:
};int x = 10;
A a1(x);B b1(a1);	// 自定义类型之间的互相转换
B b2 = a1;	// 标准流程是,先构造a1,再拷贝构造b2,编译器优化为了直接构造b2,发生隐式类型转换

在这里插入图片描述

无关联的自定义类型不能互相转化
vector<int> v;
string s;
v = (vector<int>) s;	// 错误,类型无法转换

const类型转换

  • 常变量去掉 const 属性,是危险的
// 类型转换的坑,常变量去掉 const 属性,是危险的const int N = 10;	// N 不是常量, N叫常变量
// 转换是有安全隐患的
int* p = (int*)&N;
(*p)++;
cout << N << " " << *p << endl;

在这里插入图片描述

  • 调试窗口中都是11,打印出来却是10 和 11,因此常变量去掉 const 属性,是危险的

在这里插入图片描述

  • 原因是编译器的优化
    • 编译器认为,通过常变量的地址修改常变量的方式是不安全的
    • 所以访问常变量时,编译器直接使用最初值进行替换。或者常变量初始化时,一并将初始值放到寄存器中,当需要使用常变量时,去访问寄存器中的初始值,不同的编译器实现可能不同

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

C语言的隐患:传统强制类型转换太“万能”

  • 在 C 语言中,类型转换只有一种形式: (T)value
int* p = (int*) 0x1234;
double d = (double) 3;
Base* b = (Base*) derived;

这种 (T)value 的写法语义非常模糊:

  • 编译器无法区分你是要做安全的数值转换(int ↔ double),还是危险的指针重解释(int* ↔ double*)。
  • 它不提供任何编译期检查,即使转换毫无意义,也会直接通过。
  • 可读性差,难以理解开发者的意图。

C++ 为了解决这些问题,引入了 4 种不同语义的类型转换运算符,既保持灵活性,又能在编译阶段发现潜在问题

3. C++的4种类型转换

1. static_cast

  • static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但不能用于两个不相关的类型进行转换。它在编译时执行类型检查,但不进行运行时检查

使用场景总结

  • 适用场景

    • 基本数据类型转换:如将 double转换为 int,但这种转换可能导致精度损失。
    double d = 13.94;
    int x = static_cast<int> (d); // x = 13
    
    • 在这里插入图片描述

    • 类继承层次结构中的上行转换:将派生类指针或引用转换为基类指针或引用,这类通常也可隐式完成,因为派生类对象包含其基类的完整子对象。但用 static_cast 可使意图明确这是安全的,

    class Base {};
    class Derived : public Base {};
    Derived d;// 向上转换
    Base* bPtr = static_cast<Base*>(&d); // 安全的上行转换
    
    • 枚举与整数类型转换
    • void\*转换为具体类型的指针

易错场景

  • 不能替换 dynamic_cast进行安全的向下转换:如果对指向基类对象的基类指针进行下行转换(转换为派生类指针),static_cast不会进行运行时类型检查,结果是未定义的,可能导致内存访问错误。
Base* basePtr = new Base; // 基类指针实际指向基类对象Derived* derivedPtr = static_cast<Derived*>(basePtr); // 编译通过,但运行时危险!
// derivedPtr 可能会被误当作指向完整 Derived 对象的指针使用
  • 数值截断/窄化doubleint 会舍弃小数,范围外会不可定义或实现定义行为(取决于实现)。
  • 误以为能移除 conststatic_cast 无法移除 const,要用 const_cast
  • 不能用于转换两个不相关的指针类型(如 int*double*),这种转换需要 reinterpret_cast

2. reinterpret_cast

  • reinterpret_cast是最强大但也最危险的转换,它提供最低层次的重新解释,不进行任何类型安全性检查
  • 适用场景(通常限于底层编程): 指针与整数之间的转换(如将指针值转换为 uintptr_t进行调试或哈希)。 不相关指针类型之间的转换(如 Foo*Bar*)。 函数指针类型之间的转换

  • 不可替换性与易错场景

    • 不能替换 static_cast进行有关联类型的转换:例如,不能用于有继承关系的类指针的上行或下行转换,虽然有时能编译通过,但行为是未定义的。
    • 极易导致未定义行为:其结果高度依赖于编译器、平台和内存布局,滥用是灾难性的。
    int i = 10;
    double* dPtr = reinterpret_cast<double*>(&i); // 危险!
    double d = *dPtr; // 未定义行为,试图将 int 的位模式当作 double 解释
    
    • 不具备可移植性:在一台机器上能工作的 reinterpret_cast代码,在另一台不同架构的机器上可能完全失败。

3. const_cast

const_cast专门用于修改类型的 constvolatile属性,不进行任何实际的数据转换

  • 适用场景

    • 移除 const属性:主要用于调用那些参数为非 const但实际不会修改对象内容的旧式接口或第三方库函数,而你知道被指向的对象本身并不是常量。
    void legacyFunction(char* str) { /* 该函数不修改 str */ 
    }const char* constStr = "hello";// legacyFunction(constStr); // 错误:类型不匹配legacyFunction(const_cast<char*>(constStr)); // 在确定函数行为的前提下使用
    
    • 提醒添加 volatile属性:移除const属性是危险的,需要添加volatile属性

    • volatile修饰的变量编译器在取它的值的时候,每次都会去内存中去取防止编译器去寄存器中取初始值

      •   volatile const int N = 10;// 使用了 const_cast ,这样转换是有风险的,提醒使用者需要加上 volatileint* p = const_cast<int*> (&N);	(*p)++;cout << *p << " " << N << endl;
        
      • 在这里插入图片描述

      • 在这里插入图片描述

  • 不可替换性与易错场景

    • 绝对不能用于修改原本就是常量的对象:这是最常见的严重错误,会导致未定义行为。
    const int x = 42;
    int* px = const_cast<int*>(&x);
    *px = 100; // 未定义行为!程序可能崩溃或行为异常
    
    • 不能进行实际的数据类型转换(如 int*double*),这是 static_castreinterpret_cast的职责。

易错场景

  • 对原本是 const 对象去掉 const 后修改 引发未定义行为。示例:

    const int ci = 10;
    int* p = const_cast<int*>(&ci);
    *p = 20; // UB:ci 在静态存储(只读)上,修改是未定义行为
    
  • 正确用法的前提:只有当原始对象本身非 const、只是通过 const 引用/指针传递时,用 const_cast 去掉 const 并修改才是安全的:

    int x = 10;
    const int* cp = &x;int* p = const_cast<int*>(cp);
    *p = 20; // 合法,x 变为 20
    

4. dynamic_cast

dynamic_cast用于将一个父类对象的 指针/引用 转换为子类对象的指针或引用(动态转换)

  • 向上转型子类对象 指针/引用 -> 父类 指针/引用 ( 不需要转换,赋值兼容规则(切片))

  • 向下转型父类对象 指针/引用 -> 子类指针/引用(用dynamic_cast转型是安全的)

  • 注意

    1. dynamic_cast只能用于父类含有虚函数的类

    2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0


使用场景

  • 在面向对象的多态类层次中做运行时安全的向下转换(或横向转换)。
  • 要求转换目标类型为指针或引用,且源类型必须为指向多态类型(至少有一个 virtual 函数)的指针/引用。
  • 当转换失败:对指针返回 nullptr;对引用抛 std::bad_cast

父子指针/引用的转换

// C++中的类型转换 dynamic_cast
class A {
public:virtual void f() {}int _x = 0;
};class B : public A {
public:int _y = 1;
};
void func1(A* pa) {//B* pb = (B*)pa;B* pb = dynamic_cast<B*>(pa);if (pb) {cout << "转换成功" << endl;pb->_x++;pb->_y++;}else {cout << "转换失败" << endl;}
}void test14() {A a;B b;// void func1(A* pa)  参数是父类的指针,父类的指针可能指向父类,也可能指向子类func1(&a);	// 父类指针指向父类,再转成子类,转换失败func1(&b);	// 父类指针指向子类,再转成子类,完全支持
}

在这里插入图片描述

  • 因此强制类型转换,在面对多态类型时,也是不安全的
情况说明是否危险
父类指针实际指向子类对象常见于多态,如 Base* pb = new Derived();不危险,可以安全地 dynamic_cast
⚠️ 父类指针实际指向父类对象Base* pb = new Base();危险!向下转型错误,会返回 nullptr(指针版)或抛出异常(引用版)

示例:

Base* pb1 = new Derived();
Derived* pd1 = dynamic_cast<Derived*>(pb1);  // ✅ 成功,pd1 非空Base* pb2 = new Base();
Derived* pd2 = dynamic_cast<Derived*>(pb2);  // ❌ 失败,pd2 == nullptr

dynamic_cast 的真正价值

它不仅是“让编译器提前发现错误”,而是:
在运行时(runtime)进行类型检查,防止不安全的强制类型转换。

也就是说:

  • static_cast 是编译时转换(不检查类型匹配),可能出错。
  • dynamic_cast 是运行时转换(依赖 RTTI),能在程序运行时检查实际类型

如果没有 dynamic_cast,写:

Derived* pd = static_cast<Derived*>(pb);  // ❌ 可能错误,但编译能过

即使 pb 实际上并不是指向 Derived,也会强行转过去,运行时访问子类成员时会导致未定义行为


总结一句话

“dynamic_cast 是一种安全的向下转型操作,核心作用不是“编译期发现错误”,它在运行时检查对象的真实类型,防止无效或危险的类型转换。”

“父类 指针/引用 向下转型为子类 指针/引用 是潜在危险的操作,使用 dynamic_cast 可以在运行时检测并防止这种错误,而不是仅仅在编译时。”

四者不能互换的典型对比(精要)

  • 要做运行时安全的多态向下转换 → 用 dynamic_caststatic_cast 不可替代(无检查)。
  • 去掉 or 添加 const/volatile → 用 const_cast。其他三者都不是用于修改 cv 的工具。
  • 按位/低级别重解释(不同类型的位模式互看) → 用 reinterpret_cast(但请尽量避免)。static_cast/dynamic_cast 不做按位重解释。
  • 要做常规类型转换(数值转换、明确的上/下/横向类型转换在已知安全情形下) → 优先 static_cast
  • C 风格的 (T)xT(x):它会尝试多种转换(等同于 const_cast/static_cast/reinterpret_cast 的组合行为之一),具有较高风险,不利于代码可读性与安全,建议用显式的 C++ 转换运算符。

注意

  • 强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是否还有其他不同的方法达到同一目的。
  • 如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会。强烈建议:避免使用强制类型转换

4. RTTI

RTTI,Run-time Type identification的简称,即:运行时类型识别。 是 C++ 提供的一组机制,用于在程序运行时确定对象的实际类型
当使用继承与多态时,RTTI 能帮助程序安全地识别、检查和操作对象的真实类型。
C++通过以下方式来支持RTTI

  1. typeid运算符:可以查看类型
  2. dynamic_cast运算符:可以检查类型
  3. decltype:推导表达式类型声明对象
运算符作用阶段主要用途是否依赖多态失败时行为
typeid运行时获取对象的实际类型信息✅ 是抛异常(对空指针解引用)
dynamic_cast运行时安全的向下转型✅ 是返回 nullptr / 抛异常
decltype编译时推导表达式类型❌ 否无失败情况

5. C++中类型转换的价值

C++ 引入 四种类型转换运算符static_castdynamic_castconst_castreinterpret_cast)最核心的原因,就是对比 C 语言的 “万能强转” (T)expr,提供 更明确的语义、更强的类型检查、更好的可维护性

1. 明确的语义与意图

  • C 语言的 (T)expr
    唯一的写法,背后可能做四类事情(数值转换、去掉 const、指针重解释、类层次转换),但代码阅读者无法一眼看出意图

    int x = 10;
    const int* p = &x;
    int* q = (int*)p;   // 看不出是去掉 const 还是其他操作
    
  • C++ 四种转换
    每种转换运算符表意明确。例如:

    int* q = const_cast<int*>(p);  // 立刻知道是去掉 const  提醒开发者 注意使用 
    double y = static_cast<double>(x); // 明确是数值类型转换
    

👉 价值:提高代码的可读性和自文档化能力,让意图一眼可见。


2. 更强的类型检查

  • C 风格转换:会在编译器内部依次尝试 const_caststatic_castreinterpret_cast,只要能找到一条路径就允许,往往隐藏潜在风险。

    struct A {};
    struct B {};
    A* pa = new A;
    B* pb = (B*)pa;   // 编译能过,但解引用 pb 是未定义行为
    
  • C++ 的 static_cast/dynamic_cast
    编译器会根据语义进行约束,例如 static_cast 不允许无关类指针互转(必须显式用 reinterpret_cast,一眼就能看出危险)。

👉 价值:在编译期帮助发现不合理的转换,避免 bug。


3. 更安全的运行时行为

  • C 风格转换:向下转型时不做运行时检查,解引用错误类型指针就是 UB。

  • C++ dynamic_cast:提供运行时检查,失败时返回 nullptr 或抛异常。

    Base* b = new Base;
    Derived* d = dynamic_cast<Derived*>(b); 
    if (!d) { std::cout << "安全失败\n"; }
    

👉 价值:提供运行时安全保证,特别适合多态编程,降低 UB 风险。


4. 限制滥用(逼迫开发者思考)

  • C 风格转换:什么情况都用 (T)expr,久而久之容易滥用,尤其是 reinterpret_cast 类型的危险转换被隐藏。
  • C++ 四种转换
    • const_cast 只能改 cv 限定,不能做其他事。
    • reinterpret_cast 只能做位级重解释,一眼就知道风险。
    • static_cast 只能做编译时能保证的类型安全转换

👉 价值:通过语法强制分类,迫使开发者思考“我到底在做哪种转换”,从而减少不安全的随意强转。


5. 更好的可维护性和团队协作

  • 阅读别人代码时:
    • 看到 const_cast → 立刻知道是“移除 const”。
    • 看到 dynamic_cast → 知道是“安全的向下转换”。
    • 看到 reinterpret_cast → 立刻警觉“底层危险操作”。
  • 相比之下,C 风格 (T) 一律长一个样,必须靠上下文猜。
  • 在大型工程、多人协作中,明确的转换语义可显著减少误解和隐藏 bug。

👉 价值:提高可维护性和团队开发效率。


6. 与现代 C++ 特性的兼容

  • dynamic_cast 与 RTTI(运行时类型识别)结合,为多态编程提供安全性。
  • const_castconst 正确性(const-correctness)配合,帮助保持接口设计的严谨性。
  • reinterpret_cast 与底层开发(嵌入式、驱动、协议解析)结合,让危险操作更可控。
  • static_cast 替代 C 风格转换,避免隐式陷阱。

👉 价值:契合 C++ 的类型系统和设计哲学。

一句话总结C++ 四种类型转换的价值:

  • 相比 C 风格的万能强转,它们通过语法将“不同性质的转换”区分开,让编译器更好地发现错误,增加类型 安全、提高代码可读性、提供运行时安全检查、限制滥用,从而减少未定义行为和维护成本。

6. 结语

类型转换让 C++ 更灵活,也更容易出错。

  • C++ 将转换分门别类,不是增加复杂度,而是让安全与语义更清晰

掌握四种 cast 的边界,是理解 C++ 类型系统的关键一步:
static_cast 负责编译期安全,dynamic_cast 保障运行期安全,
const_cast 管理修饰属性,reinterpret_cast 提供底层能力。

类型转换不是技巧,而是思维。
每一次显式的转换,都是在安全与灵活之间作出的选择。


以上就是本文的所有内容了,如果觉得文章对你有帮助,欢迎 点赞⭐收藏 支持!如有疑问或建议,请在评论区留言交流,我们一起进步

分享到此结束啦
一键三连,好运连连!

你的每一次互动,都是对作者最大的鼓励!


征程尚未结束,让我们在广阔的世界里继续前行! 🚀

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

相关文章:

  • MLIR 中的 Linalg Dialect
  • 昆明网站建设系统有哪些wordpress seo链接
  • MySQL基础随堂笔记1
  • 【开题答辩实录分享】以《住宅小区在线服务平台》为例进行答辩实录分享
  • C标准库--浮点<float.h>
  • 大学生兼职做网站做行业导航网站好
  • 怎样做个网站手机网页视频下载软件
  • 麻涌镇做网站医疗器械注册证查询
  • 青浦郑州阳网站建设邹城网站建设
  • 状态机的介绍
  • 算法---位运算
  • 如何使用python创建和维护sqlite3数据库
  • 顺德手机网站设计咨询用什么做网站后台
  • 深圳企业网站建设服务网站客户需求分析
  • 大模型 | VLM 初识及在自动驾驶场景中的应用
  • Android CarService调试操作
  • 建设部网站哪里可以报名考监理员聚名网域名注册官网
  • 一个坐标转换
  • 南京文化云网站建设群晖nda做网站
  • 网站开发一次性费用国外服务器免费ip地址
  • 一个虚拟空间做两个网站网站开发的技术流程图
  • wordpress建站全教程设计师网站资源
  • 公司网站建设记哪个科目网站目的
  • 连云港网站建设费用wordpress flash插件下载
  • 做模型找三视图那些网站深圳做网站收费
  • 不建议网站北京优化核酸检测
  • 网站的开发语言有哪些鞍钢建设集团网站
  • 山东省某三甲医院基于分类分级的数据安全防护建设实践
  • (六)构建多智能体旅行客服-如何切换智能体角色
  • SpringCloud-基础