从文本文件读取后,结果显示不正确
After reading from text file, results are displayed incorrectly
我编写了一个程序,它从文本文件的每一行读取四个变量(三个字符串和一个字符)。但是当我显示变量时,每行末尾都会弹出一个意想不到的字符。 (我确保变量的长度足够大)。
这是为什么? (再次溢出缓冲区?)我该如何解决这个问题?
文本文件内容:
M0001 Cool Name F 123-456789
M0002 Name Cool M 987-654321
代码:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *text;
char id[6], name[101], gender, contact[13];
text = fopen("test.txt", "r");
while (fscanf(text, "%s %[^\n]s %c %s\n", id, name, &gender, contact) != EOF)
printf("%s %s %c %s\n", id, name, gender, contact);
fclose(text);
return 0;
}
我期望的输出:
M0001 Cool Name F 123-456789
M0002 Name Cool M 987-654321
我得到的是:
M0001 Cool Name F 123-456789 1⁄4
M0002 Name Cool M 987-654321 1⁄4
%[^\n]s
吃光了从那一点开始的所有东西并把它放在 name
中。所以只有 id
和 name
被填充。 gender
和 contact
具有来自程序堆栈的 'random' 内容(因为它们未初始化)。
不小心你的堆栈有 1/4
in gender
+ contact
.
在我的机器上,程序崩溃了。
在对 fscanf()
的调用中,格式字符串:“%s %[^\n]s %c %s\n”不正确。
- '[^\n]' 将读取到行尾(这将溢出输入缓冲区:'name'。然后下一个字符不是 's',因为下一个字符是换行符。
- 应该将返回值与 4 进行比较,而不是 EOF
- input/format 说明符 '%[...]' 和 '%s' 没有溢出输入缓冲区的问题,所以应该总是有一个比输入缓冲区的长度(那些格式说明符总是将 NUL 字节附加到输入
建议代码如下:
- 干净地编译
- 记录包含每个头文件的原因
- 执行所需的功能
- 将 'name' 拆分为 'firstname' 和 'lastname' 以便于处理并匹配输入数据的格式
- 正确检查
fscanf()
的返回值
- 正确检查来自
fopen()
的任何错误,如果返回错误,则正确输出错误消息和说明系统认为函数失败的原因的文本 stderr
- 对
fscanf()
和 printf()
的调用使用适当的格式字符串
- 通过
enum
语句 将 'magic' 数字替换为有意义的名称
现在建议的代码:
#include <stdio.h> // fopen(), fclose(), fscanf(), perror(), printf()
#include <stdlib.h> // exit(), EXIT_FAILURE
enum{
MAX_ID_LEN = 6,
MAX_NAME_LEN = 20,
MAX_CONTACT_LEN = 13
};
int main( void )
{
char id[ MAX_ID_LEN ];
char firstname[ MAX_NAME_LEN ];
char lastname[ MAX_NAME_LEN ];
char gender;
char contact[ MAX_CONTACT_LEN ];
FILE *text = fopen("test.txt", "r");
if( !text )
{
perror( "fopen to read 'test.txt' failed" );
exit( EXIT_FAILURE );
}
// implied else, fopen successful
while (5 == fscanf(text, "%5s %19s %19s %c %12s",
id, firstname, lastname, &gender, contact) )
{
printf("%s %s %s %c %s\n",
id, firstname, lastname, gender, contact);
}
fclose(text);
return 0;
}
由于你名字中 space 分隔的单词的数量显然是可变的,你只能使用 %[^\n]s
来获取 "as much as possible" – 但这也会吃掉所有的以下 相关数据。一个快速的解决方案是重新设计输入格式并将名称放在最后;那么,您的 fscanf
参数将是:
"%s %c %s %s\n", id, &gender, contact, name
或者,重写代码以使用更少 fscanf
和更多 'manual' 解析:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main (void)
{
FILE *text;
char id[6], name[101], gender, contact[13];
char *lookback;
int result;
unsigned int line_number = 0;
text = fopen ("test.txt", "r");
if (text == NULL)
{
printf ("file not found!\n");
return EXIT_FAILURE;
}
do
{
result = fscanf(text, "%s %[^\n]s\n", id, name);
line_number++;
if (result == EOF)
break;
if (result != 2)
{
printf ("error in data file on line %u (expected at least 2 items)\n", line_number);
break;
}
/* at this point, 'name' also contains 'gender' and 'contact' */
lookback = strrchr (name, ' ');
if (lookback == NULL || strlen(lookback+1) > 12)
{
printf ("error in data file on line %u (expected 'contact')\n", line_number);
break;
}
/* lookback+1 because lookback itself points to the space */
strcpy (contact, lookback+1);
/* cut off at lookback */
*lookback = 0;
lookback = strrchr (name, ' ');
if (lookback == NULL || strlen(lookback+1) != 1)
{
printf ("error in data file on line %u (expected 'gender')\n", line_number);
break;
}
/* lookback now points to the space before the gender */
gender = toupper(lookback[1]);
if (gender != 'F' && gender != 'M')
{
printf ("error in data file on line %u (expected 'M' or 'F')\n", line_number);
break;
}
/* cut off again at lookback; now name is complete */
*lookback = 0;
printf ("%s %s %c %s\n", id, name, gender, contact);
} while (1);
fclose(text);
return EXIT_SUCCESS;
}
这种方法确实有一些相关的缺点。 scanf
的好处之一是它标准化了 whitespace;多个 space 和选项卡(甚至 returns)将在 扫描之前默默地转换为单个 space 。另一方面,此代码 显式 检查单个 space 字符。如果您的数据文件中的白色 space 存在差异,您也必须考虑到这一点。
处理完最后两项 'manually' 后,您可以选择完全不使用 fscanf
。您可以使用 fgets
(它还内置了行长度检查)一次阅读整行文本,并使用 strchr
和 strrchr
查找 space。为了解决可能的白色space 问题,搜索制表符和双space 行并将它们更改为单个space.
我编写了一个程序,它从文本文件的每一行读取四个变量(三个字符串和一个字符)。但是当我显示变量时,每行末尾都会弹出一个意想不到的字符。 (我确保变量的长度足够大)。
这是为什么? (再次溢出缓冲区?)我该如何解决这个问题?
文本文件内容:
M0001 Cool Name F 123-456789 M0002 Name Cool M 987-654321
代码:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *text;
char id[6], name[101], gender, contact[13];
text = fopen("test.txt", "r");
while (fscanf(text, "%s %[^\n]s %c %s\n", id, name, &gender, contact) != EOF)
printf("%s %s %c %s\n", id, name, gender, contact);
fclose(text);
return 0;
}
我期望的输出:
M0001 Cool Name F 123-456789 M0002 Name Cool M 987-654321
我得到的是:
M0001 Cool Name F 123-456789 1⁄4 M0002 Name Cool M 987-654321 1⁄4
%[^\n]s
吃光了从那一点开始的所有东西并把它放在 name
中。所以只有 id
和 name
被填充。 gender
和 contact
具有来自程序堆栈的 'random' 内容(因为它们未初始化)。
不小心你的堆栈有 1/4
in gender
+ contact
.
在我的机器上,程序崩溃了。
在对 fscanf()
的调用中,格式字符串:“%s %[^\n]s %c %s\n”不正确。
- '[^\n]' 将读取到行尾(这将溢出输入缓冲区:'name'。然后下一个字符不是 's',因为下一个字符是换行符。
- 应该将返回值与 4 进行比较,而不是 EOF
- input/format 说明符 '%[...]' 和 '%s' 没有溢出输入缓冲区的问题,所以应该总是有一个比输入缓冲区的长度(那些格式说明符总是将 NUL 字节附加到输入
建议代码如下:
- 干净地编译
- 记录包含每个头文件的原因
- 执行所需的功能
- 将 'name' 拆分为 'firstname' 和 'lastname' 以便于处理并匹配输入数据的格式
- 正确检查
fscanf()
的返回值
- 正确检查来自
fopen()
的任何错误,如果返回错误,则正确输出错误消息和说明系统认为函数失败的原因的文本stderr
- 对
fscanf()
和printf()
的调用使用适当的格式字符串
- 通过
enum
语句 将 'magic' 数字替换为有意义的名称
现在建议的代码:
#include <stdio.h> // fopen(), fclose(), fscanf(), perror(), printf()
#include <stdlib.h> // exit(), EXIT_FAILURE
enum{
MAX_ID_LEN = 6,
MAX_NAME_LEN = 20,
MAX_CONTACT_LEN = 13
};
int main( void )
{
char id[ MAX_ID_LEN ];
char firstname[ MAX_NAME_LEN ];
char lastname[ MAX_NAME_LEN ];
char gender;
char contact[ MAX_CONTACT_LEN ];
FILE *text = fopen("test.txt", "r");
if( !text )
{
perror( "fopen to read 'test.txt' failed" );
exit( EXIT_FAILURE );
}
// implied else, fopen successful
while (5 == fscanf(text, "%5s %19s %19s %c %12s",
id, firstname, lastname, &gender, contact) )
{
printf("%s %s %s %c %s\n",
id, firstname, lastname, gender, contact);
}
fclose(text);
return 0;
}
由于你名字中 space 分隔的单词的数量显然是可变的,你只能使用 %[^\n]s
来获取 "as much as possible" – 但这也会吃掉所有的以下 相关数据。一个快速的解决方案是重新设计输入格式并将名称放在最后;那么,您的 fscanf
参数将是:
"%s %c %s %s\n", id, &gender, contact, name
或者,重写代码以使用更少 fscanf
和更多 'manual' 解析:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main (void)
{
FILE *text;
char id[6], name[101], gender, contact[13];
char *lookback;
int result;
unsigned int line_number = 0;
text = fopen ("test.txt", "r");
if (text == NULL)
{
printf ("file not found!\n");
return EXIT_FAILURE;
}
do
{
result = fscanf(text, "%s %[^\n]s\n", id, name);
line_number++;
if (result == EOF)
break;
if (result != 2)
{
printf ("error in data file on line %u (expected at least 2 items)\n", line_number);
break;
}
/* at this point, 'name' also contains 'gender' and 'contact' */
lookback = strrchr (name, ' ');
if (lookback == NULL || strlen(lookback+1) > 12)
{
printf ("error in data file on line %u (expected 'contact')\n", line_number);
break;
}
/* lookback+1 because lookback itself points to the space */
strcpy (contact, lookback+1);
/* cut off at lookback */
*lookback = 0;
lookback = strrchr (name, ' ');
if (lookback == NULL || strlen(lookback+1) != 1)
{
printf ("error in data file on line %u (expected 'gender')\n", line_number);
break;
}
/* lookback now points to the space before the gender */
gender = toupper(lookback[1]);
if (gender != 'F' && gender != 'M')
{
printf ("error in data file on line %u (expected 'M' or 'F')\n", line_number);
break;
}
/* cut off again at lookback; now name is complete */
*lookback = 0;
printf ("%s %s %c %s\n", id, name, gender, contact);
} while (1);
fclose(text);
return EXIT_SUCCESS;
}
这种方法确实有一些相关的缺点。 scanf
的好处之一是它标准化了 whitespace;多个 space 和选项卡(甚至 returns)将在 扫描之前默默地转换为单个 space 。另一方面,此代码 显式 检查单个 space 字符。如果您的数据文件中的白色 space 存在差异,您也必须考虑到这一点。
处理完最后两项 'manually' 后,您可以选择完全不使用 fscanf
。您可以使用 fgets
(它还内置了行长度检查)一次阅读整行文本,并使用 strchr
和 strrchr
查找 space。为了解决可能的白色space 问题,搜索制表符和双space 行并将它们更改为单个space.