Попытка прочитать текстовый файл построчно на языке C

В основном мой входной файл имеет формат:

I 15 3 15 10 10 20  
S -5 3 15 82  
I -20 80 -4 10  
S 4 -20 8

Количество целых чисел в строке может варьироваться, но в начале каждой строки всегда есть один символ.

На основании символьного значения «I» или «S» я вставляю или ищу заданные целые числа в соответствующей строке. Поскольку нет условия EOL, подобного EOF, как мне остановиться в конце строки? В идеале я хотел бы использовать fscanf.

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

void readFileLines(char** argv)
{
    FILE* fp = fopen(argv[1], "r");
    char read;
    while(fscanf(fp, "%c\t", &read) != EOF)
    {
        //scan first letter in line until EOF
        if (read == 'I')
        {
            //loop through all values in Line
            int data;
            printf("Inputting...");
            while(fscanf(fp, "%i\t", &data) != EOL) //ERROR (EOL DOESNT WORK)
            {
                //iterate rest of line values til end of line
                printf("%d\t", data);
                
            }
        }
        else if (read == 'S')
        {
            int data;
            printf("Searching...");
            while(fscanf(fp, "%d\t", &data) != EOL) // ERROR (EOL DOESNT WORK)
            {
                printf("%d\t", data);
          
            }
        }

        printf("\n");
        //iterate through to next line in order to scan different letteer
    }
   }

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

    return EXIT_SUCCESS;
}

Я слышал, что fgets может быть полезен в этом сценарии, если использовать \n в конце каждой строки как способ указать, когда строка заканчивается, но я не совсем уверен, как работает этот метод. Пожалуйста, дайте мне знать!

Если вы замените свое внутреннее состояние != EOL на != '\n', это поможет? возможно, вам нужно проверить как EOL, так и символ новой строки, и прервать основной цикл, если вы получите символ EOL

Axnyff 24.07.2024 19:49

При условии, что fgets() передается достаточно большой массив, он прочитает целую строку и включит '\n' в качестве последнего символа в строку. Затем вы можете игнорировать или стереть его. char line[1000]; while (fgets(line, sizeof line, stdin)) { /* deal with line */ } /* all lines dealt with now */

pmg 24.07.2024 19:55

Я не понимаю следующее предложение: Based off the char value, 'I' or 'S', I insert or search for the given integers in that respective row. -- Что именно вы хотите сделать, когда "вставляете"? Что именно вы хотите сделать по-другому в зависимости от того, первая буква — 'I' или 'S'? Единственная разница в том, что вы хотите напечатать другую строку, в зависимости от того, 'I' это или 'S'?

Andreas Wenzel 24.07.2024 21:29

@Axnyff Когда fscanf вернется \n? Вы, наверное, имеете в виду != 1.

Gerhardh 25.07.2024 08:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
92
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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

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

Но в вашем конкретном случае вам не обязательно рассматривать вводимые данные как серию строк. Вместо этого вы можете рассматривать его как последовательность отдельных входных элементов, разделенных пробелами, некоторые из которых являются числами, а другие - символами 'I' или 'S'. Чтение по одному полю за раз через fscanf здесь довольно доступно, при этом обсуждается множество fscanf ловушек, и у вас не будет недостатка в беспокойстве о длине строки. Здесь вам хотелось бы лучше воспользоваться возвращаемым значением fscanf(), которое не только сигнализирует об окончании ввода, но и в других случаях сообщает вам, сколько входных элементов было успешно отсканировано и преобразовано.

Таким образом, вы можете сделать что-то вроде этого:

    char c;

    // expecting I or S:
    int num_items = fscanf(fp, " %c", &c);
    if (num_items == 1) {
        // successfully read an 'I' or an 'S' ...
    } else if (num_items == EOF) {
        // end of input ...
    } else {
        // invalid input ...
    }

... а вот так:

    // expecting numbers:
    // loop:
        int num;
        num_items = fscanf(fp, "%d", &num);
        if (num_items == 1) {
            // successfully read one number ...
        } else if (num_items == EOF) {
            // end of input ...
        } else {
            // Probably an 'I' or 'S' (which remains unread) -- end of record ...
        }

Большое спасибо, это действительно меня выручило!

Neel Varma 24.07.2024 22:12

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

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

Хотя вы можете использовать функцию sscanf для обработки ввода, эта функция имеет тот недостаток, что неопределенное поведение будет вызвано, если число выходит за пределы диапазона целевого типа данных. По этой причине я рекомендую вместо этого использовать strtol, который позволяет обнаружить такую ​​ситуацию и вывести соответствующее сообщение об ошибке вместо вызова неопределенного поведения.

Вот пример:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <errno.h>

bool get_line_from_stream( char buffer[], int buffer_size, FILE *fp );
bool process_line( char line[] );

int main( int argc, char *argv[] )
{
    FILE* fp;
    char line[200];

    // check for correct number of command-line arguments
    if ( argc != 2 )
    {
        fprintf( stderr, "Please specify an input file!\n" );
        exit( EXIT_FAILURE );
    }

    // open input file
    fp = fopen( argv[1], "r" );
    if ( fp == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    // read the input file line by line and process the
    // individual lines, one line per loop iteration
    while ( get_line_from_stream( line, sizeof line, fp ) )
    {
        if ( ! process_line( line ) )
            break;
    }

    // cleanup
    fclose( fp );
}

bool process_line( char line[] )
{
    // process the first character of the line
    switch( line[0] )
    {
        case 'I':
            printf( "Inputting..." );
            break;
        case 'S':
            printf( "Searching..." );
            break;
        default:
            fprintf( stderr, "Error reading input!\n" );
            return false;
    }

    // process the remainder of the line
    for ( char *p = &line[1]; ; ) // infinite loop
    {
        long num;
        char *q;

        // convert one number
        errno = 0;
        num = strtol( p, &q, 10 );

        // test whether conversion was successful
        if ( p == q )
        {
            // skip all whitespace characters
            while ( isspace( (unsigned char)*p ) )
                p++;

            // test whether we successfully reached the end of
            // the line
            if ( *p == '\0' )
            {
                printf( "\n" );
                return true;
            }

            fprintf( stderr, "Encountered invalid character!\n" );
            return false;
        }

        // test whether range error occurred
        if ( errno == ERANGE )
        {
            fprintf(
                stderr,
                "Could not convert character due to range error!\n"
            );
            return false;
        }

        // the character was successfully converted, so print it
        printf( " %ld", num );

        // make p point to the first non-converted character
        p = q;
    }
}

//This function will read exactly one line of input and remove the
//newline character, if it exists. On success, it will return true.
//If this function is unable to read any further lines due to
//end-of-file, it will return false. If it fails for any other reason,
//it will not return, but will print an error message and call "exit"
//instead.
bool get_line_from_stream( char buffer[], int buffer_size, FILE *fp )
{
    char *p;

    //attempt to read one line from the stream
    if ( fgets( buffer, buffer_size, fp ) == NULL )
    {
        if ( !feof(fp) )
        {
            fprintf( stderr, "Input error!\n" );
            exit( EXIT_FAILURE );
        }

        return false;
    }

    //make sure that line was not too long for input buffer
    p = strchr( buffer, '\n' );
    if ( p == NULL )
    {
        //a missing newline character is ok if the next
        //character is a newline character or if we have
        //reached end-of-file
        if ( getc(fp) != '\n' && !feof(fp) )
        {
            fprintf( stderr, "Line is too long for memory buffer!\n" );
            exit( EXIT_FAILURE );
        }
    }
    else
    {
        //remove newline character by overwriting it with a null
        //character
        *p = '\0';
    }

    return true;
}

Для ввода, указанного в вопросе, эта программа имеет следующий вывод:

Inputting... 15 3 15 10 10 20
Searching... -5 3 15 82
Inputting... -20 80 -4 10
Searching... 4 -20 8

Мне нравится ответ Джона, при условии, что входной файл находится под строгим контролем и не имеет реальной возможности содержать недопустимый контент. Т.е. входной файл создается какой-то другой программой, которая никогда не выводит мусор. John’s уловит множество неверных данных, но вы можете сделать больше.

Однако, если входной файл может быть введен человеком или программой, в которой может быть ошибка, вам действительно следует выполнить больше проверок и отчетов об ошибках, что практически невозможно сделать с помощью fscanf.

Поскольку ваш ввод основан на строках, я бы предложил использовать fgets() для чтения строк. Затем используйте strtok() для анализа полей. Затем используйте strtol(), чтобы преобразовать целые числа в длинные, которые следует проверить по диапазону, прежде чем рассматривать как целые. (Вот почему я использую strtol().)

Вот мой код. Я также принял пробелы и табуляции в качестве разделителей и разрешил пустые строки ввода. Я думаю, что он хорошо справляется с проверкой ввода.

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

int main(int argc, char **argv) {
  char iline[256];
  int line_num = 0;

  while (fgets(iline, sizeof(iline), stdin) != NULL) {
    line_num++;
    char *token;

    token = strtok(iline, " \t\n");
    if (token == NULL) {
      continue;  // Ignore blank lines.
    }
    if (strcmp(token, "S") == 0) {
      printf("S");  // Process 'S'.
    }
    else if (strcmp(token, "I") == 0) {
      printf("I");  // Process 'I'.
    }
    else {
      printf("Error line %d, invalid prefix: '%s'\n", line_num, token);
      continue;  // Should return error; this is only for testing.
    }

    token = strtok(NULL, " \t\n");
    while (token != NULL) {  // Process each of the numeric fields.
      char *p = NULL;
      errno = 0;
      long val = strtol(token, &p, 10);
      if (errno != 0 || p == token || p == NULL || *p != '\0') {
        printf("Error line %d, invalid number: '%s'\n", line_num, token);
        break;  // Should return error; this is only for testing.
      }
      printf(",%ld", val);

      token = strtok(NULL, " \t\n");
    }
    printf("\n");
  }

  return 0;
}

Мне любопытно: какой тип недопустимого ввода, по вашему мнению, будет обрабатываться лучше с помощью этого подхода, чем тот, который я обрисовал?

John Bollinger 24.07.2024 23:50

Я не пробовал ваш код, но думаю, что «SIS1 2 3» будет интерпретироваться как S с нулевыми целыми числами, I с нулевыми целыми числами, S с 1, 2, 3. Что, возможно, нормально принять. Но я предпочитаю иметь четко определенный синтаксис ввода и применять его. Кроме того, этот метод не позволит вам напечатать сообщение с номером строки, вызывающим нарушение (который, конечно, я не включил).

Streve Ford 25.07.2024 00:38

Другие вопросы по теме