一、表达式计算的基本规则
1. 表达式的类型由操作数决定
在C++中,表达式的计算结果类型由==操作数的类型决定==,而不是由赋值目标的类型决定。
int a = 100000, b = 100000;
long long c = a * b; // 错误!a*b先以int计算,**已溢出**
long long d = (long long)a * b; // 正确,将a提升为long long
2. 整数提升规则
C++进行算术运算时,会按照以下规则进行类型提升:
-
如果两个操作数类型不同,将较低精度的类型提升到较高精度的类型
-
整数提升的顺序:
bool → char → short → int → long → long long
int a = 5;
long b = 10;
auto c = a + b; // c的类型是long,因为b的类型更高
二、隐式类型转换的陷阱
1. 整数溢出
int a = 1000000;
int b = 1000000;
int c = a * b; // 溢出!结果是错误的
// 实际上:1,000,000 * 1,000,000 = 1,000,000,000,000
// 但int最大值约21亿,所以溢出
2. 有符号与无符号混合
unsigned int a = 10;
int b = -5;
auto c = a + b; // 先将b转换为unsigned int,再进行计算
cout << c; // 输出5,但可能不符合预期
三、常见的溢出场景
1. 乘法溢出
int a = 100000, b = 100000;
int c = a * b; // 溢出
2. 加法溢出
int a = 2000000000;
int b = 2000000000;
int c = a + b; // 溢出
3. 组合数计算
int n = 100000;
int combinations = n * (n-1) / 2; // 溢出发生在乘法阶段
四、如何避免溢出
1. 使用更大的类型
int a = 1000000, b = 1000000;
long long c = (long long)a * b; // 正确
2. 提前进行类型转换
int n = 100000;
long long comb = 1LL * n * (n-1) / 2; // 使用1LL提升整个表达式
3. 使用强制类型转换
int a = 1000000, b = 1000000;
long long c = static_cast<long long>(a) * b;
4. 注意除法顺序
// 危险:可能先溢出再除法
int a = 1000000, b = 1000000, c = 1000;
long long x = a * b / c; // 溢出
// 安全:先除法再乘法
long long y = a / c * b; // 但注意整数除法的截断
long long z = 1LL * a * b / c; // 最佳:用更大的类型
五、算法竞赛中的最佳实践
1. 默认使用long long
在不确定是否会溢出时,使用long long。
typedef long long ll;
ll a = 1000000, b = 1000000;
ll c = a * b; // 安全
2. 在乘法前进行类型提升
int a = 1000000, b = 1000000;
ll c = 1LL * a * b; // 安全写法
3. 使用自定义类型别名
typedef long long ll;
typedef unsigned long long ull;
4. 注意输入范围
-
仔细阅读题目给出的数据范围
-
估算中间结果的最大值
-
选择合适的整数类型
六、实用技巧
1. 快速检查是否溢出
#include <climits>
int a, b;
if (b > 0 && a > INT_MAX / b) {
// 乘法会溢出
}
if (b < 0 && a < INT_MIN / b) {
// 乘法会溢出
}
2. 使用内置溢出检查
C++20及以上版本:
#include <utility>
auto [result, overflow] = std::mul_overflow(a, b);
if (overflow) {
// 处理溢出
}
3. 无符号数的特性
无符号整数溢出是定义良好的(取模),但可能不是你想要的行为:
unsigned int a = 4000000000;
unsigned int b = 4000000000;
unsigned int c = a + b; // 结果是 8000000000 % 2^32
七、总结
-
表达式类型由操作数决定,与赋值目标无关
-
在进行可能产生大数的计算时,提前提升类型
-
算法竞赛中,习惯性使用
long long 或在乘法前加1LL -
仔细估算中间结果的范围,而不仅仅是最终结果
-
注意有符号和无符号的混合运算
记住:预防溢出总是比调试溢出更容易。在写代码时,多思考一下数据的范围,就能避免很多难以发现的错误。
