11.2字符串输入
目录
二、字符串输入
2.1 分配空间
-
如果想把一个字符串读入程序,首先必须预留储存该字符串的空间,然后用输入函数获取该字符串。
-
分配空间有两种方法:第一种是在声明时显示指明数组的大小,
如,char name[81]; ,现在 name 是一个已分配块(81字节)的地址。
-
第二种方法是使用 C 库函数来分配内存。
2.2 不幸的 gets() 函数
-
gets() 函数读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个 C 字符串。
-
gets() 函数经常和 puts() 函数配对使用,用于显示字符串,并在末尾添加换行符。
// 使用 gets() 和 puts() 输出: #include <stdio.h> //Enter a string, please. #define STLEN 81 //I want to learn about string theory!(输入) int main(void) //Your string twice: { //I want to learn about string theory! char words[STLEN]; //I want to learn about string theory! puts("Enter a string, please."); //Done gets(words); // 典型用法 printf("Your string twice:\n"); printf("%s\n", words); puts(words); puts("Done."); return 0; } //整行输入(除了换行符)都被储存在 words 中,puts(words)和 printf("%s\n, words")的效果相同。
-
gets() 函数只知道数组的开始处,并不知道数组中有多少个元素。
-
如果输入的字符串过长,会导致缓冲区溢出,即多余的字符超出了指定的目标空间。
-
如果这些多余的字符只是占用了尚未使用 的内存,就不会立即出现问题;如果它们擦写掉程序中的其他数据,会导致程序异常中止;或者还有其他情况。
2.3 gets() 的替代品
2.3.1 fgets() 函数(和 fputs())
- fgets() 函数通过第 2 个参数限制读入的字符数来解决溢出的问题。
- fgets() 和 gets() 的区别:
- fgets() 函数的第2个参数指明了读入字符的最大数量。如果该参数的值是 n,那么 fgets() 将读入 n-1 个字符,或者读到遇到的第一个换行符为止。
- 如果 fgets() 读到一个换行符,会把它储存在字符串中,gets() 会丢弃换行符。
- fgets() 函数的第 3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以 stdin(标准输入)作为参数,该标识符定义在stdio.h 中。
- 因为 fgets() 函数把换行符放在字符串的末尾(假设输入行不溢出),通常要与 fputs() 函数(和 puts() 类似)配对使用,除非该函数不在字符串末尾添加换行符。
//使用 fgets() 和 fputs() 输出:Enter a string, please.
#include <stdio.h> // apple pie(输入)
#define SELEN 14 // Your string twice (puts(), then fputs()):
int main(void) // apple pie
{ //
char words[SELEN]; // apple pie
puts("Enter a string, please."); // Enter another string, please.
fgets(words, SELEN, stdin); // strawberry shortcake(输入)
printf("Your string twice (puts(), then fputs()):\n");
puts(words); // Your string twice (puts(), then fputs()):
fputs(words, stdout); // strawberry sh
// strawberry shDone.
puts("Enter another string, please.");
fgets(words, SELEN, stdin);
printf("Your string twice (puts(), then fputs()):\n");
puts(words);
fputs(words, stdout);
puts("Done.");
return 0;
}
//第 1 次输入 apple pie,比 fgets() 读入的整行输入短,因此 apple pie\n\0 被储存在数组中。当 puts() 显示该字符串时又在末尾添加了换行符,apple pie 后面有一行空行。因为fputs()不在字符串末尾添加换行符,所以并未打印出空行。
//第 2 行输入 strawberry shortcake,超过了指定的大小,所以 fgets() 只读入了 13 个字符,并把 strawberry sh\0 储存在数组中。
- fputs() 函数的第 2 个参数指明它要写入的文件。如果要显示在计算机显示器上,应使用 stdout(标准输出)作为该参数。
- fputs() 函数返回指向 char 的指针。
- 如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同。
- 如果函数读到文件结尾,它将返回一个特殊的指针:空指针,该指针保证不会指向有效的数据。
- 在代码中,可以用数字 0 来代替,不过在 C 语言中用宏 NULL 来代替更常见(如果在读入数据时出现某些错误,该函数也返回 NULL)。
// fgets3.c -- 使用 fgets()
#include <stdio.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;
}
2.3.2 空字符和空指针
- 空字符(或'\0')是用于标记 C 字符串末尾的字符,其对应字符编码是 0。由于其他字符的编码不可能是 0,所以不可能是字符串的一部分。
- 空指针(或 NULL)有一个值,该值不会与任何数据的有效地址对应。通常,函数使用它返回一个有效地址表示某些特殊情况发生,例如遇到文件结尾或未能按预期执行。
- 空字符是整数类型,而空指针是指针类型。
- 空字符和空指针都可以用数值 0 来表示,但是两者是不同类型的 0。
- 空字符是一个字符,占 1 字节;而空指针是一个地址,通常占 4 字节。
2.3.3 gets_s() 函数
-
gets_s() 只从标准输入中读取数据,所以不需要第 3 个参数。
-
gets_s() 会丢弃读到的换行符。
-
如果gets_s()读到最大字符数都没有读到换行符,会执行以下几步:
- 首先把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针。
- 接着,调用依赖实现的“处理函数”(或选择其他函数),可能会中止或退出程序。
-
如果输入行太长,使用 gets() 会擦写现有数据,存在安全隐患。
-
gets_s() 函数很安全,但是,如果不希望程序中止或退出,就要知道如何编写特殊的“处理函数”。
-
如果打算让程序继续运行,gets_s() 会丢弃该输入行的其余字符,无论你是否需要。
-
当输入与预期不符时,gets_s() 完全没有 fgets() 函数方便、灵活。鉴于此,fgets() 通常是处理类似情况的最佳选择。
2.3.4 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) // 即,ret_val != NULL { while (st[i] != '\n' && st[i] != '\0') i++; if (st[i] == '\n') st[i] = '\0'; else while (getchar() != '\n') continue; } return ret_val; } //们设计的 s_gets()函数并不完美,它最严重的缺陷是遇到不合适的输入时毫无反应。丢弃多余的字符时,既不通知程序也不告知用户。 //丢弃过长输入行中的余下字符原因:输入行中多出来的字符会被留在缓冲区中,成为下一次读取语句的输入。
2.4 scanf() 函数
- scanf()和gets()或fgets()的区别在于它们如何确定字符串的末尾:
- scanf()更像是“获取单词”函数,而不是“获取字符串”函数;
- 如果预留的存储区装得下输入行,gets()和fgets()会读取第1个换行符之前所有的字符。
- scanf()函数有两种方法确定输入结束。无论哪种方法,都从第1个非空白字符作为字符串的开始。如果使用%s转换说明,以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束(字符串不包括空白字符)
- 根据输入数据的性质,用 fgets() 读取从键盘输入的数据更合适。因为,scanf()无法完整读取书名或歌曲名,除非这些名称是一个单词。
作业
11.13.1.& 11.13.2
#include <stdio.h>
#define LEN 10
char * getnchar(char * str, int n);
int main(void)
{
char input[LEN];//存储字符串的数组
char *check;
printf("Enter string:\n");
check = getnchar(input, LEN - 1);
if (check == NULL)
puts("Input failed.");
else
puts(input);
puts("Done.\n");
return 0;
}
char * getnchar(char * str, int n)
{
int i;
char ch;
for (i = 0; i < n; i++)//读取9个字符,超过跳出。
{
ch = getchar();
if (ch != EOF && ch != ' ' && ch != '\n' && ch != '\t')
str[i] = ch;
else //读取到文件结尾,空白,换行符,制表符结束
break;
}
if (ch == EOF)
return NULL;
else
{
str[i] = '\0';
return str; //返回指针名
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话