模拟实现atoi函数


一、 📜前言

atoi (ASCII to integer)是 C 标准库函数,作用是将字符串转换为 32 位有符号整数。我们需要处理空白字符、正负号、数字转换、溢出等核心场景,下面一步步拆解实现逻辑。


二、🎯先明确atoi的规则

  1. 跳过前导空白:字符串开头的空格、 \t\n 等空白字符需要忽略。
  2. 处理正负号:支持可选的 + / - 符号,决定最终结果的正负。
  3. 转换连续数字:只读取连续的数字字符,遇到非数字字符时立即停止。
  4. 无数字则返回 0:如果开头没有有效数字(如全空白、符号后无数字),则返回 0。
  5. 溢出处理:若转换结果超出 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 标准库的字符处理头文件,其包含的isspaceisdigit是现成的字符判断函数,比我们自己写*str == ' ' || *str == '\t'更全面(能覆盖所有空白字符:空格、制表符\t、换行\n、回车\r等)、更方便。

2. 函数定义

int my_atoi(const char *str) 
  • 返回值:int(最终转换的整数)
  • 参数:const char *strconst表示我们不会修改输入的字符串,符合 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_MININT_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
  • 中途溢出判断:
    • 思考:为什么要中途判断?答:比如转换"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。

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;
}

四、🔖总结

  1. 安全第一:先处理空指针,避免程序崩溃;用long long存储中间结果,避免数值溢出导致的未定义行为。
  2. 分步处理:按 “跳过空格→处理符号→转换数字→处理溢出” 的逻辑,覆盖atoi的所有核心规则。
  3. 边界全覆盖:重点处理溢出、无有效数字、非数字截断等场景,确保和atoi行为一致。
  4. 复用标准库:使用isspace/isdigit而非手动判断字符,让代码更简洁、判断更全面。

一、核心功能与基础规则(必记)

要点 1:atoi 核心行为

  • 定义:将 C 风格字符串(const char*)转换为int类型整数(ASCII to Integer)。
  • 核心五步逻辑(记忆口诀:空格→符号→数字→截断→溢出):
    1. 忽略前导空白字符(空格、\t\n\r等);
    2. 识别可选的正负号(+/-,默认正数,多个符号视为无有效数字);
    3. 读取连续数字字符,遇到非数字字符立即截断;
    4. 无有效数字(全空格、符号后无数字等)返回0;
    5. 超出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:溢出判断时机(关键)

  • 正确时机:每转换一位数字后立即判断(中途预判),而非全部转换完再判断;
  • 错误示范:先转换所有数字再判断→中间结果溢出触发未定义行为。

三、边界场景

边界场景输入示例正确输出核心处理逻辑
空指针NULL0先判断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:常见错误

  1. 忽略空指针处理→程序段错误;
  2. int存储中间结果→溢出触发未定义行为;
  3. 溢出判断滞后→全部转换完再判断;
  4. 手动判断空白 / 数字→覆盖不全(如漏判\t);
  5. 允许多个符号(如"++123")→错误返回 123(正确应返回 0);
  6. 硬编码INT_MAX/INT_MIN→用2147483647代替(1LL<<31)-1(适配性差)。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值