Разбор ввода с scanf в C
У меня возникло много проблем, пытаясь понять, как использовать scanf()
. Кажется, что он отлично работает с целыми числами, будучи довольно прямым scanf("%d", &i)
.
Когда я сталкиваюсь с проблемами, я использую scanf()
в циклах, пытаясь прочитать ввод. Например:
do {
printf("counter: %d: ", counter);
scanf("%c %c%d", &command, &prefix, &input);
} while (command != 'q');
-
Когда я вхожу в корректно структурированный ввод, например c P101
, он, кажется, снова зацикливается, прежде чем запрашивать меня. Это похоже на одно:
scanf("%c", &c)
в цикле while. Он повторит цикл дважды, прежде чем снова спросить меня. Что делает цикл дважды, и как его остановить?
-
Когда я вхожу в меньшее количество входных данных, которые программным образом не будут иметь другого символа или номера, например q
, нажатие клавиши ввода подсказывает мне ввести больше. Как мне получить scanf()
для обработки как одиночных, так и двойных символов?
Ответы
Ответ 1
Когда вы вводите "c P101
", программа фактически получает "c P101\n
". Большинство спецификаторов преобразования пропускают ведущие пробелы, включая символы новой строки, но %c
- нет. В первый раз вокруг всего, пока читается "\n
" , второй раз "\n" считывается в command
, "c
" считывается в prefix
и "P
", который не является числом, поэтому преобразование завершается с ошибкой, и "P101\n
" остается в потоке. В следующий раз "P
" будет сохранен в команде, "1
" будет сохранен в префикс, а 1
(от оставшегося "01
" ) будет сохранен на входе с "\n
" , все еще включенным поток в следующий раз. Вы можете исправить эту проблему, поместив пробел в начале строки формата, которая будет пропускать любые ведущие пробелы, включая символы новой строки.
Во втором случае происходит аналогичное, когда вы вводите "q
" , "q\n
" вводится в поток, первый раз вокруг "q
" читается, второй раз "\n
" читается, только на третьем вызове отображается второе "q
" , вы можете снова избежать проблемы, добавив символ пробела в начале строки формата.
Лучший способ сделать это - использовать что-то вроде fgets() для обработки строки за раз, а затем использовать sscanf() для синтаксического анализа.
Ответ 2
На вопрос 1, я подозреваю, что у вас возникла проблема с вашим printf()
, так как нет завершающего "\n".
Поведение printf
по умолчанию - это вывод буфера, пока он не будет иметь полную строку. То есть, если вы явно не измените буферизацию на stdout
.
Для вопроса 2 вы только что нажали одну из самых больших проблем с scanf()
. Если ваш ввод точно совпадает со строкой сканирования, которую вы указали, ваши результаты будут не такими, как вы ожидаете.
Если у вас есть опция, вы получите лучшие результаты (и меньше проблем с безопасностью), проигнорировав scanf()
и сделав собственный синтаксический анализ. Например, используйте fgets()
, чтобы прочитать целую строку в строке, а затем обработать отдельные поля строки — возможно, даже используя sscanf()
.
Ответ 3
Это действительно сломано! Я этого не знал
#include <stdio.h>
int main(void)
{
int counter = 1;
char command, prefix;
int input;
do
{
printf("counter: %d: ", counter);
scanf("%c %c%d", &command, &prefix, &input);
printf("---%c %c%d---\n", command, prefix, input);
counter++;
} while (command != 'q');
}
counter: 1: a b1
---a b1---
counter: 2: c d2
---
c1---
counter: 3: e f3
---d 21---
counter: 4: ---e f3---
counter: 5: g h4
---
g3---
Результат, похоже, соответствует ответу Роберта.
Ответ 4
Как только у вас есть строка, содержащая строку. то есть "C P101", вы можете использовать возможности синтаксического анализа sscanf.
См:
http://www.cplusplus.com/reference/clibrary/cstdio/sscanf.html
Ответ 5
Возможно, использование цикла while, а не цикла do... while поможет. Таким образом, условие проверяется перед выполнением кода.
Попробуйте следующий фрагмент кода:
while(command != 'q')
{
//statements
}
Кроме того, если вы знаете длину строки заранее, с циклами for можно работать намного проще, чем с циклами while. Есть также несколько хитрых способов динамического определения длины.
В качестве финальной напыщенной речи: scanf()
не "сосет". Он делает то, что делает, и это все.
Функция gets()
очень опасна (хотя и удобна для приложений без риска), поскольку изначально не выполняет никакой проверки ввода. Это ОЧЕНЬ широко известно как точка взлома, особенно атаки переполнения буфера, перезаписи пространства в регистрах, не выделенных для этой переменной. Поэтому, если вы решите использовать его, потратьте некоторое время на то, чтобы исправить и исправить ошибки.
Тем не менее, почти неизменно, либо fgets()
, либо POSIX getline()
должны использоваться для чтения строки - отмечая, что обе функции включают в себя новую строку во входной строке, в отличие от gets()
. Вы можете удалить завершающий символ новой строки из string
, прочитанного fgets()
или getline()
, используя string[strcspn(string, "\n")] = '\0';
- это работает надежно.
Ответ 6
scanf отстой, по причинам, которые вы уже обнаружили. Гораздо лучше использовать что-то, чтобы получить всю строку, а затем проанализировать ее.