Как работает флаш?

Я не уверен, правильно ли я понимаю, как работает сброс в C. Я просто не могу заставить его работать, как описано в нескольких руководствах и справочниках. Вот пример с комментариями:

#include <stdio.h>


int main(void) {
    int  x;
    char ch;

    printf("Prompt: ");
    scanf("%d", &x); /* I key in 67 and press Enter. At this very moment,
                        the input buffer should contain the ASCII codes
                        for the numbers 6 and 7 as well as the ASCII code
                        for the newline character, which is 10. The scanf
                        function is going to find the ASCII codes for 6
                        and 7, convert them to the integer 67, assign it
                        to the variable x and remove them from the
                        buffer. At this point, what still remains in the
                        buffer is the newline character. */
    fflush(stdin);   /* I want to do some more input. So, I flush the
                        buffer to remove anything that still might be
                        there, but it doesn't really look like fflush is
                        doing that. */

    printf("Prompt: "); /* I'm not going to be able to get my hands on
                           the following line of code because fflush is
                           not doing its job properly. The remaining
                           newline character is going to be read into the
                           variable ch automatically, thus not allowing
                           me to enter anything from the keyboard. */
    scanf("%c", &ch);

    printf("x: %d, ch: %d\n", x, ch);
    /*
       OUTPUT:
       Prompt: 67
       Prompt: x: 67, ch: 10
    */


    return 0;
}

person Community    schedule 17.05.2016    source источник


Ответы (3)


Помимо того, что fflush() не определяется во входных потоках, как указал Сурав...

Я набираю 67 и нажимаю Enter. В этот самый момент входной буфер должен содержать коды ASCII для чисел 6 и 7, а также код ASCII для символа новой строки, который равен 10. Функция scanf найдет коды ASCII для чисел 6 и 7, преобразует преобразовать их в целое число 67, присвоить его переменной x и удалить из буфера. На данный момент в буфере все еще остается символ новой строки.

И если бы пользователь ввел что-то, что не является числом, могли бы произойти другие вещи.

Если перед первой цифрой были нецифры, x не будет инициализирован. Вы бы не знали, потому что не проверяли возвращаемое значение scanf().

Пользователь мог предвидеть следующее приглашение (для символа) и ввести что-то вроде 67 x, ожидая, что 67 удовлетворит ваше первое приглашение, а x — второе. (И он не будет рад, что ваша программа удалила часть его записи.)

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

Поэтому мой общий совет: не использовать функции *scanf() для пользовательского ввода, а вместо этого использовать fgets() для чтения пользовательского ввода построчно и анализировать ввод в памяти на досуге.

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

Ниже приведен только грубый набросок; в зависимости от ваших приложений вы захотите организовать это по-разному:

const size_t BUFFERSIZE = 1024;
char buffer[ BUFFERSIZE ];
long x;
char ch;
printf( "Prompt: " );
if ( fgets( buffer, BUFFERSIZE, stdin ) == NULL )
{
    // Error occurred, use feof() / ferror() as appropriate
}
else
{
    size_t len = strlen( buffer );
    if ( buffer[ len - 1 ] != '\n' )
    {
        // The line entered was too long for the buffer,
        // there is unread input. Handle accordingly, e.g.
        // resizing the buffer and reading the rest.
    }
    else
    {
        // You got the whole line; blot out the newline
        buffer[ len - 1 ] = '\0';

        // Assuming a decimal number first
        char * curr = buffer;
        errno = 0;
        long x = strtol( curr, &curr, 10 );
        if ( errno == ERANGE )
        {
            // Number exceeds "long" range, handle condition
        }
        else if ( curr == buffer )
        {
            // What you got was not a number, handle condition
        }

        // Keep parsing until you hit end-of-string
    }
}
person DevSolar    schedule 17.05.2016
comment
size_t len = strlen( buffer ); if ( buffer[ len ] != '\n' ) всегда верно, так как buffer[ len ] равно 0. Предложите stackoverflow.com/q/2693776/2410359 - person chux - Reinstate Monica; 17.05.2016
comment
@chux: Проклятие написания таких ответов в тестовых примерах с перерывом в офисе: нет тестирования, следует подумать. Исправлена. - person DevSolar; 17.05.2016

Не делайте fflush(stdin);, это вызовет неопределенное поведение.

Цитирую C11,

Если поток указывает на поток вывода или поток обновления, в котором не была введена самая последняя операция, функция fflush вызывает доставку любых незаписанных данных для этого потока в хост-среду для записи в файл; в противном случае поведение не определено.

и stdin не является выходным потоком.

person Sourav Ghosh    schedule 17.05.2016
comment
Хм, тогда что мне использовать вместо этого? - person ; 17.05.2016
comment
@МихаилР. существует много способов... один из подходов - использовать scanf("%d%*c", &x); для отмены новой строки.. - person Sourav Ghosh; 17.05.2016
comment
@SouravGhosh, что, если они введут пробел перед нажатием клавиши ввода? - person M.M; 17.05.2016
comment
@M.M Мэтт, сэр, именно поэтому я упомянул слишком широко во-первых, while (getchar() != \n); также будет альтернативой. :) - person Sourav Ghosh; 17.05.2016

Для завершения:

Некоторые реализации определяют fflush для входных потоков. Примерами являются msvcrt от Microsoft и библиотека GNU. Другие, такие как BSD libc, могут дополнительно предоставлять отдельная функция для очистки буфера.

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

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

Кроме того, вне stdio libc могут быть буферы, которые не затронуты.

Поэтому вы должны явно читать и отбрасывать данные, которые вам не нужны.

person a3f    schedule 17.05.2016