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

     

   

  最近参加了一场面试,面试官给出的一道面试题是实现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函数的处理结果。废话说了那么多,该上点实在的代码了。


#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的运行结果为:

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实现(二)—— 函数的改进

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值