<leetcode系列> String to Integer (atoi) 以及atoi源码实现

String to Integer (atoi)

Implement atoi to convert a string to an integer.

Hint: Carefully consider all possible input cases. If you want a challenge, please do not see below and ask yourself what are the possible input cases.

Notes: It is intended for this problem to be specified vaguely (ie, no given input specs). You are responsible to gather all the input requirements up front.

原题链接: https://leetcode.com/problems/string-to-integer-atoi/

题目大意: 类似于实现一个自己的atoi函数.

编译调试了很久, 遇到了不少感觉怪异的测试用例.列举如下:

测试用例期望值
“+-2”0
“10522545459”2147483647
” 105225454”105225454
“-2247483647”-2147483648
” 123adf”123
“ad12”0
” -123 5a43f”-123

还有一些没有列举出来. 大部分都是对数据溢出的处理问题.当数据溢出时, 正数的期望值是INT_MAX, 负数的期望值是INT_MIN.以及字符串开头的空格,符号位等.

其中数据溢出有两种情况.
1. 在已解析出的数值为214748364时, 而下一位的数值是8. 而int的最大值是2147483647.则数据溢出
2. 在已解析出的数值为314748364时, 下一位无论是任何数, 均会数据溢出.

最终提交代码如下:

int myAtoi(char* str) {
    if (NULL == str) {
        return 0;
    }

    const int MAX_SIZE = 10;

    char* pStr = str;
    char* pSta = str;// 用于前置空格的排除
    bool sig = false; // false -- +; true -- -
    int num  = 0;
    int tmp  = 0;
    int count = 0; // 记录当前数值长度
    while ('\0' != *pStr) {
        if ((*pStr >= '0') && (*pStr <= '9')) {
            if (MAX_SIZE == count) { // 数据溢出
                return sig ? INT_MIN : INT_MAX;
            }
            int tmp = *pStr - '0';
            // 正负数分别计算
            num = sig ? (10 * num - tmp) : (10 * num + tmp);
            ++count;
        } else {
            if (pStr == pSta) {
                switch (*pStr) {
                case '-':
                    sig = true;
                    break;
                case '+':
                    sig = false;
                    break;
                case ' ':
                    ++pSta;
                    break;
                default:
                    return 0;
                }
            } else {
                break;
            }
        }

        ++pStr;
    }
    // 数据溢出
    if (sig && (num > 0)) {
        return INT_MIN;
    } else if (!sig && (num < 0)) {
        return INT_MAX;
    }

    return num;
}

显然, 在上述代码中, 肯定存在不少缺陷并且不够精炼.整个函数看起来繁琐, 各种标志位.效率当然也会大打折扣.

相信atoi函数对于大部分C/C++程序员都是一个比较熟悉的函数.在网站上查询了一下, atoi源代码确实写得很漂亮, 和我的代码比较起来, 简直一个天上一个地下啊.

可以看到atoi库函数的源码实现如下,供大家参阅:

int atoi(const char *nptr)
{
    return (int) strtol(nptr, (char **) NULL, 10);
}

long __XL(strtol)(const char * __restrict str, char ** __restrict endptr,
  int base   __LOCALE_PARAM )
{
    return __XL_NPP(_stdlib_strto_l)(str, endptr, base, 1   __LOCALE_ARG );
}

// 下述函数才是真正完成任务的函数
// 实际调用时, str 是输入的nptr, endptr 是 NULL, base 是 10, sflag 是 1
// 即 unsigned long  _XL_NPP(_stdlib_strto_l)(nptr, NULL, 10, 1)
unsigned long __XL_NPP(_stdlib_strto_l)(register const Wchar * __restrict str,
Wchar ** __restrict endptr, int base,
int sflag   __LOCALE_PARAM )
{
    unsigned long number, cutoff;
#if _STRTO_ENDPTR
    const Wchar *fail_char;
#define SET_FAIL(X)       fail_char = (X)
#else
#define SET_FAIL(X)       ((void)(X)) /* Keep side effects. */
#endif
    unsigned char negative, digit, cutoff_digit;
    assert(((unsigned int)sflag) <= 1);
    SET_FAIL(str);
    while (ISSPACE(*str)) { /* 忽略str中开头的空格 */
        ++str;
    }
    /* 0 代表 正数; 1 代表 负数 */
    negative = 0;
    switch(*str) {
    /* 注意到没有break, 无论*str是'+'或是'-',str均会自加 */
    case '-': negative = 1; 
    case '+': ++str;
    }

    /* 上述操作结束后, 将str前面的空格或是符号位均已跳过.
       只跳过一个符号位,若出现"+-2"的情况, 那么当前*str是'-' */

    if (!(base & ~0x10)) { /* 0x10的十进制是16, 当base等于16时, 该条件为真;否则为假;    */
    // 当然,还有可能有其他情况下会进入.比如base=0x******10(*代表任意数);
        base += 10; /* default is 10(26). */
        if (*str == '0') {
            SET_FAIL(++str);
            base -= 2;  /* Now base is 8 or 16 (24). */
            if ((0x20|(*str)) == 'x') { /* WARNING: assumes ascii. */
                ++str;
                base += base;   /* Base is 16 (16 or 48). */
            }
        }
        if (base > 16) {    /* Adjust in case base wasn't dynamic. */
            base = 16;
        }
    }
    number = 0;
    if (((unsigned)(base - 2)) < 35) { /* 最大能计算的进制是36进制. */
        cutoff_digit = ULONG_MAX % base; // 计算当数值等于临界值时最大还能加上多大的数
        cutoff = ULONG_MAX / base; // 计算判断数据溢出的临界值
        do {
            // 判断每一位的数值; 代码执行类似如下
            /* if (((Wuchar)(*str - '0')) <= 9) {
                digit = *str - '0';
            } else {
                if (*str >= 'A') {
                    // (0x20|(*str)) 将*str的第6位置1.
                    // 即将所有大写字母的ascii码变为小写字母的ascii码, 而小写字母则不变
                    // 不清楚的同学可以百度ascii码表, 字母的大小写ascii码只是在二进制的第六位不同.
                    // 大写字母的二进制第六位是0, 而小写的二进制第六位是1
                    digit = (((0x20|(*str)) - 'a' + 10));
                } else {
                    digit = 40; // 该位是非法字符.
                }
            } */
            digit = (((Wuchar)(*str - '0')) <= 9)
                ? (*str - '0')
                : ((*str >= 'A')
                   ? (((0x20|(*str)) - 'a' + 10)) /* WARNING: assumes ascii. */
                  : 40);

            // 遇到非法字符, 如16进制, 遇到了'?', 10进制,遇到了'A'
            if (digit >= base) {
                break;
            }
            SET_FAIL(++str);
            // 判断加上该位的数值后数据溢出
            if ((number > cutoff)
                || ((number == cutoff) && (digit > cutoff_digit))) {
                number = ULONG_MAX;
                negative &= sflag;
                SET_ERRNO(ERANGE);
            } else { // 未溢出
                number = number * base + digit;
            }
        } while (1);
    }
#if _STRTO_ENDPTR
    if (endptr) {
        *endptr = (Wchar *) fail_char;
    }
#endif
    {
        // ((unsigned long)(-(1+LONG_MIN))) == LONG_MAX, 但不知道为什么要这样获取long的最大值.
        // 当欲计算的数为负数时, tmp = LONG_MAX + 1;为正数时, tmp = LONG_MAX
        unsigned long tmp = ((negative)
             ? ((unsigned long)(-(1+LONG_MIN)))+1
             : LONG_MAX);
        if (sflag && (number > tmp)) {
            number = tmp;
            SET_ERRNO(ERANGE);
        }
    }
    return negative ? (unsigned long)(-((long)number)) : number;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值