【问题标题】:How to separate numbers from strings in txt file in C?如何在C中的txt文件中将数字与字符串分开?
【发布时间】:2022-01-21 20:48:18
【问题描述】:

txt 文件:

44.56 john doe  
100.21 jane doe

如何计算数字的总和?这个函数给出 0

double get_sum(FILE *out)
{
    double sum = 0;
    double value;
    char name;
    char surname;

    while(fscanf(out, "%lf %s %s", &value, name, surname) != EOF)
    {
        sum += value;
    }
    return sum;
}

【问题讨论】:

  • 如果 fscanf 返回 0,那么您正在添加一个未初始化的值。
  • 旁注:为什么将输入文件的流命名为out?这个名字暗示它是一个输出文件,而不是一个输入文件。
  • 不是您的主要问题,而是:在您编写的代码中,您真的想要while(fscanf(out, "%lf %s %s", ...) == 3,而不是与 EOF 进行比较。

标签: c string sum txt


【解决方案1】:

排队

while(fscanf(out, "%lf %s %s", &value, name, surname) != EOF)

比较函数fscanfEOF的返回值不好。例如,如果函数只能匹配一个参数,那么它将返回1,但您的程序仍会表现得好像所有3 参数都已匹配,并且您的程序将尝试处理不存在的数据.因此,您应该改写以下内容:

while( fscanf(out, "%lf %s %s", &value, name, surname) == 3 )

但是,这可能不是您遇到的直接问题的原因。该问题可能是由于%s 转换格式说明符需要一个指针来写入。但是,您传递的是单个 char 的值(而不是指针)。

假设您启用了警告,您的编译器应该已经警告过您。有关详细信息,请参阅此问题:Why should I always enable compiler warnings?

为了解决这个问题,你应该换行

char name;
char surname;

到:

char name[50];
char surname[50];

此外,您可能应该限制写入这些字符串的字符数,以防止出现buffer overflow,如下所示:

while( fscanf(out, "%lf %49s %49s", &value, name, surname) == 3 )

对于基于行的输入,我通常建议您使用函数fgets 一次读取一行。然后,您可以在每一行上使用函数 sscanf 来解析它。

如果您像现在一样使用 fscanf,并且如果它与一行中的 3 个字段不完全匹配,那么解析器将与行不同步,并且无法自行重新同步在新行的开头,这可能意味着您的程序将在文件的其余部分出现异常,而不是仅在一行中出现异常。

这是一个使用fgetssscanf 代替的程序:

#include <stdio.h>

double get_sum( FILE *fp )
{
    char line[200];
    double sum = 0;

    while ( fgets( line, sizeof line, fp ) != NULL )
    {
        double value;
        char name[50];
        char surname[50];

        if ( sscanf( line, "%lf %49s %49s", &value, name, surname ) == 3 )
        {
            sum += value;
        }
        else
        {
            printf( "WARNING: skipping line due to parse failure!\n" );
        }
    }

    return sum;
}

int main( void )
{
    //calling this function would also work for an opened file, but
    //for simplicity, I am only passing it "stdin"
    double sum = get_sum( stdin );

    printf( "The sum is: %lf\n", sum );
}

有输入

44.56 john doe
100.21 jane doe

从问题来看,这个程序有以下输出:

The sum is: 144.770000

如果您现在改为向程序提供包含一行无效输入的输入

44.56 john doe
invalid_input_line
100.21 jane doe

它只会在解析无效行时失败,但仍会正确处理其他行:

WARNING: skipping line due to parse failure!
The sum is: 144.770000

如前所述,您的程序能够从此错误中恢复,因为它使用fgets 一次读取一行,并且使用sscanf 而不是fscanf。否则,从此类错误中恢复会更加复杂。

【讨论】:

    【解决方案2】:

    只需告诉fscanf 忽略namesurname 插槽,如下所示:

    double get_sum(FILE *out) {
        double sum = 0;
        double value;
    
        while (fscanf(out, "%lf%*s%*s", &value) != EOF) {
            sum += value;
        }
        return sum;
    }
    

    问题在于您将指针传递给char 而不是指向可以保存内容的char array 的指针。由于您没有溢出并导致未定义的行为。

    但是,如果您真的想读入名称,请尝试:

    double get_sum(FILE *out) {
        double sum = 0;
        double value;
        char name[5];
        char surname[4];
    
        while (fscanf(out, "%lf%s%s", &value, name, surname) != EOF) {
            sum += value;
        }
        return sum;
    }
    

    这里的缓冲区足够长以容纳示例文本文件数据。在现实生活中(如果需要),您将拥有足够大的名称来处理最长的名字。与原始代码的重要区别在于 namesurnamefscanf 函数所期望的指针。

    但是考虑到名称的长度有时是不可预测的,在读取该行上的值后,只需将剩余的行读入缓冲区并忽略它。

    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    double get_sum(FILE *in) {
        double sum = 0;
        double value;
        char *remainingLine = NULL;
        size_t bufLen = 0;
    
        while (fscanf(in, "%lf", &value) == 1) {
            sum += value;
            // ignore remaining character in line
            getline(&remainingLine, &bufLen, in);
        }
        free(remainingLine);
        return sum;
    }
    
    int main(void) {
        FILE *f = fopen("text.txt", "r");
        assert(f);
        double s = get_sum(f);
        fclose(f);
        printf("Sum is %f", s);
    }
    

    带有text.txt的文件包含

    1.03 First Surname
    2.2Here is another long "name" (sum should be 10.63)
    3.4 first middle last (next line is lonely and last)
    4
    

    运行最后一个程序应该会产生类似

    Sum is 10.630000
    

    【讨论】:

    • 我使用了第一个代码,但仍然得到 0.000
    • 我用int main(void) { FILE *f = fopen("text.txt", "r"); assert(f); double s = get_sum(f); fclose(f); printf("Sum is %f", s); } 来测试它。你是怎么测试的?
    • @BoR 如果您输入了两个额外的按键以使其成为char name[50];char surname[40];,您可以节省大约 170 个字符的解释。 :-)
    • 除 assert(f) 外相同。
    • @user17997056,不检查f,你怎么知道文件被打开了。另请记住,如果您的数据与“规范”不匹配,事情就会开始出错。例如,如果一行包含12.34 first middle surname,则程序会因为三个名字在线而崩溃。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-14
    • 2019-10-03
    • 1970-01-01
    • 2023-01-10
    • 2011-01-15
    • 2015-12-02
    • 1970-01-01
    相关资源
    最近更新 更多