前言
我们都知道,atoi函数用于将一个字符串转换成整数。atoi函数看起来似乎很容易实现,你甚至可以很快写出一个版本,但是是否符合要求呢?
简易版本
最简单的考虑,就是遍历字符串,每遇到一个数字就加上原来的值乘以10。例如字符串“1234”转整数是这样的计算流程:
- 遇到字符1,得到结果1;
- 遇到字符2,得到结果1 * 10 + 2,即12;
- 遇到字符3,得到结果12 * 10 + 3,即123;
- 遇到字符4,得到结果123 * 10 + 4,即1234;
代码实现如下:1
2
3
4
5
6
7
8
9
10
11
12int my_atoi(const char *str)
{
if(NULL == str)
return 0;
int ret = 0;
while(0 != *str)
{
ret = ret * 10 + *str - '0';
str++;
}
return ret;
}
看起来既简洁又没有什么问题,输入数值时也似乎能得到正确结果。真的是这样吗?如果传入以下字符串参数,会是什么结果呢?1
2
3
4
5
6"-1"
"+1"
" "
"111111111111"
""
"1aab"
是不是发现并不是想象中的那样?那么实现atoi到底需要注意什么呢?
实现atoi函数需要注意什么
你可能已经注意到了,实现atoi需要考虑下面这些场景:
- 输入正负号
- 开头有空格
- 转换后的数值超出int的表示范围
- 出错时返回0与正确转换0的区别
- 输入非数字
- 空字符串
现在来看,前面的实现还能满足要求吗?
再次实现
那么重新考虑上面的要求,我们如何实现呢?我们需要考虑以下几种情况
- 如果开头是负号,则标记为负数;正号或数值,则标记为正数
- 跳过开头的空格,从第一个有效字符开始
- 使用更大类型存储计算值,如果负数比INT_MIN还小或正数比INT_MAX还大,则表明溢出,返回INT_MIN或INT_MAX,或者在下次计算之前与INT_MIN/10或INT_MAX/10比较
- 使用全局变量记录出错情况,区别正常转换为0或最大最小值
- 遇到非数值时即退出
根据上面这些考虑,我们重新实现代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
int errCode = 0;
int my_atoi(char* str) {
int negative = 0;
int overflow = 0;
int cutoff;
int cutlim;
/*计算结果存储*/
int ret = 0;
errCode = 0;
if(NULL == str)
{
errCode = NULL_PTR_ERR;
return 0;
}
/*跳过开始的空格*/
while(' ' == (*str))
str++;
/*跳过空格之后,到达了字符串结尾,则退出*/
if(0 == *str)
return 0;
/*负数*/
if(*str == '-' )
{
negative = 1;
str++;
}
/*正数*/
else if(*str == '+')
{
negative = 0;
str++;
}
/*正数*/
else if(isdigit(*str))
{
negative = 0;
}
/*如果不是以上内容,则直接退出*/
else
{
errCode = INPUT_ERR;
return 0;
}
cutoff = INT_MAX /10;
cutlim = INT_MAX % 10;
while(isdigit(*str))
{
/*如果当前结果已经大于int最大值除以10或者等于最大值除以10,并且当前数值大于其余数,则溢出*/
if (ret > cutoff || (ret == cutoff && (int)(*str-'0') > cutlim))
{
errCode = ERANGE;
overflow = 1;
}
else
{
/*计算结果*/
ret = ret*10 + (*str -'0');
}
str++;
}
/*如果溢出,则返回最值*/
if(overflow)
return negative?INT_MIN:INT_MAX;
/*根据正负号返回正确的结果*/
return negative?-ret:ret;
}
int main(void)
{
/*只有一个负号*/
int result = my_atoi("-");
printf("-:%d,errCode:%d\n",result,errCode);
/*空指针*/
result = my_atoi(NULL);
printf("NULL:%d,errCode:%d\n",result,errCode);
/*空字符串*/
result = my_atoi(" ");
printf(" :%d,errCode:%d\n",result,errCode);
/*负数*/
result = my_atoi("-1");
printf(" -1:%d,errCode:%d\n",result,errCode);
/*负数溢出*/
result = my_atoi(" -11111111111");
printf(" -11111111111:%d,errCode:%d\n",result,errCode);
/*正数*/
result = my_atoi("+123");
printf("+123:%d,errCode:%d\n",result,errCode);
/*正数溢出*/
result = my_atoi("+123111111111111111");
printf("+123111111111111111:%d,errCode:%d\n",result,errCode);
return 0;
}
运行结果:1
2
3
4
5
6
7-:0,errCode:0
NULL:0,errCode:1
:0,errCode:0
-1:-1,errCode:0
-11111111111:-2147483648,errCode:2
+123:123,errCode:0
+123111111111111111:2147483647,errCode:2
总结
上面的代码中errCode的设置需要根据需求而定,例如如果认为空字符串转换是非法的,那么前面的代码将不符合要求。但这些都不是重点,重点是我们在考虑实现atoi函数的时候,需要考虑多种异常场景,这在平常实现其他功能接口的时候也是一样的。
思考
前面的代码有什么不足?你忽略了哪些场景?