C语言中从键盘输入字符串时的一些问题
1.scanf()
scanf()在输入字符串时有很大的弊端, 例如:
1). scanf()在从键盘读入字符时并不会根据所定义的字符数组的大小来控制读入多少个 , 而是从scanf( ) 中传入的地址开始一直访问下一个元素的内存 , 碰见空格符或者回车符时才停止读入并存入结束符’\0’ , 这就有可能造成了一个在C中非常严重的问题 , 访问非法内存 . 如果所输字符数量大于字符数组的长度 , 当scanf()将把字符存入字符数组的最后一个元素后 , scanf()还会继续往后访问内存 , 将接收的字符继续存入 , 但之后的内存是我们未申请的非法内存 , 会发生未知错误 , 例如以下代码:
2). 第二点也就是第一点中的遇到空格符停止读入并存入结束符’\0’, 也就是说scanf()存不了空格符
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 10
void main() {
char str1[N];
printf("请输入字符串str1\n");
scanf("%s", str1);
printf("str1为:\n%s\n",str1);
system("pause");
}
代码中 , 字符数组最多存十个元素 , 结果却输出了超过十个的字符并发生了错误程序异常终止
2.gets()
gets(char* str)函数 , gets()函数与scanf()类似 , 但读入时只碰见 '\n’才停止读入并自动加上 ‘\0’,也存在和scanf()一样的访问非法内存的可能 .
最新版本的C标准(2011年)最终将该功能从其规范中删除。该函数在C ++中已弃用(截至2011年标准,遵循C99 + TC3)。所以不建议使用gets()函数
3.fgets()
fgets(char* str, int n,stdin )函数在输入字符串时是从标准输入流中读取一个长度为(n - 1)的字符串 , 并存放到字符数组str中
其中n是要求得到的字符个数 , 但实际上只会读入(n - 1)个,在最后加一个’\0’ . 如果在读取完(n - 1)个字符之前碰见换行符’\n’ ,读入即结束, 但’\n’ 也会作为有效字符存入字符串中,然后在’\n’后再存入’\0’
1). fgets( )函数的优点是不会再像scanf()函数一样发生访问非法内存的问题 , 也可以存如scanf()存不了的空格符
2). 说起缺点 , fgets在取完(n - 1)个字符之前碰见换行符’\n’ ,’\n’ 也会作为有效字符存入字符串中,然后在’\n’后再存入’\0’
在很多时候我们都不希望有这个换行符的出现 , 只能再写代码消去这个’\n’
3). fgets()函数在连续输入多个字符串时(也不能算是fgets()函数的缺点) , 但有时也会给我们带来麻烦) , 例如:
char str1[10];
char str2[10];
在输入str1时输入了超过9个的字符如 : abcdefghigklm\n
这时我们会发现 , str2根本不需要我们重新在键盘输入 , 而是存入了 abcdefghijklm’\n’ 中的 jklm\n
此时
字符串str1是"abcdefghi"
字符串str2是"jklm"
这是因为输入str1的函数在读取标准输入流中的字符时读到 i 时读取完成 , 此时剩下的jklm\n还在缓存区静静地等待被读取
此时fgets()在输入str2时直接读取了缓冲区的 jklm\n由于遇到了’\n’(或者读入了(n - 1)个字符)再存入’\0’,此时str2的输入已经完成 , 并不需要我们再次输入 .
让我们举个例子来看一下这几个特点 :
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 10
#include<string.h>
void main() {
char str[N];
char str1[N];
char str2[N];
printf("请输入字符串str\n");
fgets(str, N, stdin);
printf("请输入字符串str1\n");
fgets(str1, N, stdin);
printf("请输入字符串str2\n");
fgets(str2, N, stdin);
printf("str为:\n%s\n", str);
printf("str1为:\n%s\n", str1);
printf("str2为:\n%s\n", str2);
system("pause");
}
运行时并未像scanf()那样出现越界问题导致程序异常终止的情况 , 但也输出了我们不想看到的 ‘\n’ , str2也并不是从我们再次输入的 , 而是从缓存区直接读取的
4. 我们也可以利用输入单个字符的函数与循环来输入一个字符串
举个例子 , 如以下代码 :
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 10
void main() {
char str1[N];
printf("请输入字符串str1\n");
for (int i = 0, ch;; ++i) {
ch = getchar();
if (i == N - 1 || ch == '\n') {
str1[i] = '\0';
break;
}
str1[i] = ch;
}
printf("str1为:\n%s\n", str1);
system("pause");
}
可以看出输入也可以不越界
而且也不会像fgets()函数一样输入的字符串小于数组长度(n - 1)时有换行符 ‘\n’ 的问题
当然 , 循环形式和输入字符的函数还有其他, 例如, 用scanf("%c",ch) 也可以 , 这里就不一 一 举例 .总之 , 对库函数不满意的话 , 自己也可以写一个符合自己要求的函数或代码来实现 .
例如 :
#include<stdio.h>
#include<stdlib.h>
#define N 10
void fun(char* str) {
for (int i = 0, ch = '\0'; ch != '\n'; ++i) {
ch = getchar();
if (i >= N) {//当超出字符串长度时, 继续接受键盘输入的字符,直到输入\n为止
//这是为了防止字符串输入完成后输入的字符会被下一个需要输入的数据接收
continue;
}
if (ch == '\n' || i == N - 1) {
str[i] = '\0';
continue;
}
str[i] = ch;
}
}
void main() {
char str[N];
char str1[N];
char str2[N];
printf("请输入字符串str\n");
fun(str);
printf("请输入字符串str1\n");
fun(str1);
printf("请输入字符串str2\n");
fun(str2);
printf("str为:\n%s\n", str);
printf("str1为:\n%s\n", str1);
printf("str2为:\n%s\n", str2);
system("pause");
}
如图, 即可以输入空格, 也不会越界 , 也没有从缓存区读取字符存入字符数组的问题