11.1 表示字符串和字符串I/O
字符串是以空字符(\0)结尾的char类型数据。
strings1.c 演示在程序中表示字符串的几种方式
#include <stdio.h>
#define MSG "I am a symbolic string constant."
#define MAXLENGTH 81
int main(void)
{
char words[MAXLENGTH] = "I am a string in an array.";
const char* pt1 = "Something is pointing at me.";
puts("Here are some strings:");
puts(MSG);
puts(words); /* puts函数只显示字符串,而且在显示的字符串末尾加上换行符 */
puts(pt1);
words[8] = 'p';
puts(words);
printf("%s, %p, %c\n", "We", "are", *"space farers");
return 0;
}
在程序中定义字符串
(1)字符串字面量(字符串常量)
用双引号括起来的内容称为字符串字面量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。
字符串常量属于静态存储类别(static storage class),这说明在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。
用双引号括起来的内容被释为指向该字符串储存位置的指针。
(2)字符串数组和初始化
const char m1[40] = "Limit yourself to one line's worth.";
const char m2[] = "Limit yourself to one line's worth."; /* 建议在指针初始化为字符串字面量时使用const限定符 */
让编译器计算数组的大小只能在初始化数组时。
const char * pt1= “Limit yourself to one line’s worth.”;
注意:数组和指针的区别
数组名m2是常量,指针名pt1是变量。如果打算修改字符串,就不要用指针指向字符串字面量。
初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。
#include <stdio.h>
#define MSG "I'm special"
int main(void)
{
char ar[] = MSG;
const char* pt = MSG;
printf("address of \"I'm special\":%p \n", "I'm special");
printf(" address ar:%p\n", ar);
printf(" address pt:%p\n", pt);
printf(" address of MSG:%p\n", MSG);
printf("address of \"I'm special\":%p \n", "I'm special");
return 0;
}
(4)字符串数组
arrchar.c–指向字符串的指针数据和char类型数组的数组
#include <stdio.h>
#define SLEN 40
#define LIM 5
int main(void)
{
const char* mytalents[LIM] = {
"Adding number swiftly",
"Multiplying accurately", "Stashing data",
"Following instrcutions to the letter",
"Understanding the C language"
};
char yourtalents[LIM][SLEN] = {
"Walking in a straight line",
"Sleeping", "Watching televisions",
"Mailing letters", "Reading email"
};
int i;
puts("Let's compare talents.");
printf("%-36s %-25s\n", "My talents", "Your talents");
for (i = 0; i < LIM; i++) {
printf("%-36s %-25s\n", mytalents[i], yourtalents[i]);
}
printf("\nsizeof mytalents: %zd, sizeof yourtalents: %zd\n",
sizeof(mytalents), sizeof(yourtalents));
return 0;
}
综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。但是,指针数组也有自身的缺点。mytalents 中的指针指向的字符串字面量不能更改;而yourtalentsde 中的内容可以更改。所以,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。
11.2 字符串输入
如果想把一个字符串读入程序,首先必须预留储存该字符串的空间,然后用输入函数获取该字符串。
gets函数
C11标准委员会采取了更强硬的态度,直接从标准中废除了gets()函数。
问题出在 gets()唯一的参数是 words,它无法检查数组是否装得下输入行。上一章介绍过,数组名会被转换成该数组首元素的地址,因此,gets()函数只知道数组的开始处,并不知道数组中有多少个元素。
“Segmentation fault”(分段错误)似乎不是个好提示,的确如此。在UNIX系统中,这条消息说明该程序试图访问未分配的内存。
#include <stdio.h>
#include <string.h>
#define STLEN 81
int main(void)
{
char words[STLEN];
puts("Enter a string, please.");
gets(words);
printf("Your string twice:\n");
printf("%s\n", words);
puts(words);
puts("Done.");
return 0;
}
fgets()和fputs()函数用法
fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets()将读入n-1个字符,或者读到遇到的第一个换行符为止。
如果fgets()读到一个换行符,会把它储存在字符串中。这点与gets()不同,gets()会丢弃换行符。
fgets()函数的第3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中
#include <stdio.h>
#include <string.h>
#define STLEN 14
int main(void)
{
char words[STLEN];
puts("Enter a string, please.");
fgets(words, STLEN, stdin); /* apple ie\n\0 被储存在数组中 */
printf("Your string twice(puts(), then fputs()):\n");
puts(words);
fputs(words, stdout); /* fputs()不在字符串末尾添加换行符 */
puts("Enter another string, please.");
fgets(words, STLEN, stdin); /* fgets()只读入了13个字符,并把strawberry sh\0 储存在数组中。 */
printf("Your string twice(puts(), then fputs()):\n");
puts(words); /* puts()函数会在待输出字符串末尾添加一个换行符 */
fputs(words, stdout);
puts("Done.");
return 0;
}
fgets()函数返回指向 char的指针。如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同。但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。
下面程序演示了一个简单的循环,读入并显示用户输入的内容,直到fgets()读到文件结尾或空行(即,首字符是换行符)。
#include <stdio.h>
#include <string.h>
#define STLEN 10
int main(void)
{
char words[STLEN];
puts("Enter strings (empty line to quit):");
while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') {
fputs(words, stdout);
}
puts("Done.");
return 0;
}
第一次读 By the wa\0
第二次读 y, the ge\0
最后一次读 tion\n\0
fgets()储存换行符有好处也有坏处。坏处是你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。好处是对于储存的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。如果不是一整行,要妥善处理一行中剩下的字符。
首先,如何处理掉换行符?一个方法是在已储存的字符串中查找换行符,并将其替换成空字符:
while(words[i] != '\n') {
// 假设\n在words中
i++;
}
words[i] = '\0';
其次,如果仍有字符串留在输入行怎么办?一个可行的办法是,如果目标数组装不下一整行输入,就丢弃那些多出的字符:
while(getchar() != '\n') {
// 读取但不储存输入,包括\n
continue;
}
程序清单11.9在程序清单11.8的基础上添加了一部分测试代码。该程序读取输入行,删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。
#include <stdio.h>
#include <string.h>
#define STLEN 10
int main(void)
{
char words[STLEN];
int i;
puts("Enter strings (empty line to quit):");
while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') {
i = 0;
while (words[i] != '\n' && words[i] != '\0') {
i++;
}
if (words[i] == '\n') {
words[i] = '\0';
}
else {
while (getchar() != '\n') {
continue;
}
}
puts(words);
}
puts("Done.");
return 0;
}
s_gets()函数
如果 fgets()返回 NULL,说明读到文件结尾或出现读取错误,s_gets()函数跳过了这个过程。
如果字符串中出现换行符,就用空字符替换它;
如果字符串中出现空字符,就丢弃该输入行的其余字符,然后返回与fgets()相同的值。
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val != NULL) {
while (st[i] != '\n' && st[i] != '\0') {
i++;
}
if (st[i] == '\n') {
st[i] = '\0';
}