Давайте начнем с некоторых основ.При чтении строк из файла (или строк ввода от пользователя) вы, как правило, захотите использовать строчно-ориентированную функцию ввода, такую как 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 */
После выполнения преобразования у вас есть три условия для проверки, прежде чем вы получите хорошее целочисленное преобразование,
- , если НЕТцифры были преобразованы, затем
p == endptr
(и для man-страницы возвращение устанавливается на ноль).Таким образом, чтобы проверить, возникло ли это условие, вы можете проверить: if (p == endptr && tmp == 0)
; - , если была ошибка во время преобразования цифр, независимо от того, какая ошибка произошла,
errno
будет установлено в ненулевое значениепозволяя вам проверить на ошибку в преобразовании с if (errno)
.Вы также можете углубиться в то, что произошло, как указано на странице руководства, но для целей проверки здесь достаточно знать, произошла ли ошибка;и, наконец, - , если цифры были преобразованы и ошибки не было, вы все еще не сделали этого.
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
работают практически одинаково), вы значительно опередите игру в обработке числовых преобразований.