← 目录 / 第二章 · 变量与数据类型 / 2.1 数据类型转换

2.1 数据类型转换

不同数据类型之间可以相互转换,有些是编译器自动完成的,有些需要程序员手动指定,理解转换规则能避免很多隐蔽的 bug。

数据类型转换

不同数据类型之间可以相互转换,有些是编译器自动完成的(隐式转换),有些需要程序员手动指定(强制转换)。理解转换规则能避免很多隐蔽的 bug——很多"程序结果不对但又找不到错"的情况,根源都在这里。

2.1.1 为什么需要类型转换

可以把每种数据类型想象成一个不同大小的"箱子":char 是最小的箱子,只能装 1 字节的东西;int 是中等大小的箱子,能装 4 字节;double 是专门用来装小数的大箱子,能装 8 字节。

当程序需要把一个数据从一种类型的"箱子"挪到另一种类型的"箱子"里时,就发生了类型转换。挪的方向不同,结果也不同:

类型大小说明
char1 字节最小的整数类型,通常用来存字符
short2 字节较小范围的整数,竞赛中较少单独使用
int4 字节最常用的整数类型,范围约 ±21 亿
long long8 字节大范围整数,防止 int 溢出时使用
float4 字节单精度小数,精度较低
double8 字节双精度小数,竞赛中最常用的小数类型
📦
装箱与拆箱的类比:int(中箱子)里的东西倒进 double(大箱子),空间绰绰有余,数据完整无损,这叫升级转换,编译器会自动帮你做。反过来,把 double(大箱子,比如 3.99)硬塞进 int(小箱子),装不下的小数部分会被直接丢弃,只剩下 3,这叫降级转换,往往需要你手动明确指定,因为这一步可能丢失信息。

2.1.2 隐式类型转换(自动)

运算时,如果两个不同类型的数据凑在一起做加减乘除,精度低的类型会自动向精度高的类型转换,这个过程叫类型提升(type promotion),完全不需要你写任何代码,编译器在背后悄悄完成。提升的顺序如下:

char short int long long float double 精度由低到高,自动提升 →

来看一个最简单的例子:一个 int 和一个 double 相加,int 会先被临时提升为 double,再参与运算,结果自然也是 double

C++ · 隐式转换(正常情况)
1int a = 3;
2double b = 1.5;
3double c = a + b; // a 自动提升为 3.0,再与 1.5 相加,c = 4.5 ✓

但下面这个例子最容易让初学者踩坑——两个 int 相除,结果依然是 int,即使你把它赋值给一个 double 变量也没用,因为类型提升只发生在运算的那一刻,而不是赋值的时候:

C++ · 隐式转换(常见陷阱)
1int x = 7, y = 2;
2double z = x / y; // 期望 z = 3.5,实际 z = 3.0 ✗

为什么会这样?把这一行拆开来看,运算是分两步走的:

7 / 2 3 ① 两个 int 相除,做整数除法,小数部分直接舍弃
3 3.0 ② 赋值给 double 时才发生转换,但小数部分早就没了
⚠️
关键原则:隐式转换发生在运算的时候,不是赋值的时候。x / y 先以整数方式计算得到 3,然后才把 3 转成 3.0 赋给 z。要得到 3.5,必须在做除法之前就让其中一个数变成小数——这正是下一节"强制类型转换"要解决的问题。

2.1.3 强制类型转换(手动)

当自动转换不满足需求时,可以由程序员手动指定转换方式,明确告诉编译器"我要把这个值当成另一种类型来用"。C++ 里常见的写法有两种:

1
C 风格强制转换:(类型) 表达式,例如 (double)x。这是从 C 语言继承下来的写法,简洁但不够安全(无法检查转换是否合理)。
2
C++ 函数式转换:类型(表达式),例如 double(x)。写法更像"调用一个函数",是 C++ 里更推荐的写法,竞赛中最常见。

回到刚才 7 / 2 的例子,只要在除法发生之前把其中一个数转换成 double,编译器就会用小数除法计算:

C++ · 强制转换解决除法陷阱
1int x = 7, y = 2;
2
3double r1 = (double)x / y; // C 风格:先把 x 转成 7.0,r1 = 3.5 ✓
4double r2 = double(x) / y; // 函数式:效果相同,r2 = 3.5 ✓
5double r3 = double(x / y); // ⚠️ 陷阱:括号只包住了结果,先整除得 3,再转成 3.0
🚫
括号位置决定一切:第 3、4 行是把 x 单独转换后再参与除法,第 5 行却是把整个 x / y 的结果转换——这时候除法已经用整数方式算完了,转换来得太晚,救不回丢失的小数部分。写强制转换时,一定要确认括号包住的是"转换前的那个数",而不是"运算后的结果"。

强制转换也常用于 charint 之间的互转,这在处理字符时非常实用(下一节 ASCII 编码会详细展开):

C++ · char 与 int 互转
1int code = int('A'); // code = 65
2char ch = char(code + 1); // ch = 'B'
💡
竞赛建议:竞赛中用 类型(表达式) 的 C++ 函数式转换简洁够用。在正式工程项目中,C++ 还提供了四种更安全、意图更明确的命名转换:static_castdynamic_castconst_castreinterpret_cast,写法是 static_cast<类型>(表达式)。竞赛入门阶段先了解即可,不必强求现在就用。

2.1.4 常见陷阱与易错点

把本节的内容汇总成几条最容易踩坑的规则,写代码时可以对照检查:

1
整数除法陷阱:int / int 永远是整数除法,哪怕结果赋值给 double 也无法挽回丢失的小数。必须在除法之前把其中一个操作数转换成浮点类型。
2
强制转换是截断,不是四舍五入:(int)3.9 的结果是 3,不是 4!小数部分直接被砍掉。如果需要四舍五入,要用 <cmath> 里的 round() 函数(见 2.4 节),再转换成整数。
3
大类型转小类型可能溢出:把一个超出范围的值塞进更小的类型,结果会变得不可预测。例如 int big = 300; char c = (char)big;,由于 char 最大只能表示到 127,c 不会等于 300,而是发生"溢出绕回",变成一个意想不到的负数。
4
括号位置容易写错:强制转换的括号一定要紧贴"转换前的那个数",写成 (double)x / y 而不是 (double)(x / y),一字之差,结果完全不同。