首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

13.3  字符串的读/写

使用printf函数或puts函数来编写字符串是很容易的。读入字符串却有点麻烦,主要是因为输入的字符串可能比用来存储的字符串变量更长。为了一次性读入字符串,可以使用scanf函数或gets函数,也可以每次一个字符的方式读入字符串。

13.3.1  用printf函数和puts函数写字符串

转换说明%s允许printf函数写字符串。参考下面的例子:

char str[] = "Are we having fun yet?"

printf("Value of str: %s\n", str);

输出会是

Value of str: Are we having fun yet?

printf函数会逐个写字符串中的字符直到遇到空字符才停止。(如果空字符丢失,printf函数会越过字符串的末尾继续写,直到最终在内存的某个地方找到空字符为止。)

如果只显示字符串的一部分,可以使用转换说明%.ps。这里的p是要显示的字符数量。语句

printf("%.6s\n", str);

会显示出

Are we

就像数一样,字符串可以在指定域内显示。转换说明%ms会在大小为m的域内显示字符串。(对于超过m个字符的字符串,printf函数会显示出整个字符串,而不会截断。)如果字符串少于m个字符,则会在域内右对齐输出。为了强制左对齐,可以在m前加一个减号。m值和p值可以组合使用:转换说明%m.ps会使字符串的前p个字符在大小为m的域内显示。

printf函数不是唯一一个字符串输出函数。C函数库还提供puts函数。此函数可以按如下方式使用:

puts(str);

puts函数只有一个参数,此参数就是需要显示的字符串,参数中没有格式串。在写完字符串后,puts函数总会添加一个额外的换行符,因此显示会移至下一输出行的开始处。

13.3.2  用scanf函数 和gets函数读字符串

转换说明%s允许scanf函数读入字符串:

scanf("%s", str);

在scanf函数调用中,不需要在str前添加运算符&。因为str是数组名,编译器会自动把它当作指针来处理。

调用时,scanf函数会跳过空白字符,然后读入字符,并且把读入的字符存储到str中,直到遇到空白字符为止。scanf函数始终会在字符串末尾存储一个空字符。

用scanf函数读入字符串永远不会包含空白字符。因此,scanf函数通常不会读入一整行输入。换行符会使scanf函数停止读入,空格符或制表符也会产生同样的结果。为了每次读入一整行输入,可以使用gets函数。类似于scanf函数,gets函数把读入的字符放到数组中,然后存储一个空字符。然而,在其他方面gets函数有些不同于scanf函数:

l gets函数不会在开始读字符串之前跳过空白字符(scanf函数会跳过)。

l gets函数会持续读入直到找到换行符才停止(scanf函数会在任意空白字符处停止)。此外,gets函数会忽略掉换行符,而不会把它存储到数组中,用空字符代替换行符。

为了领会scanf函数与puts函数之间的差异,请考虑下面的程序段:

char sentence[SENT_LEN+1];

printf("Enter a sentence: \n");

scanf("%s", sentence);

假定在提示信息后

Enter a sentence:

用户输入信息

To C, or not to C: that is the question.

scanf函数会把字符串"To"存储到sentence中。下一次scanf函数调用将从单词To后面的空格处继续读入这行。

现在假设用gets函数替换掉scanf函数:

gets(sentence);

当用户输入和先前相同的信息时,gets函数会把字符串

"To C, or not to C: that is the question."

存储到sentence中。

在把字符读入数组时,scanf函数和gets函数都无法检测何时填满数组。因此,它们可能越过数组的边界存储字符,这会导致程序行为异常。通过用转换说明%ns代替%s可以使scanf函数更安全。这里的数字n指出可以存储的最大字符的数量。可惜的是,gets函数天生就是不安全的。fgets函数(22.5.2节)则是一种更安全的选择。

关于gets函数和puts函数的最后一点提示:既然这些函数比scanf函数和printf函数简单,因此通常运行也就更快。

13.3.3  逐个字符读字符串

对许多程序而言,因为scanf函数和gets函数都有风险且不够灵活,C程序员经常会编写自己的输入函数。通过每次一个字符的方式来读入字符串,这类函数可以提供比标准输入函数更大程度的控制。

如果决定设计自己的输入函数,那么就需要考虑下面这些问题:

l 在开始存储字符串之前,函数应该跳过空白字符吗?

l 什么字符会导致函数停止读取:换行符、任意空白字符、还是其他一些字符?需要存储这类字符还是忽略掉?

l 如果输入的字符串太长以致无法存储,那么程序应该做些什么:忽略额外的字符,还是把它们留给下一次的输入操作?

假定需要的函数不会跳过空白字符,在第一个换行符处(不把换行符存储到字符串中)停止读取,并且忽略额外的字符。函数将有如下原型:

int read_line(char str[], int n);

str表示用来存储输入的数组,而且n是最大读入字符的数量。如果输入行包含多于n个的字符,read_line函数将忽略多余的字符。read_line函数会返回实际存储在str中的字符数量(在0到n之间的任意数)。不可能总是需要read_line函数的返回值,但是有这个返回值也没问题。

          read_line函数主要由一个循环构成,只要str留有空间,那么此循环就逐个读入字符并且把它们存储起来。在读入换行符时循环终业。(严格地说,如果getchar函数读入字符失败,也应该终止循环,但是这里暂时忽略这种复杂情况。)下面是read_line函数的完整定义:

int read_line(char str[],  int n)

{

  char ch;

  int i = 0;

  while  ((ch = getchar()) ! = '\n')

    if  (i < n)

     str[i++] = ch;

  str[i] = '\0'             /*  terminates string */

  return i;                  /* number of characters stored */

}

返回之前,read_line函数在字符串的末尾放置了一个空字符。就像scanf函数和gets函数一样,标准函数会自动在输入字符串的末尾放置一个空字符。然而,如果你正在写自己的输入函数,那么必须要考虑这一点。