Як очистити вхідний буфер в C?


84

У мене є така програма:

int main(int argc, char *argv[])
{
  char ch1, ch2;
  printf("Input the first character:"); // Line 1
  scanf("%c", &ch1); 
  printf("Input the second character:"); // Line 2
  ch2 = getchar();

  printf("ch1=%c, ASCII code = %d\n", ch1, ch1);
  printf("ch2=%c, ASCII code = %d\n", ch2, ch2);

  system("PAUSE");  
  return 0;
}

Як пояснив автор наведеного вище коду: Програма не працюватиме належним чином, оскільки в рядку 1, коли користувач натискає клавішу Enter, вона залишає у вхідному буфері 2 символ: Enter key (ASCII code 13)і \n (ASCII code 10). Отже, у рядку 2 він прочитає \nі не буде чекати, поки користувач введе символ.

Добре, я зрозумів. Але моє перше запитання: чому другий getchar()( ch2 = getchar();) не читає Enter key (13), а не \nсимвол?

Далі автор запропонував 2 способи вирішення таких проблем:

  1. використання fflush()

  2. напишіть таку функцію:

    void
    clear (void)
    {    
      while ( getchar() != '\n' );
    }
    

Цей код працював насправді. Але я не можу пояснити, як це працює? Оскільки в операторі while, який ми використовуємо getchar() != '\n', це означає читати будь-який окремий символ, крім '\n'? якщо так, у вхідному буфері все ще залишається '\n'символ?

Відповіді:


89

Програма не працюватиме належним чином, оскільки в рядку 1, коли користувач натискає клавішу Enter, вона залишає у вхідному буфері 2 символ: клавішу Enter (код ASCII 13) та \ n (код ASCII 10). Отже, у рядку 2 він прочитає \ n і не буде чекати, поки користувач введе символ.

Поведінка, яку ви бачите в рядку 2, є правильною, але це не зовсім правильне пояснення. У потоках текстового режиму не має значення, які закінчення рядків використовує ваша платформа (чи повернення каретки (0x0D) + подача лінії (0x0A), гола CR або гола LF). Про це подбає бібліотека середовища виконання C: ваша програма бачитиме лише '\n'нові рядки.

Якщо ви набрали символ і натиснули клавішу Enter, тоді цей введений символ буде прочитаний рядком 1, а потім '\n'буде прочитаний рядком 2. Дивіться, я використовую scanf %cдля читання відповіді Y / N, але пізніше введення пропускається. у розділі поширених запитань про comp.lang.c.

Що стосується запропонованих рішень, дивіться (знову ж таки з посібника із питань comp.lang.c):

які в основному заявляють, що єдиним портативним підходом є:

int c;
while ((c = getchar()) != '\n' && c != EOF) { }

Ваш getchar() != '\n'цикл працює, оскільки як тільки ви зателефонуєте getchar(), повернутий символ уже був видалений із потоку введення.

Крім того, я відчуваю зобов’язання відмовити вас scanfповністю використовувати : Чому всі кажуть не використовувати scanf? Що я повинен використовувати замість цього?


1
Це буде висіти, чекаючи, поки користувач натисне Enter. Якщо ви хочете просто очистити stdin, використовуйте терміни безпосередньо. stackoverflow.com/a/23885008/544099
Ішмаель

@ishmael Ваш коментар не має сенсу, оскільки натискання клавіші Enter не вимагає очищення stdin; спочатку потрібно отримати вхідні дані. (І це єдиний стандартний спосіб читання вводу на C. Якщо ви хочете мати можливість читати клавіші негайно, вам доведеться використовувати нестандартні бібліотеки.)
jamesdlin

@jamesdlin У Linux, getchar () буде чекати, поки користувач натисне Enter. Тільки тоді вони вирвуться з циклу while. Наскільки мені відомо, termios - це стандартна бібліотека.
ішмаель

@ishmael Ні, ви помиляєтесь в обох пунктах. Це питання про те, як щодо очищення решти введених даних із stdin після прочитання вводу, а також у стандартній C, якщо для введення спочатку потрібно ввести рядок і натиснути Enter. Після введення цього рядка getchar()ці символи буде прочитано та повернуто; користувачеві не потрібно буде натискати Enter інший раз. Крім того, в той час як termios може бути загальним на Linux , це не є частиною стандартної бібліотеки C .
jamesdlin

в чому причина scanf(" %c", &char_var)роботи та ігнорування нового рядка, просто додавши пробіл перед специфікатором% c?
Катєліна Сірбу,

44

Ви можете зробити це (також) таким чином:

fseek(stdin,0,SEEK_END);

Ого ... до речі, чи є стандарт, скажімо fseek, доступним для stdin?
ikh

3
Не знаю, думаю, що це не згадується, але stdin - це FILE *, а fseek приймає параметр FILE *. Я протестував його, і він працює на Mac OS X, але не на Linux.
Рамі Аль Зухурі

Будь ласка, поясніть. Це буде чудова відповідь, якщо автор напише наслідки цього методу. Чи є якісь проблеми?
EvAlex

7
@EvAlex: з цим є багато проблем. Якщо це працює у вашій системі, чудово; якщо ні, то це не дивно, оскільки ніщо не гарантує, що воно буде працювати, коли стандартний вхід - це інтерактивний пристрій (або невидимий пристрій, такий як труба, розетка або FIFO, але назвемо лише кілька інших способів, якими він може не вдається).
Джонатан Леффлер

Чому це має бути SEEK_END? Чи можемо ми замість цього використовувати SEEK_SET? Як поводиться FP stdin?
Раджеш,

11

Портативний спосіб очищення до кінця рядка, який ви вже намагалися частково прочитати, це:

int c;

while ( (c = getchar()) != '\n' && c != EOF ) { }

Це зчитує та відкидає символи, поки не отримає \nсигнал, що сигналізує про кінець файлу. Він також перевіряє проти, EOFякщо вхідний потік закриється до кінця рядка. Тип cповинен бути int(або більшим), щоб мати можливість зберігати значення EOF.

Немає портативного способу з’ясувати, чи є після поточного рядка ще рядки (якщо їх немає, тоді getcharбуде заблоковано введення).


чому while ((c = getchar ())! = EOF); не працює? Це нескінченна петля. Не вдалося зрозуміти розташування EOF у stdin.
Раджеш

@Rajesh Це буде очищено, доки вхідний потік не буде закритий, чого не буде, якщо пізніше з’явиться більше вхідних даних
MM

@Rajesh Так, ти маєш рацію. Якщо на stdin немає нічого для змивання, коли це викликається, він заблокує (зависне) через потрапляння в нескінченний цикл.
Джейсон Енокс

11

Рядки:

int ch;
while ((ch = getchar()) != '\n' && ch != EOF)
    ;

не читає лише символи перед linefeed ( '\n'). Він зчитує всі символи в потоці (і відкидає їх) аж до наступного подавання рядка (або зустрічається EOF). Щоб тест був істинним, він повинен спочатку прочитати подачу лінії; отже, коли цикл зупиняється, подача рядка була останнім прочитаним символом, але його було прочитано.

Що стосується того, чому він читає подачу лінії замість повернення каретки, це тому, що система переклала повернення в подачу лінії. Коли натискається клавіша Enter, це сигналізує про кінець рядка ... але потік замість цього містить подачу рядка, оскільки це звичайний маркер кінця рядка для системи. Це може залежати від платформи.

Крім того, використання fflush()на вхідному потоці працює не на всіх платформах; наприклад, це зазвичай не працює на Linux.


Примітка: while ( getchar() != '\n' );це нескінченний цикл повинен getchar()повертатися EOFчерез кінець файлу.
chux

Для перевірки на EOF також int ch; while ((ch = getchar()) != '\n' && ch != EOF);можна використовувати
Дмитро

8

Але я не можу пояснити, як це працює? Оскільки в операторі while ми використовуємо getchar() != '\n', це означає читання будь-якого окремого символу, крім '\n'?? якщо так, у вхідному буфері все ще залишається '\n'символ ??? Я щось нерозумію ??

Ви можете не усвідомлювати, що порівняння відбувається після getchar() видалення символу з вхідного буфера. Отже, коли ви досягаєте '\n', він споживається, і тоді ви вириваєтеся з циклу.


6

Ви можете спробувати

scanf("%c%*c", &ch1);

де% * c приймає та ігнорує новий рядок

ще один метод замість fflush (stdin), який викликає невизначену поведінку, яку ви можете написати

while((getchar())!='\n');

не забувайте про крапку з комою після циклу while


2
1) "%*c"сканує будь-який символ (і не зберігає його), будь то новий рядок чи щось інше. Код покладається на те, що 2-й символ - це новий рядок. 2) while((getchar())!='\n');- це нескінченний цикл, який повинен getchar()повернутися EOFчерез кінець файлу.
chux

1
другий метод залежить від стану, як новий рядок, нульовий символ, EOF тощо (над ним був новий рядок)
капіл

1

Я стикаюся з проблемою, намагаючись реалізувати рішення

while ((c = getchar()) != '\n' && c != EOF) { }

Я публікую невелике коригування "Код B" для тих, хто, можливо, має ту ж проблему.

Проблема полягала в тому, що програма продовжувала ловити символ '\ n', незалежно від символу Enter, ось код, який дав мені проблему.

Код А

int y;

printf("\nGive any alphabetic character in lowercase: ");
while( (y = getchar()) != '\n' && y != EOF){
   continue;
}
printf("\n%c\n", toupper(y));

і коригування полягало в тому, щоб "зловити" символ (n-1) безпосередньо перед тим, як буде обчислюватися умовний у циклі while, ось код:

Код В

int y, x;

printf("\nGive any alphabetic character in lowercase: ");
while( (y = getchar()) != '\n' && y != EOF){
   x = y;
}
printf("\n%c\n", toupper(x));

Можливе пояснення полягає в тому, що для того, щоб цикл while розірвався, він повинен призначити значення '\ n' змінній y, тож це буде останнє призначене значення.

Якщо я щось пропустив із поясненням, кодом А чи кодом Б, скажіть, будь ласка, я ледве новачок у с.

сподіваюся, це комусь допоможе


Вам слід мати справу зі своїми " очікуваними " символами всередині whileциклу. Після циклу ви можете вважати, stdinщо він чистий / чистий і yбуде містити або ' \n', або EOF. EOFповертається, коли немає нового рядка і буфер вичерпано (якщо введення переповнює буфер до [ENTER]натискання). - Ви ефективно використовуєте, while((ch=getchar())!='\n'&&ch!=EOF);щоб пережовувати кожен символ у stdinвхідному буфері.
veganaiZe

0
unsigned char a=0;
if(kbhit()){
    a=getch();
    while(kbhit())
        getch();
}
cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;

-або-

використовувати можливо використовувати _getch_nolock().. ???


Питання стосується С, а не С ++.
Spikatrix

1
Зверніть увагу, що kbhit()і getch()є функціями, декларованими <conio.h>та доступними лише в стандартній комплектації для Windows. Їх можна емулювати на Unix-подібних машинах, але це вимагає певної обережності.
Джонатан Леффлер

0

Ще одне рішення, про яке ще не згадувалося, полягає у використанні: rewind (stdin);


2
Це повністю зламає програму, якщо stdin буде перенаправлено у файл. А для інтерактивного введення не гарантовано нічого робити.
Мельпомена


-1

Короткий, портативний та оголошений у stdio.h

stdin = freopen(NULL,"r",stdin);

Не зациклюється на нескінченному циклі, коли на stdin немає нічого для змиву, як наведено наступний рядок:

while ((c = getchar()) != '\n' && c != EOF) { }

Трохи дорого, тому не використовуйте його в програмі, яка потребує багаторазового очищення буфера.

Вкрав у колеги :)


Не працює на Linux. Натомість використовуйте терміни безпосередньо. stackoverflow.com/a/23885008/544099
Ішмаель

2
Не могли б ви докладно розповісти, чому добре відома лінія - це можливий нескінченний цикл?
Cacahuete Frito

Вся причина freopenіснує в тому, що ви не можете переносити stdin. Це макрос.
Мельпомена

1
ishmael: Усі наші комп’ютери в Cyber ​​Command - це Linux, і на них це чудово працює. Я протестував його як на Debian, так і на Fedora.
Джейсон Енокс,

Те, що ви називаєте "нескінченним циклом", - це програма, яка очікує на введення. Це не одне й те саме.
jamesdlin

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.