在C++里,字符串本身没有提供像整数那样的自增运算符,但当我们需要对一个表示非负整数的字符串做加一操作时,可以通过模拟竖式加法来完成。这种做法常见于算法题中的大数处理,也能用于生成连续编号等实际场景。

为什么不能直接对字符加一
很多初学者看到字符串"123"想变成"124",会写出类似下面的代码:把最后一个字符取出来加一再放回去。这种方式在末位不是九的时候看似可行,但一旦遇到"129"或"999",就会产生错误的进位结果。
字符在C++里本质是整数,'9' + 1得到的是冒号对应的ASCII值,并不是'0'。如果我们不手动处理进位,字符串就无法正确表达数值加一后的结果。因此必须按位模拟加法,从最低位向高位传递进位标志。
#include <iostream>
#include <string>
using namespace std;
// 错误示例:未处理进位
string bad_increment(string s) {
if (!s.empty()) {
s.back() = s.back() + 1; // 若末尾是'9'会得到错误字符
}
return s;
}
int main() {
string a = "129";
cout << bad_increment(a) << endl; // 输出异常,非"130"
return 0;
}
基础算法:从末位向前进位
核心思路是设置一个进位变量carry,初始为1,代表要加的数字一。从字符串最后一个字符开始,将当前字符转成数字并与进位相加,如果和为十,则当前位变零、进位保持为一;否则当前位更新为和的字符、进位变零,循环结束。
如果遍历完所有字符后进位仍为一,说明原字符串全是九,需要在最前面插入字符'1'。这样既能处理普通情况,也能正确处理"999"变成"1000"的边界。
#include <string>
// 正确示例:模拟加一
std::string increment_string(std::string s) {
if (s.empty()) return "1";
int carry = 1;
for (int i = s.size() - 1; i >= 0 && carry; --i) {
int val = (s[i] - '0') + carry;
if (val == 10) {
s[i] = '0';
carry = 1;
} else {
s[i] = '0' + val;
carry = 0;
}
}
if (carry) {
s.insert(s.begin(), '1');
}
return s;
}
处理前导零与输入校验
在实际工程中,数字字符串可能带有前导零,例如"0012"。上述算法会将其变成"0013",这在数值上没错,但如果业务要求规范化输出,可以在返回前去掉前导零,只保留一个零当结果为零时。
另外,应当校验字符串是否只包含数字字符。若包含字母或符号,直接加一没有意义。下面示例在函数中增加了简单校验,遇到非法字符返回空串表示失败,调用方可以据此报错。
#include <string>
std::string safe_increment(const std::string& s) {
if (s.empty()) return "1";
for (char c : s) {
if (c < '0' || c > '9') return "";
}
std::string res = s;
int carry = 1;
for (int i = res.size() - 1; i >= 0 && carry; --i) {
int val = (res[i] - '0') + carry;
if (val == 10) {
res[i] = '0';
carry = 1;
} else {
res[i] = '0' + val;
carry = 0;
}
}
if (carry) res.insert(res.begin(), '1');
// 去除前导零,但保留至少一个字符
size_t pos = res.find_first_not_of('0');
if (pos == std::string::npos) return "0";
return res.substr(pos);
}
原地修改与返回新串的对比
上面的写法都返回了新字符串,调用者拿到结果即可,不会改动原对象。若字符串很长且频繁自增,返回新串会带来拷贝开销。此时可以改为引用参数,在原串上直接修改,避免分配新内存。
下表列出两种方式的差异:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 返回新串 | 接口清晰,无副作用 | 大字符串有拷贝成本 |
| 原地修改 | 节省内存,适合循环 | 调用后原值改变,易误用 |
在算法题里通常返回新串更省心;在生成序列号的服务中,若持有可变的字符串状态,原地修改会更高效。选择时根据调用频率与代码可维护性权衡即可。
完整可运行示例
下面给出一个包含主函数的完整示例,演示对多个测试用例调用自增函数并输出,方便你直接编译验证。
#include <iostream>
#include <string>
std::string increment_string(std::string s) {
if (s.empty()) return "1";
int carry = 1;
for (int i = s.size() - 1; i >= 0 && carry; --i) {
int val = (s[i] - '0') + carry;
if (val == 10) {
s[i] = '0';
carry = 1;
} else {
s[i] = '0' + val;
carry = 0;
}
}
if (carry) s.insert(s.begin(), '1');
return s;
}
int main() {
std::string tests[] = {"123", "129", "999", "0", "100"};
for (auto& t : tests) {
std::cout << t << " + 1 = " << increment_string(t) << std::endl;
}
return 0;
}
通过上述模拟进位的方式,我们就能在C++中以字符串形式稳健地实现数字加一。理解字符与数值的转换关系,以及进位传递的终止条件,是写出正确代码的关键。