整型和字符数组之间的转换(略带进制的转化)
C语言和python语言不同,C语言有严格的输入控制,所以类型很清晰,python的类型,一般输入的都是字符串类型的。要用的时候就直接转换一下,很方便。没有太多限制,但是我们在实现C语言类型转换的时候,一个是程序执行的时候有一个隐式的转换,那个是在同一种类型下,比如int 和long的转换,int和double的转换,这种向上的转换,这样的系统自己帮我们做了。还有一种强制的转换,我们想把float的类型变成int类型的。那就加上一个 (int) 要转换的数 ,我们发现这些是在同时数值情况下的一种转换。C语言里面也有字符类型char,这个类型很灵活,因为在c语言里面没有字符串这个类型,而我们从一个文本里面读取一个数据的时候,往往将读取到的数据保存在一个字符数组里面,然后通过这样的一个处理,将其变成一个字符串的形式。那么当我们想要操作字符串里面的那些个数的时候,要把他们当作数来计算的时候,我们就不能用强制转换这一类的方法了。简化的说就是将字符的类型转换成数字类型,在标准库里面,有一个atoi 的函数,它包含在头文件stdlib.h里面。我们可以调用他,但是我们有时不仅仅是只是要将他转换成int类型,可能还有其他的类型,或是将字符型转换成整型等。下面就是一些具体的函数实现。
atoi
我们首先判断,所个的字符串里面的字符是不是在‘0’-‘9’之间的字符,不是的话,那就没有什么可以转换的必要了,这是一个先决的条件。有了这个的保证,那么我们就是要看这个字符有几位。好比‘8024’ 四位数,我们知道在10进制中,对应的位置可以用乘上10次幂就好,那么现在们可以这么想,用字符的长度作为循环的条件,对于最高位我们可以将其多次乘上10,就可以做到将数字对到10进制数的对应位置。字符的数字‘1’是不可以用作数值1来和整型数运算的,我们同样要转换一下,在ascii码里面,‘1’代表是49,‘0’代表是48,看出来没? 只要拿字符‘1’减去‘0’。
函数实现:
1 int atoi(char s[]) 2 { 3 int i,n; 4 5 n = 0; 6 for( i = 0;a[i] >='0' && a[i] <= '9';i++) 7 n = n * 10 + (s[i] - '0'); 8 return n; 9 } 10
看上去很简单的样子,其实这个函数有很多的局限性,比如无法对带符号的字符串进行转换,以及对空格的回车符的处理等,功能还是不太全的,那考虑将他加上去把。那就是加上对符号的判断。这个应该是很好实现的。下面实现下。
1 int atoi(char s[]) 2 { 3 int i,n,sign; 4 for(i = 0; isspace(s[i]);i++) 5 ; 6 sign = (s[i] == '-')? -1:1; 7 if(s[i] == '+' || s[i] == '-') 8 i++; 9 for(n = 0; isdigit(s[i]);i++) 10 n = n*10 + (s[i] - '0'); 11 return sign * n; 12 }
在上面的那个函数上,用到了<ctype.h>里面的两个函数,isspace和isdigit 从字面上看的出来,一个是判断空白字符的一个是判断字符数字的。这样可以简化一些代码,功能和第一个函数里面for 的功能一样的。回到这个函数,有了对符号的处理,能够输出一个带符号的数了。这个是在上面的函数上的一个提升,对于哪些恶意的输入,是在没办法识别,比如输入-9 0,这个就不能判断出了,哪有数字是这样的。但是对于数字本身,因为是int类型的,所以数的大小有约束,可以考虑考虑将这边优化下。
同样,我们能将字符类型的转为int的,反过来可不可以?这个必须可以的。我们简单分析下,如果要将一个数字转为一个字符,我们得现将这个数分解出来,把每个位上的数取出来,这个有一个套路的,这边很简单的就可以实现。然后就是对数字的正负符号的一个考虑,这个可以通过判断数的正负来解决。下面就是简单的实现下这个函数。
itoa:
1 void itoa(int n,char s[]) 2 { 3 int i,sign; 4 5 if((sign = n) < 0) 6 n = - n; 7 i = 0; 8 do 9 { 10 s[i++] = n%10 + '0'; 11 }while((n/=10) > 0); 12 13 if(sign < 0) 14 s[i++] = '-'; 15 s[i] = '\0'; 16 reverse(s); //自编函数,实现数组的逆序。在后面实现 17 }
引入另一个变量,保存原来的数,用来判断正负,对于一开始将n变号的那个操作,是简化了后面循环里面的操作,不然还要判断一下这个数的正负,比较麻烦。最后的那个reverse函数,是将这个数组逆序,我们是将这个数变成了一个字符串,可以简化的用%s将其输出,而我们保存的时候是逆序的就是个位上的数在前面,那就把数组再此逆序,还原成正常的顺序。
问题来了,我们发现最小的负数如果用n= -n这个来处理的话,是要出错的,最大的正数(对二补码中)比最小负数的绝对值小1,如果直接转换,那就会出现溢出状况,这个就不是太好的,那就要我们改下,让函数更加的完善。改动的地方就是n=-n,不能这么做了,那就不变,直接拿原来的数操作。我们这边用到宏定义。
解决方案:
#define abs(x) ((x)< 0? -(x):(x)) //n%10的值进行判断,保证是正的。 void itoa(int n,char s[]) { int i,sign ; sign = n; i = 0; do { s[i++] = abs(n%10) +'0'; }while((n/=10) != 0); if(sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); }
通过宏定义的一个绝对值函数,将取位的操作转换为正的,保证程序正确运行。这边也有一个比较重要的一点就是,循环的结束条件,因为有了负数,我们不能再用">0"来判别结束了,不然就是一个死循环了。改动的也就是这两个部分。这样对对二补码运算的一个支持把。函数到这边了,应该差不多了,不过我们可以再加一个小的功能,控制一个输出,输出的长度。这就多了一个参数。这个改动有一定的好处。下面是改后的代码:
void itoa(int n,char s[],int w) { int i,sign ; sign = n; i = 0; do { s[i++] = abs(n%10) +'0'; }while((n/=10) != 0); if(sign < 0) s[i++] = '-'; while(i < w) s[i++] = ' '; s[i] = '\0'; reverse(s); }
上面也就是更改的地方,很简单的一个操作,作用就是让输出长度可以自己控制。多余的用空格填补在左边。
引申:
既然我们能将十进制的数转换成十进制形式保存在字符中,那么我们能不能将十进制的数转换成16进制,8进制,2进制等等这些常用的进制数呢? 那么我们可以试一下。
itob: 将整数n转换成以b为底的数
分析下,二进制就是0和1表示的,8进制是0开头的,后面的数是0~7表示的,16进制是0~9 a~f表示的。(A~F暂时不考虑)。那么对于前面的几个数还是可以的,都不超过9,但是到了16进制的时候,那个字符部分,我们可以用‘a’+多出的数,怎么算多出的数,就是超过10的数,最多超过5,也就是到f。看上去还是不太复杂的。那就可以实现实现了,和上面的itoa那个很相似的。
void itob(int n,char s[],int b) { int i,sign,j; if((sign = n) < 0) n = -n; //保证是正数。 i = 0; do { j = n % b; s[i++] = (j <= 9) ? j+'0' : j+'a' - 10; //转换过程,一个条件表达式解决。 }while((n/=b) > 0); if(sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); }
这就解决了这个问题,我们平时在简单的进制转换的时候基本是也是这个方法,只是在处理十六进制的时候有点复杂,那个a~f和A~F的切换,虽然这两个都是可以代表十六进制的,但是我们还是要完美的实现一下,这边还有一个不足的就是对于8进制的数,前面有一个0开头,16进制的有一个0x开头,这个的实现估计也很简单把,添加相应的条件语句就可以了。
补充:
说到了进制了,那就将十六进制,这个较为复杂的一个转换成对应的十进制数。通过十六进制的数字组成的一个字符数组来实现下,这个就大大简化了。我们在输入十六进制和八进制的时候是有对应的格式输入的,%i就是可以实现输入。输出就是分别是x或是o格式输出的。忽略前面的符号标志。这个暂且不讨论,要是这么进行转换的话,讨巧的就是利用格式输出。而对于用直接输入通过细节转换的方法,我目前也就想到用我上面说的那个字符数组来实现,对于直接操作十六进制数这个难度有点大。这些不考虑了,就用字符数组来实现把。从屏幕读入十六进制得数,然后进行判断然后转换。下面是实现的代码。
int htoi(char s[]) { int hexdigit, i, flag,n; i = 0; if(s[i] == '0') { ++i; if(s[i] == 'x'|| s[i] == 'X') ++i; } n = 0; flag = 1; for(; flag == 1;++i) { if(s[i] >='0' && s[i] <= '9') hexdigit = s[i]- '0'; //0~9的情况下 else if(s[i] >= 'a' && s[i] <= 'f') hexdigit = s[i] - 'a' + 10; //a~f的情况下 else if(s[i] >= 'A' && s[i] <= 'F') hexdigit = s[i] - 'A' +10; //A~F的情况下 else flag = 0; //什么情况都不是,表明不是一个合格的十六进制。直接退出循环。 if(flag == 1) n = 16 * n +hexdigit; } return n; }
利用字符数组就是能够很清晰的将每个部分的情况都能表示的很清楚,如果用直接格式输入的方法,我们比较难的对一个十六进制的数进行操作,如果直接将十六进制除一个十进制数,那么这个十六进制数处理的结果就会很奇怪,我目前是没有想到如何操作十六进制数来实现这个转换,希望能想到的能交流下。
对于其他的进制之间的转换,大体就是根据这样的一个形式来实现了,高位的一般都是要不断的去乘上要转换的数制的大小就好了。上面红色加粗的部分就是这个的关键部分,如何去理解,可以笔算下,这个相当于嵌套着提取公因数的做法。需要多想想。
reverse函数的补充:
前面在整型转换成字符的时候都有一个逆序的操作,我们在存储时是用的逆序存储的,就是高位存储在后面(要对应数组的构造想)。所以我们要想得到正确的答案,难么逆序是必须要做的,对一个字符数组做逆序,一般有两个方法。
方法一:引入另一个数组,但是好理解。我们利用了string.h里面一个函数strlen(),他返回的是数组有效的字符的长度,也就是忽略了最后的'\0'。这个就是让处理变得简单了。
void reverse(char from[],char to[]) { int i,j; j = strlen(from); for(i = 0;i < strlen(from);i++) to[i] = from[--j]; to[i] = '\0'; }
方法二:对于方法一,我们可以继续优化下,可以之用到要逆序的数组,而不需要用到另一个数组,还有就是上面的处理还是不到位的,没有对空白字符的处理,'\n'是要放在最后的。所以下面是一个比较完整而且实用的方法
1 void reverse(char s[]) 2 { 3 int i,j,temp; 4 i = 0; 5 while(s[i] != '\0') //将i移动到最后 6 ++i; 7 --i; //看倒数第二个是不是换行符 8 if(s[i] == '\n') 9 --i; //是的话就向前一个。 10 j = 0; 11 while(j < i) //排除了最后的“无效”字符,将前面的数对调,这个操作应该很熟悉的 12 { 13 temp = s[j]; 14 s[j] = s[i]; 15 s[i] = temp; 16 } 17 }
说了这么多,也就是为了说明一下,在自己实现类型转换的时候能够更加的有选择性,附带提到的进制转换也是比较常用的。这些基础的内容,个人感觉还是弄的比较清楚比较好的。
用心。