首页 > 解决方案 > 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

标签: carraysstringstructtypedef

解决方案


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 数组复制到其最终目的地。


推荐阅读