不能直接用atoi处理用户输入,因为它静默截断非法字符、无法区分成功与失败、不报告溢出、不校验格式合法性且无错误反馈机制,而安全转换需明确失败位置与原因。

为什么不能直接用 atoi 处理用户输入
因为 atoi 遇到非法字符(比如 "123abc" 或 " -42x")会静默截断,返回部分转换结果,且无法区分成功与失败——atoi("abc") 和 atoi("0") 都返回 0,但语义完全不同。
真实场景中,你要的不是“尽量转”,而是“明确知道转没转成”。比如命令行参数校验、配置文件解析、网络协议字段解析,都要求失败可感知、错误可定位。
-
atoi不报告溢出:输入"99999999999999999999"可能返回任意未定义值 - 不跳过前导空格以外的无效字符:
atoi(" +123xyz")返回123,但你可能期望报错 - 无错误码或异常机制,调用方只能靠输入字符串反推,不可靠
手写 stoi 级别安全转换的要点
标准库 std::stoi 已解决上述问题,但理解它怎么做的,才能在嵌入式、旧编译器或自定义规则(如禁止符号、限定进制)时自己写对。
核心逻辑分三步:跳过空格 → 解析符号和数字 → 溢出检查。关键不是“能不能转”,而是“在哪一步失败”。
立即学习“C++免费学习笔记(深入)”;
- 必须用
long long中间存储,避免在int上直接累加导致未定义行为 - 溢出判断不能等乘完再比:比如
res * 10 + digit > INT_MAX这种写法本身就会溢出;应提前判res > (INT_MAX - digit) / 10 - 允许的起始字符只有空格、
'+'、'-'、数字;其余一律视为错误(比如" 0x1A"不算合法十进制) - 结束位置要返回:
std::stoi的idx参数就是干这个的,方便后续解析
简例(只处理十进制有符号):
int safe_atoi(const char* s, size_t* endpos = nullptr) {
if (!s) return 0;
const char* p = s;
while (*p == ' ') ++p;
int sign = 1;
if (*p == '+' || *p == '-') {
sign = (*p == '-') ? -1 : 1;
++p;
}
long long res = 0;
while (*p >= '0' && *p <= '9') {
int digit = *p - '0';
if (sign == 1 && (res > INT_MAX / 10 || (res == INT_MAX / 10 && digit > INT_MAX % 10))) {
throw std::out_of_range("integer overflow");
}
if (sign == -1 && (-res < INT_MIN / 10 || (-res == INT_MIN / 10 && digit > -(INT_MIN % 10)))) {
throw std::out_of_range("integer underflow");
}
res = res * 10 + digit;
++p;
}
if (endpos) *endpos = p - s;
return static_cast<int>(res * sign);
}
std::stoi 的坑:抛什么异常、什么时候抛
std::stoi 看似简单,但异常类型和触发时机容易误判。它不抛 std::invalid_argument 给所有格式错,也不总抛 std::out_of_range 给所有溢出。
- 空字符串或纯空格 →
std::invalid_argument - 开头有空格但后面无有效数字(如
" "或" xyz")→std::invalid_argument - 数字合法但超出
int范围 →std::out_of_range - 输入是
"123"但idx指向末尾后一位,说明全吃了;若*idx是'x',说明停在非法字符处——这时不抛异常,但你得自己检查idx是否推进了 - 注意:C++11 起才保证异常安全;老代码若用
strtol手动封装,需清errno再调用,否则残留错误码会误导
要不要自己实现?看这三点再决定
除非你在受限环境(无 STL、裸机、超小内存),否则别重复造轮子。但“不用自己写”不等于“不用懂原理”。
- 项目用 C++11 以上且允许异常 → 直接用
std::stoi,配合try/catch和idx参数 - 禁用异常(
-fno-exceptions)→ 用std::strtol,检查endptr和errno,注意errno = 0必须在调用前设 - 需要自定义行为(如忽略逗号、支持下划线分隔符、十六进制自动识别)→ 在
std::stoi基础上预处理字符串,而不是重写解析逻辑
最常被忽略的一点:很多人以为“只要不崩溃就行”,但线上服务里一次静默截断可能让配置加载错位、权限数值归零、超时时间变成负数——这些 bug 往往压根不会 crash,只会悄悄搞砸业务逻辑。










