字符串的输入输出
本文部分内容参考了C Primer Plus(Fifth Edition)
C语言字符串表示
字符串是C语言中最常用也是最重要的数据类型,但是C语言没有专门提供这种类型。因为字符串由字符组成,所以声明字符串,我们用字符数组。字符数组是字符串的变量表示方法。纯字符数组和字符串的区别和联系就是:字符串是一个以'\0'结尾的字符数组。因此,我们声明一个字符数组char ch[32]实际上它最多只能存储31个可显示字符,最后一个字符是'\0',它是字符串结尾的标志。
字符串还有一种表示方法,那就是字符串常量(字符串字面量)。例如printf("%s","King and Queen");这个表达式语句中"King and Queen"就是字符串常量。实际上,它也是一个元素为字符常量的数组,这个数组内容为(char []){'K','i','n','g',' ','a','n','d',' ','Q','u','e','e','n','\0'};千万注意别忘了'\0'。
因为字符串常量如"Anytime"可以表示这个字符串(准确说是元素为字符常量的数组)的首地址。因此,我们可以用指针来操作字符串。我们可以这样声名:
char * chptr = "Anytime";
但不能这样:
char * chptr; *chptr = "Anytime";
因为*chptr表示chptr所指向的地址上面的内容,如果这个指针未初始化,那么这就是个很危险的操作,指针有可能乱指向内存空间,如果指向的是系统文件,它就会修改系统文件。即使指针已经初始化,我们也不应该这样做,原因有两个:
1.这样做可能导致溢出(超出了安全的内存空间)
2.会更改不应该更改的内容(详见这篇文章,它解释得很好)
因此,指针操作字符串常量不安全,一般只用来传输字符串变量的地址(内容为字符变量的数组)。
字符串基本输入
scanf()虽然有专门的%s来输入字符串,但它的终止条件是遇到如空格,换行符等空白字符。不如说它是用来处理单词输入的。而处理长字符串输入,最早是用gets(),当它读到换行符时丢弃'\n'并结束输入,它很好用(对于很早以前来说),但我不希望大家掌握它,尽管很多随便的程序都在用它。
char cha[5]; gets(cha);
这个函数真的非常好用,直接把数组首地址代进去就行了。你可否注意到一个严重的缺陷?这个函数不知道这个数组的大小,也就是不知道它最多只能接受多少字符的输入,这就会导致溢出!有一些UNIX系统的代码大量使用了gets(),使得黑客有机会通过这个漏洞编写程序将垃圾数据写入系统,导致系统瘫痪,这就是流行于这些UNIX计算机之间的蠕虫病毒。
我推荐大家使用fgets()函数,这个函数使用起来比gets()安全,但更加麻烦。
char * cha[16]; fgets(cha, 16, stdin); //fgets(名称,大小,读取文件) //如果要用这个函数从键盘读取,请在读取文件的地方用上stdin
传入的大小为n,它就最多读取n - 1个字符或遇到换行符时终止。例如,上面的代码运行后我输入"1234567890123456"后,cha = "123456789012345"。这个函数看起来很完美,但十全十美的东西是不存在的。fgets()的缺陷在于它读到换行符时保存了换行符!下面是C Primer Plus第五版的有关程序示例:
此时,我们很迫切想编写一个函数,让它丢弃fgets()保存的换行符及后面的无效字符。如果不丢弃无效字符,就会导致后面的语句误读了缓冲区,就像初学字符输入时输入一个字符回车后再要输入一个字符,可还没有输入就已经执行到后面去了。我们把我们自己编写的函数取名为s_gets(),我们让它的返回值和fgets()的返回值一样。我们编写的函数代码如下:
1 char * s_gets(char * sptr, int size){ 2 int i = 0; //i表示读取项数 3 char * re; //re返回和fgets()一样的数值 4 re = fgets(sptr,size,stdin); 5 if(re){ //如果re != NULL 6 while( (sptr[i] != '\n') && (sptr[i] != '\0') ) //读取sptr[i]直到读到'\0'或'\n' 7 i++; 8 if(sptr[i] == '\n') //如果读到的是'\n' 9 sptr[i] = '\0'; //把它变成'\0' 10 else 11 while( ( getchar() ) != '\n' ) //如果读到的是别的东西,一直读到'\n',防止后面的语句误读 12 continue; 13 } 14 return re; 15 }
如果日后需要进行安全的字符串输入,用这个函数就OK了。运用这个函数的示例运行:
字符串基本输出
printf()函数提供了一个接近于完美的字符串输出,而且,它还可以直接输出数字(如%d,%u等)。printf()函数的通用性很强,因此,如果不想使用其它的字符串输出函数,一定要记住这个。相信即使刚学C的初学者也知道这个函数的用法。
puts()函数简洁易用,直接给出字符串的地址就行了。需要注意的是,puts()函数在字符串输出后会加上'\n',所以puts()和gets()以及上面我们自己写的s_gets()配套使用。
char cha[16] = "Memory"; puts(cha);
fputs()函数主要和fgets()配套,需要提醒的是,这两个函数不仅可以用在标准I/O上,还可以进行文件处理,而且一般是用在文件处理上的。fputs()函数需要两个参数,第一个参数给出字符串地址,第二个参数给出输出位置。因为它和fgets()配套使用,所以它在输出字符串之后不加上'\n'。
char * cha[16] = "Memory\n"; fputs(cha,stdin);
下面给出C Primer Plus第五版中有关的例程:
自己编写自定义的字符串输入输出函数!
你也可以自己编写你自己的输入输出函数,而且,假设你编写的函数没有什么太大的错误,这些函数比上面所提到的大部分函数都更加可靠和灵活。我们可以使用getchar()和putchar()来完成字符串的输入输入功能。
下面是我写的函数,读者在读完后也应该自己动手写写,说不定以后还能用上呢!
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define SIZE 16 5 6 char * ud_gets(char * st, int size){ //输入 7 int i = 0; //计数器 8 while( ( ( st[i] = getchar() ) != '\n') && (i < size - 1) ) 9 i++;//如果读到非'\n'字符或未超出限定范围,则继续读取 10 while( (i > size - 1) && (getchar() != '\n')) 11 continue;//如果读取超过限定范围且后面还有字符,则丢弃 12 st[i] = '\0'; //结束读取 13 return st; 14 } 15 16 char * ud_puts(char * st, int ad_enter){ //输出 17 int i = 0; 18 while(putchar(st[i]) != '\0') //如果没有读到字符串结尾就继续读 19 i++; 20 if(ad_enter) //如果加上'\n'的开关被打开 21 putchar('\n'); //输出换行符 22 return st; 23 } 24 25 int main(int argc, char * argv[]){ 26 char cha[SIZE]; 27 ud_gets(cha,SIZE); 28 ud_puts(cha,0); 29 getch(); 30 return 0; 31 }
运行结果: