Разбор ввода с 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 отстой, по причинам, которые вы уже обнаружили. Гораздо лучше использовать что-то, чтобы получить всю строку, а затем проанализировать ее.