While 循环在 C 语言中具有 feof 条件和 fgets 无缘无故地详细说明了两次?

While loop in C Language with feof condition and fgets elaborate twice for no reason?

提问人:sirducas 提问时间:10/18/2022 更新时间:10/19/2022 访问量:148

问:

我有一个文本文件,里面有很多记录(大约 20k 行),是这样写的:

00000000090020120200728012
00000010520020120200729012
00000002740020120200729012
00000002710020120200736012
00000001601020120200755012
00000002870020120200758012
00000010690020120200753013
00000001760020120200800013
00000008480020120200800013
00000009370020120200733014
00000001500020120200739014
00000012400020120200743014
00000008720020120200715015
00009100570020120200734017
00000002060020120200734017
00000002050020120200734017
00000003670020120200734017

这些记录包含 2020 年访问办公室的数据信息,每条记录都可以用这种结构读取(我将以第一行为例):

逐个字符读取字符串,记录按以下方式拆分:

  • 0000(索引 [0-3] -> 无用数据)
  • 000009(索引 [4-9] -> 徽章 ID)
  • 0(索引 [10] -> 的作用类似于布尔值,0 表示输入 - 访问 - ,1 表示输出 - 退出 -)
  • 02012020(索引 [11-18] -> 日期格式)
  • 0728(索引 [19-23] -> 小时和分钟的访问)
  • 012(索引 [24-26] ->仅提供有关门 ID 的信息)

我有这个代码,我写了这个代码,用于计算指定门上指定徽章 ID 的记录(在我的情况下,我只需要读取 001、002、003 门):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define NUMLEN 27

int main () {
    printf("\n-- 32bit version\n");
    int entriesCounter = 0;
    char buff[NUMLEN];
    char requiredBadge[7];
    char badge[7];
    char entry[2];
    char day[3];
    char month[3];
    char year[5];
    char hours[3];
    char minutes[3];
    char gate[4];
    FILE *fp;
    fp = fopen("Storico2020.txt", "r");
    if (fp == NULL) {
        printf("Error open");
        exit(1);
    }
    printf("\nInsert ID badge for counting accesses: ");
    scanf("%s", requiredBadge);
    while(!feof(fp)) {
        fgets(buff, NUMLEN, fp);    // example -> init:0000 | badge:000352 | entry:1 | data:01012019 | time:0030 | gate:023
        strncpy(badge, buff+4, 6);
        badge[6] = '\0';
        strncpy(entry, buff+10, 1);
        entry[1] = '\0';
        strncpy(day, buff+11, 2);
        day[2] = '\0';
        strncpy(month, buff+13, 2);
        month[2] = '\0';
        strncpy(year, buff+15, 4);
        year[4] = '\0';
        strncpy(hours, buff+19, 2);
        hours[2] = '\0';
        strncpy(minutes, buff+21, 2);
        minutes[2] = '\0';
        strncpy(gate, buff+23, 3);
        gate[3] = '\0';
        if (strcmp(requiredBadge, badge) == 0 && strcmp(entry, "1") == 0) {
            printf("\nBadge: %s | in date: %s/%s/%s | gate: %s | hour: %s:%s", badge, day, month, year, gate, hours, minutes);
            entriesCounter++;
        }
        
    }
    fclose(fp);
    printf("\n********** TOTAL ACCESSES OF BADGE ID %s: %d ***************\n" ,requiredBadge, entriesCounter);
    
    system("PAUSE");

    return 0;
}

但是,这里的问题是它计算了找到 TWICE 的每一条记录,我真的不知道为什么。我在 if 条件中也输入了条目 == 1,因为我需要计算一次办公室的入口,而不是出口。 你可以帮我吗?例如,如果将 ID 徽章计算002341则它计算了 342 次访问,但它只需要计算一半。请帮帮我!

C 文件 printf fgets feof

评论

1赞 William Pursell 10/18/2022
stackoverflow.com/questions/5431941/......
1赞 Aconcagua 10/18/2022
虽然 C++ 这个答案仍然涵盖了相同的问题,并且很好地解释了这个问题......
0赞 sirducas 10/18/2022
好的,谢谢,很抱歉重复了这个问题。
1赞 sirducas 10/18/2022
谢谢,实际上我解决了增加 NUMLEN 常数的问题,它现在可以工作了。谢谢你们,再次对不起这个问题,我没有注意到这个错误。
1赞 William Pursell 10/18/2022
@user3121023确实如此。不知道如何更改副本,所以我只会投票给 repoen。

答:

1赞 user3121023 10/18/2022 #1

问题是缓冲区太小,无法在一次调用中读取所有数字和换行符。 将读取大多数字符。 让我们在一次调用中读取数字,但没有空间读取换行符。在第二次调用中读取换行符。使用将提供足够大的缓冲区,以便在一次调用中读取数字和换行符。
用作 while 条件可能是一个问题。当读取最后一行时,没有错误,因此循环再次迭代。而是用作 while 条件。
请考虑使用 提取字段。 最多扫描两个字符。
fgetsfgetsNUMLEN - 1#define NUMLEN 27fgetsfgets#define NUMLEN 30fgets!feof(fp)fgetssscanf%2s

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define NUMLEN 30

int main () {
    printf("\n-- 32bit version\n");
    int entriesCounter = 0;
    char const *filename = "Storico2020.txt";
    char buff[NUMLEN] = "";
    char requiredBadge[7] = "";
    char badge[7] = "";
    char entry[2] = "";
    char day[3] = "";
    char month[3] = "";
    char year[5] = "";
    char hours[3] = "";
    char minutes[3] = "";
    char gate[4] = "";
    FILE *fp = NULL;

    fp = fopen( filename, "r");
    if (fp == NULL) {
        perror ( filename);
        exit(1);
    }
    printf("\nInsert ID badge for counting accesses: ");
    scanf("%6s", requiredBadge);
    while( fgets(buff, NUMLEN, fp)) {
        // example -> init:0000 | badge:000352 | entry:1 | data:01012019 | time:0030 | gate:023
        if ( 8 != sscanf ( buff + 4, "%6s%1s%2s%2s%4s%2s%2s%3s"
        , badge
        , entry
        , day
        , month
        , year
        , hours
        , minutes
        , gate)) {
            fprintf ( stderr, "bad record %s\n", buff);
            continue;
        }
        if (strcmp(requiredBadge, badge) == 0 && strcmp(entry, "1") == 0) {
            printf("\nBadge: %s | in date: %s/%s/%s | gate: %s | hour: %s:%s", badge, day, month, year, gate, hours, minutes);
            entriesCounter++;
        }

    }
    fclose(fp);
    printf("\n********** TOTAL ACCESSES OF BADGE ID %s: %d ***************\n" ,requiredBadge, entriesCounter);

    // system("PAUSE");

    return 0;
}
0赞 William Pursell 10/18/2022 #2

在验证输入时需要更加小心。always(always)检查 scanf 返回的值。而且,尽管下面的代码不是我这样做的方式,但看到您肆意复制所有这些数据,我感到很痛苦。你不需要。如果复制数据只是因为想要以 null 结尾的字符串,但又不想破坏缓冲区,则应重新考虑。例如:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void
show(const char *header, const char *str, size_t len)
{
    fputs(header, stdout);
    fwrite(str, 1, len, stdout);
}

int
main(int argc, char **argv)
{
    char buff[1024];
    unsigned line = 0;
    unsigned entriesCounter = 0;
    const char * const badge = buff + 4;
    const char * const entry = buff + 10;
    const char * const day = buff + 11;
    const char * const month = buff + 13;
    const char * const year = buff + 15;
    const char * const hours = buff + 19;
    const char * const minutes = buff + 21;
    const char * const gate = buff + 23;
    char * const end = buff + 26;

    if( argc < 2 ){
        fprintf(stderr, "missing badge argument\n");
        exit(EXIT_FAILURE);
    }
    const char *requiredBadge = argv[1];
    const  char *path = argc > 2 ? argv[2] : "stdin";
    FILE  *fp = argc > 2 ? fopen(argv[2], "r") : stdin;

    if( fp == NULL ){
        perror(path);
        exit(1);
    }

    while( *end = '\0', fgets(buff, sizeof buff, fp) != NULL ){
        line += 1;
        if( *end != '\n' ){
            fprintf(stderr, "Unexpected input in line %u\n", line);
            continue;
        }
        if( strncmp(requiredBadge, badge, entry - badge) == 0
            && *entry == '1'
        ) {
            printf("%5d: ", ++entriesCounter);
            show("Badge: ", badge, entry - badge);
            show(" | in date: ", day, month - day);
            show("/", month, year - month);
            show("/", year, hours - year);
            show(" | gate: ", gate, end - gate);
            show(" | hour: ", hours, minutes - hours);
            show(":", minutes, gate - minutes);
            putchar('\n');
        }
    }
}

/* Sample input:
00000010520020120200729012
00000002740020120200729012
00000002711020120200736012
00000002710020120200736012
00000002711020120200736012
00000001601020120200755012
00000002870020120200758012
00000010690020120200753013
00000001760020120200800013
00000008480020120200800013
00000009370020120200733014
*/

请注意,这会稍微调整界面,并将所需的徽章作为命令行参数读取,而不是从输入流中获取它。