leetcode 上有一道实现 atoi 题目。两年前我参加百度校招面试就被问到过这个题目。解决一个问题的方法很多,最简单的方法自然是用一个现成的方法解决之。譬如:
c++ 简单版本:
#include <sstream>
class Solution {
public:
int myAtoi(string s) {
int v {0};
std::istringstream stream {s};
stream >> v;
return v;
}
};
c 简单版本:
#include <stdlib.h>
#include <limits.h>
int myAtoi(char* s) {
long long v = atoll(s);
return v > INT_MAX ? INT_MAX : v < INT_MIN ? INT_MIN : v;
}
c++ 版本对应的 c 版本: sscanf(str, "%d", &v) 是无效的,因为 c 没有规定溢出下的情况,而 c++ 有。恰好, c++ 溢出时的处理和本道题目一致,故方法真的很简单。
如果要重新发明 atoi 的轮子(面试的时候就是酱紫!),而不是用轮子替代轮子,下面是一个参考实现:
#include <limits.h>
#define is_digit(a) ((a) >= '0' && (a) <= '9')
#define is_space(a) ((a) == ' ' || (a) == '\t')
int myAtoi(char* s) {
if(!s || !*s) return 0;
while(is_space(*s)) ++s;
int sign = *s != '-';
if(*s == '+' || *s == '-') ++s;
if(!is_digit(*s)) return 0;
unsigned v = *s - '0', upper = sign > 0 ? INT_MAX : INT_MIN;;
while(*++s && is_digit(*s)){
if(v > upper/10 || (v = v * 10 + (*s-'0')) > upper){
return sign ? INT_MAX : INT_MIN;
}
}
return sign ? v : -v;
}
最核心部分不是计算,而是溢出的判断。溢出的判断有多种方法:
- 使用 long long 判断是否溢出:一般来说 sizeof(int) < sizeof(long long),这样的话,试用 long long 直接保存 int * 10 + int 的结果没有问题,然后和 INT_MAX、INT_MIN 对比;
- 将 *10 操作转化为加法操作,然后使用符号改变来判断溢出;
- 比较 v 和 MAX/10、-INT/10 的大小,判断溢出(这里采用的方法)。
不采用第一种方法不是因为其不好,而是某些地方不够通用,譬如我们求 atoll,这样哪来的更长的 int 呢?第二种方法比较通用,就是需要多次计算加法。 第三种简单些,也通用些,但有些基本假定,譬如 sizeof(int) 长度不能太短,需要保证 UINT_MAX - INT_MAX > 9 !那么 int 至少得 5 位,几乎所有系统都满足的吧!
注:补充几句。补码表示的整数在正负取值上并不对称,譬如 32 位整数的 INT_MAX = 2147483647,INT_MIN = -2147483648,UINT_MAX = 4294967295 。
作者:文琼