面试题strtoi实现(一)—— 函数的简单实现

来源:互联网 发布:聚类算法 编辑:程序博客网 时间:2024/06/05 06:23
最近参加了一场面试,面试官给出的一道面试题是实现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循环里,而不是在循环

条件里,因为后者会导致指针多移动了一个位置,指向空格,制表符后的第二个非空格字符;要是输入字符串

只有空格怎么办?那么此时指向的将是'\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?在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h> 
  2. #include <ctype.h> 
  3. #include <limits.h> 
  4.  
  5. #include <string.h> 
  6.  
  7. #define E_NULL_POINTER  -1 
  8. #define E_INV_CHAR      -2 
  9. #define E_OUT_RANGE     -3 
  10.  
  11. #define NO_ERROR  0 
  12.  
  13. #define MAX_INPUT_STR_LEN  256 
  14. #define MIN_BASE 2 
  15. #define MAX_BASE 36 
  16.  
  17.  
  18.  
  19.  
  20.  
  21. int my_strtoi(constchar *string,char **endptr,int base) 
  22.     char *p = string; 
  23.     int sum = 0; 
  24.     int sign = 1; 
  25.     unsigned int overflow = 0; 
  26.  
  27.     if(NULL == p) 
  28.     { 
  29.         printf("\ninput string is NULL , can't be converted to int \n"); 
  30.         return E_NULL_POINTER; 
  31.     } 
  32.  
  33.     while(isspace(*p)) 
  34.     { 
  35.         p++; 
  36.     } 
  37.  
  38.     if(*p == '-'
  39.     { 
  40.         sign = -1; 
  41.         p++; 
  42.     } 
  43.     else if(*p =='+'
  44.     { 
  45.         p++; 
  46.     } 
  47.  
  48.     if(-1 == sign) 
  49.     { 
  50.         overflow = -INT_MIN; 
  51.     } 
  52.     else 
  53.     { 
  54.         overflow = INT_MAX; 
  55.     } 
  56.  
  57.       
  58.     if((base < MIN_BASE && base != 0) || (base > MAX_BASE)) 
  59.     { 
  60.         base = 10; 
  61.     } 
  62.     else if(base == 0) 
  63.     { 
  64.         base = 10; 
  65.  
  66.         if(*p == '0'
  67.         { 
  68.              
  69.             base = 8; 
  70.  
  71.             p++; 
  72.  
  73.             if(*p == 'x' || *p =='X'
  74.             { 
  75.                 base = 16; 
  76.                 p++; 
  77.             } 
  78.         } 
  79.     } 
  80.  
  81.      
  82.     printf("\ninput string is %s , base = %d \n",string,base); 
  83.      
  84.     /*
  85.         For the "if sentence " below :
  86.        
  87.         if *p is found to be '\0', that indicates that there is no valid character.
  88.        otherwise, it indicates that the first invalid character is found.
  89.       
  90.        We may just handle the case that *p == '\0', because the other cases will be
  91.        handle in the following while loop as well.
  92.      */ 
  93.     if(!isalnum(*p)) 
  94.     { 
  95.          
  96.         *endptr = p; 
  97.         return E_INV_CHAR; 
  98.     } 
  99.  
  100.  
  101.      
  102.     while(*p != '\0'
  103.     { 
  104.         if(isdigit(*p)) 
  105.         { 
  106.             if( (*p - '0') > base -1) 
  107.             { 
  108.                 *endptr = p; 
  109.                 return E_INV_CHAR; 
  110.             } 
  111.             else 
  112.             { 
  113.                 if((overflow - (*p -'0')) / base < sum) 
  114.                 { 
  115.                     *endptr = p; 
  116.                     return E_OUT_RANGE;     
  117.                 } 
  118.                 else 
  119.                 { 
  120.                     sum = sum * base + (*p - '0'); 
  121.                 } 
  122.             } 
  123.         } 
  124.         else if(isupper(*p)) 
  125.         { 
  126.             if((*p - 'A' + 10) > (base -1)) 
  127.             { 
  128.                 *endptr = p; 
  129.                 return E_INV_CHAR; 
  130.             } 
  131.             else 
  132.             { 
  133.                 if((overflow - (*p -'A' + 10)) / base < sum) 
  134.                 { 
  135.                     *endptr = p; 
  136.                     return E_OUT_RANGE;     
  137.                 } 
  138.                 else 
  139.                 { 
  140.                     sum = sum * base + (*p - 'A' + 10); 
  141.                 } 
  142.             } 
  143.         } 
  144.         else if(islower(*p)) 
  145.         { 
  146.             if((*p - 'a' + 10) > (base -1)) 
  147.             { 
  148.                 *endptr = p; 
  149.                 return E_INV_CHAR; 
  150.             } 
  151.             else 
  152.             { 
  153.                 if((overflow - (*p -'a' + 10)) / base < sum) 
  154.                 { 
  155.                     *endptr = p; 
  156.                     return E_OUT_RANGE;     
  157.                 } 
  158.                 else 
  159.                 { 
  160.                     sum = sum * base + (*p - 'a' + 10); 
  161.                 } 
  162.             } 
  163.         } 
  164.         else 
  165.         { 
  166.             *endptr = p; 
  167.             return E_INV_CHAR;     
  168.         } 
  169.  
  170.         p++; 
  171.          
  172.     } 
  173.  
  174.  
  175.     return (sum * sign); 
  176.  
  177.  
  178.  
  179.  
  180. void strtoi_ret_check(int ret,char **ppc_invalid) 
  181.     if(E_NULL_POINTER == ret) 
  182.     { 
  183.         printf("Error : invalid argument, input string can not be NULL \n"); 
  184.     } 
  185.     else if (E_INV_CHAR == ret) 
  186.     { 
  187.         if(**ppc_invalid == '\0'
  188.         { 
  189.             printf("Error : No valid char ( digit or letter)  is found \n"); 
  190.         } 
  191.         else 
  192.         { 
  193.             printf("Error : Invalid char:  %s is found during conversion \n",*ppc_invalid); 
  194.         }               
  195.     } 
  196.     else if(E_OUT_RANGE == ret) 
  197.     { 
  198.         printf("Error : Overflow is found at %s , input string is too long \n",*ppc_invalid); 
  199.     } 
  200.     else 
  201.     { 
  202.         // printf("Input string %s is converted to int : %d \n"); 
  203.  
  204.         printf("Input string is converted to int : %d \n",ret); 
  205.     } 
  206.  
  207.  
  208.  
  209. int main(void)  
  210.     // your code goes here 
  211.  
  212.     int ret; 
  213.     char *temp = NULL; 
  214.     char **end_ptr = &temp; 
  215.     char test_str[MAX_INPUT_STR_LEN] = {0}; 
  216.     int  len; 
  217.      
  218.  
  219.     printf("INT_MIN = %d , INT_MAX = %d \n", INT_MIN,INT_MAX); 
  220.  
  221.       
  222.     ret = my_strtoi(NULL,&temp,0); 
  223.     strtoi_ret_check(ret,end_ptr); 
  224.      
  225.  
  226.     ret = my_strtoi(" ",end_ptr,0); 
  227.     strtoi_ret_check(ret,end_ptr); 
  228.  
  229.  
  230.     ret = my_strtoi("  -0123",end_ptr,0); 
  231.     strtoi_ret_check(ret,end_ptr); 
  232.  
  233.      
  234.     ret = my_strtoi("  -123",end_ptr,16); 
  235.     strtoi_ret_check(ret,end_ptr); 
  236.  
  237.     ret = my_strtoi("  0X123",end_ptr,16); 
  238.     strtoi_ret_check(ret,end_ptr); 
  239.  
  240.     ret = my_strtoi("  012389",end_ptr,0); 
  241.     strtoi_ret_check(ret,end_ptr); 
  242.      
  243.  
  244.     ret = my_strtoi(" 2ab",end_ptr,16); 
  245.     strtoi_ret_check(ret,end_ptr); 
  246.  
  247.     ret = my_strtoi(" 24 ",end_ptr,16); 
  248.     strtoi_ret_check(ret,end_ptr); 
  249.      
  250.  
  251.     ret = my_strtoi(" 46",end_ptr,36); 
  252.     strtoi_ret_check(ret,end_ptr); 
  253.  
  254.     ret = my_strtoi(" 234",end_ptr,38); 
  255.     strtoi_ret_check(ret,end_ptr); 
  256.  
  257.     ret = my_strtoi(" 1234567890123",end_ptr,38); 
  258.     strtoi_ret_check(ret,end_ptr); 
  259.  
  260.      
  261.     ret = my_strtoi(" aaaaaaaa123456 ",end_ptr,16); 
  262.     strtoi_ret_check(ret,end_ptr); 
  263.      
  264.  
  265.     while(1) 
  266.     { 
  267.         printf("\nplease input the string you want to convert to integer : \n"); 
  268.          
  269.         scanf("%s",test_str); 
  270.         /*
  271.         fgets(test_str,MAX_INPUT_STR_LEN + 1,stdin);
  272.         len = strlen(test_str);
  273.        
  274.         if(len > 0)
  275.         {
  276.             test_str[len - 1] = '\0';
  277.         }   
  278.         */ 
  279.         if(strcmp(test_str,"exit") == 0) 
  280.         { 
  281.             break
  282.         } 
  283.  
  284.         ret = my_strtoi(test_str,end_ptr,0); 
  285.         strtoi_ret_check(ret,end_ptr); 
  286.     } 
  287.  
  288.     printf("process finishes \n"); 
  289.      
  290.     getchar(); 
  291.      
  292.     return 0; 

程序在DEV-C++  5.6.2的运行结果为:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. INT_MIN = -2147483648 , INT_MAX = 2147483647 
  2.  
  3. input string is NULL , can't be converted to int 
  4. Error : invalid argument, input string can not be NULL 
  5.  
  6. input string is   , base = 10 
  7. Error : No valid char ( digit or letter)  is found 
  8.  
  9. input string is   -0123 , base = 8 
  10. Input string is converted to int : -83 
  11.  
  12. input string is   -123 , base = 16 
  13. Input string is converted to int : -291 
  14.  
  15. input string is   0X123 , base = 16 
  16. Error : Invalid char:  X123 is found during conversion 
  17.  
  18. input string is   012389 , base = 8 
  19. Error : Invalid char:  89 is found during conversion 
  20.  
  21. input string is  2ab , base = 16 
  22. Input string is converted to int : 683 
  23.  
  24. input string is  24  , base = 16 
  25. Error : Invalid char:    is found during conversion 
  26.  
  27. input string is  46 , base = 36 
  28. Input string is converted to int : 150 
  29.  
  30. input string is  234 , base = 10 
  31. Input string is converted to int : 234 
  32.  
  33. input string is  1234567890123 , base = 10 
  34. Error : Overflow is found at 123 , input string is too long 
  35.  
  36. input string is  aaaaaaaa123456  , base = 16 
  37. Error : Overflow is found at a123456  , input string is too lon 
  38.  
  39. please input the string you want to convert to integer : 
  40. 123 456 a 
  41.  
  42. input string is 123 , base = 10 
  43. Input string is converted to int : 123 
  44.  
  45. please input the string you want to convert to integer : 
  46.  
  47. input string is 456 , base = 10 
  48. Input string is converted to int : 456 
  49.  
  50. please input the string you want to convert to integer : 
  51.  
  52. input string is a , base = 10 
  53. Error : Invalid char:  a is found during conversion 
  54.  
  55. please input the string you want to convert to integer : 
  56. 2147483646 2147483648 
  57.  
  58. input string is 2147483646 , base = 10 
  59. Input string is converted to int : 2147483646 
  60.  
  61. please input the string you want to convert to integer : 
  62.  
  63. input string is 2147483648 , base = 10 
  64. Error : Overflow is found at 8 , input string is too long 
  65.  
  66. please input the string you want to convert to integer : 
  67. exit 
  68. process finishes 
  69.  
  70. -------------------------------- 
  71. Process exited with return value 0 
  72. 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实现(二)—— 函数的改进

0 0
原创粉丝点击