面试题strtoi实现(一)—— 函数的简单实现
2014-10-13 20:24
447 查看
最近参加了一场面试,面试官给出的一道面试题是实现strtoi,结果悲催的跪倒在这道题上。当时赶去面试
的时候太匆忙,居然还找错地方了。见到面试官的时候已经迟到了。到了后头一直晕。。。结果可想而知。
当然,给自己找了主观上的借口,还是得客观的分析下为啥那个程序没有完整的写出来。头晕的我,当时
精力不太集中,一边想着程序的大体框架,一边又不断地考虑各种错误检查和处理,又考虑着题目中一些没有
明确说明的情况。结果什么都没做好:错误处理考虑到了很多情况,但不完备,并且零散地分部在if语句中,
其实想清楚后是可以分好类的。至于程序框架部分,写了个七七八八,但还是没写好。
事后,分析和总结了这次失利的情况。内功有待增强,临场发挥很欠缺,特别不该在匆忙,欠准备中做事情。
当然,针对这类写程序的问题:(1)优先写出大体程序框架 (2)考虑错误检查及处理,针对题目中不明晰的
地方请教面试官。毕竟,错误检查及处理未做好,顶多就算考虑不完备;要是程序的大体框架,主体逻辑都没
写好,那就给别人“写不出程序”的印象了。
好了,说了那么多废话,咱们回到正题,来看看面试题,并找找解决思路吧。题目是用英文描述的,我也
记不得具体描述了。需要考虑2~32进制;出错时将end_ptr指向第一次发现的非法字符的位置;还需要考虑
溢出情况;从描述上来看,合法的输入数据的格式为:
[若干空格符,制表符] [正负号] [标明进制的字符] [数字字母串] [字符串结束符]
在百度百科里找了个相关的描述。
题目描述:实现my_strtoi函数,完成字符串到整型数值的转换。
函数定义:
int my_strtoi(const char *src_str,char **end_ptr,int base);
函数说明:
这个函数会将参数src_str字符串根据参数base来转换成整型数。
参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值
为16则采用16进制等。当base值为0时则是采用10进制做转换,但遇到如’0x’前置字符则会使用16进制做
转换、遇到’0’前置字符而不是’0x’的时候会使用8进制做转换。
一开始strtoi()会扫描参数src_str字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,
再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。若参数end_ptr不为NULL,则会将遇到不合条件
而终止的src_str中的字符指针由end_ptr返回;若参数end_ptr为NULL,则会不返回非法字符串。
OK,按照我们上面总结的,先来“画出”程序的主题逻辑。
(1)通过while循环,跳过前导空格符或者制表符。需要注意的是指针自增要放到while循环里,而不是在循环
条件里,因为后者会导致指针
4000
多移动了一个位置,指向空格,制表符后的第二个非空格字符;要是输入字符串
只有空格怎么办?那么此时指向的将是'\0'后面的那个字符。也就是指针越界,访问了“非法内存”。
(2)发现了" + "或者" - "时,记录下数值的符号类型,将指针往后移一位;没发现的情况,也是当做”正值“
处理,但指针不向后移动。
(3)处理数字或者字母。如何将它们转int呢?对于扫描”123“字符串,转换为整型数,有两种方式:从左往右
扫描,也就是从”最高位“。扫描到1,记录下来,当扫描到2时,怎么办?1 * 10 + 2 = 12 。那接下来扫描到3
怎么办? 12 * 10 + 3 = 123 。 如果是从右往左扫描呢?
那就相当于从”最低位“计入。扫描到3,计入。接着扫描
到2,就是2 * 10 + 3 = 23 。 再扫描到了1,那么就是1* 10 * 10 + 23 = 123 。[扫描数字字符] - '0' + 10
就是该数字
字符所表示的数值。
在字符串格式已经给定,我们可以通过类似strlen这样的函数快速求得字符串长度的情况下,两种方法都
可行。但如果追求更高效,还是从左到右扫描,毕竟求字符串长度,也是要耗时的。这里只是提供给大家这
两种思路。我们的程序采用从左到右扫描输入字符串。
那如果是字母呢? 大小写同等视之。如果是'a',我们按照数值10来对待;如果是'Z',我们按照35对待。
也就说,单个字母和数字所能表示最大数值35。现在知道为什么我们所能支持的最大进制为36了吧?
[扫描字母] - 'a' + 10 (小写)或者[扫描字母]
- 'A' + 10 (大写)就是该字母所表示的数值。
(4)扫描到字符串结束符'\0',非法字符或者数值溢出时,扫描循环结束,返回结果。
接下来就是考虑错误情况及题目未明晰部分了。
(1)输入字符串为NULL。
(2)输入字符串中仅包含'\0',或者仅包含在空格,制表符。
(3)扫描过程中遇到非法字符。可以是在扫描前导空格,制表符过程中;也可以是在要扫描”数值符号“字符;
也可以是在扫描后面的数字,字母字符。
(4)扫描到的数值溢出,也就是"小于” INT_MIN(如-2147483648),或者“大于”INT_MAX(如2147483647),
此时也要结束处理。注意,此处的“小于”,“大于”的比较,是不能通过int类型的数值比较进行的,因为整型的
两个数相加,结果会溢出,直接数值比较,肯定会得出错误的结果。
但我们知道unsigned int是能表示比int更广的数值范围的,是否可以通过直接将扫描的数值的正值(累加
而来)与此符号下的最大数值表示 (-INT_MIN或者INT_MAX)进行比较呢?看上去似乎比较可行,但是此处得
小心了。unsigned int 比int能多表示的范围并大不了多少。当base > 2 时,就可能出现上面所说的溢出问题。
比如:base为10,对21474836469进行扫描时,我们发现2147483646
< INT_MAX (2147483647)。此时并
没有溢出,于是继续扫描后面的字符9,此时如果进行
2147483646 x 10 + 9 与 2147483647 的比较,显然比较
结果会因为数值溢出而无效。那怎么办呢?
比较简单的一个办法是:(overflow - (*p - 'A' + 10)) / base < sum 。 看见没? 压根不给你因为累加而出现
溢出的机会。 上面表示了在不越界的前提下,如果计入当前扫描的字符,前面那些位所能组成的最大数值。
需要厘清的问题点基本都厘清了,但还有少许不明晰的,也是题目中未指明的。
(1)传入NULL指针,返回什么?
(2)扫描到非法字符时,返回什么?非法字符串由end_ptr指向,但是否需要返回当前已经扫描到的数值呢?
(3)扫描过程中,发现溢出时,是返回当前已经扫描到的数值,还是该符号对应的,能表示的最大或最小值
呢?比如,负数的最小是INT_MIN(如-2147483648),正数最大是INT_MAX(如2147483647)。
(4)扫描数字,字母过程中,如果遇到空格或者制表符该如何处理? 如123 456 34 ,此时是返回12345634,
还是想scanf读入字符串那样返回123,一次扫描结束,并提示后面的“ 456 34 ”为非法字符串?
鉴于题目中并未明确说明,我也未从面试官那里获悉这些。所以这里我们可以“自由处理”,当然,也可以
参照strtol函数的处理结果。废话说了那么多,该上点实在的代码了。
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include <string.h>
#define E_NULL_POINTER -1
#define E_INV_CHAR -2
#define E_OUT_RANGE -3
#define NO_ERROR 0
#define MAX_INPUT_STR_LEN 256
#define MIN_BASE 2
#define MAX_BASE 36
int my_strtoi(const
char *string,char **endptr,int base)
{
char *p = string;
int sum = 0;
int sign = 1;
unsigned int overflow = 0;
if(NULL == p)
{
printf("\ninput string is NULL , can't be converted to int \n");
return E_NULL_POINTER;
}
while(isspace(*p))
{
p++;
}
if(*p == '-')
{
sign = -1;
p++;
}
else if(*p ==
'+')
{
p++;
}
if(-1 == sign)
{
overflow = -INT_MIN;
}
else
{
overflow = INT_MAX;
}
if((base < MIN_BASE && base != 0) || (base > MAX_BASE))
{
base = 10;
}
else if(base == 0)
{
base = 10;
if(*p ==
'0')
{
base = 8;
p++;
if(*p == 'x' || *p ==
'X')
{
base = 16;
p++;
}
}
}
printf("\ninput string is %s , base = %d \n",string,base);
/*
For the "if sentence " below :
if *p is found to be '\0', that indicates that there is no valid character.
otherwise, it indicates that the first invalid character is found.
We may just handle the case that *p == '\0', because the other cases will be
handle in the following while loop as well.
*/
if(!isalnum(*p))
{
*endptr = p;
return E_INV_CHAR;
}
while(*p != '\0')
{
if(isdigit(*p))
{
if( (*p -
'0') > base -1)
{
*endptr = p;
return E_INV_CHAR;
}
else
{
if((overflow - (*p -
'0')) / base < sum)
{
*endptr = p;
return E_OUT_RANGE;
}
else
{
sum = sum * base + (*p -
'0');
}
}
}
else if(isupper(*p))
{
if((*p -
'A' + 10) > (base -1))
{
*endptr = p;
return E_INV_CHAR;
}
else
{
if((overflow - (*p -
'A' + 10)) / base < sum)
{
*endptr = p;
return E_OUT_RANGE;
}
else
{
sum = sum * base + (*p -
'A' + 10);
}
}
}
else if(islower(*p))
{
if((*p -
'a' + 10) > (base -1))
{
*endptr = p;
return E_INV_CHAR;
}
else
{
if((overflow - (*p -
'a' + 10)) / base < sum)
{
*endptr = p;
return E_OUT_RANGE;
}
else
{
sum = sum * base + (*p -
'a' + 10);
}
}
}
else
{
*endptr = p;
return E_INV_CHAR;
}
p++;
}
return (sum * sign);
}
void strtoi_ret_check(int ret,
char **ppc_invalid)
{
if(E_NULL_POINTER == ret)
{
printf("Error : invalid argument, input string can not be NULL \n");
}
else if (E_INV_CHAR == ret)
{
if(**ppc_invalid == '\0')
{
printf("Error : No valid char ( digit or letter) is found \n");
}
else
{
printf("Error : Invalid char: %s is found during conversion \n",*ppc_invalid);
}
}
else if(E_OUT_RANGE == ret)
{
printf("Error : Overflow is found at %s , input string is too long \n",*ppc_invalid);
}
else
{
// printf("Input string %s is converted to int : %d \n");
printf("Input string is converted to int : %d \n",ret);
}
}
int main(void)
{
// your code goes here
int ret;
char *temp = NULL;
char **end_ptr = &temp;
char test_str[MAX_INPUT_STR_LEN] = {0};
int len;
printf("INT_MIN = %d , INT_MAX = %d \n", INT_MIN,INT_MAX);
ret = my_strtoi(NULL,&temp,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" ",end_ptr,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" -0123",end_ptr,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" -123",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 0X123",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 012389",end_ptr,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 2ab",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 24 ",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 46",end_ptr,36);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 234",end_ptr,38);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 1234567890123",end_ptr,38);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" aaaaaaaa123456 ",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
while(1)
{
printf("\nplease input the string you want to convert to integer : \n");
scanf("%s",test_str);
/*
fgets(test_str,MAX_INPUT_STR_LEN + 1,stdin);
len = strlen(test_str);
if(len > 0)
{
test_str[len - 1] = '\0';
}
*/
if(strcmp(test_str,"exit") == 0)
{
break;
}
ret = my_strtoi(test_str,end_ptr,0);
strtoi_ret_check(ret,end_ptr);
}
printf("process finishes \n");
getchar();
return 0;
}
程序在DEV-C++ 5.6.2的运行结果为:
[plain]
view plaincopyprint?
INT_MIN = -2147483648 , INT_MAX = 2147483647
input string is NULL , can't be converted to int
Error : invalid argument, input string can not be NULL
input string is , base = 10
Error : No valid char ( digit or letter) is found
input string is -0123 , base = 8
Input string is converted to int : -83
input string is -123 , base = 16
Input string is converted to int : -291
input string is 0X123 , base = 16
Error : Invalid char: X123 is found during conversion
input string is 012389 , base = 8
Error : Invalid char: 89 is found during conversion
input string is 2ab , base = 16
Input string is converted to int : 683
input string is 24 , base = 16
Error : Invalid char: is found during conversion
input string is 46 , base = 36
Input string is converted to int : 150
input string is 234 , base = 10
Input string is converted to int : 234
input string is 1234567890123 , base = 10
Error : Overflow is found at 123 , input string is too long
input string is aaaaaaaa123456 , base = 16
Error : Overflow is found at a123456 , input string is too lon
please input the string you want to convert to integer :
123 456 a
input string is 123 , base = 10
Input string is converted to int : 123
please input the string you want to convert to integer :
input string is 456 , base = 10
Input string is converted to int : 456
please input the string you want to convert to integer :
input string is a , base = 10
Error : Invalid char: a is found during conversion
please input the string you want to convert to integer :
2147483646 2147483648
input string is 2147483646 , base = 10
Input string is converted to int : 2147483646
please input the string you want to convert to integer :
input string is 2147483648 , base = 10
Error : Overflow is found at 8 , input string is too long
please input the string you want to convert to integer :
exit
process finishes
--------------------------------
Process exited with return value 0
Press any key to continue . . .
关于程序错误处理部分的说明:
(1)传入NULL指针,返回什么? (此处我们定义了E_NULL_POINTER专门处理这种情况)
(2)扫描到非法字符时,返回什么?非法字符串由end_ptr指向,但是否需要返回当前已经扫描到的数值呢?
(此处定义了E_INV_CHAR来对应这种情况,并不返回已经扫描到的数值)
(3)扫描过程中,发现溢出时,是返回当前已经扫描到的数值,还是该符号对应的,能表示的最大或最小值
呢?比如,负数的最小是INT_MIN(如-2147483648),正数最大是INT_MAX(如2147483647)。
(此处定义了E_OUT_RANGE来对应这种情况,并不返回已经扫描到的数值,也不返回INT_MAX或INT_MIN)
(4)扫描数字,字母过程中,如果遇到空格或者制表符该如何处理? 如123 456 34 ,此时是返回12345634,
还是想scanf读入字符串那样返回123,一次扫描结束,并提示后面的“ 456 34 ”为非法字符串?
(此处定义了E_INV_CHAR来对应这种情况,并不返回已经扫描到的数值)
需要说明下程序中需要注意的几点:
(1)对传入NULL指针或者只包含空格,制表符字符串指针的特殊情况的处理。
(2)程序对溢出的判断方式,及比较对象采用的是绝对值(比较时针对绝对值)
(3)对base的判断和计算。注意,此代码仅处理base为0,数字,字母串以0或者0x,0X开头的情况,并不能
处理base为8,数字,字母串以0;
或者base为16,字母串以0或者0x,0X开头的情况。请特别注意!!
(4)base进制下,可以表达的最大数值为base
- 1 。从左到右扫描字符串转为字符串的“计算公式”。
(5)将扫描到的字符如何转为数值。例如对于数字字符,[扫描数字字符]
- '0' + 10 才能转换为数值。
(6)指针的使用。要是用错了,很容易出现指针的越界访问,或者访问空指针这样的情况。请看main中测试
程序代码部分,如果是用初始化为NULL的temp,需要传入&temp,因为my_strtoi中会根据错误情况修改指针
指向;如果是传入char
**,请记得将其先指向一个char *的指针,不然my_strtoi中修改end_ptr,用到*end_ptr
时,引用的就是非法内存了。
(7)上面的main程序测试代码中,我们看到用的是scanf("%s",test_str);读入输入字符串。当然,带来的问题
是明显的,如果我们输入123
456 ab 会被当做3个字符串。我们会看到代码中也有给出使用fgets的例子,但
已经被注释掉了,因为fgets读入的时候会在字符串末尾加入一个换行符'\n',所以需要单独处理下。
上面贴出的程序有哪些问题呢?
(1)设定的几种返回值,明显会与输入串为-1,-2这样的情况冲突。
(2)效率有待提高。比如可以用下register变量。
(3)使用了ctype.h里面判断字符类型的一系列函数,这个理论情况下是没有的,得自己实现。
(4)判断溢出,每次都要做那么“复杂”的运算,低效。这种判断方法有待改进。
如果自己大脑堵塞,找不到办法去优化,改进怎么办? Google,百度找别人写的程序,如果可以,找glibc
中函数的实现。毕竟glibc可是那么多大拿编写和维护的。说道这里,不得不开启本文的兄弟篇——
面试题strtoi实现(二)—— 函数的改进
的时候太匆忙,居然还找错地方了。见到面试官的时候已经迟到了。到了后头一直晕。。。结果可想而知。
当然,给自己找了主观上的借口,还是得客观的分析下为啥那个程序没有完整的写出来。头晕的我,当时
精力不太集中,一边想着程序的大体框架,一边又不断地考虑各种错误检查和处理,又考虑着题目中一些没有
明确说明的情况。结果什么都没做好:错误处理考虑到了很多情况,但不完备,并且零散地分部在if语句中,
其实想清楚后是可以分好类的。至于程序框架部分,写了个七七八八,但还是没写好。
事后,分析和总结了这次失利的情况。内功有待增强,临场发挥很欠缺,特别不该在匆忙,欠准备中做事情。
当然,针对这类写程序的问题:(1)优先写出大体程序框架 (2)考虑错误检查及处理,针对题目中不明晰的
地方请教面试官。毕竟,错误检查及处理未做好,顶多就算考虑不完备;要是程序的大体框架,主体逻辑都没
写好,那就给别人“写不出程序”的印象了。
好了,说了那么多废话,咱们回到正题,来看看面试题,并找找解决思路吧。题目是用英文描述的,我也
记不得具体描述了。需要考虑2~32进制;出错时将end_ptr指向第一次发现的非法字符的位置;还需要考虑
溢出情况;从描述上来看,合法的输入数据的格式为:
[若干空格符,制表符] [正负号] [标明进制的字符] [数字字母串] [字符串结束符]
在百度百科里找了个相关的描述。
题目描述:实现my_strtoi函数,完成字符串到整型数值的转换。
函数定义:
int my_strtoi(const char *src_str,char **end_ptr,int base);
函数说明:
这个函数会将参数src_str字符串根据参数base来转换成整型数。
参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值
为16则采用16进制等。当base值为0时则是采用10进制做转换,但遇到如’0x’前置字符则会使用16进制做
转换、遇到’0’前置字符而不是’0x’的时候会使用8进制做转换。
一开始strtoi()会扫描参数src_str字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,
再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。若参数end_ptr不为NULL,则会将遇到不合条件
而终止的src_str中的字符指针由end_ptr返回;若参数end_ptr为NULL,则会不返回非法字符串。
OK,按照我们上面总结的,先来“画出”程序的主题逻辑。
(1)通过while循环,跳过前导空格符或者制表符。需要注意的是指针自增要放到while循环里,而不是在循环
条件里,因为后者会导致指针
4000
多移动了一个位置,指向空格,制表符后的第二个非空格字符;要是输入字符串
只有空格怎么办?那么此时指向的将是'\0'后面的那个字符。也就是指针越界,访问了“非法内存”。
(2)发现了" + "或者" - "时,记录下数值的符号类型,将指针往后移一位;没发现的情况,也是当做”正值“
处理,但指针不向后移动。
(3)处理数字或者字母。如何将它们转int呢?对于扫描”123“字符串,转换为整型数,有两种方式:从左往右
扫描,也就是从”最高位“。扫描到1,记录下来,当扫描到2时,怎么办?1 * 10 + 2 = 12 。那接下来扫描到3
怎么办? 12 * 10 + 3 = 123 。 如果是从右往左扫描呢?
那就相当于从”最低位“计入。扫描到3,计入。接着扫描
到2,就是2 * 10 + 3 = 23 。 再扫描到了1,那么就是1* 10 * 10 + 23 = 123 。[扫描数字字符] - '0' + 10
就是该数字
字符所表示的数值。
在字符串格式已经给定,我们可以通过类似strlen这样的函数快速求得字符串长度的情况下,两种方法都
可行。但如果追求更高效,还是从左到右扫描,毕竟求字符串长度,也是要耗时的。这里只是提供给大家这
两种思路。我们的程序采用从左到右扫描输入字符串。
那如果是字母呢? 大小写同等视之。如果是'a',我们按照数值10来对待;如果是'Z',我们按照35对待。
也就说,单个字母和数字所能表示最大数值35。现在知道为什么我们所能支持的最大进制为36了吧?
[扫描字母] - 'a' + 10 (小写)或者[扫描字母]
- 'A' + 10 (大写)就是该字母所表示的数值。
(4)扫描到字符串结束符'\0',非法字符或者数值溢出时,扫描循环结束,返回结果。
接下来就是考虑错误情况及题目未明晰部分了。
(1)输入字符串为NULL。
(2)输入字符串中仅包含'\0',或者仅包含在空格,制表符。
(3)扫描过程中遇到非法字符。可以是在扫描前导空格,制表符过程中;也可以是在要扫描”数值符号“字符;
也可以是在扫描后面的数字,字母字符。
(4)扫描到的数值溢出,也就是"小于” INT_MIN(如-2147483648),或者“大于”INT_MAX(如2147483647),
此时也要结束处理。注意,此处的“小于”,“大于”的比较,是不能通过int类型的数值比较进行的,因为整型的
两个数相加,结果会溢出,直接数值比较,肯定会得出错误的结果。
但我们知道unsigned int是能表示比int更广的数值范围的,是否可以通过直接将扫描的数值的正值(累加
而来)与此符号下的最大数值表示 (-INT_MIN或者INT_MAX)进行比较呢?看上去似乎比较可行,但是此处得
小心了。unsigned int 比int能多表示的范围并大不了多少。当base > 2 时,就可能出现上面所说的溢出问题。
比如:base为10,对21474836469进行扫描时,我们发现2147483646
< INT_MAX (2147483647)。此时并
没有溢出,于是继续扫描后面的字符9,此时如果进行
2147483646 x 10 + 9 与 2147483647 的比较,显然比较
结果会因为数值溢出而无效。那怎么办呢?
比较简单的一个办法是:(overflow - (*p - 'A' + 10)) / base < sum 。 看见没? 压根不给你因为累加而出现
溢出的机会。 上面表示了在不越界的前提下,如果计入当前扫描的字符,前面那些位所能组成的最大数值。
需要厘清的问题点基本都厘清了,但还有少许不明晰的,也是题目中未指明的。
(1)传入NULL指针,返回什么?
(2)扫描到非法字符时,返回什么?非法字符串由end_ptr指向,但是否需要返回当前已经扫描到的数值呢?
(3)扫描过程中,发现溢出时,是返回当前已经扫描到的数值,还是该符号对应的,能表示的最大或最小值
呢?比如,负数的最小是INT_MIN(如-2147483648),正数最大是INT_MAX(如2147483647)。
(4)扫描数字,字母过程中,如果遇到空格或者制表符该如何处理? 如123 456 34 ,此时是返回12345634,
还是想scanf读入字符串那样返回123,一次扫描结束,并提示后面的“ 456 34 ”为非法字符串?
鉴于题目中并未明确说明,我也未从面试官那里获悉这些。所以这里我们可以“自由处理”,当然,也可以
参照strtol函数的处理结果。废话说了那么多,该上点实在的代码了。
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include <string.h>
#define E_NULL_POINTER -1
#define E_INV_CHAR -2
#define E_OUT_RANGE -3
#define NO_ERROR 0
#define MAX_INPUT_STR_LEN 256
#define MIN_BASE 2
#define MAX_BASE 36
int my_strtoi(const
char *string,char **endptr,int base)
{
char *p = string;
int sum = 0;
int sign = 1;
unsigned int overflow = 0;
if(NULL == p)
{
printf("\ninput string is NULL , can't be converted to int \n");
return E_NULL_POINTER;
}
while(isspace(*p))
{
p++;
}
if(*p == '-')
{
sign = -1;
p++;
}
else if(*p ==
'+')
{
p++;
}
if(-1 == sign)
{
overflow = -INT_MIN;
}
else
{
overflow = INT_MAX;
}
if((base < MIN_BASE && base != 0) || (base > MAX_BASE))
{
base = 10;
}
else if(base == 0)
{
base = 10;
if(*p ==
'0')
{
base = 8;
p++;
if(*p == 'x' || *p ==
'X')
{
base = 16;
p++;
}
}
}
printf("\ninput string is %s , base = %d \n",string,base);
/*
For the "if sentence " below :
if *p is found to be '\0', that indicates that there is no valid character.
otherwise, it indicates that the first invalid character is found.
We may just handle the case that *p == '\0', because the other cases will be
handle in the following while loop as well.
*/
if(!isalnum(*p))
{
*endptr = p;
return E_INV_CHAR;
}
while(*p != '\0')
{
if(isdigit(*p))
{
if( (*p -
'0') > base -1)
{
*endptr = p;
return E_INV_CHAR;
}
else
{
if((overflow - (*p -
'0')) / base < sum)
{
*endptr = p;
return E_OUT_RANGE;
}
else
{
sum = sum * base + (*p -
'0');
}
}
}
else if(isupper(*p))
{
if((*p -
'A' + 10) > (base -1))
{
*endptr = p;
return E_INV_CHAR;
}
else
{
if((overflow - (*p -
'A' + 10)) / base < sum)
{
*endptr = p;
return E_OUT_RANGE;
}
else
{
sum = sum * base + (*p -
'A' + 10);
}
}
}
else if(islower(*p))
{
if((*p -
'a' + 10) > (base -1))
{
*endptr = p;
return E_INV_CHAR;
}
else
{
if((overflow - (*p -
'a' + 10)) / base < sum)
{
*endptr = p;
return E_OUT_RANGE;
}
else
{
sum = sum * base + (*p -
'a' + 10);
}
}
}
else
{
*endptr = p;
return E_INV_CHAR;
}
p++;
}
return (sum * sign);
}
void strtoi_ret_check(int ret,
char **ppc_invalid)
{
if(E_NULL_POINTER == ret)
{
printf("Error : invalid argument, input string can not be NULL \n");
}
else if (E_INV_CHAR == ret)
{
if(**ppc_invalid == '\0')
{
printf("Error : No valid char ( digit or letter) is found \n");
}
else
{
printf("Error : Invalid char: %s is found during conversion \n",*ppc_invalid);
}
}
else if(E_OUT_RANGE == ret)
{
printf("Error : Overflow is found at %s , input string is too long \n",*ppc_invalid);
}
else
{
// printf("Input string %s is converted to int : %d \n");
printf("Input string is converted to int : %d \n",ret);
}
}
int main(void)
{
// your code goes here
int ret;
char *temp = NULL;
char **end_ptr = &temp;
char test_str[MAX_INPUT_STR_LEN] = {0};
int len;
printf("INT_MIN = %d , INT_MAX = %d \n", INT_MIN,INT_MAX);
ret = my_strtoi(NULL,&temp,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" ",end_ptr,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" -0123",end_ptr,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" -123",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 0X123",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 012389",end_ptr,0);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 2ab",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 24 ",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 46",end_ptr,36);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 234",end_ptr,38);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" 1234567890123",end_ptr,38);
strtoi_ret_check(ret,end_ptr);
ret = my_strtoi(" aaaaaaaa123456 ",end_ptr,16);
strtoi_ret_check(ret,end_ptr);
while(1)
{
printf("\nplease input the string you want to convert to integer : \n");
scanf("%s",test_str);
/*
fgets(test_str,MAX_INPUT_STR_LEN + 1,stdin);
len = strlen(test_str);
if(len > 0)
{
test_str[len - 1] = '\0';
}
*/
if(strcmp(test_str,"exit") == 0)
{
break;
}
ret = my_strtoi(test_str,end_ptr,0);
strtoi_ret_check(ret,end_ptr);
}
printf("process finishes \n");
getchar();
return 0;
}
#include <stdio.h> #include <ctype.h> #include <limits.h> #include <string.h> #define E_NULL_POINTER -1 #define E_INV_CHAR -2 #define E_OUT_RANGE -3 #define NO_ERROR 0 #define MAX_INPUT_STR_LEN 256 #define MIN_BASE 2 #define MAX_BASE 36 int my_strtoi(const char *string,char **endptr,int base) { char *p = string; int sum = 0; int sign = 1; unsigned int overflow = 0; if(NULL == p) { printf("\ninput string is NULL , can't be converted to int \n"); return E_NULL_POINTER; } while(isspace(*p)) { p++; } if(*p == '-') { sign = -1; p++; } else if(*p == '+') { p++; } if(-1 == sign) { overflow = -INT_MIN; } else { overflow = INT_MAX; } if((base < MIN_BASE && base != 0) || (base > MAX_BASE)) { base = 10; } else if(base == 0) { base = 10; if(*p == '0') { base = 8; p++; if(*p == 'x' || *p == 'X') { base = 16; p++; } } } printf("\ninput string is %s , base = %d \n",string,base); /* For the "if sentence " below : if *p is found to be '\0', that indicates that there is no valid character. otherwise, it indicates that the first invalid character is found. We may just handle the case that *p == '\0', because the other cases will be handle in the following while loop as well. */ if(!isalnum(*p)) { *endptr = p; return E_INV_CHAR; } while(*p != '\0') { if(isdigit(*p)) { if( (*p - '0') > base -1) { *endptr = p; return E_INV_CHAR; } else { if((overflow - (*p - '0')) / base < sum) { *endptr = p; return E_OUT_RANGE; } else { sum = sum * base + (*p - '0'); } } } else if(isupper(*p)) { if((*p - 'A' + 10) > (base -1)) { *endptr = p; return E_INV_CHAR; } else { if((overflow - (*p - 'A' + 10)) / base < sum) { *endptr = p; return E_OUT_RANGE; } else { sum = sum * base + (*p - 'A' + 10); } } } else if(islower(*p)) { if((*p - 'a' + 10) > (base -1)) { *endptr = p; return E_INV_CHAR; } else { if((overflow - (*p - 'a' + 10)) / base < sum) { *endptr = p; return E_OUT_RANGE; } else { sum = sum * base + (*p - 'a' + 10); } } } else { *endptr = p; return E_INV_CHAR; } p++; } return (sum * sign); } void strtoi_ret_check(int ret, char **ppc_invalid) { if(E_NULL_POINTER == ret) { printf("Error : invalid argument, input string can not be NULL \n"); } else if (E_INV_CHAR == ret) { if(**ppc_invalid == '\0') { printf("Error : No valid char ( digit or letter) is found \n"); } else { printf("Error : Invalid char: %s is found during conversion \n",*ppc_invalid); } } else if(E_OUT_RANGE == ret) { printf("Error : Overflow is found at %s , input string is too long \n",*ppc_invalid); } else { // printf("Input string %s is converted to int : %d \n"); printf("Input string is converted to int : %d \n",ret); } } int main(void) { // your code goes here int ret; char *temp = NULL; char **end_ptr = &temp; char test_str[MAX_INPUT_STR_LEN] = {0}; int len; printf("INT_MIN = %d , INT_MAX = %d \n", INT_MIN,INT_MAX); ret = my_strtoi(NULL,&temp,0); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" ",end_ptr,0); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" -0123",end_ptr,0); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" -123",end_ptr,16); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" 0X123",end_ptr,16); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" 012389",end_ptr,0); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" 2ab",end_ptr,16); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" 24 ",end_ptr,16); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" 46",end_ptr,36); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" 234",end_ptr,38); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" 1234567890123",end_ptr,38); strtoi_ret_check(ret,end_ptr); ret = my_strtoi(" aaaaaaaa123456 ",end_ptr,16); strtoi_ret_check(ret,end_ptr); while(1) { printf("\nplease input the string you want to convert to integer : \n"); scanf("%s",test_str); /* fgets(test_str,MAX_INPUT_STR_LEN + 1,stdin); len = strlen(test_str); if(len > 0) { test_str[len - 1] = '\0'; } */ if(strcmp(test_str,"exit") == 0) { break; } ret = my_strtoi(test_str,end_ptr,0); strtoi_ret_check(ret,end_ptr); } printf("process finishes \n"); getchar(); return 0; }
程序在DEV-C++ 5.6.2的运行结果为:
[plain]
view plaincopyprint?
INT_MIN = -2147483648 , INT_MAX = 2147483647
input string is NULL , can't be converted to int
Error : invalid argument, input string can not be NULL
input string is , base = 10
Error : No valid char ( digit or letter) is found
input string is -0123 , base = 8
Input string is converted to int : -83
input string is -123 , base = 16
Input string is converted to int : -291
input string is 0X123 , base = 16
Error : Invalid char: X123 is found during conversion
input string is 012389 , base = 8
Error : Invalid char: 89 is found during conversion
input string is 2ab , base = 16
Input string is converted to int : 683
input string is 24 , base = 16
Error : Invalid char: is found during conversion
input string is 46 , base = 36
Input string is converted to int : 150
input string is 234 , base = 10
Input string is converted to int : 234
input string is 1234567890123 , base = 10
Error : Overflow is found at 123 , input string is too long
input string is aaaaaaaa123456 , base = 16
Error : Overflow is found at a123456 , input string is too lon
please input the string you want to convert to integer :
123 456 a
input string is 123 , base = 10
Input string is converted to int : 123
please input the string you want to convert to integer :
input string is 456 , base = 10
Input string is converted to int : 456
please input the string you want to convert to integer :
input string is a , base = 10
Error : Invalid char: a is found during conversion
please input the string you want to convert to integer :
2147483646 2147483648
input string is 2147483646 , base = 10
Input string is converted to int : 2147483646
please input the string you want to convert to integer :
input string is 2147483648 , base = 10
Error : Overflow is found at 8 , input string is too long
please input the string you want to convert to integer :
exit
process finishes
--------------------------------
Process exited with return value 0
Press any key to continue . . .
INT_MIN = -2147483648 , INT_MAX = 2147483647 input string is NULL , can't be converted to int Error : invalid argument, input string can not be NULL input string is , base = 10 Error : No valid char ( digit or letter) is found input string is -0123 , base = 8 Input string is converted to int : -83 input string is -123 , base = 16 Input string is converted to int : -291 input string is 0X123 , base = 16 Error : Invalid char: X123 is found during conversion input string is 012389 , base = 8 Error : Invalid char: 89 is found during conversion input string is 2ab , base = 16 Input string is converted to int : 683 input string is 24 , base = 16 Error : Invalid char: is found during conversion input string is 46 , base = 36 Input string is converted to int : 150 input string is 234 , base = 10 Input string is converted to int : 234 input string is 1234567890123 , base = 10 Error : Overflow is found at 123 , input string is too long input string is aaaaaaaa123456 , base = 16 Error : Overflow is found at a123456 , input string is too lon please input the string you want to convert to integer : 123 456 a input string is 123 , base = 10 Input string is converted to int : 123 please input the string you want to convert to integer : input string is 456 , base = 10 Input string is converted to int : 456 please input the string you want to convert to integer : input string is a , base = 10 Error : Invalid char: a is found during conversion please input the string you want to convert to integer : 2147483646 2147483648 input string is 2147483646 , base = 10 Input string is converted to int : 2147483646 please input the string you want to convert to integer : input string is 2147483648 , base = 10 Error : Overflow is found at 8 , input string is too long please input the string you want to convert to integer : exit process finishes -------------------------------- Process exited with return value 0 Press any key to continue . . .
关于程序错误处理部分的说明:
(1)传入NULL指针,返回什么? (此处我们定义了E_NULL_POINTER专门处理这种情况)
(2)扫描到非法字符时,返回什么?非法字符串由end_ptr指向,但是否需要返回当前已经扫描到的数值呢?
(此处定义了E_INV_CHAR来对应这种情况,并不返回已经扫描到的数值)
(3)扫描过程中,发现溢出时,是返回当前已经扫描到的数值,还是该符号对应的,能表示的最大或最小值
呢?比如,负数的最小是INT_MIN(如-2147483648),正数最大是INT_MAX(如2147483647)。
(此处定义了E_OUT_RANGE来对应这种情况,并不返回已经扫描到的数值,也不返回INT_MAX或INT_MIN)
(4)扫描数字,字母过程中,如果遇到空格或者制表符该如何处理? 如123 456 34 ,此时是返回12345634,
还是想scanf读入字符串那样返回123,一次扫描结束,并提示后面的“ 456 34 ”为非法字符串?
(此处定义了E_INV_CHAR来对应这种情况,并不返回已经扫描到的数值)
需要说明下程序中需要注意的几点:
(1)对传入NULL指针或者只包含空格,制表符字符串指针的特殊情况的处理。
(2)程序对溢出的判断方式,及比较对象采用的是绝对值(比较时针对绝对值)
(3)对base的判断和计算。注意,此代码仅处理base为0,数字,字母串以0或者0x,0X开头的情况,并不能
处理base为8,数字,字母串以0;
或者base为16,字母串以0或者0x,0X开头的情况。请特别注意!!
(4)base进制下,可以表达的最大数值为base
- 1 。从左到右扫描字符串转为字符串的“计算公式”。
(5)将扫描到的字符如何转为数值。例如对于数字字符,[扫描数字字符]
- '0' + 10 才能转换为数值。
(6)指针的使用。要是用错了,很容易出现指针的越界访问,或者访问空指针这样的情况。请看main中测试
程序代码部分,如果是用初始化为NULL的temp,需要传入&temp,因为my_strtoi中会根据错误情况修改指针
指向;如果是传入char
**,请记得将其先指向一个char *的指针,不然my_strtoi中修改end_ptr,用到*end_ptr
时,引用的就是非法内存了。
(7)上面的main程序测试代码中,我们看到用的是scanf("%s",test_str);读入输入字符串。当然,带来的问题
是明显的,如果我们输入123
456 ab 会被当做3个字符串。我们会看到代码中也有给出使用fgets的例子,但
已经被注释掉了,因为fgets读入的时候会在字符串末尾加入一个换行符'\n',所以需要单独处理下。
上面贴出的程序有哪些问题呢?
(1)设定的几种返回值,明显会与输入串为-1,-2这样的情况冲突。
(2)效率有待提高。比如可以用下register变量。
(3)使用了ctype.h里面判断字符类型的一系列函数,这个理论情况下是没有的,得自己实现。
(4)判断溢出,每次都要做那么“复杂”的运算,低效。这种判断方法有待改进。
如果自己大脑堵塞,找不到办法去优化,改进怎么办? Google,百度找别人写的程序,如果可以,找glibc
中函数的实现。毕竟glibc可是那么多大拿编写和维护的。说道这里,不得不开启本文的兄弟篇——
面试题strtoi实现(二)—— 函数的改进
相关文章推荐
- 面试题strtoi实现(一)—— 函数的简单实现
- 面试题strtoi实现(一)—— 函数的简单实现
- 面试题strtoi实现(一)—— 函数的简单实现
- 面试题strtoi实现(二)—— 函数的改进
- C++ 通过main()函数输入参数,实现简单的四则运算
- 华为一道面试题,不能用系统的字符串函数求对等数,用最简单的方法实现。
- 一个简单用C语言实现的日志函数
- Printf()函数简单实现
- 汇编:简约不简单的不定参函数实现方法
- 函数指针之回调函数简单实现
- 在linux系统下,简单实现类似windows的_splitpath函数的功能
- 简单的printf函数实现
- 【面试题】printf函数实现
- Flex3学习轨迹:缓动函数简单实现
- string.h 函数的简单实现
- 用最简单的函数实现功能:判断一个int数据是否是2的x次幂(不能使用循环)。
- 利用LineDDA函数来实现简单的图片移动动画
- 关于成员函数的Command模式的简单实现
- 可变参数列表:简单printf函数的实现
- c++ 实现对配置文件的读写 根据windows API 函数简单改写