的scanf
:
scanf("%s", file_name);
不会消耗你按输入文件名之后的换行符。它保持在标准输入流中(stdin
)。该scanf
:
scanf ("%[^\n]%*s", insert);
扫描的一切,直到一个换行符,然后扫描并丢弃的字符串。 %[^\n]
将失败,如果下一个字符是\n
。所以,scanf
,在你的情况下失败并返回下一个字符是\n
。 insert
保持未初始化并包含垃圾。
fprintf(f, "%s", insert);
将垃圾写入f
。
如何解决问题?两种方法固定问题:
- 使用
getchar();
刚过第一scanf
摆脱第一scanf
遗留下来的换行符。
使用
scanf (" %[^\n]%*s", insert);
,而不是
scanf ("%[^\n]%*s", insert);
之前%[^\n]
扫描的空间,并丢弃所有whitespace characters(如\n
,空间等),如果有的话,直到第一个非空白字符所看到在下面的C11标准的报价中:
7.21.6.2 fscanf函数
[...]
- 的空白字符(一个或多个)构成的指令是通过读取输入到第一执行非空白字符(保持未读),或直到不能再读取字符。该指令永远不会失败
我倾向于第二种方案,因为它需要的照顾任何的空白字符数。
这两种解决方案都能正常工作,但是您还有其他问题。在您提供第二个
scanf
的数据后,您的程序不会停止扫描!解决这个问题很简单。请从第二个
scanf
中删除
%*s
。
想知道为什么?引述C11标准,
7.21.6.2 fscanf函数
[...]
- 转换标识符和它们的含义如下:
[...]
s匹配一系列非空白字符。
[...]
所以,%s
将跳过前导空白字符和停止扫描,当它遇到一个空白字符或,如果长度字段存在,直到指定的长度或,直到出现空格字符,以先发生者为准。粗体部分解释它。
为了提高安全性,请限制
scanf
为
insert
和
file_name
读取的字符数,以便您可以避开
buffer overflows。另外,请检查
scanf
和
fopen
的返回值,看看它们是否成功。在您的情况下,
scanf
都会在成功时返回1。如果不是1,则发生错误。
fopen
,失败时返回
NULL
。如果不是
NULL
,则
fopen
成功。
最终方案:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char file_name[100];
char insert[100];
printf("Enter the file name:");
if(scanf("%94s", file_name) != 1) //Scans a maximum of 94 chars,+1 for \0 at the end,+4 for ".txt"
{
printf("scanf failed\n");
exit(-1); //Exit the program with a negative return value
}
strcat(file_name, ".txt"); //Improves readability,do one thing in one line
FILE *f = fopen(filename, "a");
if(f == NULL) //if fopen failed to open the filename,
{
printf("fopen failed\n");
exit(-1);
}
printf("Insert:");
if(scanf (" %99[^\n]", insert) != 1) // Note the space before %[^\n],99 scans a maximum of 99 chars,+1 for the \0 at the end
{
printf("scanf failed\n");
fclose(f); //Free resources before exiting
exit(-1);
}
fprintf(f, "%s", insert);
fclose(f); //Close the file after use
}
如果你要放弃第二
scanf
遗留的换行符,使用
scanf (" %99[^\n]%*c", insert)
,而不是
scanf (" %99[^\n]", insert)
虽然这个程序并没有什么不同。
如果你想知道为什么我从陈述章引号“
7.21.6.2 fscanf函数”那是因为,
fscanf
是完全一样
scanf
时,它的第一个参数是
stdin
。从C11标准再次引用,
7.21.6.4的scanf函数
[...]
- 的
scanf
功能相当于fscanf
与参数之前插入到scanf
参数stdin
。
为什么提示输入文件名?你能想象如果'grep'这样工作吗?使用命令行参数:它极大地简化了你的程序! –
您还错过了最后一个fclose()。这可能是好的,因为它可能在程序终止时自动关闭,但在正常使用中,您可能会写入少量的输出,直到您刷新或关闭,因为它将被缓冲。 – Rup
编译所有警告和调试信息(例如'gcc -Wall -Wextra -g')。学习如何**使用调试器**('gdb')。阅读您正在使用的每个功能的文档,例如[scanf的](http://en.cppreference.com/w/c/io/fscanf)。总是使用'scanf'的返回项目数并检查失败('scanf','fopen',...) –