文章目录
一、 📜前言
atoi (ASCII to integer)是 C 标准库函数,作用是将字符串转换为 32 位有符号整数。我们需要处理空白字符、正负号、数字转换、溢出等核心场景,下面一步步拆解实现逻辑。
二、🎯先明确atoi的规则
- 跳过前导空白:字符串开头的空格、
\t、\n等空白字符需要忽略。 - 处理正负号:支持可选的
+/-符号,决定最终结果的正负。 - 转换连续数字:只读取连续的数字字符,遇到非数字字符时立即停止。
- 无数字则返回 0:如果开头没有有效数字(如全空白、符号后无数字),则返回 0。
- 溢出处理:若转换结果超出 32 位有符号整数范围 [-2^31, 2^31-1] ,则返回边界值( -2^31 或 2^31-1 )。
三、💻用C语言实现my_atoi
一.完整代码
代码如下:
#include <stdio.h>
#include <ctype.h> // 用于isspace(判断空白字符)、isdigit(判断数字字符)函数
// 模拟实现atoi函数
int my_atoi(const char *str) {
// 1. 处理空指针
if (str == NULL) {
return 0;
}
// 2. 跳过前导空白字符(空格、制表符、换行等)
while (isspace(*str)) {
str++;
}
// 3. 处理符号位
int sign = 1; // 默认正数
if (*str == '+' || *str == '-') {
sign = (*str == '-') ? -1 : 1;
str++;
}
// 4. 逐位转换数字,并处理溢出
long long result = 0; // 用long long避免中间计算溢出
const int INT_MIN = - (1LL << 31); // -2147483648
const int INT_MAX = (1LL << 31) - 1; // 2147483647
while (isdigit(*str)) {
// 逐位计算:当前结果 * 10 + 新数字
result = result * 10 + (*str - '0');
str++;
// 提前判断溢出,避免超出long long范围(可选,使用更严谨)
if (sign == 1 && result > INT_MAX) {
return INT_MAX;
}
if (sign == -1 && -result < INT_MIN) {
return INT_MIN;
}
}
// 5. 应用符号并返回(确保在int范围内)
result *= sign;
if (result > INT_MAX) {
return INT_MAX;
}
if (result < INT_MIN) {
return INT_MIN;
}
return (int)result;
}
// 测试用例
int main() {
// 测试各种场景
printf("my_atoi(\" 123\") = %d\n", my_atoi(" 123")); // 123
printf("my_atoi(\"-456\") = %d\n", my_atoi("-456")); // -456
printf("my_atoi(\"+789abc\") = %d\n", my_atoi("+789abc")); // 789
printf("my_atoi(\"abc123\") = %d\n", my_atoi("abc123")); // 0
printf("my_atoi(\"2147483648\") = %d\n", my_atoi("2147483648")); // 2147483647(溢出)
printf("my_atoi(\"-2147483649\") = %d\n", my_atoi("-2147483649"));// -2147483648(溢出)
printf("my_atoi(\"\") = %d\n", my_atoi("")); // 0
printf("my_atoi(\" - 123\") = %d\n", my_atoi(" - 123")); // 0(符号后是空格,无有效数字)
return 0;
}
二.逐行拆解代码逻辑
1. 头文件引入
#include <stdio.h>
#include <ctype.h> // 用于isspace(判断空白字符)、isdigit(判断数字字符)函数
<ctype.h>是 C 标准库的字符处理头文件,其包含的isspace和isdigit是现成的字符判断函数,比我们自己写*str == ' ' || *str == '\t'更全面(能覆盖所有空白字符:空格、制表符\t、换行\n、回车\r等)、更方便。
2. 函数定义
int my_atoi(const char *str)
- 返回值:int(最终转换的整数)
- 参数:
const char *str:const表示我们不会修改输入的字符串,符合 C 语言 “只读参数” 的实践;char*是 C 风格字符串的标准表示。
3. 空指针防护(第一步:避免程序奔溃)
// 1. 处理空指针
if (str == NULL) {
return 0;
}
- 思考:为什么要处理?答:如果调用者传入
NULL(比如my_atoi(NULL)),直接访问*str会导致段错误(Segmentation Fault),程序崩溃。 - 处理逻辑:按
atoi的行为,无有效输入时返回 0。
4. 跳过前导空白字符(第二步:清理无效前缀)
// 2. 跳过前导空白字符(空格、制表符、换行等)
while (isspace(*str)) {
str++;
}
isspace(*str):判断当前字符是否是空白字符(返回非 0 表示是,0 则表示不是)。str++:指针向后移动一位,直到遇到非空白字符。- 示例:输入" 123",指针会跳过前面的空格,指向’1’。
- 思考:为什么要跳过?答:
atoi的规则就是忽略开头的空白,比如atoi(" -456")会正确返回- 456。
5. 处理正负号(第三步:确定数值符号)
// 3. 处理符号位
int sign = 1; // 默认正数
if (*str == '+' || *str == '-') {
sign = (*str == '-') ? -1 : 1;
str++;
}
sign变量:记录数值的符号,默认是 1(正数),遇到’-'则设为 - 1。- 三元运算符
(*str == '-') ? -1 : 1:等价于:
if (*str == '-') {
sign = -1;
} else {
sign = 1;
}
str++:处理完符号后,指针向后移动,指向后续的数字字符。- 注意:如果符号后没有数字(比如" - 123"),后续转换会返回 0,符合
atoi规则。
6. 溢出处理的前置准备(关键!避免数值溢出)
// 4. 逐位转换数字,并处理溢出
long long result = 0; // 用long long避免中间计算溢出
const int INT_MIN = - (1LL << 31); // -2147483648
const int INT_MAX = (1LL << 31) - 1; // 2147483647
- 思考:为什么用long long?
int的范围是-2^31(-2147483648)到 2^31-1(2147483647)。如果用int存储中间结果,比如转换"2147483648"时,result*10 + 8会超出int范围,导致未定义行为(数值错乱)。long long是 8 字节,范围远大于int,能安全存储中间结果。 INT_MIN和INT_MAX的定义:1LL:将数字 1 转为long long类型(避免1 << 31时,int类型溢出)。1LL << 31:表示 2 的 31 次方(二进制左移 31 位),结果是 2147483648。INT_MIN = - (1LL << 31):-2147483648;INT_MAX = (1LL << 31) - 1:2147483647。- 不直接写2147483647的原因:不同平台
int范围可能略有差异(虽然主流是 32 位,但用移位更通用些)。
7. 核心:逐位转换数字(第四步:数值计算)
while (isdigit(*str)) {
// 逐位计算:当前结果 * 10 + 新数字
result = result * 10 + (*str - '0');
str++;
// 提前判断溢出,避免超出long long范围(可选,使用更严谨)
if (sign == 1 && result > INT_MAX) {
return INT_MAX;
}
if (sign == -1 && -result < INT_MIN) {
return INT_MIN;
}
}
- 循环条件
isdigit(*str):只要当前字符是数字('0'~'9'),就继续转换。 - 核心公式:
result = result * 10 + (*str - '0')- 原理:ASCII 码中,数字字符’0’-'9’是连续的,‘5’ - ‘0’ = 5,‘9’ - ‘0’ = 9,通过 数字字符 - ‘0’ 这样就能把字符转成对应的数值。
示例:转换"123"的过程: - 第一次循环:
result = 0*10 + ('1'-'0') = 1 - 第二次循环:
result = 1*10 + ('2'-'0') = 12 - 第三次循环:
result = 12*10 + ('3'-'0') = 123
- 原理:ASCII 码中,数字字符’0’-'9’是连续的,‘5’ - ‘0’ = 5,‘9’ - ‘0’ = 9,通过 数字字符 - ‘0’ 这样就能把字符转成对应的数值。
- 中途溢出判断:
- 思考:为什么要中途判断?答:比如转换"2147483648",当
result累加到 214748364 时,下一次计算214748364*10 +8 = 2147483648,已经超过INT_MAX,直接返回INT_MAX,无需继续循环。 sign == -1时,判断-result < INT_MIN:比如result=2147483649,-result=-2147483649,小于INT_MIN(-2147483648),返回INT_MIN。
- 思考:为什么要中途判断?答:比如转换"2147483648",当
8. 最终溢出检查 + 应用符号(第五步:返回正确结果)
// 5. 应用符号并返回(确保在int范围内)
result *= sign;
if (result > INT_MAX) {
return INT_MAX;
}
if (result < INT_MIN) {
return INT_MIN;
}
return (int)result;
result *= sign:给最终数值加上符号(正数乘 1,负数乘 - 1)。- 最终溢出检查:双重保险(即使中途判断漏了,这里也能兜底)。
(int)result:将long long转换回int(此时result已在int范围内,转换安全)。
三、测试用例详解(main函数)
int main() {
// 测试各种场景
printf("my_atoi(\" 123\") = %d\n", my_atoi(" 123")); // 123:前导空格+正数
printf("my_atoi(\"-456\") = %d\n", my_atoi("-456")); // -456:负号+数字
printf("my_atoi(\"+789abc\") = %d\n", my_atoi("+789abc")); // 789:正号+数字+非数字截断
printf("my_atoi(\"abc123\") = %d\n", my_atoi("abc123")); // 0:非数字开头,无有效数字
printf("my_atoi(\"2147483648\") = %d\n", my_atoi("2147483648")); // 2147483647:超出INT_MAX,返回上限
printf("my_atoi(\"-2147483649\") = %d\n", my_atoi("-2147483649"));// -2147483648:超出INT_MIN,返回下限
printf("my_atoi(\"\") = %d\n", my_atoi("")); // 0:空字符串
printf("my_atoi(\" - 123\") = %d\n", my_atoi(" - 123")); // 0:符号后是空格,无有效数字
return 0;
}
四、🔖总结
- 安全第一:先处理空指针,避免程序崩溃;用
long long存储中间结果,避免数值溢出导致的未定义行为。 - 分步处理:按 “跳过空格→处理符号→转换数字→处理溢出” 的逻辑,覆盖
atoi的所有核心规则。 - 边界全覆盖:重点处理溢出、无有效数字、非数字截断等场景,确保和
atoi行为一致。 - 复用标准库:使用
isspace/isdigit而非手动判断字符,让代码更简洁、判断更全面。
一、核心功能与基础规则(必记)
要点 1:atoi 核心行为
- 定义:将 C 风格字符串(
const char*)转换为int类型整数(ASCII to Integer)。 - 核心五步逻辑(记忆口诀:空格→符号→数字→截断→溢出):
- 忽略前导空白字符(空格、
\t、\n、\r等); - 识别可选的正负号(+/-,默认正数,多个符号视为无有效数字);
- 读取连续数字字符,遇到非数字字符立即截断;
- 无有效数字(全空格、符号后无数字等)返回0;
- 超出
int范围时,截断为INT_MAX(2147483647)或INT_MIN(-2147483648)。
- 忽略前导空白字符(空格、
要点 2:参数 / 返回值规范(避坑点)
- 参数:必须加
const(const char*),表示只读输入字符串; - 返回值:固定为
int,无论输入数字多长,最终仅返回int范围值。
二、溢出处理
要点 1:溢出本质与风险
- 本质:转换结果超出·int·范围(-2^31 ~ 2^31-1);
- 风险:若用
int存储中间结果,溢出会触发 “未定义行为”(数值错乱、程序崩溃)。
要点 2:两种主流溢出判断方案
| 方案 | 核心思路 | 优点 | 缺点 | 记忆要点 |
|---|---|---|---|---|
| long long 兜底法 | 1. 用long long(8 字节)存储转换的中间结果(避免 int 溢出);2. 每转换一位数字后,对比结果与INT_MAX/INT_MIN;3. 若超出范围,直接返回对应边界值(INT_MAX/INT_MIN);4. 未溢出则最终转 int 返回。 | 逻辑简单,不易出错 | 依赖long long类型;少量额外内存(可忽略) | 先存后判,简单直接 |
| 纯 int 预判法 | 核心:转换前先判断 “当前结果再乘 10 + 新数字” 是否会溢出,溢出则直接返回边界值,不执行计算。分正负预判:✅ 正数(sign=1):① result > INT_MAX/10 → 乘 10 必超;② result == INT_MAX/10 && digit >7 → 末位超 7 必超(INT_MAX 末位是 7);✅ 负数(sign=-1):① result < INT_MIN/10 → 乘 10 必超;② result == INT_MIN/10 && digit >8 → 末位超 8 必超(INT_MIN 末位是 8)。 | 不依赖大类型:效率更高 | 逻辑复杂;需要分正负判断 | 先判后存,避免溢出(先判后算,正数看7、负数看8) |
要点 3:溢出判断时机(关键)
- 正确时机:每转换一位数字后立即判断(中途预判),而非全部转换完再判断;
- 错误示范:先转换所有数字再判断→中间结果溢出触发未定义行为。
三、边界场景
| 边界场景 | 输入示例 | 正确输出 | 核心处理逻辑 |
|---|---|---|---|
| 空指针 | NULL | 0 | 先判断str==NULL,直接返回 0(避免段错误) |
| 空字符串 | “” | 0 | 无有效数字,返回 0 |
| 全空白字符 | " \t\n" | 0 | 跳过所有空白后无字符,返回 0 |
| 符号后无数字 | “+”、“- 123” | 0 | 处理符号后下一位非数字,判定为无有效数字 |
| 非数字开头 | “abc123” | 0 | 无有效数字,直接返回 0 |
| 数字中混非数字 | “12a34” | 12 | 遇到非数字字符a,截断并返回已转换的 12 |
| 刚好 INT_MAX | “2147483647” | 2147483647 | 正常转换,无溢出 |
| 超出 INT_MAX | “2147483648” | 2147483647 | 溢出,返回 INT_MAX |
| 刚好 INT_MIN | “-2147483648” | -2147483648 | 注意:−2^31无法用int正数表示,需用1LL<<31定义 |
| 超出 INT_MIN | “-2147483649” | -2147483648 | 溢出,返回 INT_MIN |
四、实现优化与避坑
要点 1:优化思路
- 空间优化:直接通过指针移动(
str++)遍历,无需额外数组 / 容器(空间复杂度O(1)); - 效率优化:遇到非数字字符立即终止循环,溢出后直接返回,不遍历剩余字符;
要点 2:常见错误
- 忽略空指针处理→程序段错误;
- 用
int存储中间结果→溢出触发未定义行为; - 溢出判断滞后→全部转换完再判断;
- 手动判断空白 / 数字→覆盖不全(如漏判\t);
- 允许多个符号(如"++123")→错误返回 123(正确应返回 0);
- 硬编码INT_MAX/INT_MIN→用2147483647代替(1LL<<31)-1(适配性差)。

2万+

被折叠的 条评论
为什么被折叠?



