Что я могу использовать для преобразования входных данных вместо scanf?
=======================================================================

Я очень часто видел, как люди отговаривали других от использования `scanf` и говорили, что есть лучшие альтернативы. Тем не менее, все, что я в итоге вижу, это "не использовать `scanf`" или "здесь правильная строка формата", и никогда не упоминаются примеры "лучших альтернатив".

Например, давайте возьмем этот фрагмент кода:

```
scanf("%c", &c);

```

Здесь читаются пробелы, которые остались во входном потоке после последнего преобразования. Обычно предлагаемое решение этого заключается в использовании:

```
scanf(" %c", &c);

```

или не использовать `scanf`.

Поскольку `scanf` является плохим, каковы некоторые опции ANSI C для преобразования входных форматов, которые обычно может обрабатывать `scanf` (такие как целые числа, числа с плавающей точкой и строки) без использования `scanf`?

Наиболее распространенные способы чтения ввода:

* используя `fgets` с фиксированным размером, который обычно предлагается, и
* используя `fgetc`, что может быть полезно, если вы читаете только один `char`.

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

* `strtoll`, чтобы преобразовать строку в целое число
* `strtof`/`d`/`ld`, чтобы преобразовать строку в число с плавающей запятой
* `sscanf`, что не так плохо, как простое использование `scanf`, хотя в нем есть большинство недостатков, упомянутых ниже
* Нет хороших способов проанализировать входные данные, разделенные разделителями, в простом ANSI C. Либо используйте `strtok_r` из POSIX, либо `strtok_s` из не очень широко внедренного Приложения K. Вы также можете [свернуть свой собственный](https://github.com/JL2210/minilibc/raw/master/src/string/strtok_r.c "My implementation of strtok_r") используя `strcspn` и `strspn`, так как он не требует специальной поддержки ОС.
* Это может быть излишним, но вы можете использовать лексеры и парсеры (наиболее распространенные примеры - `flex` и `bison`).
* Без преобразования, просто используйте строку

---

Поскольку вы точно не поняли, почему `scanf` плох в вашем вопросе, я уточню:

* С помощью спецификаторов преобразования `%[...]` и `%c`, `scanf` не использует пустое пространство. Это, по-видимому, не так широко известно, о чем свидетельствуют многочисленные дубликаты [этого вопроса](/questions/135330/scanf-leaves-the-new-line-char-in-the-buffer).
* Существует некоторая путаница относительно того, когда использовать унарный оператор `&` при обращении к аргументам `scanf` (особенно со строками).
* Очень легко игнорировать возвращаемое значение из `scanf`. Это может легко вызвать неопределенное поведение при чтении неинициализированной переменной.
* Очень легко забыть предотвратить переполнение буфера в `scanf`. `scanf("%s", str)` так же плохо, если не хуже, чем `gets`.
* Вы не можете обнаружить переполнение при преобразовании целых чисел с помощью `scanf`. **На самом деле, переполнение вызывает [неопределенное поведение](/questions/135330/scanf-leaves-the-new-line-char-in-the-buffer) в этих функциях.**

---



Почему `scanf` плохо?
=====================

Основная проблема заключается в том, что `scanf` никогда не предназначался для работы с пользовательским вводом. Он предназначен для использования с "идеально" отформатированными данными. Я процитировал слово "отлично", потому что оно не совсем верно. Но он не предназначен для анализа данных, которые столь же ненадежны, как и пользовательский ввод. По своей природе ввод пользователя не предсказуем. Пользователь неправильно понимает инструкции, делает опечатки, случайно нажимает ввод до того, как они будут сделаны, и т.д. Можно разумно спросить, почему функция, которая не должна использоваться для пользовательского ввода, читает из `stdin`. Если вы опытный пользователь * nix, объяснение не будет сюрпризом, но может запутать пользователей Windows. В системах * nix очень часто создаются программы, работающие по pipeопроводу, что означает, что вы отправляете выходные данные одной программы в другую, передавая по конвейеру `stdout` первой программы в `stdin` второй. Таким образом, вы можете убедиться, что вывод и ввод являются предсказуемыми. В этих условиях `scanf` действительно работает хорошо. Но при работе с непредсказуемым вводом вы рискуете всевозможными неприятностями.

Так почему же нет простых в использовании стандартных функций для пользовательского ввода? Здесь можно только догадываться, но я предполагаю, что старые хардкорные хакеры C просто думали, что существующие функции были достаточно хороши, даже если они очень неуклюжи. Кроме того, когда вы смотрите на типовые терминальные приложения, они очень редко читают пользовательский ввод из `stdin`. Чаще всего вы передаете весь пользовательский ввод в качестве аргументов командной строки. Конечно, есть исключения, но для большинства приложений пользовательский ввод очень незначительный.

Так что вы можете сделать?
==========================

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

```
#define bsize 100

char buffer[bsize];
int x,y;
float f, g;
int r;

printf("Enter two integers: ");
fflush(stdout); // Make sure that the printf is executed before reading
if(! fgets(buffer, bsize, stdin)) {
        fprintf(stderr, "An error occured\n");
        exit(EXIT_FAILURE);
}

if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) {
        fprintf(stderr, "An error occurred. You entered:\n%s\n", buffer);
        fprintf(stderr, "%d successful conversions", r);
        exit(EXIT_FAILURE);
}

printf("Enter two floats: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) {
        fprintf(stderr, "An error occurred\n");
        exit(EXIT_FAILURE);
}

if((r = sscanf(buffer, "%f%f", &f, &g)) != 2) {
        fprintf(stderr, "An error occurred. You entered:\n%s\n", buffer);
        fprintf(stderr, "%d successful conversions", r );
        exit(EXIT_FAILURE);
}

printf("You entered %d %d %f %f\n", x, y, f, g);

```

Подобные действия позволят устранить распространенную проблему, заключающуюся в том, что завершающий символ новой строки может мешать вводу гнезда. Но у него есть другая проблема, которая заключается в том, что строка длиннее, чем `bsize`. Вы можете проверить это с помощью `if(buffer[strlen(buffer)-1] != '\n')`. Если вы хотите удалить символ новой строки, вы можете сделать это с помощью `buffer[strcspn(buffer, "\n")] = 0`.

В общем, я бы посоветовал не ожидать, что пользователь введет ввод в каком-то странном формате, который вы должны анализировать для различных переменных. Если вы хотите назначить переменные `height` и `width`, не запрашивайте их одновременно. Разрешить пользователю нажимать ввод между ними. Кроме того, этот подход является очень естественным в одном смысле. Вы никогда не получите ввод от `stdin`, пока не нажмете ввод, так почему бы не всегда читать всю строку? Конечно, это может привести к проблемам, если строка длиннее буфера. Я помнил, чтобы упомянуть, что пользовательский ввод неуклюже в C? :)

Чтобы избежать проблем со строками длиннее буфера, вы можете использовать функцию, которая автоматически выделяет буфер соответствующего размера, вы можете использовать `getline()`. Недостаток в том, что вам понадобится `free` результат впоследствии.

Активизация игры
================

Если вы серьезно относитесь к созданию программ на C с пользовательским вводом, я бы порекомендовал взглянуть на такую библиотеку, как `ncurses`. Потому что тогда вы, вероятно, также захотите создавать приложения с некоторой графикой терминала К сожалению, вы потеряете некоторую переносимость, если вы сделаете это, но это дает вам гораздо лучший контроль над пользовательским вводом. Например, он дает вам возможность мгновенно прочитать нажатие клавиши, а не ждать, пока пользователь нажмет ввод.



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

ИМО, вот самые большие проблемы с `scanf`:

* **Риск переполнения буфера** - если вы не укажете ширину поля для спецификаторов преобразования `%s` и `%[`, вы рискуете переполнением буфера (пытаясь прочитать больше входных данных, чем размер буфера для хранения). К сожалению, нет хорошего способа указать это в качестве аргумента (как в случае с `printf`) - вы должны либо жестко закодировать его как часть спецификатора преобразования, либо выполнить некоторые макропрограммы.
* **Принимает входные данные, которые должны быть отклонены.** - Если вы читаете ввод с помощью спецификатора преобразования `%d` и набираете что-то вроде `12w4`, вы ожидаете, что `scanf` отклонит этот ввод, но это не так - он успешно преобразует и назначает `12`, оставляя `w4` во входном потоке, чтобы запутать следующее чтение.

Итак, что вы должны использовать вместо этого?

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

```
char input[100];
if ( !fgets( input, sizeof input, stdin ) )
{
  // error reading from input stream, handle as appropriate
}
else
{
  // process input buffer
}

```

Одна из странностей `fgets` заключается в том, что он будет хранить завершающий символ новой строки в буфере, если есть место, поэтому вы можете легко проверить, набрал ли кто-то больше ввода, чем вы ожидали:

```
char *newline = strchr( input, '\n' );
if ( !newline )
{
  // input longer than we expected
}

```

Как вы справляетесь с этим, зависит только от вас - вы можете либо отказаться от всего ввода из-под контроля, либо отобрать любой оставшийся ввод с помощью `getchar`:

```
while ( getchar() != '\n' ) 
  ; // empty loop

```

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

Чтобы токенизировать входные данные (разделить их на основе одного или нескольких разделителей), вы можете использовать `strtok`, но будьте осторожны - `strtok` изменяет свой ввод (он перезаписывает разделители с помощью ограничителя строки), и вы не можете сохранить его состояние (то есть вы не можете частично токенизировать одну строку, затем начать токенизацию другой, а затем продолжить с того места, где вы остановились в исходной строке). Есть вариант, `strtok_s`, который сохраняет состояние токенизатора, но AFAIK его реализация необязательна (вам нужно проверить, что `__STDC_LIB_EXT1__` определен, чтобы увидеть, доступен ли он). 

После того, как вы введете токены, если вам нужно преобразовать строки в числа (т.е., `"1234"` => `1234`), у вас есть варианты. `strtol` и `strtod` преобразуют строковые представления целых и действительных чисел в их соответствующие типы. Они также позволяют вам уловить проблему `12w4`, о которой я упоминал выше - один из их аргументов - указатель на первый символ, не преобразованный в строку:

```
char *text = "12w4";
char *chk;
long val;
long tmp = strtol( text, &chk, 10 );
if ( !isspace( *chk ) && *chk != 0 )
  // input is not a valid integer string, reject the entire input
else
  val = tmp;

```
 

В этом ответе я собираюсь предположить, что вы читаете и
интерпретация строк текста.
Возможно, вы предлагаете пользователю, который что-то печатает, и
ударяя ВОЗВРАТ. Или, возможно, вы читаете структурированные строки
текст из файла данных какого-то рода.

Поскольку вы читаете строки текста, имеет смысл организовать
ваш код вокруг библиотечной функции, которая читает, ну, строка
текст.
Стандартная функция - `fgets()`, хотя есть и другие (включая [`getline`](https://linux.die.net/man/3/getline)). И тогда следующий шаг должен интерпретировать
эта строка текста как-то.

Вот основной рецепт вызова `fgets` для чтения строки
текст:

```
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);

```

Это просто читает в одной строке текста и печатает его обратно.
Как написано, у него есть пара ограничений, которые мы рассмотрим в
минута. У этого также есть очень хорошая особенность: что число 512 мы
в качестве второго аргумента передается `fgets` - размер массива
`line` мы просим `fgets` прочитать. Этот факт - что мы можем
сказать `fgets`, сколько ему позволено читать - значит, что мы можем
убедитесь, что `fgets` не переполнит массив, читая слишком много
в это.

Итак, теперь мы знаем, как прочитать строку текста, но что, если мы действительно
хотел прочитать целое число, или число с плавающей запятой, или
один символ или одно слово? (То есть, что если
`scanf` вызов, который мы пытаемся улучшить, использовал формат
спецификатор типа `%d`, `%f`, `%c` или `%s`?)

Легко переосмыслить строку текста - строку - как любую из этих вещей.
Чтобы преобразовать строку в целое число, самое простое (хотя
Несовершенный) способ сделать это - позвонить в `atoi()`.
Чтобы преобразовать в число с плавающей запятой, существует atof().
(И есть и лучшие способы, как мы увидим через минуту.)
Вот очень простой пример:

```
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);

```

Если вы хотите, чтобы пользователь набрал один символ (возможно, `y` или
`n` как ответ да/нет), вы можете буквально взять первый
символ строки, например:

```
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);

```

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

Наконец, если вы хотите, чтобы пользователь набрал строку, определенно не содержащую
пробел, если вы хотите обработать строку ввода

```
hello world!

```

как строка `"hello"`, за которой следует что-то еще (что к чему
`scanf` формат `%s` сделал бы), ну, в этом случае, я
немного выпуклый, не так легко переосмыслить
таким образом, в конце концов, так что ответ на эту часть вопроса будет иметь
немного подождать.

Но сначала я хочу вернуться к трем вещам, которые я пропустил.

(1) Мы звонили

```
fgets(line, 512, stdin);

```

для чтения в массив `line`, где 512 - это размер
массив `line`, поэтому `fgets` знает, что не переполнить его. Но сделать
убедитесь, что 512 является правильным номером (особенно, чтобы проверить, если возможно
кто-то подправил программу для изменения размера) надо читать
обратно туда, где `line` был объявлен. Это неприятность, так
Есть два гораздо лучших способа синхронизировать размеры.
(a) используйте препроцессор, чтобы сделать имя для размера:

```
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);

```

Или (b) используйте оператор C `sizeof`:

```
fgets(line, sizeof(line), stdin);

```

(2) Вторая проблема в том, что мы не проверяли
ошибка. Когда вы читаете ввод, вы всегда должны проверять
возможность ошибки. Если по какой-либо причине `fgets` не может
прочитайте строку текста, которую вы просили, это указывает на это
возвращая нулевой указатель Таким образом, мы должны были делать такие вещи, как

```
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
    printf("Well, never mind, then.\n");
    exit(1);
}

```

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

```
printf("you typed: \"%s\"\n", line);

```

Если я запускаю это и набираю "Стив", когда он мне подсказывает, он печатает

```
you typed: "Steve
"

```

Это `"` во второй строке, потому что строка, которую он прочитал и
распечатка была на самом деле `"Steve\n"`.

Иногда эта дополнительная новая строка не имеет значения (например, когда мы позвонили
`atoi` или `atof`, так как они оба игнорируют любые дополнительные нечисловые значения
ввод после числа), но иногда это имеет большое значение. Так
часто мы хотим убрать эту новую строку. Есть несколько
способы сделать это, что я получу через минуту. (Я знаю, что был
говорю это много. Но я вернусь ко всем этим вещам, я обещаю.)

В этот момент вы можете подумать: "Я думал, вы сказали `scanf`
не было хорошо, и этот другой путь был бы намного лучше.
Но `fgets` начинает выглядеть как неприятность.
Звонить `scanf` было так просто! Я не могу продолжать использовать это? "

Конечно, вы можете продолжать использовать `scanf`, если хотите. (И для действительно
простые вещи, в некотором смысле это проще.) Но, пожалуйста, не
приходи ко мне плакать, когда он потерпит неудачу из-за одной из своих 17 причуд
и недостатки, или идет в бесконечный цикл из-за ввода вашего
не ожидал, или когда вы не можете понять, как использовать его, чтобы сделать
что-то более сложное. И давайте посмотрим на `fgets`
фактические неприятности:

1. Вы всегда должны указывать размер массива. Да, конечно,
это совсем не неприятность - это особенность, потому что буфер
переполнение - это действительно плохо.
2. Вы должны проверить возвращаемое значение. На самом деле, что мытье,
потому что для правильного использования `scanf` необходимо проверить его возвращение
значение тоже.
3. Вы должны убрать `\n` обратно. Это, я признаю, правда
неприятность. Я хотел бы, чтобы была стандартная функция, которую я мог бы указать
У тебя с этим не было этой маленькой проблемы. (Пожалуйста, никто
`gets`.) Но по сравнению с `scanf` 17 разных
неприятности, я возьму эту одну неприятность `fgets` в любой день.

Так как же раздеть эту новую строку? Три способа:

(а) Очевидный путь:

```
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';

```

(b) Tricky & компактный способ:

```
strtok(line, "\n");

```

К сожалению, этот не всегда работает.

(c) Другой компактный и слегка неясный способ:

```
line[strcspn(line, "\n")] = '\0';

```

И теперь, когда это не так, мы можем вернуться к другому
вещь, которую я пропустил из-за недостатков `atoi()` и `atof()`.
Проблема в том, что они не дают вам ничего полезного
признак успеха или неудачи: они тихо игнорируют
завершающий нечисловой ввод, и они спокойно возвращают 0, если есть
нет числового ввода вообще. Предпочтительные альтернативы - которые
Также есть определенные другие преимущества - это `strtol` и `strtod`.
`strtol` также позволяет использовать базу, отличную от 10, что означает, что вы можете
получить эффект (среди прочего) `%o` или `%x` с помощью `scanf`.
Но показ того, как правильно использовать эти функции, сам по себе является историей,
и было бы слишком отвлекает от того, что уже поворачивается
в довольно фрагментированный рассказ, поэтому я не собираюсь говорить
что-нибудь еще о них сейчас.

Остальная часть основного повествования касается того, что вы можете попробовать
разобрать, что сложнее, чем просто одно число или
характер. Что делать, если вы хотите прочитать строку, содержащую два
числа, или несколько разделенных пробелами слов, или конкретные
обрамление знаков препинания? То, где вещи становятся интересными, и
где вещи, вероятно, усложняются, если вы пытаетесь
делать вещи, используя `scanf`, и там, где гораздо больше
опций теперь, когда вы чисто прочитали одну строку текста, используя `fgets`,
хотя полная история всех этих вариантов могла бы
книга, так что мы только сможем поцарапать поверхность здесь.

1. Моя любимая техника - разбить линию на
"слова", разделенные пробелами, затем делайте что-то еще с каждым
"слово". Одна из основных стандартных функций для этого
`strtok` (у которого также есть свои проблемы, и который также оценивает целое
отдельное обсуждение). Мои собственные предпочтения - это отдельная функция
для построения массива указателей на каждый разбитый на части
"слово", функция, которую я описываю в
[эти заметки курса](https://www.eskimo.com/~scs/cclass/notes/sx10h.html).
В любом случае, когда у вас есть "слова", вы можете продолжить процесс
каждый, возможно, с тем же `atoi`/`atof`/`strtol`/`strtod`
функции, которые мы уже рассмотрели.
2. Как ни парадоксально, хотя мы тратили изрядное количество
время и усилия здесь, чтобы понять, как отойти от `scanf`,
еще один прекрасный способ справиться со строкой текста, которую мы только что прочитали
`fgets` должен передать его `sscanf`. Таким образом, вы в конечном итоге
большинство преимуществ `scanf`, но без большинства
недостатки.
3. Если ваш входной синтаксис особенно сложен, может быть целесообразно использовать библиотеку "regexp" для его анализа.
4. Наконец, вы можете использовать любые специальные решения для анализа
вы. Вы можете перемещаться по строке персонажа одновременно с
`char *` проверка указателя на ожидаемые символы. Или вы можете
поиск определенных символов с использованием таких функций, как `strchr` или `strrchr`,
или `strspn` или `strcspn`, или `strpbrk`. Или вы можете разобрать/конвертировать
и пропустите группы цифр, используя `strtol` или
`strtod` функции, которые мы пропустили ранее.

Там, очевидно, гораздо больше, что можно сказать, но, надеюсь,
это введение поможет вам начать.



> 
>   Что я могу использовать для анализа ввода вместо scanf?
> 
> 
> 

Вместо `scanf(some_format, ...)` рассмотрим `fgets()` с `sscanf(buffer, some_format_and %n, ...)`

Используя `" %n"`, код может просто определить, был ли успешно отсканирован весь формат, и что в конце не было лишнего мусора без пробелов.

```
// scanf("%d %f fred", &some_int, &some_float);
#define EXPECTED_LINE_MAX 100
char buffer[EXPECTED_LINE_MAX * 2];  // Suggest 2x, no real need to be stingy.

if (fgets(buffer, sizeof buffer, stdin)) {
  int n = 0;
  // add ------------->    " %n" 
  sscanf(buffer, "%d %f fred %n", &some_int, &some_float, &n);
  // Did scan complete, and to the end?
  if (n > 0 && buffer[n] == '\0') {
    // success, use 'some_int, some_float'
  } else {
    ; // Report bad input and handle desired.
  }

```
 

Давайте сформулируем требования разбора как:

* допустимый ввод должен быть принят (и преобразован в другую форму)
* неверный ввод должен быть отклонен
* **когда любой ввод отклонен, необходимо предоставить пользователю описательное сообщение, которое объясняет (понятным языком "легко понимаемый обычными людьми, не являющимися программистами") причину его отклонения (чтобы люди могли понять, как исправить проблема)**

Для простоты давайте рассмотрим одно простое десятичное целое число (введенное пользователем) и больше ничего. Возможные причины отклонения ввода пользователя:

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

Позвольте также определить "входные данные содержат недопустимые символы" правильно; и сказать, что:

* начальные и конечные пробелы будут игнорироваться (например, "  

5 "будет трактоваться как" 5")
* допускается ноль или одна десятичная точка (например, "1234" и "1234.000" обрабатываются так же, как и "1234")
* должна быть хотя бы одна цифра (например, "." отклонено)
* допускается не более одной десятичной точки (например, "1.2.3" отклонено)
* запятые, которые не находятся между цифрами, будут отклонены (например, ", 1234" отклонено)
* запятые после десятичной точки будут отклонены (например, "1234.000.000" отклонено)
* запятые после другой запятой отклоняются (например, "1, 234" отклоняется)
* все остальные запятые будут игнорироваться (например, "1234" будет рассматриваться как "1234")
* знак минус, который не является первым непробельным символом, отклоняется
* положительный знак, который не является первым непробельным символом, отклоняется

Исходя из этого, мы можем определить, что необходимы следующие сообщения об ошибках:

* "Неизвестный символ в начале ввода"
* "Неизвестный символ в конце ввода"
* "Неизвестный символ в середине ввода"
* "Число слишком мало (минимум....)"
* "Число слишком высокое (максимум....)"
* "Число не является целым числом"
* "Слишком много десятичных знаков"
* "Нет десятичных цифр"
* "Плохая запятая в начале номера"
* "Плохая запятая в конце числа"
* "Плохая запятая в середине номера"
* "Плохая запятая после десятичной точки"

С этого момента мы можем видеть, что подходящая функция для преобразования строки в целое число должна была бы различать очень разные типы ошибок; и что что-то вроде "`scanf()`" или "`atoi()`" или "`strtoll()`" совершенно и совершенно бесполезно, потому что они не дают вам никаких сведений о том, что было не так с входными данными (и используют совершенно неуместное и неуместное определение из того, что является/не является "допустимым вводом").

Вместо этого давайте начнем писать что-то бесполезное:

```
char *convertStringToInteger(int *outValue, char *string, int minValue, int maxValue) {
    return "Code not implemented yet!";
}

int main(int argc, char *argv[]) {
    char *errorString;
    int value;

    if(argc < 2) {
        printf("ERROR: No command line argument.\n");
        return EXIT_FAILURE;
    }
    errorString = convertStringToInteger(&value, argv[1], -10, 2000);
    if(errorString != NULL) {
        printf("ERROR: %s\n", errorString);
        return EXIT_FAILURE;
    }
    printf("SUCCESS: Your number is %d\n", value);
    return EXIT_SUCCESS;
}

```

**соответствовать заявленным требованиям; эта функция `convertStringToInteger()` может в конечном итоге составить несколько сотен строк кода сама по себе.**

Теперь, это был просто "анализ одного простого десятичного целого числа". Представьте, что вы хотите разобрать что-то сложное; например, список структур "имя, улица, номер телефона, адрес электронной почты"; или, может быть, как язык программирования. В этих случаях вам может понадобиться написать тысячи строк кода, чтобы создать анализ, который не является шуткой.

Другими словами...

> 
> Что я могу использовать для анализа ввода вместо scanf?
> 
> 
> 

Напишите (потенциально тысячи строк) кода самостоятельно, чтобы удовлетворить ваши требования.



Вот пример использования `flex` для сканирования простого ввода, в данном случае это файл чисел с плавающей запятой ASCII, который может быть в американском (`n,nnn.dd`) или европейском (`n.nnn,dd`) форматах. Это просто скопировано из гораздо более крупной программы, поэтому могут быть некоторые неразрешенные ссылки:

```
/* This scanner reads a file of numbers, expecting one number per line.  It  */
/* allows for the use of European-style comma as decimal point.              */

%{
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  #ifdef WINDOWS
    #include <io.h>
  #endif
  #include "Point.h"

  #define YY_NO_UNPUT
  #define YY_DECL int f_lex (double *val)

  double atofEuro (char *);
%}

%option prefix="f_"
%option nounput
%option noinput

EURONUM [-+]?[0-9]*[,]?[0-9]+([eE][+-]?[0-9]+)?
NUMBER  [-+]?[0-9]*[\.]?[0-9]+([eE][+-]?[0-9]+)?
WS      [ \t\x0d]

%%

[[[email protected]](/cdn-cgi/l/email-protection)#%&*/].*\n

^{WS}*{EURONUM}{WS}*  { *val = atofEuro (yytext); return (1); }
^{WS}*{NUMBER}{WS}*   { *val = atof (yytext); return (1); }

[\n]
.

%%

/*------------------------------------------------------------------------*/

int scan_f (FILE *in, double *vals, int max)
{
  double *val;
  int npts, rc;

  f_in = in;
  val  = vals;
  npts = 0;
  while (npts < max)
  {
    rc = f_lex (val);

    if (rc == 0)
      break;
    npts++;
    val++;
  }

  return (npts);
}

/*------------------------------------------------------------------------*/

int f_wrap ()
{
  return (1);
}

```
 

Другие ответы дают правильные детали низкого уровня, поэтому я ограничусь более высоким уровнем: во-первых, проанализируйте, как вы ожидаете, как будет выглядеть каждая строка ввода. Попробуйте описать ввод с помощью формального синтаксиса - если повезет, вы обнаружите, что он может быть описан с помощью обычной грамматики или, по крайней мере, безконтекстной грамматики. Если регулярной грамматики достаточно, вы можете закодировать конечный автомат, который распознает и интерпретирует каждую командную строку по одному символу за раз. Затем ваш код будет читать строку (как объяснено в других ответах), а затем сканировать символы в буфере через конечный автомат. В определенных состояниях вы останавливаете и конвертируете отсканированную до сих пор подстроку в число или что-то еще. Вы можете, вероятно, "свернуть свое", если это так просто; если вы обнаружите, что вам нужна полная контекстно-свободная грамматика, вам лучше выяснить, как использовать существующие инструменты синтаксического анализа (например, `lex` и `yacc` или их варианты).