Продолжая ваш комментарий о возможности преобразования любого целочисленного представления значения в буфере в целое, ничто не заменит strtol
. В то время как вы можете использовать sscanf
, используя спецификатор %n
, чтобы получить количество потребляемых символов, чтобы знать, сколько нужно продвигать указатель - почему? Любое из ваших scanf
преобразований практически не имеет возможности сообщения об ошибках, кроме как успешно / неудачно. strtol
имеет встроенную возможность работать с буфером, конвертируя значения в long
по мере продвижения.
Как? Прототип для strtol
:
long int strtol(const char *nptr, char **endptr, int base);
Если строка, содержащая числа, предоставлена nptr
, endptr
будет установлен на один символ после последней цифры, использованной после успешного преобразования, а base
предоставляет основание числа преобразований (например, основание). 2, 8, 10, 16
и т. Д.), Где основание 0
позволит преобразовать из восьмеричного, десятичного или шестнадцатеричного числа в зависимости от того, начинается ли строковое представление: 0
(восьмеричное) или 0x
(шестнадцатеричное) или 1-9
( десятичное).
Таким образом, если вы передадите strtol
строку, содержащую число, при успешном преобразовании возвращаемое значение будет long
, которое можно проверить по INT_MIN/INT_MAX
, чтобы определить, находится ли оно в диапазоне int
, endptr
будет установлен на единицу после последней преобразованной цифры (настроен на использование для следующего значения), и все, что вам нужно, это установить p = endptr;
и продолжать.
Далее, вы знаете, если nptr == endptr
после преобразования - цифры не были преобразованы. strtol
также устанавливает errno
при переполнении / недополнении, так что у вас есть способ проверить успешное преобразование в long
. Кроме того, если вы хотите проверить, что будет дальше, endptr
указывает на начальный символ остальной части содержимого буфера. Вы узнаете вплоть до персонажа, что произошло с конверсией и что еще предстоит конвертировать.
Так как же вы используете это? Это действительно прямо вперед. Сначала давайте определим пару констант для числа целых чисел в массиве и максимального количества символов в вашем буфере, например,
#include <stdio.h>
#include <stdlib.h> /* for strtol */
#include <limits.h> /* for INT_MIN/INT_MAX */
#include <errno.h> /* for errno */
#define ARSZ 100
#define MAXC 1024
Теперь объявите ваш массив, ваш буфер для хранения ввода и счетчик для отслеживания количества целочисленных значений, преобразованных и сохраненных в массиве:
int arr[ARSZ] = {0};
char buf[MAXC] = "";
size_t n = 0;
Теперь давайте откроем файл для чтения с именем файла, предоставленным в качестве 1-го аргумента вашей программе (или прочитанным из stdin
по умолчанию, если не указан аргумент), и подтвердим, что у нас есть действительный поток открытых файлов, например,
/* 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;
}
Теперь пришло время приступить к чтению строк ввода и преобразованию любых символьных ссылок в числовые значения в long
с strtol
. Сначала прочитайте строку ввода в ваш буфер с помощью fgets
и объявите ваши nptr
(сокращенные до p
), endptr
и временное long
значение tmp
для хранения возврата из strtol
, например,
while (fgets (buf, MAXC, fp)) { /* read each line of input */
char *p = buf, *endptr; /* nptr & endptr for strtol */
long tmp; /* temp long for strtol */
В строке ввода вы теперь переходите к циклическому преобразованию значений с strtol
, проверяя, что (1) цифры были преобразованы, (2) переполнение / недополнение не произошло (например, подгонка значения в long
), ( 3) что значение находится в диапазоне int
, или (4) предупреждают, что значение превышает размер int
. (о вспомогательной функции nextdigit
мы поговорим позже):
/* protect array bounds, loop while not end of buf */
while (n < ARSZ && *p && *p != '\n') {
errno = 0; /* reset errno each iteration */
tmp = strtol (p, &endptr, 0); /* call strtol, update endptr */
if (p == endptr) /* validate digits converted */
fputs ("error: no digits converted.\n", stderr);
else if (errno) /* validate conversion */
fputs ("error: over/underflow occurred.\n", stderr);
/* validate tmp is in range of integer */
else if (INT_MIN <= tmp && tmp <= INT_MAX)
arr[n++] = tmp;
else
fputs ("error: value exceeds range of int.\n", stderr);
if (!(p = (char *)nextdigit (endptr))) /* get next digit */
break;
}
( примечание: условия цикла while
, n < ARSZ
- не хранить больше целых чисел, чем у вас есть место, в то время как *p && *p != '\n'
(ваш не в конце строки например, nul-символ или символ новой строки.) Вы можете опустить проверку новой строки, и она будет безобидно обрабатываться в теле цикла, но почему? Простая проверка полностью исключает тело цикла )
Что такое вспомогательная функция nextdigit()
?Из прочтения страницы strtol (3) - Linux вы знаете, что strtol
пропустит любой начальный пробел до начала следующего числа для преобразования, но что, если у вас есть запятая?отдельный файл или другие символы между вашими номерами?Очень прост в обращении, просто сканируйте вперед в вашем буфере, проверяя символы по мере продвижения, пока не найдете следующий 0-9
, или вы не найдете следующий +/-
и следующий символ после явного знака - 0-9
.
Это все, что делает nextdigit
, возвращая адрес начала следующего значения для преобразования, или NULL
, если нет других цифр для преобразования.
/* scan forward in 'p' to find next valid signed integer beginning */
const char *nextdigit (const char *p)
{
while (*p) {
if (('0' <= *p && *p <= '9') ||
((*p == '-' || *p == '+') && '0' <= *(p + 1) && *(p + 1) <= '9'))
return p;
p++;
}
return NULL;
}
( примечание: вы можете и должны включить ctype.h
и заменить чек ('0' <= *p && *p <= '9')
простым isdigit(*p)
, и то же самое с isdigit(*(p+1))
, но полный ручной тест был показан в целях иллюстрации)
Обратите внимание на код, как endptr
(начальный адрес для сканирования вперед) передается в nextdigit()
, а результат присваивается p
и проверено не NULL
до начала следующей итерации (с p
, подготовленным для повторной передачи в strtol
для следующего преобразования) - промойте и повторяйте, пока в вашем буфере не закончатся символы.
Подход к преобразованию буфера в целочисленные значения таким способом позволяет вам выбрать и преобразовать все целочисленные значения в строке - независимо от того, насколько грязен формат.
Если сложить все вместе, вы можете сделать:
#include <stdio.h>
#include <stdlib.h> /* for strtol */
#include <limits.h> /* for INT_MIN/INT_MAX */
#include <errno.h> /* for errno */
#define ARSZ 100
#define MAXC 1024
/* scan forward in 'p' to find next valid signed integer beginning */
const char *nextdigit (const char *p)
{
while (*p) {
if (('0' <= *p && *p <= '9') ||
((*p == '-' || *p == '+') && '0' <= *(p + 1) && *(p + 1) <= '9'))
return p;
p++;
}
return NULL;
}
int main (int argc, char **argv) {
int arr[ARSZ] = {0};
char buf[MAXC] = "";
size_t n = 0;
/* 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 (buf, MAXC, fp)) { /* read each line of input */
char *p = buf, *endptr; /* nptr & endptr for strtol */
long tmp; /* temp long for strtol */
/* protect array bounds, loop while not end of buf */
while (n < ARSZ && *p && *p != '\n') {
errno = 0; /* reset errno each iteration */
tmp = strtol (p, &endptr, 0); /* call strtol, update endptr */
if (p == endptr) /* validate digits converted */
fputs ("error: no digits converted.\n", stderr);
else if (errno) /* validate conversion */
fputs ("error: over/underflow occurred.\n", stderr);
/* validate tmp is in range of integer */
else if (INT_MIN <= tmp && tmp <= INT_MAX)
arr[n++] = tmp;
else
fputs ("error: value exceeds range of int.\n", stderr);
if (!(p = (char *)nextdigit (endptr))) /* get next digit */
break;
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < n; i++) { /* output results */
if (i && i %10 == 0) /* 10-values per row */
putchar ('\n');
printf (" %4d", arr[i]);
}
putchar ('\n'); /* tidy up with newline */
}
Теперь давайте посмотрим на строки, которые этот подход может обработать для преобразования в целочисленные значения, например
Пример входных файлов
Значения, разделенные пробелом:
$ cat dat/10int_space.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
Neзначения, разделенные строкой:
$ cat dat/10int_nl.txt
8572
-2213
6434
16330
3034
12346
4855
16985
11250
1495
Целые числа в беспорядке:
$ cat dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a
- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495
Пример использования / Вывод
Как справедлива процедура преобразования?
Разделенные пробелами:
$ ./bin/fgets_strtol_any_fixed <dat/10int_space.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
Разделенные строкой:
$ ./bin/fgets_strtol_any_fixed <dat/10int_nl.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
Нечестивый беспорядок:
$ ./bin/fgets_strtol_any_fixed <dat/10intmess.txt
error: no digits converted.
error: no digits converted.
error: no digits converted.
error: no digits converted.
error: no digits converted.
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
Все значения, независимо от того, разделены ли пробелы,Отделенные символом новой строки, или посыпанные посередине «Быстрая коричневая лиса ...», были преобразованы правильно.
Найдите время, чтобы переварить справочную страницу для strtol
, а затем разобраться, как это реализовано выше.После того, как вы овладеете strtol
(и strtoul
(для unsigned long
), strtod
(для double
) и т. Д. - все они работают практически одинаково, числовое преобразование практически невозможно)Я не знаю, если у вас есть дополнительные вопросы.