c - c BAD 使用结构访问 fscanf
问题描述
我正在读取格式如下的文本文件:
名字 姓 年龄 NumberOfSiblings 母亲 父亲
导入头文件:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
结构体定义如下:
typedef struct {
int person_ID; //not included in file
char full_name[20];
char sex[2];
char countryOfOrigin[20];
int num_siblings;
float parentsAges[2]; //this should store mother and fathers age in an array of type float
} PersonalInfo;
void viewAllPersonalInformation(){
FILE* file = fopen("People.txt", "r");
if (file == NULL){
printf("File does not exist");
return;
}
int fileIsRead = 0;
int idCounter = 0;
PersonalInfo People[1000];
//headers
printf("%2s |%20s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");
do{
fileIsRead = fscanf(file, "%s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
People[idCounter].person_ID = idCounter;
printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
idCounter++;
}
while(fileIsRead != EOF);
fclose(file);
printf("Finished reading file");
}
int main() {
viewAllPersonalInformation();
return 0;
}
People.txt 的样子:
约翰奥唐奈 F 爱尔兰 3 32.5 36.1
玛丽·麦克马洪 M 英格兰 0 70 75
彼得汤普森 F 美国 2 51 60
解决方案
fscanf()
遇到空格时将停止读取。%s
在全名的情况下,您希望使用格式说明符读取两个字符串。%s
找到空格后立即停止,因此它只会将名字存储在 full_name 中,姓氏将转到第二个%s
,因此在countryOfOrigin
.
因此,如果您想阅读“Peter Thompson”,那么您需要引入两个字符串(char 数组)来存储名字和姓氏,然后将它们连接起来。
但是,由于您要阅读单词数量不同的全名,我建议您使用fgets()
(它也具有缓冲区溢出保护)。例如“Peter Thompson”有 2 个,“Mary Mc Mahon”有 3 个。所以,如果你坚持使用fscanf()
,你%s
会使用多少?2个还是3个?您不知道,这取决于您在运行时获得的输入。也许有一些正则表达式可以解决问题fscanf()
,但相信使用fgets()
然后解析文件读取的行更适合练习。
现在我们用 读取了一行文件fgets()
,我们该怎么做呢?我们仍然不知道每个全名包含的单词数!如何发现?通过计算该行包含的空格。如果它包含w
空格,则它具有w + 1
标记(在您的示例中可能是单词、数字或字符)。
通过一个简单的 if-else 语句,我们可以在您的示例中区分这两种情况,当有 6 个空格(7 个标记)和 7 个空格(“Mary Mc Mahon M England 0 70 75”的 8 个标记)时。
现在,如何从字符串(行)中提取到标记(全名、年龄等)?我们可以有一个循环并使用一堆 if-else 语句来表示,直到我找到第二个(或第三个,取决于空格的数量)空格,我会将当前标记附加到full_name
. 然后,下一个标记将是性别,依此类推。
当然你可以这样做,但由于我有点懒,我将基于你对 的出色工作fscanf()
,并使用它sscanf()
来提取令牌。当然,使用这种方法,我们需要使用一两个(取决于空格的数量)额外的字符串,以便临时存储姓氏(在我们将其附加到名称之前strcat()
)。
最小的完整工作示例:
#include <stdio.h>
#include <string.h>
#define P 1000 // Max number of people
#define L 256 // Max length of line read from file (-1)
typedef struct {
int person_ID; //not included in file
char full_name[32];
char sex[2];
char countryOfOrigin[16];
int num_siblings;
float parentsAges[2];
} PersonalInfo;
int count_whitespaces(char* str)
{
int whitespaces_count = 0;
while(*str)
{
if(*str == ' ')
whitespaces_count++;
str++;
}
return whitespaces_count;
}
void viewAllPersonalInformation(){
FILE* file = fopen("People.txt", "r");
if (file == NULL){
printf("File does not exist");
return;
}
int fileIsRead = 0;
int idCounter = 0;
PersonalInfo People[P];
// line of file, placeholder for biworded surnames, surname.
char line[L], str[8], surname[16];
//headers
// You have 7 format specifiers for the headers, but only 6 six in fscanf!!!
printf("%2s |%5s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");
// read into 'line', from 'file', up to 255 characters (+1 for the NULL terminator)
while(fgets(line, L, file) != NULL) {
//fileIsRead = fscanf(file, "%s %s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
// eat trailing newline of fgets
line[strcspn(line, "\n")] = 0;
// Skip empty lines of file
if(strlen(line) == 0)
continue;
if(count_whitespaces(line) == 6)
{
sscanf(line, "%32s %16s %c %16s %d %f %f", People[idCounter].full_name, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
}
else // 7 whitespaces, thus 8 token in the string
{
sscanf(line, "%32s %8s %16s %c %16s %d %f %f", People[idCounter].full_name, str, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
// Separate name and first word of surname with a space
strcat(People[idCounter].full_name, " ");
strcat(People[idCounter].full_name, str);
}
// Separate name and surname with a space
strcat(People[idCounter].full_name, " ");
strcat(People[idCounter].full_name, surname);
People[idCounter].person_ID = idCounter;
printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
idCounter++;
if(idCounter == P)
{
printf("Max number of people read, stop reading any more data.\n");
break;
}
};
fclose(file);
printf("Finished reading file.\n");
}
int main() {
viewAllPersonalInformation();
return 0;
}
输出:
ID | Name |Sex | Born In |Number of siblings |Mother's age |Father's Age
0 John O'Donnell F Ireland 3 32.500000 36.099998
1 Mary Mc Mahon M England 0 70.000000 75.000000
2 Peter Thompson F America 2 51.000000 60.000000
Finished reading file.
你注意到格式说明符中的数字了sscanf()
吗?他们正在防止缓冲区溢出。
动态内存分配怎么样?
在上面的代码中,我估计了姓名、原籍国等的最大长度。现在如何让这些尺寸动态化?我们可以,但我们仍然需要初步估计。
因此,我们可以在一个固定长度的临时数组中读取名称,然后用strlen()
. 有了这些信息,我们现在可以动态分配内存(通过 char 指针指向),然后将strcpy()
字符串从 temp 数组复制到其最终目的地。
推荐阅读
- oracle - ORA-02395: 超出了 IO 使用的调用限制并使用游标作为替代
- java - ChipNavigationBar如何初始化java代码
- ionic-framework - Ionic 5 离子载玻片作为“虚拟载玻片”
- fortran - 支持ieee_arithmetic的gfortran/ifort/nagfor/g95第一个版本是什么?
- python - 如何在 PySpark 中为大型数据集使用采样方法?
- node.js - 登录后没有出现任何错误的html文件
- c# - 使用命令行(在开发服务器中)添加对 .net 框架项目的包引用
- javascript - React/Gatsby 中的图表 js
- typescript - TypeScript 中没有浮动承诺检查
- java - 我的 BroadcastReceiver 上没有收到任何操作 - AndroidStudio