อ่านบรรทัดจากไฟล์ใน C และแยกจำนวนอินพุต

Aug 17 2020

ฉันมีไฟล์input.dat ในไฟล์นี้มี 3 บรรทัด:

1 2 3
5 7 10 12
8 9 14 13 15 17

ฉันจะอ่านหนึ่งในสามบรรทัดโดยใช้ C และส่งคืนจำนวนองค์ประกอบ ตัวอย่างเช่นผมต้องการที่จะอ่านบรรทัดที่ 2 5 7 10 12ในหน่วยความจำและยังกลับจำนวนของค่าในบรรทัดที่ 2 4ซึ่งเป็น รหัสของฉันอยู่ด้านล่าง ...

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

#define STRING_SIZE 2000

int main() {
    FILE *fp = fopen("in.dat", "r");
    char line[STRING_SIZE];
    int lcount = 0, nline = 1, sum = 0, number;

    if (fp != NULL) {
        while (fgets(line, STRING_SIZE, fp) != NULL) {
            if (lcount == nline) {
                while (sscanf(line, "%d ", &number)) {
                    sum++;
                }
                break;
            } else {
                lcount++;
            }
        }
        fclose(fp);
    }
    exit(0);
}

เมื่อฉันเรียกใช้รหัสนี้มันไม่เคยหยุดนิ่งเหมือนลูปตาย ปัญหาที่นี่คืออะไร?

คำตอบ

1 chqrlie Aug 17 2020 at 19:57

ลูปwhile (sscanf(line, "%d ", &number))จะแยกวิเคราะห์ตัวเลขแรกในบรรทัด

คุณควรใช้strtolแทน:

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

#define STRING_SIZE 2000

int main() {
    FILE *fp = fopen("in.dat", "r");
    char line[STRING_SIZE];
    int lcount = 0, nline = 1;

    if (fp != NULL) {
        while (fgets(line, STRING_SIZE, fp) != NULL) {
            if (lcount == nline) {
                char *p = line, *q;
                int count = 0;
                for (;;) {
                    long val = strtol(p, &q, 0);    // parse an integer
                    if (q == p) {
                        // end of string or not a number
                        break;
                    }
                    // value was read into val. You can use it for whatever purpose
                    count++;
                    p = q;
                }
                printf("%d\n", count);
                break;
            } else {
                lcount++;
            }
        }
        fclose(fp);
    }
    return 0;
}
1 DavidC.Rankin Aug 17 2020 at 22:05

คุณมีความคิดไปตามเส้นทางที่เหมาะสมกับการใช้งานของsscanf()ชิ้นเดียวของปริศนาที่คุณกำลังขาดหายไปคือวิธีการที่จะใช้การชดเชยเพื่อให้คุณอ่านค่าในบรรทัดถัดไปที่มีการเรียกร้องต่อไปยังline sscanf()คุณทำได้โดยการติดตามจำนวนอักขระที่ใช้ในการโทรแต่ละครั้งเพื่อsscanf()ใช้การ"%n"แปลง (ไม่ได้เพิ่มจำนวน Conversion ของคุณที่ส่งคืนโดยsscanf()) ตัวอย่างเช่นการอ่านบรรทัดจากสตรีมไฟล์ที่เปิดอยู่fpคุณสามารถทำได้:

#define MAXC  1024      /* if you need a constant, #define one (or more) */
...
    char line[MAXC] = "";   /* buffer to hold each line */
    ...
    while (fgets (line, MAXC, fp)) {    /* reach each line in file */
        int offset = 0,                 /* offset in line for next sscanf() read */
            nchr = 0,                   /* number of char consumed by last read */
            val,                        /* integer value read with sscanf() */
            nval = 0;                   /* number of values read in line */
        /* conververt each integer at line + offset, saving no. of chars consumed */
        while (sscanf (line + offset, "%d%n", &val, &nchr) == 1) {
            printf (" %d", val);        /* output value read */
            offset += nchr;             /* update offset with no. chars consumend */
            nval++;                     /* increment value count */
        }
        printf ("  -  %d values\n", nval);  /* output no. values in line */
    }

( หมายเหตุ: strtol()ให้การรายงานข้อผิดพลาดที่ดีกว่าsscanf()การแปลงที่ล้มเหลว)

หากคุณรวมเข้ากับตัวอย่างที่อ่านจากชื่อไฟล์ที่ให้ไว้เป็นอาร์กิวเมนต์แรกของโปรแกรม (หรืออ่านจากstdinค่าเริ่มต้นหากไม่มีการระบุอาร์กิวเมนต์) คุณสามารถทำได้:

#include <stdio.h>

#define MAXC  1024      /* if you need a constant, #define one (or more) */

int main (int argc, char **argv) {

    char line[MAXC] = "";   /* buffer to hold each line */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (fgets (line, MAXC, fp)) {    /* reach each line in file */
        int offset = 0,                 /* offset in line for next sscanf() read */
            nchr = 0,                   /* number of char consumed by last read */
            val,                        /* integer value read with sscanf() */
            nval = 0;                   /* number of values read in line */
        /* conververt each integer at line + offset, saving no. of chars consumed */
        while (sscanf (line + offset, "%d%n", &val, &nchr) == 1) {
            printf (" %d", val);        /* output value read */
            offset += nchr;             /* update offset with no. chars consumend */
            nval++;                     /* increment value count */
        }
        printf ("  -  %d values\n", nval);  /* output no. values in line */
    }

    if (fp != stdin)                    /* close file if not stdin */
        fclose (fp);
}

ตัวอย่างการใช้ / ผลลัพธ์

ด้วยข้อมูลที่คุณแสดงในชื่อไฟล์dat/nvals.txtคุณจะได้รับ:

$ ./bin/fgetsnvals dat/nvals.txt
 1 2 3  -  3 values
 5 7 10 12  -  4 values
 8 9 14 13 15 17  -  6 values

ตรวจสอบสิ่งต่างๆและแจ้งให้เราทราบหากคุณมีคำถามเพิ่มเติม

1 RobinHellmers Aug 17 2020 at 21:41

คำตอบของchqrlieเวอร์ชันที่สะอาดกว่าเล็กน้อย เริ่มต้นด้วยสตริงนั่นคือสิ่งที่คำถามเกี่ยวกับหลังจากfgets()นั้น

sscanf() จะไม่ก้าวผ่านสตริงมันจะอ่านตั้งแต่ต้นเสมอ

strtol()มองหาlong intจุดเริ่มต้นของสตริงโดยไม่สนใจช่องว่างเริ่มต้น ให้กลับที่อยู่ของตำแหน่งที่หยุดการสแกน

คู่มือของstrtol()ระบุว่าควรตรวจสอบข้อผิดพลาดในการแปลงข้อผิดพลาด

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define STRING_SIZE 2000

int main(void)
{
    char line[STRING_SIZE] = "5 7 10 12";

    char* start = line;
    char* end;

    int count = 0;

    while(1)
    {
        /**
         * strtol() look for long int in beginning of the string
         * Ignores beginning whitespace
         * 
         * start: where to strtol() start looking for long int
         * end: where strtol() stops scanning for long int
         */
        errno = 0; // As strol() manual says

        strtol(start, &end, 0);

        if (errno != 0)
        {
            printf("Error in strtol() conversion.\n");
            exit(0);
        }

        if (start == end) break; // Quit loop

        start = end;
        count++;
    }
    

    printf("count: %d\n", count);

    return 0;
}