Читайте целые числа из определенной строки из файла в c - PullRequest
0 голосов
/ 16 декабря 2018

Здравствуйте, я некоторое время боролся с этой проблемой, и после некоторых исследований в Интернете, которые не принесли никаких плодов, я решил попросить о помощи.

Мне нужно прочитать несколько целых чиселиз файла и из определенной строки, а затем что-то с ними сделать.

Я знаю этот прием для обработки строк символов

 while(fgets(pointer_to_string, length, "file_name.txt"))
 line++;       /*increment line-integer type- by 1*/

 if(line == your_line) /*do something with the strings at that line*/

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

5
1 78 45 32 2

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

int a[20];
int num; /*number on first line*/
int* p;
p = a;
p = (int*)malloc(num*sizeof(int));

Конечно, выделение памяти будет сделано после того, как я прочитал первое число из файла.

Так что, я думаю, мне будет проще показать вам мою борьбу:

int main()
{

FILE* file = fopen("files.txt", "r");

int a[20], first_num, j = 0;
int* p = a, line = 1;
while(!feof(file))
{

    if ( line == 1 )
    {


        fscanf(file, "%d", &first_num);
        p = (int*)malloc(first_num*sizeof(int));
    }
    else
    {


        for ( j = 0; j <  first_num; j++)
            fscanf(file, "%d", (p + j));
    }


    line++;

}

for ( j = 0; j < first_num; j++)
{
    printf("\t%d\t", *(p + j));
}
printf("\n%d", first_num);
free(p);

fclose(file);


return 0;
}

Как ни странно, эта программа действительно работает для этого примера (количество элементов в 1-й строке и массив во 2-м), ноУ меня такое чувство, что оно некорректно или, по крайней мере, я не могу назвать его «чистым», в основном потому, что я не совсем уверен, как работает этот цикл, я знаю, что функция «feof» используется для достижения конца файла, чтобыпока меня там нет, он будет возвращать ненулевое значение, и поэтому я могу запомнить число в 1-й строке, но я не знаю, когда и как он проверяет цикл. Сначала яЯ думал, что он делает это в конце каждой строки, но это подразумевает, что если бы я изменил 'else' с помощью:

else if ( line == 2 )

, он все равно должен был бы работать должным образом, чего не происходитТак что я был бы признателен за некоторые объяснения того, как на самом деле работает этот цикл.

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

Мой реальный вопрос заключается в том, как читать целые числа, разделенные пробелом из определенной строки из файла, а не обязательно тот пример, который я вам дал (этот для того, кто не против помочь мнеушел)

Ответы [ 2 ]

0 голосов
/ 16 декабря 2018

Мой реальный вопрос - как читать целые числа, разделенные пробелом из определенной строки из файла ... (?)

Шаг 1: допустим, вы ожидаете до N целые числа.Прочитайте строку с щедрой, но разумной максимальной длиной.Я бы использовал 2x ожидаемый максимальный размер.Хорошо, если учесть дополнительный интервал, но строки экстремальной длины, вероятно, ошибочны или враждебны.

#define INT_PER_LINE_MAX 20

// About 1 digit per 3 bits, 28/93 is just above log10(2)
#define CHARACTERS_PER_INT_MAX ((sizeof(int)*CHAR_BIT - 1)*28/93 + 2)

// Room for N int, separators and \0
#define LINE_SIZE (INT_PER_LINE_MAX * (CHARACTERS_PER_INT_MAX + 1) + 1)

// I like 2x to allow extra spaces, leading zeros, etc.
char buf[LINE_SIZE * 2];

while (fgets(buf, sizeof buf, file) {
  ...

Шаг 2. Вызов функции для анализа целых чисел N из строки

while (fgets(buf, sizeof buf, file) {
  int a[INT_PER_LINE_MAX];
  int count = parse_ints(a, INT_PER_LINE_MAX, buf);
  if (count < 0) {
    puts("bad input");
  } else {
    printf("%d int found\n", count);
  }
}

Шаг 3: Сделайте parse_ints().Разберите строку, используя strtol(), а также @ David C. Rankin или используйте sscanf() с "%d %n".В последнем подходе отсутствует надежная защита от переполнения.

int parse_ints(int *a, int n, const char *buf) {

  int i;
  // enough room? and more to parse?
  for (i=0; i<n && *buf; i++) {
    int value;
    int n;  // save offset where scanning stopped.
    if (sscanf(buf, "%d %n", &value, &n) != 1) {
      return -1;  // No int scanned.
    }
    a[i] = value;
    buf += n;  // advance the buffer
  }
  if (*buf) {
    return -1;  // Unexpected extra text left over
  }
  return i;
}   
0 голосов
/ 16 декабря 2018

Давайте начнем с некоторых основ.При чтении строк из файла (или строк ввода от пользователя) вы, как правило, захотите использовать строчно-ориентированную функцию ввода, такую ​​как fgets или POSIX getline, чтобы убедиться, что вы читаетеВся строка за раз и отсутствие того, что осталось в вашем входном буфере, зависит от того, какой scanf спецификатор преобразования использовался последним.С fgets вам нужно будет предоставить буфер достаточного размера для размещения всей строки или динамически выделять и realloc по мере необходимости, пока не будет прочитана вся строка (getline обрабатывает это для вас).Вы проверяете, что вся строка была прочитана, проверяя, является ли последний символ символом '\n' или что длина буфера меньше максимального размера (оба значения оставлены ниже).

Как только вы получитечтение строки текста, у вас есть две опции, вы можете использовать sscanf для преобразования цифр в буфере в целочисленные значения (либо заранее зная число, содержащееся в строке, либо предоставив достаточное количество спецификаторов преобразования или путем преобразования каждого из них по отдельности и использования спецификатора "%n" , чтобы сообщить количество символов, извлеченных для этого преобразования, и увеличения начала в вашем буфере на эту величину для следующего преобразования)

Другой вариант, который на сегодняшний день является наиболее гибким и надежным с точки зрения проверки ошибок и составления отчетов, состоит в том, чтобы использовать strtol и использовать параметр endptr по назначению, чтобы указатель указывал на одну последнюю цифру.преобразован , позволяющий вам пройти по вашему буферуTly преобразование значений, как вы идете.См .: strtol (3) - страница руководства Linux strtol предоставляет возможность различать ошибки, при которых не были преобразованы цифры , где произошло переполнение или недостаточное значение (устанавливая errno в соответствующее значение), и позволяет проверить, остаются ли дополнительные символы после преобразования, с помощью параметра endptr для управления циклом преобразования вашего значения.

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

Давайте начнем с вашего образца входного файла:

Пример входного файла

$ cat dat/int_file.txt
5
1 78 45 32 2

Когда вы сталкиваетесь с одним значением в первой строке, большую часть времени вы просто захотите преобразовать исходное значение в fscanf (file, "%d", &ival);, что хорошо, но - ловушки при использовании любого из scanf family is ВЫ должны учитывать любые символы, оставшиеся во входном буфере после преобразования.Хотя спецификатор преобразования "%d" обеспечит необходимое преобразование, извлечение символов останавливается с последней цифрой, оставляя '\n' непрочитанным.Пока вы учитываете этот факт, можно использовать fscanf, чтобы получить первое значение.Тем не менее, вы должны проверить каждый шаг на этом пути.

Давайте посмотрим на начало примера, выполняя только это, открывая файл (или читая из stdin, если имя файла не указано), проверяя файл какоткрыть, а затем проверить first_num, например,

#include <stdio.h>
#include <stdlib.h> /* for malloc/free & EXIT_FAILURE */
#include <errno.h>  /* for strtol validation */
#include <limits.h> /* for INT_MIN/INT_MAX */

#define MAXC 1024   /* don't skimp on buffer size */

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

    int first_num,      /* your first_num */
        *arr = NULL,    /* a pointer to block to fill with int values */
        nval = 0;       /* the number of values converted */
    char buf[MAXC];     /* buffer to hold subsequent lines read */
    /* open file passed as 1st argument (default: stdin if no argument) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r"): stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-file");
        exit (EXIT_FAILURE);
    }

    if (fscanf (fp, "%d", &first_num) != 1) {   /* read/validate int */
        fputs ("error: invalid file format, integer not first.\n", stderr);
        exit (EXIT_FAILURE);
    }

. На этом этапе ваш входной буфер содержит:

\n
1 78 45 32 2

Поскольку вы собираетесь приступить к линейно-ориентированномупрочитав оставшиеся строки в файле, вы можете просто сделать первый вызов fgets с целью чтения и отбрасывания '\n', например,

    if (!fgets (buf, MAXC, fp)) {   /* read/discard '\n' */
        fputs ("error: non-POSIX ending after 1st integer.\n", stderr);
        exit (EXIT_FAILURE);
    }

( примечание: проверка. Если файл завершился строкой без POSIX (например, нет '\n'), fgets завершится ошибкой, и если вы не проверяете, вы, скорее всего, вызовете неопределенное поведение, пытаясь позднее прочитать файлпоток, в котором не осталось символов для чтения, и после этого попытка чтения из буфера с неопределенным содержимым)

Вы можете выделить место для first_num количества целых чисел в этой точке и назначитьrting адрес для этого нового блока в arr для заполнения целочисленными значениями, например,

    /* allocate/validate storage for first_num integers */
    if (!(arr = malloc (first_num * sizeof *arr))) {
        perror ("malloc-arr");
        exit (EXIT_FAILURE);
    }

Для чтения оставшихся значений в вашем файле вы можете просто сделать один вызов fgets, а затем переключиться на преобразование целочисленных значений, содержащихся в заполненном буфере, но, немного подумав, вы можете выработать подход, которыйбудет читать столько строк, сколько необходимо, до тех пор, пока не будут преобразованы целые числа first_num или не будет найдено EOF.Независимо от того, берете ли вы входные данные или конвертируете значения в буфер, надежный подход - это один и тот же Цикл постоянно, пока вы не получите то, что вам нужно, или не закончите данные , например,

while (fgets (buf, MAXC, fp)) { /* read lines until conversions made */
    char *p = buf,  /* nptr & endptr for strtol conversion */
        *endptr;
    if (*p == '\n')     /* skip blank lines */
        continue;
    while (nval < first_num) {  /* loop until nval == first_num */
        errno = 0;              /* reset errno for each conversion */
        long tmp = strtol (p, &endptr, 0);  /* call strtol */
        if (p == endptr && tmp == 0) {  /* validate digits converted */
            /* no digits converted - scan forward to next +/- or [0-9] */
            do
                p++;
            while (*p && *p != '+' && *p != '-' && 
                    ( *p < '0' || '9' < *p));
            if (*p)     /* valid start of numeric sequence? */
                continue;   /* go attempt next conversion */
            else
                break;      /* go read next line */
        }
        else if (errno) {   /* validate successful conversion */
            fputs ("error: overflow/underflow in conversion.\n", stderr);
            exit (EXIT_FAILURE);
        }
        else if (tmp < INT_MIN || INT_MAX < tmp) {  /* validate int */
            fputs ("error: value exceeds range of 'int'.\n", stderr);
            exit (EXIT_FAILURE);
        }
        else {  /* valid conversion - in range of int */
            arr[nval++] = tmp;      /* add value to array */
            if (*endptr && *endptr != '\n') /* if chars remain */
                p = endptr;         /* update p to endptr */
            else        /* otherwise */
                break;  /* bail */
        }
    }
    if (nval == first_num)  /* are all values filled? */
        break;
}

Теперь давайтераспакуйте это немного.Первое, что происходит, вы объявляете указатели, необходимые для работы с strtol, и присваиваете начальный адрес buf, который вы заполняете с fgets до p, а затем читаете строку из вашего файла.Нет необходимости пытаться преобразовать пустую строку, поэтому мы проверяем первый символ в buf и, если это '\n', мы получаем следующую строку с:

    ...
    if (*p == '\n')     /* skip blank lines */
        continue;
    ...

Как только у вас естьнепустая строка, вы запускаете цикл преобразования и будете пытаться выполнять преобразования до тех пор, пока число ваших значений не станет равным first_num или вы не достигнете конца строки.Ваше управление циклом просто:

    while (nval < first_num) {  /* loop until nval == first_num */
        ...
    }

В цикле вы будете полностью проверять ваши попытки преобразования с strtol, сбрасывая errno = 0; перед каждым преобразованием и назначая возврат преобразования временному long intзначение.(например, от строки к длинной), например,

        errno = 0;              /* reset errno for each conversion */
        long tmp = strtol (p, &endptr, 0);  /* call strtol */

После выполнения преобразования у вас есть три условия для проверки, прежде чем вы получите хорошее целочисленное преобразование,

  1. , если НЕТцифры были преобразованы, затем p == endptr (и для man-страницы возвращение устанавливается на ноль).Таким образом, чтобы проверить, возникло ли это условие, вы можете проверить: if (p == endptr && tmp == 0);
  2. , если была ошибка во время преобразования цифр, независимо от того, какая ошибка произошла, errno будет установлено в ненулевое значениепозволяя вам проверить на ошибку в преобразовании с if (errno).Вы также можете углубиться в то, что произошло, как указано на странице руководства, но для целей проверки здесь достаточно знать, произошла ли ошибка;и, наконец,
  3. , если цифры были преобразованы и ошибки не было, вы все еще не сделали этого.strtol преобразование в значение long, которое может или не может быть совместимо с int (например, long - 8-bytes на x86_64, в то время как int - 4-bytes. Таким образом, чтобы обеспечить преобразованное значениепоместится в ваш целочисленный массив, вам нужно убедиться, что возвращаемое значение находится в пределах INT_MIN и INT_MAX до , когда вы присваиваете значение элементу arr.

( примечание: с 1. выше, только то, что ни одна цифра не была преобразована, не означает, что в строке не было никаких цифр, это просто означает, что первое значение не было цифрой.существует строка, в которой указатель используется для поиска следующих +/- или [0-9] для определения в дальнейшем числовых значений. Такова цель цикла while в этом блоке кода)

Как только вы получитецелочисленное значение, напомним, что endptr будет установлен на следующий символ после последней преобразованной цифры. Быстрая проверка, не является ли *endptr символом с нулевым окончанием и не концом строки, подскажет вамли чартерыостаются те, которые доступны для преобразования.Если это так, просто обновите p = endptr, чтобы указатель теперь указывал на одну точку после последней преобразованной цифры и повторял.(вы также можете сканировать вперед в этой точке с помощью того же цикла while, который использовался выше, чтобы определить, существует ли другое числовое значение - это оставлено вам)

Как только цикл завершится, все, что вам нужно сделать, это проверитьесли nval == first_num, чтобы узнать, нужно ли вам продолжать собирать значения.

В целом, вы можете сделать что-то похожее на:

#include <stdio.h>
#include <stdlib.h> /* for malloc/free & EXIT_FAILURE */
#include <errno.h>  /* for strtol validation */
#include <limits.h> /* for INT_MIN/INT_MAX */

#define MAXC 1024   /* don't skimp on buffer size */

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

    int first_num,      /* your first_num */
        *arr = NULL,    /* a pointer to block to fill with int values */
        nval = 0;       /* the number of values converted */
    char buf[MAXC];     /* buffer to hold subsequent lines read */
    /* open file passed as 1st argument (default: stdin if no argument) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r"): stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-file");
        exit (EXIT_FAILURE);
    }

    if (fscanf (fp, "%d", &first_num) != 1) {   /* read/validate int */
        fputs ("error: invalid file format, integer not first.\n", stderr);
        exit (EXIT_FAILURE);
    }

    if (!fgets (buf, MAXC, fp)) {   /* read/discard '\n' */
        fputs ("error: non-POSIX ending after 1st integer.\n", stderr);
        exit (EXIT_FAILURE);
    }

    /* allocate/validate storage for first_num integers */
    if (!(arr = malloc (first_num * sizeof *arr))) {
        perror ("malloc-arr");
        exit (EXIT_FAILURE);
    }

    while (fgets (buf, MAXC, fp)) { /* read lines until conversions made */
        char *p = buf,  /* nptr & endptr for strtol conversion */
            *endptr;
        if (*p == '\n')     /* skip blank lines */
            continue;
        while (nval < first_num) {  /* loop until nval == first_num */
            errno = 0;              /* reset errno for each conversion */
            long tmp = strtol (p, &endptr, 0);  /* call strtol */
            if (p == endptr && tmp == 0) {  /* validate digits converted */
                /* no digits converted - scan forward to next +/- or [0-9] */
                do
                    p++;
                while (*p && *p != '+' && *p != '-' && 
                        ( *p < '0' || '9' < *p));
                if (*p)     /* valid start of numeric sequence? */
                    continue;   /* go attempt next conversion */
                else
                    break;      /* go read next line */
            }
            else if (errno) {   /* validate successful conversion */
                fputs ("error: overflow/underflow in conversion.\n", stderr);
                exit (EXIT_FAILURE);
            }
            else if (tmp < INT_MIN || INT_MAX < tmp) {  /* validate int */
                fputs ("error: value exceeds range of 'int'.\n", stderr);
                exit (EXIT_FAILURE);
            }
            else {  /* valid conversion - in range of int */
                arr[nval++] = tmp;      /* add value to array */
                if (*endptr && *endptr != '\n') /* if chars remain */
                    p = endptr;         /* update p to endptr */
                else        /* otherwise */
                    break;  /* bail */
            }
        }
        if (nval == first_num)  /* are all values filled? */
            break;
    }
    if (nval < first_num) { /* validate required integers found */
        fputs ("error: EOF before all integers read.\n", stderr);
        exit (EXIT_FAILURE);
    }

    for (int i = 0; i < nval; i++)  /* loop outputting each integer */
        printf ("arr[%2d] : %d\n", i, arr[i]);

    free (arr);         /* don't forget to free the memory you allocate */

    if (fp != stdin)    /* and close any file streams you have opened */
        fclose (fp);

    return 0;
}

( примечание: окончательная проверка if (nval < first_num) после выхода из цикла чтения и преобразования)

Пример использования / Вывод

С вашим файлом примера вы получите следующее:

$ ./bin/fgets_int_file dat/int_file.txt
arr[ 0] : 1
arr[ 1] : 78
arr[ 2] : 45
arr[ 3] : 32
arr[ 4] : 2

Зачем идти на дополнительные неприятности?

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

A Более сложный входной файл

$ cat dat/int_file2.txt
5

1 78

      45

32 2 144 91 270

foo

Какие изменения необходимы для обработки получения тех же первых пяти целочисленных значений изэтот файл?(подсказка: нет - попробуйте)

Еще более сложный входной файл

Что, если мы снова повысим ставку?

$ cat dat/int_file3.txt
5

1 two buckle my shoe, 78 close the gate

      45 is half of ninety
foo bar
32 is sixteen times 2 and 144 is a gross, 91 is not prime and 270 caliber

baz

Чтоизменения необходимы, чтобы прочитать первые 5 целочисленных значений из этого файла?(подсказка: нет)

Но я хочу указать строку для начала чтения с

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

Пример чтения входных данных с заданной строки

$ cat dat/int_file4.txt
5

1,2 buckle my shoe, 7,8 close the gate

      45 is half of ninety
foo bar
32 is sixteen times 2 and 144 is a gross, 91 is not prime and 270 caliber

baz

   1 78 45 32 2 27 41 39 1111

a quick brown fox jumps over the lazy dog

Что мне нужно изменить?Единственные необходимые изменения - это изменения, чтобы пропустить первые строки 10 и начать цикл преобразования со строки 11.Для этого вам нужно добавить переменную для хранения значения строки, с которой начинается чтение целых чисел (скажем, rdstart), и переменную для хранения количества строк, чтобы мы знали, когда начинать чтение (например, linecnt),например,

    int first_num,
        *arr = NULL,
        nval = 0,
        rdstart = argc > 2 ? strtol(argv[2], NULL, 0) : 2,
        linecnt = 1;

( примечание: строка, с которой начинается целочисленное чтение, берется в качестве второго аргумента программы или используется строка по умолчанию 2, если ни одна не указана- и да, вы должны применить те же полные проверки к этому использованию strtol, но я оставляю вам)

Что еще нужно изменить?Немного.Вместо того, чтобы просто читать и отбрасывать '\n', оставленное fscanf, просто сделайте это linecnt-1 раз (или просто linecnt раз с момента инициализации linecnt = 1;).Для этого просто поместите ваш первый вызов в fgets в цикле (и измените сообщение об ошибке, чтобы оно имело смысл), например,

    while (linecnt < rdstart) { /* loop until linecnt == rdstart */
        if (!fgets (buf, MAXC, fp)) {   /* read/discard line */
            fputs ("error: less than requested no. of lines.\n", stderr);
            exit (EXIT_FAILURE);
        }
        linecnt++;  /* increment linecnt */
    }

Вот и все.(и обратите внимание, что он продолжит обрабатывать и первые 3 входных файла, просто пропустив второй параметр ...)

Пример вывода на начало строки 11

это работает?

$ ./bin/fgets_int_file_line dat/int_file4.txt 11
arr[ 0] : 1
arr[ 1] : 78
arr[ 2] : 45
arr[ 3] : 32
arr[ 4] : 2

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.Есть много способов сделать это, но, безусловно, если вы научитесь использовать strtol (все функции strtoX работают практически одинаково), вы значительно опередите игру в обработке числовых преобразований.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...