Как использовать scanf и fgets для чтения файла

Мне нужно прочитать следующий текстовый файл:

2 2
Kauri tree
Waterfall
0 0 W S
0 1 E N

Я хотел использовать scanf чтобы получить первую строку и использовать fgets для второй и третьей строки затем используйте scanf снова для остальных строк.

Я написал код так:

#include <stdio.h>

#define NUM_OF_CHAR 2

int main()
{
    int node, edge;
    scanf("%d %d", &node, &edge);

    FILE* fp;
    fp = stdin;

    char* str[NUM_OF_CHAR];  //should be char str[NUM_OF_CHAR];

    for (int i = 0; i < node; i++) {
        fgets(str[i], 2, fp);     //should be fgets(str, 2, fp);
    }
    printf("%s", str[0]);         //printf("%s", str);
}

ввод, который я ввел, был:

2 2
hello

я получил Segmentation fault

Я видел подобный вопрос здесь, человек упомянул, что я могу позвонить fgets один раз для получения первой строки, но проигнорируйте ее и затем используйте fgets снова, чтобы получить вторую строку. Но я не знаю, как это сделать.

3 ответа

Решение

Локальные переменные, определенные внутри функций, будут иметь, если явно не инициализировано, неопределенное значение. Для указателей это означает, что они указывают на, казалось бы, случайное местоположение. Использование любой неинициализированной переменной, кроме как для ее инициализации, приводит к неопределенному поведению.

Что здесь происходит, так это fgets будет использовать (неинициализированный и, казалось бы, случайный) указатель и использовать его для записи в память, на которую он указывает. Эта память в большинстве случаев не принадлежит вам или вашей программе и может даже перезаписать некоторые другие важные данные. Это может привести к сбоям или другому странному поведению или результатам.

Самое простое решение - сделать str массив массивов символов, например

#define NUM_OF_STRINGS 2
#define STRING_LENGTH 64
...
char str[NUM_OF_STRINGS][STRING_LENGTH];
...
fgets(str[i], sizeof str[i], stdin);

Вы должны убедиться, что STRING_LENGTH выше достаточно, чтобы соответствовать каждой строке, включая символ новой строки и терминатор строки. В случае того, что я показываю выше с этим 64 это означает, что вы можете иметь строки не более 62 символов.


Теперь что касается другой проблемы, которую я указал, с первым вызовом fgets чтение пустой строки

Если у вас есть вход

2 2
hello

Ввод сохраняется в буфере в памяти, а затем scanf а также fgets читает из этого буфера. Буфер с приведенным выше вводом будет выглядеть примерно так

+----+----+----+----+----+----+----+----+----+
|  2 |  2 | \n | ч | е | л | л | о | \n |
+----+----+----+----+----+----+----+----+----+

После scanf вызов прочитать два числа, как выглядит входной буфер

+----+----+----+----+----+----+----+
| \n | ч | е | л | л | о | \ n |
+ ---- + ---- + ---- + ---- + ---- + ---- + ---- +

Так что самый первый звонок fgets в цикле увидим это новая строка. Таким образом, он читает эту новую строку, а затем все готово, оставляя строку "hello\n" в буфере для второго вызова fgets,

Есть несколько способов решить эту проблему. То, что я лично предпочитаю, это использовать fgets универсально читать строки, и если вам нужен простой анализ строки, то используйте sscanf (обратите внимание на ведущие s Также, пожалуйста, смотрите здесь для хорошей справки всех scanf варианты) для этого.

Другой способ - просто читать символы из ввода, по одному символу за раз, и отбрасывать их. Когда вы читаете новую строку, остановите цикл и продолжите работу с остальной частью программы.

Рассмотрим следующий пример, где комментарии объясняют некоторые важные моменты:

#include <stdio.h>

#define NUM_OF_CHAR 2
#define LEN_OF_STR 20

int main()
{
    int node, edge;
    FILE* fp;
    fp = stdin;
    char strbuf[LEN_OF_STR];
    // stream is available after that
    // reading numbers
    fscanf(fp, "%d %d", &node, &edge);
    // reading strings
    for (int i = 0; i < node; i++) {
        // reading line from input stream
        fgets(strbuf, LEN_OF_STR, fp);
    }
    // cleaning input buffer
    while (getchar() != '\n');
    // reading lines with data
    char str[NUM_OF_CHAR];
    int a, b;
    for (int i = 0; i < node; i++) {
        // reading two numbers and two characters
        fscanf(fp, "%d %d %c %c", &a, &b, &str[0], &str[1]);
        // do something with dada, e.g. output
        printf("%d %d %c %c\n", a, b, str[0], str[1]);
    }
    return 0;
}

Когда вы читаете данные с scanf или же fscanf Вы можете проверить результаты, например:

    if (4 == fscanf(fp, "%d %d %c %c", &a, &b, &str[0], &str[1]))
    {
        // actions for correct data
    }
    else
    {
        // actions for wrong input
    }

здесь строка формата имеет 4 спецификатора - "%d %d %c %c", поэтому мы проверяем как "сравнить возвращаемое значение с 4"

Я решил свою проблему. Я не должен использовать char* указатель и сделать его указателем на массив. Первый параметр, который передается в fgets функция должна быть char* поэтому я должен был использовать только массив.

также, так как scanf отсканировал уже с первой строки, если я использую fgets Затем он автоматически получит следующую строку.

#include <stdio.h>

#define NUM_OF_CHAR 100

int main()
{
    int node, edge;
    scanf("%d %d", &node, &edge);

    FILE* fp;
    fp = stdin;

    char str[NUM_OF_CHAR] = {'\0'};

    for (int i = 0; i < node; i++) {
        fgets(str, NUM_OF_CHAR, fp);
    }
    printf("%s", str);
}
Другие вопросы по тегам