C++ 类型转换

在编写程序时,我们经常会遇到不同类型的数据混在一起运算或赋值的情况,比如拿 int 整数和 double 小数相乘,或者把两个整数相除算出的平均值存进 double 变量里。

在 C++ 中,把一种数据类型的值转换成另一种数据类型,就叫做 “类型转换”。C++ 的类型转换主要分为 2 种:① 隐式类型转换;② 显式类型转换。

C++ 隐式类型转换

在 C++ 中,隐式类型转换也叫做 “自动转换”。这种转换是由编译器在背后自动完成的,不需要我们手动写转换代码,日常主要发生在 “混合算术运算” 和 “赋值与初始化” 这两类场景中。

1. 混合运算中的隐式转换

当不同类型的数据放在同一个算术表达式中运算时,C++ 必须先把它们转换成同一种类型,然后才能继续计算。混合运算中的自动转换遵循一个很好记的原则:朝着表示范围更大、精度更高的方向转换,尽量保证数据不丢失。常见的规则有以下 2 条:

  • 整型与浮点型混合运算:编译器会先将整型自动转换为浮点型,再按浮点数计算。比如 int 和 double 一起运算时,会先把 int 转换为 double,最终算出的结果也是 double 类型。
  • 较小的整型参与运算:像 char、short 这类比 int 范围更小的类型,在参与普通算术运算时会先自动提升为 int 类型,这叫做 “整数提升”。比如 'A' + 1 会先把 'A' 的 ASCII 码 65 转为 int,再算出整数 66。

示例 1:“int * double” 的隐式转换

#include <iostream>

int main() {
    int count = 5;
    double price = 1.5;
    double total = count * price;
    std::cout << total << '\n';
    return 0;
}

运行结果如下。

7.5

分析:

在这个例子中,count 是 int 类型,price 是 double 类型。执行 count * price 时,C++ 会先把 count 参与运算的值自动转换为 double 类型的 5.0,再与 1.5 相乘得到 7.5。

这里小伙伴们要留意一点:发生转换的只是 count 在当前算式里产生的一份临时值,变量 count 本身在内存中的类型依然是 int,并不会因为参与了一次乘法就变成 double。

2. 赋值与初始化中的隐式转换

在给变量赋值或初始化时,如果等号(=)左右两边的类型不一致,同样会触发隐式类型转换。赋值时的规则很直接:等号右边的值,会自动转换为等号左边变量的类型。

如果我们把整数 95 赋给 double 变量,它会自动转为 95.0,数值不会有任何损失。但如果反过来把 double 小数赋给 int 整型变量,由于整型无法保存小数部分,小数位就会直接丢失。下面我们来看一个例子。

示例 2:赋值运算的隐式转换

#include <iostream>

int main() {
    double positive = 3.9;
    double negative = -3.9;

    int a = positive;
    int b = negative;

    std::cout << a << '\n';
    std::cout << b << '\n';
    return 0;
}

运行结果如下。

3
-3

分析:

从运行结果可以看出,把浮点数赋值给整型变量时,C++ 并不会进行四舍五入,而是直接抹去小数部分,干脆利落地保留整数部分,因此 3.9 变成了 3,-3.9 变成了 -3。

提示 把高精度的 double 赋给低精度的 int 时容易丢失小数,这种转换也叫 “窄化转换”。在现代 C++ 中,如果使用花括号初始化变量,比如写成 int num{3.9};,编译器就会直接报错来帮我们拦截这类精度丢失。

C++ 显式类型转换

隐式类型转换是由编译器自动进行的,而 “显式类型转换” 则需要我们手动编写代码,明确指定把一个值转换成哪种目标类型,所以也叫 “强制类型转换”。

为什么有了自动转换,还需要手动做强制转换呢?我们先来看一个日常开发中非常容易踩坑的例子:把两个 int 整型变量相除,然后将结果保存到 double 变量里。

示例 3:两个整型变量相除

#include <iostream>

int main() {
    int total = 15;
    int count = 2;
    double average = total / count;
    std::cout << average << '\n';
    return 0;
}

运行结果如下。

7

分析:

明明 average 定义成了 double 类型,为什么输出的却是 7 而不是 7.5 呢?根据上一节学过的运算符优先级,除号(/)的优先级高于赋值号(=),程序会先算完等号右边的 total / count,最后才赋给 average。对于这个例子来说,上面代码等价于下面两步:

int temp = total / count;    // 先按整数除法算出 7
double average = temp;       // 再转为 double 类型的 7.0

可以看到,赋值时的隐式转换发生在整数除法结束之后,左边的 double 没法把前面已经丢掉的 0.5 补回来。而参与除法的是两个变量,我们又不能直接在变量名后面写 .0,这时就需要用到显式类型转换了。

在现代 C++ 中,对基础数值做显式类型转换时,最常用的是 static_cast 运算符。

语法:

static_cast<目标类型>(值或表达式)

说明:

尖括号 <> 里面填写想要转换成的目标类型,比如 double 或 int。后面的小括号 () 里面填写需要转换的变量、数值或表达式。

示例 4:使用 static_cast 保留小数

#include <iostream>

int main() {
    int total = 15;
    int count = 2;
    double average = static_cast<double>(total) / count;
    std::cout << average << '\n';
    return 0;
}

运行结果如下。

7.5

分析:

static_cast<double>(total) 先把除号左边的 total 临时转换成了 double 类型的 15.0,右边的 count 随后自动转换为 2.0,整个算式按浮点数除法算出了准确结果 7.5。

平时写代码时大家要特别注意:想让两个 int 变量相除保留小数,只需对其中一个变量使用 static_cast<double> 即可,千万不要把整个除法算式包进小括号里:

// 正确方式
double res = static_cast<double>(total) / count;

// 错误方式
double res = static_cast<double>(total / count);

如果写成上面的错误方式,程序会先计算小括号里面的整数除法 total / count 得到整数 7,外面的 static_cast<double> 只是事后把 7 变成 7.0,小数部分依然丢失了。

反过来,当业务上确实需要把浮点数显式截断为整数时,我们可以使用 static_cast<int>。此时同样要留意转换的位置,“先转换再相加” 与 “先相加再转换” 的结果往往是不一样的。请看下面例子。

示例 5:“先转后加” 与 “先加后转”

#include <iostream>

int main() {
    double a = 3.6;
    double b = 10.6;

    int sum1 = static_cast<int>(a) + static_cast<int>(b);
    int sum2 = static_cast<int>(a + b);

    std::cout << sum1 << '\n';
    std::cout << sum2 << '\n';
    return 0;
}

运行结果如下。

13
14

分析:

为什么两个算式的结果相差了 1 呢?因为浮点数的小数部分相加后可能会产生进位。计算 sum1 时,程序先把 a 和 b 各自截断为整数 3 和 10,相加得到 13。而计算 sum2 时,程序先算小括号内的 3.6 + 10.6 得到 14.2,最后再截断小数就得到了 14。

在实际开发中,如果商品单价、身高等数据从一开始就需要带小数,直接定义为 double 变量最省心。只有当总分、人数等数据原本就是整数,仅在求平均值等算式中需要临时改变运算类型时,我们才使用 static_cast 做显式转换。

static_cast 与 C 语言风格强制转换

在阅读一些早期 C++ 代码或 C 语言代码时,我们可能会见到像 (double)total 或 (int)(a + b) 这样直接用小括号括住类型名的写法,这叫做 C 语言风格的强制类型转换。在现代 C++ 开发中,我们更推荐使用 static_cast,主要原因有两点:

意图清晰,方便查找:小括号 () 在代码里到处都在用,而 static_cast 一眼就能看出这里做了强制类型转换,在大型项目中搜索排查非常方便。

编译期检查更安全:C 语言风格的 (类型) 转换限制太宽松,容易把毫不相干的类型强行转换而埋下隐患。而 static_cast 会在编译阶段做安全检查,能够提前拦截不合理的类型转换。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号