结合 C++ 常用的C++11 系列函数(stoi/stod 等)和C 风格函数(atoi/atof 等),梳理字符串转数字的常见问题、现象、原因及规避方案。
一、整体分类
主要分为:格式非法 、数值溢出 、截断解析 、空/空白字符串 、符号与进制问题 、浮点数特殊场景。
二、逐个问题详解 + 示例
1. 字符串含非数字字符(最常见)
场景1:开头就是非法字符
字符串首字符不是数字、正负号,无法解析。
atoi/atof:直接返回 0,无任何报错,难以察觉。stoi/stod:直接抛出invalid_argument异常,程序崩溃。
cpp
string s = "abc123";
int a = atoi(s.c_str()); // 结果:0
int b = stoi(s); // 抛异常
场景2:中间/末尾夹杂非数字
函数会从左到右解析,遇到非法字符立即停止,只取前面合法部分。
cpp
string s = "123xyz456";
int a = atoi(s.c_str()); // 结果:123
int b = stoi(s); // 结果:123(不会报错,只截断)
2. 数值超出目标类型范围(溢出)
字符串表示的数字太大/太小,超过接收变量的取值范围。
① C 风格 atoi/atol/atof
无报错、无提示 ,直接产生未定义行为,结果乱码、负数、随机值。
cpp
// int 范围:-2147483648 ~ 2147483647
string s = "999999999999";
int num = atoi(s.c_str());
// 溢出,结果不可预测
② C++11 stoi/stoll/stod
溢出会抛出 out_of_range 异常,程序直接终止。
cpp
string s = "999999999999";
int num = stoi(s); // 数值超出 int 范围,抛异常
3. 空字符串 / 全空白字符串
- 空串
""、全空格" "、制表符"\t"、换行"\n" atoi:返回0stoi:抛出invalid_argument异常
cpp
string s = "";
atoi(s.c_str()); // 0
stoi(s); // 异常
4. 正负号使用异常
- 多个正负号 :如
"+-123"、"--456"atoi返回 0stoi抛参数异常
- 符号不在开头 :
"123-45"
解析到123就截断,后面忽略。
cpp
string s = "-+123";
atoi(s.c_str()); // 0
stoi(s); // 异常
5. 浮点数专属问题
(1)整型函数解析小数字符串
用 stoi/atoi 解析带小数点的字符串:
- 自动截断小数部分,只保留整数:
cpp
string s = "3.99";
int a = atoi(s.c_str()); // 3
int b = stoi(s); // 3
(2)科学计数法
1e3、2.5E-2 这类科学计数:
atof/stod支持正常转换atoi/stoi识别到e/E停止,只解析前面数字
(3)无穷、非数值(NaN)
字符串 "inf" "nan":浮点函数可识别,整型函数判定为非法。
6. 进制不匹配问题
函数默认只解析十进制:
- 八进制
0123、十六进制0x1A:
atoi/stoi不会按进制解析,只会当成普通十进制:"0123"→ 123"0x1A"→ 0(x是非法字符)
如果需要解析十六进制/八进制,不能用普通转换函数。
7. 前导空格问题
字符串开头有空格 :
两类函数都能自动忽略前导空白,正常解析:
cpp
string s = " 678";
atoi(s.c_str()); // 678
stoi(s); // 678
⚠️ 仅开头空格允许,中间/末尾空格仍属于截断。
三、两类函数问题对比表
| 问题场景 | atoi/atof(C风格) | stoi/stod(C++11) |
|---|---|---|
| 非法开头字符 | 返回 0,无告警 | 抛 invalid_argument 异常 |
| 数值溢出 | 未定义行为,结果错乱 | 抛 out_of_range 异常 |
| 空串/全空白 | 返回 0 | 抛异常 |
| 中间含非数字 | 截断前面合法部分 | 截断前面合法部分 |
| 前导空格 | 自动忽略,正常解析 | 自动忽略,正常解析 |
四、通用规避方案(实用写法)
1. 使用 C++11 系列函数:必须捕获异常
cpp
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;
bool strToInt(const string& s, int& outNum)
{
try
{
outNum = stoi(s);
return true;
}
catch (const invalid_argument&)
{
cout << "参数非法" << endl;
}
catch (const out_of_range&)
{
cout << "数值溢出" << endl;
}
return false;
}
2. 使用 C 风格函数:提前做格式校验
手动遍历字符串,判断是否为合法数字、检查范围,避免得到误导性的 0。
3. 通用稳健方案:stringstream
流转换会通过状态位判断转换是否成功,不抛异常、不静默出错:
cpp
#include <sstream>
bool strToInt(const string& s, int& num)
{
stringstream ss(s);
ss >> num;
// 判断转换是否成功 + 字符串是否全部解析完毕
return ss.good() && ss.eof();
}
优点:能精准区分「转换失败」和「正常转成 0」,工业代码常用。
五、总结高频考点
- C 风格函数最大坑:出错只返回 0,无法区分是真 0 还是转换失败;溢出无提示。
- C++11 stoi 系列最大坑:非法格式、溢出直接抛异常,不捕获就崩程序。
- 中间含字母/符号:两类函数都会截断解析。
- 空串、多正负号:属于典型非法格式。
- 追求健壮性优先用 stringstream + 状态位判断。