Cpp学习13 第 10 章 类型转换、类型别名与类型推导
10.1 — 隐式类型转换
当需要一种数据类型,但提供了另一种数据类型时,编译器会自动执行隐式类型转换。
隐式类型转换 (也称为自动类型转换或强制转换 )是由编译器在上下文中预期使用其他类型时自动执行的。C++ 中绝大多数类型转换都是隐式类型转换。
从 C++23 开始,共有 14 种不同的标准转换。这些转换大致可以分为 5 个大类。
表格,略。
如果编译器找不到可接受的类型转换,则编译将失败并出现编译错误。
即使编译器知道如何将 double 值转换为 int 值,但在使用大括号初始化时,不允许进行缩小范围的转换。
10.2 — 浮点数和整型提升
数据类型使用的位数称为其宽度。宽度较大的数据类型使用更多的位数,而宽度较小的数据类型使用较少的位数。
numeric promotions 数值提升是指将某些较窄的数值类型(例如 char )转换为某些较宽的数值类型(通常是 int 或 double ),以便更高效地进行处理。
所有数值型转换都是保值的。 保值转换 (也称为安全转换 )是指每个可能的源值都可以转换为目标类型的相等值。
由于数值提升是安全的,编译器会根据需要自由地使用数值提升,并且不会发出警告。
(我好像没必要去区分是提升还是转换吧…)
10.3 — 数值转换
编译器通常不会对隐式保值转换发出警告。
略。
10.4 — 窄化转换、列表初始化和 constexpr 初始化器
在 C++ 中, 窄化转换是一种潜在的不安全的数值转换,其中目标类型可能无法容纳源类型的所有值。
由于 窄化转换 可能不安全且容易出错,因此应尽可能避免缩小转换范围。
如果需要执行缩小转换,请使用 static_cast 将其转换为显式转换。
从 constexpr 浮点类型转换为更窄的浮点类型不被视为缩小范围,即使会导致精度损失。
使用 constexpr 初始化器 列表初始化
这使我们能够避免:
- 大多数情况下都必须使用字面后缀
- 不得不使用 static_cast 来使初始化代码变得臃肿不堪。
1 | int main() |
这也适用于复制和直接初始化。
解释:
列表初始化({}) 禁止“窄化转换”(Narrowing Conversion)。
想用列表初始化一个 float 时,通常不得不 写后缀。
如果编译器在编译时明确知道这个值的大小,并且确定它能被目标类型精确表示,那么它就不算“窄化转换”。
把值定义为 constexpr(编译期常量)时,编译器就知道它很安全,于是放心地让你直接赋给 float,不再要求写后缀或强转。
10.5 — 算术转换
在 C++ 中,某些运算符要求其操作数类型必须相同。如果使用不同类型的操作数调用这些运算符,则其中一个或两个操作数将使用一组称为 “常规算术转换” 的规则隐式转换为匹配的类型。由常规算术转换规则生成的匹配类型称为操作数的公共类型 。
这就是避免使用无符号整数的主要原因之一——在算术表达式中将它们与有符号整数混用时,可能会导致意想不到的结果。而且编译器很可能甚至不会发出警告。
10.6 — 显式类型转换(强制转换)和 static_cast
| Cast | Description | Safe? 安全的? |
|---|---|---|
| static_cast | 在编译时执行相关类型之间的类型转换。 | 是的 |
| dynamic_cast | 在多态(继承)层次结构中,对指针或引用执行运行时类型转换。 | 是的 |
| const_cast | 添加或删除常量。 | 仅用于添加常量 |
| reinterpret_cast | 将一种类型的位级表示重新解释为另一种类型 | 不 |
| C-style casts | 执行 static_cast 、 const_cast 或 reinterpret_cast 的某种组合。 | 不 |
应该使用 static_cast 来转换大多数值。
static_cast 能更清晰地表明我们打算进行类型转换。虽然 static_cast比直接初始化列表的方式更冗长,但它使类型转换更容易被发现和查找。最终,这会让你的代码更安全、更易于理解。
10.7 — 类型定义和类型别名
Type aliases 类型别名
1 | using Distance = double; // define Distance as an alias for type double |
在现代 C++ 中,命名约定是将自定义的类型别名(或其他任何类型)以大写字母开头,且不加后缀。大写字母有助于区分类型名称与变量和函数名称(它们以小写字母开头),并防止它们之间发生命名冲突。
由于作用域是标识符的一个属性,类型别名标识符遵循与变量标识符相同的作用域规则:在代码块内定义的类型别名具有块级作用域,只能在该代码块内使用;而在全局命名空间中定义的类型别名具有全局作用域,可以在整个文件内使用。
Typedefs
typedef (即“类型定义”的缩写)是一种较早的类型别名创建方式。
1 | // The following aliases are identical |
优先使用类型别名而不是类型定义。
使用类型别名实现平台无关编码:固定宽度整数类型 实际上只是各种基本类型的类型别名。
使用类型别名使复杂类型更易于阅读
到处都需要输入 std::vector<std::pair<std::string, int>> 这种类型,既繁琐又容易出错。使用类型别名要简单得多:
1 |
|
使用类型别名来记录值的含义
1 | using TestScore = int; |
类型别名应主要用于那些能显著提升代码可读性或可维护性的情况。这既是一门科学,也是一门艺术。类型别名在代码的多个位置都能发挥作用时,比仅在少数位置使用时更有价值。
10.8 — 使用 auto 关键字的对象的类型推断
类型推导 (有时也称为类型推断 )是一项允许编译器从对象的初始化器推断对象类型的功能。
类型推断必须有可供推断的内容。
当对象的类型无关紧要时,请使用类型推导来定义变量。(承接 Lambda、迭代器、模板类型推导)
当您需要与初始化器类型不同的特定类型,或者当您的对象在需要明确类型的情况下使用时,请优先使用显式类型。
10.9 — 函数的类型推导
使用 auto 返回类型时,函数内的所有返回语句必须返回相同类型的值,否则会出错。
返回类型推断的最大优势在于,编译器推断函数的返回类型可以消除返回类型不匹配的风险(防止意外的转换)。
在其他情况下,函数的返回类型可能很长很复杂,或者并不那么显而易见。在这种情况下,可以使用 auto 简化。
返回类型推导的缺点
使用 auto 返回类型的函数必须先完整定义才能使用(仅进行前向声明是不够的)。
优先使用显式返回类型,而不是返回类型推导(除非返回类型不重要、难以表达或脆弱)。
尾随返回类型语法
1 | auto add(int x, int y) -> int |
对于返回类型复杂的函数,在函数末尾添加返回类型可以使函数更易于阅读;
尾随返回类型语法可用于对齐函数名称,从而使连续的函数声明更易于阅读。
类型推导不能用于函数参数类型。
10.x — 第十章总结和测验
passed.
- 标题: Cpp学习13 第 10 章 类型转换、类型别名与类型推导
- 作者: 铁名_IronName
- 创建于 : 2026-08-09 14:05:36
- 更新于 : 2026-08-09 15:57:18
- 链接: https://blog.ironname.top/2026/Cpp/Cpp学习13/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。