算法练习之一

今天瞎逛论坛,看到一个很不错的题目,拿来练练手,记录一下,以便日后回看。

先看题目:

输入一个表示整数的字符串,把该字符串转换成整数并输出,例如输入字符串"345", 则输出整数 345。
给定函数原型 int StrToInt(const char *str) ,完成函数 StrToInt,实现字符串转换成整数的功能,不得用库函数 atoi(即便准许使用,其对于溢出情况的处理也达不到题目的要求.)

输入:       输出:

123        123

-123        -123

23a4        23

2147483648    2147483647

-2147483649   -2147483648

10522545459   2147483647

题目分析:

初看这套题貌似很简单,但是真正开始动手敲键盘的时候才发现里面有太多的陷阱。

陷阱一:是否考虑到了正负号的问,即代码能正确处理123与-123

陷阱二:是否考虑到字符串中有非法字符,例如23a4,该如何处理。我们目前假设输出23是正确的

陷阱三:如果输入的字符串超出了int型整数所能表示的最大范围怎么办?例如输入10522545459,我们知道int型最大范围为-2147483648~2147483647。我们假设一旦溢出时,输出最大    或最小数。在写代码过程中,溢出问题是最大的障碍。

陷阱四:字符串是空怎么办?默认字符串为空是输出0.

OK,将以上陷阱考虑清楚以后开始写代码了

  1 #include <iostream>
  2 #include <string.h>
  3 #include <limits>
  4 #include <limits.h>
  5 using namespace std;
  6 
  7 const int MAXSIZE=1000;
  8 enum Status{ kValid = 0, kInvalid};
  9 int g_nStatus = kValid;
 10 
 11 /*版本一
 12 此版本基本实现了将字符串型整数转换为int整数,但是对于一些非法的输入依旧是不行的
 13 例如:23a4,它只会返回0,而不是23;还有一旦越界后,它也是输出的0
 14 */
 15 int StrToInt( const char* str )
 16 {
 17     g_nStatus = kInvalid;
 18     long long num=0;
 19 
 20     if( str != NULL )
 21     {
 22         const char* digit = str;
 23         bool minus = false;
 24 
 25         if( *digit == '+' )
 26         {
 27             digit++;
 28         }
 29         else if( *digit == '-' )
 30         {
 31             digit++;
 32             minus=true;
 33         }
 34 
 35         while( *digit != '' )
 36         {
 37             if( *digit >='0' && *digit <='9' )
 38             {
 39                 num = num*10 + ( *digit - '0' );
 40 
 41                 if( num > numeric_limits<int>::max() )
 42                 {
 43                     num=0;
 44                     break;
 45                 }
 46                 digit++;
 47             }
 48             else
 49             {
 50                 num=0;
 51                 break;
 52             }
 53         }
 54         if( *digit == '' )
 55         {
 56             g_nStatus = kValid;
 57             if( minus )
 58             {
 59                 num=0-num;
 60             }
 61         }
 62     }
 63 
 64     return static_cast<int>(num);
 65 }
 66 
 67 /*版本二:
 68 此版本较上面的有了更好的改进,首先对于溢出问题有了很好的处理,INT_MAX是标准库里定义的int型最大值INT_MIN是最小值
 69 其次,在遇到非法字符时,循环跳出,但之前记录的数依旧会返回,也就是实现了23a4返回23的功能。
 70 小贴士:INT_MAX=2147483647   INT_MIN=-2147483648 (需要头文件limits.h) 这也是( cur-1 > INT_MAX - res*10 )这样写的原因
 71 
 72 但是,(显然这个版本还是有错),如果输入10522545459他会输出1932610867,
 73 原因是如果之前所得数已经较大了,再乘10就必定溢出,也就是超出int所能表示最大范围,会发生逻辑错误。
 74 */
 75 int StrToInt_2( const char* str )
 76 {
 77     int res=0;
 78     int i=0;
 79     int signal = '+';
 80     int cur;
 81 
 82     if( !str )
 83     {
 84         return 0;
 85     }
 86 
 87     while( isspace(str[i]))
 88     {
 89         i++;
 90     }
 91 
 92     if( str[i] == '+' || str[i] == '-')
 93     {
 94         signal = str[i];
 95         i++;
 96     }
 97 
 98     while( str[i] >= '0' && str[i] <= '9' )
 99     {
100         cur=str[i]-'0';
101         if( (signal == '+' ) && ( cur > INT_MAX - res*10 ) )
102         {
103             res=INT_MAX;   
104             break;
105         }
106         else if( (signal == '-' ) && ( cur-1 > INT_MAX - res*10 ) )
107         {
108             res=INT_MIN;   
109             break;
110         }
111 
112         res=res*10+cur;
113         i++;
114     }
115 
116     return signal=='-'?-res:res;
117 }
118 
119 /*版本三:
120 此版本我没有想出来,而是参考july的博文写出的。
121 它的做法最重要的是巧妙的规避了计算 n*10 这一乘法步骤,转换成计算除法 MAX_INT/10 代替,不能不说此法颇妙。
122 这样的话在超出int型最大范围前就能提早发现它将要溢出。关键代码在***处
123 目前这个版本的代码是最为接近正确答案的了,不过也许会有更好的代码,待日后发现再说吧!
124 
125 这个版本所存在的问题就显得无足轻重了:它的实现与 linux 内核的 atoi 函数的实现, 都有一个共同的问题: 
126 即使出错, 函数也返回了一个值, 导致调用者误认为自己传入的参数是正确的, 但是可能会导致程序的其他部分产生莫名的错误且很难调试”。
127 */
128 int StrToInt_3( const char* str )
129 {
130     const int MAX_DIV=INT_MAX/10;
131     const int MIN_DIV=INT_MIN/10;
132     const int MAX_R=INT_MAX%10;
133     const int MIN_R=INT_MIN%10;
134     //另一种实现,可以不需要limit.h的头文件,便于移植
135     /*
136     const int MAX = (int)((unsigned)~0 >> 1);
137     const int MIN = -(int)((unsigned)~0 >> 1) - 1;
138     const int MAX_DIV = (int)((unsigned)~0 >> 1) / 10;
139     const int MIN_DIV = (int)((((unsigned)~0 >> 1) + 1) / 10);
140     const int MAX_R = (int)((unsigned)~0 >> 1) % 10;
141     const int MIN_R = (int)((((unsigned)~0 >> 1) + 1) % 10);
142     */
143 
144     int res=0;
145     int i=0;
146     int signal = '+';
147     int cur;
148 
149     if( !str )
150     {
151         return 0;
152     }
153 
154     while( isspace(str[i]))
155     {
156         i++;
157     }
158 
159     if( str[i] == '+' || str[i] == '-')
160     {
161         signal = str[i];
162         i++;
163     }
164 
165     while( str[i] >= '0' && str[i] <= '9' )
166     {
167         cur=str[i]-'0';
168         if( (signal == '+' ) && ( res > MAX_DIV || (res == MAX_DIV && cur >= MAX_R)) )  //***
169         {
170             res=INT_MAX;   /*或res=MAX*/
171             break;
172         }
173         else if( (signal == '-' ) && ( res > MIN_DIV || (res == MIN_DIV && cur >= MIN_R)) ) //***
174         {
175             res=INT_MIN;   /*或res=MIN*/
176             break;
177         }
178 
179         res=res*10+cur;
180         i++;
181     }
182 
183     return signal=='-'?-res:res;
184 }
185 
186 
187 int main( void )
188 {
189     char str[MAXSIZE];
190     memset( str, 0, MAXSIZE);
191     while( cin.getline( str, MAXSIZE ))
192     {
193         cout<<str<<"          "<<StrToInt_2(str)<<endl;
194     }
195 
196     return 0;
197 }

写完这道题以后,也给我自己提个醒:

1.拿到一道题时候要充分考虑输入的各种情况,能够对所有非法输入有一个正确处理,这就是所谓的程序的鲁棒性(健壮性)

2.所有题目题目的解法都离不开基础,程序中用的都是基础的数据结构。int+array=all

3.善于分析题目中隐含的小知识,细节决定成败。这也是我目前所或缺的。

原文地址:https://www.cnblogs.com/HXloveLL/p/3683796.html