字符串按位减法是模拟手算逐位减法,从右向左遍历,手动处理借位,避免整数溢出;需补零对齐、逆序构建结果后反转,并去除前导零(除结果为"0"外)。

什么是字符串按位减法?
字符串按位减法不是标准库操作,而是指把两个表示非负整数的字符串(如 "123" 和 "45")当作十进制数,逐位模拟手算减法:从低位到高位,借位、相减、记录结果。它常用于大整数减法(超出 long long 范围),且要求不依赖 std::stoi 或 atoll 等转换函数。
为什么不能直接转成整数再减?
因为输入可能长达几百位,例如 "999999999999999999999999999999",远超 unsigned long long 的最大值(约 1.8×10¹⁹)。一旦调用 stoull 会抛出 std::out_of_range 异常,或静默溢出——结果完全不可信。
- 必须逐字符处理,每位对应一个数字(
'0'→ 0,'9'→ 9) - 必须手动管理借位:当前位不够减时,向前一位借 1,本位加 10
- 结果字符串不能有前导零(除非结果就是
"0")
如何安全实现减法逻辑(不反转字符串)?
推荐从右向左遍历,用两个指针分别指向两字符串末尾,同时维护一个借位标志 carry(初始为 0)。关键点在于:被减数当前位是 a[i],减数当前位是 b[j],真实计算值为 (a_digit - b_digit - carry);若为负,则需借位(carry = 1),并给结果加 10。
string subtract(string a, string b) {
// 假设 a >= b(否则先交换并标记负号,此处略去符号处理)
int i = a.size()-1, j = b.size()-1, carry = 0;
string res;
while (i >= 0 || j >= 0) {
int digit_a = (i >= 0) ? a[i--] - '0' : 0;
int digit_b = (j >= 0) ? b[j--] - '0' : 0;
int diff = digit_a - digit_b - carry;
if (diff 1 && res.back() == '0') res.pop_back();
reverse(res.begin(), res.end());
return res;
}
注意:res 是逆序构建的(先算个位,最后才高位),所以必须 reverse。若省略这步,结果会完全颠倒。
容易忽略的边界情况有哪些?
实际写的时候,这几个点最常出错:
-
a和b长度不同:短字符串高位补 0,不能直接跳过(见上面digit_a/digit_b的三元判断) - 借位贯穿全程:比如
"1000" - "1",最后三位都要借位,carry必须持续传递到最高位 - 结果为
"0":若输入是"5" - "5",循环后res = "0",此时while (res.size()>1 && ...)不会删掉它——逻辑正确;但如果误写成res.size() > 0,就会清空成空串 - 未处理
a 的情况:上述代码假设 <code>a >= b。真实使用中应先用字典序或长度+字典序比较两字符串大小,必要时交换并添加负号前缀
真正难的不是算法本身,而是借位状态和索引边界的同步——多一重条件漏判,结果就错一位,而且很难调试。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











