Fatal
написал 14 июля 2004 года в 22:23 (11140 просмотров)
Ведет себя
как мужчина; открыл 123 темы в форуме, оставил 484 комментария на сайте.
Как чистить стандартный поток вывода, после неправильного введенного символо с помощью функции scanf? с помощью fflush не получается — это работает только в консоли под Window/Dos. Я пробую писать программы под FreeBSD, а там ничерта не работает fflush или ведет себя по другому. Почему так? Я пробовал также с функцией clearerr(stdin), тоже не выходит.
#include
#include
int main(void)
{
int i=0,t,n;
do{
t=scanf(«%d»,&n);
fflush(stdin);
//clearerr(stdin);
if(t==1)break;
++i;
}while(i<3);
printf(«\ni=%d\n»,i);
return 0;
}
Последние комментарии
- OlegL, 17 декабря 2023 года в 15:00 → Перекличка 21
- REDkiy, 8 июня 2023 года в 9:09 → Как «замокать» файл для юниттеста в Python? 2
- fhunter, 29 ноября 2022 года в 2:09 → Проблема с NO_PUBKEY: как получить GPG-ключ и добавить его в базу apt? 6
- Иванн, 9 апреля 2022 года в 8:31 → Ассоциация РАСПО провела первое учредительное собрание 1
- Kiri11.ADV1, 7 марта 2021 года в 12:01 → Логи catalina.out в TomCat 9 в формате JSON 1
ecobeing.ru
Экология и вегетарианство на благо всем живым существам Планеты.
попробуйте
#include
#include
int main(void)
{
int i=0,t,n;
do{
t=scanf(«%d»,&n);
if ( t <= 0 ) {
if ( ferror( stdin ) || feof( stdin ) ) clearerr( stdin );
else rewind( stdin );
}
else
break;
++i;
} while(i<3);
printf(«\ni=%d\n»,i);
return 0;
}
Удачи
— sas
PS code was not compiled and so may have errors
Так почему же в FreeBSD не работает fflush? Почему он не очищает поток?
Потому что:
1 На сколько я знаю fflush stdin не может гарантированно работать по стадартам
2 Он не очищает аппаратных буферов
3 scanf имеет собственный буфер
Можно и по другому чистить:
scanf( «%*[^\n]» );
что означает пропусти весь инпут до '\n'
Я не знаю что более портабельно — rewind или очистка буфера. Пока я испльзую rewind. На всех «моих» платформах он работает
Кстати scanf семейство я не люблю. Гораздо более гибко читать строку и парзить самому, т.к. например в Вашем коде не будет работать даже строка, где будет более одного целого.
Например:
1 2 3 4 5
Удачи
— sas
ЗЫ
После моего ответа посмотрел в стандарте о том что лучше использовать rewind или очистку буфера и нашел, что rewind НЕ ГОДИТСЯ для использования! По стандарту он может применятся только к файлам, которые поддерживают позционирование. stdin НЕ ПОДДЕРЖИВАЕТ его!
Правильнее использовать scanf очистку! Кстати предидущее решение НЕ будет работать если входная строка содержит более одного '\n' В этом случае надо вызвать еще один scanf т.е.
if ( 0 == t ) {
scanf( «%*[^\n]» );
scanf( «%*c» ); /* уберем оставшиеся '\n' */
}
Вот теперь я надеюсь все
1. fflush — это стаднартная функция ANSI и она обязана работать по стандартам!
2. к сожалению я не понимаю, что такое аппаратный буфер …
3. scanf выводит в стандартный поток вывода — stdin
scanf( «%*[^\n]» ) — эта строка будет пропускать и оставлять их в stdin? Если да, то это не годится, т.к. это будет влиять на другие функции, которые используют stdin. К примеру:
…
char s[1000], b[1000];
scanf(«%s»,&s);/*ввести слова через пробелы и нажать в конце Enter: ыаыва ываыва ываыв Enter*/
/*сканф затяпает только одно слово до пробела, а остальные оставит в буфере stdin*/
fgets(b,999,stdin);/*эта функция не будет ожидать ввода, он съест все что осталось после сканфа в stdin*/
…
В man описана еще одна функция (не понятно почему она объявлена в stdio — это ведь не стандартная функция) int fpurge(FILE*). Так вот если ее подставить вместо fflush(stdin), т.е. fpurge(stdin), то все будет чики-пуки, что на местно жаргоне означает хорошо.
Функция fflush возвращает код ошибки 9 (Bad file descriptor), а функция fpurge возвращает 0 (OK). Не понятно почему fflush не понравился дескриптор стандартного вывода?!
И еще я заметил, что fputs(«Hello!\n»,stdin) тоже не фурычит. Т.е. если после этого вызова написать fgets он будет ожидать ввода, хотя по логике он должен взять из буфера stdin. В чем может быть проблема, может надо открыть заново stdin. Но как это сделать? Чертовщина какая-то! Конечно можно fflush заменить на fpurge(File*), но нужна стандартная функция, которая бы обеспечивала переносимость. Конечно вывернуться можно, но все же хочется по-нормальному. Это глюк BSD?!
У кого есть возможность попробовать в Linux кусок кода, который я привел ниже, попробуйте, пожалуйста. И напишите будет ли нормально работать fflush в линуксе.
Правильно. Но Вам надо внимательнее читать man:
<<
The function fflush() forces a write of all buffered data for the given
output or update stream via the stream’s underlying write function. The
open status of the stream is unaffected.
>>
stdin == input stream — Только чтение из него возможно
Аппаратный буфер клавиатуры — там хранятся scan codes нажатых клавиш до того как они будут прочитаны драйвером клавиатуры
scanf читает из stdin
scanf считывает и удаляет из буфера stdin пpочитанные символы. Опять же Вам надо внимательнее читать man. Звездочка после процента означает для scanf следующее: возьми из буфера и забудь
[^\n] — возьми любой символ, только не '\n’, что означает что буфер stdin после этого вызова будет содержать от 1 до N '\n'
Второй предложенный вызов уберет и эти оставшиеся '\n'
Опять же из-за аппаратного буфера не может быть 100% уверенности что после этих вызовов буфер stdin будет полностью пустым
2 строчки кода и 2 ошибки включая одну критическую:
1. Советую никогда не использовать числа при объявлении массивов, только константы ( define в С или const int|long в С++)
#define BUFSZ 1000
2. В первый вызов scanf Вы передаете указатель на указатель. Это и есть критическая ошибка
3. Опять же читайте man: fgets читает максимум size — 1 символ или до появления '\n' если он появится до size — 1. Это означает что:
fgets( b, BUFSZ, stdin );
fpurge — расширение BSD В Linux например есть __fpurge отличается тем, что не возвращает значения. Как Вы уже верно заметили — это не стандартная функция и использовать ее поэтому не рекомендуется
Чтобы понять почему она работает опять читаем внимательно man:
<<
The function fpurge() erases any input or output buffered in the given
stream. For output streams this discards any unwritten output. For
input streams this discards any input read from the underlying object but
not yet obtained via getc(3); this includes any text pushed back via
ungetc.
>>
те она работает, в отличии от fflush, и на input streams
Cм выше или man fflush работает на output потоках
Опять же это не глюк BSD. Вы не можете писать в stdin напрямую. Абсолютно не понятно чего Вы хотите добиться в Вашем приложении.
Исходя из вышеизложенного fflush НЕ ДОЛЖЕН РАБОТАТЬ по Вашему сценарию.
ИТОГ:
1) Будьте внимательнее и читайте man
2) Старайтесь эксперементировать и объяснять сами
3) Иногда лучше задать вопрос не о конкретной ошибке, которая у Вас есть, а о Вашем замысле в целом. Это может помочь выявить принципиальные ошибки в замысле/алгоритме/подходе к проблеме
Успехов
— sas
Спасибо большое за такой расклад!
Про man — я не очень хорошо понимаю английский.
Ну так тогда почему fflush чистит поток stdin в Windows (Borland С++, Visual С++).
Я конкретной программы не разробатываю, я просто учюсь, и хочу понять почему стандартная функция fflush работает по разному на разных платформах. А преподы знать не знают про Unix
Ну технический английский Вам просто необходимо подтягивать если Вы собираетесь заниматься программированием. Не так страшен черт как его малюют, хотя в начале будет и тяжело. Покупайте словари и вперед.
По стандарту ANSI С и 1999 года fflush на input потоках не определен.
MS же в своем helpe четко говорит что для инпут потоков fflush чистит буфер. Кроме того MS уверяет что их реализация поддерживает ANSI C :)
MS в своем репертуаре.
Удачи
— sas
PS Купите так называемую белую книгу Кернигана и Ричи «The C Programming Language (ANSI C)» и пусть она станет Вашим проводником :) Ничего лучше по С я не читал.
Вы бы не могли, если вы знаете, какой-нибудь адрес в инете где описывается стандарт ANSI
Я советую изучить Kernigan & Ritchi (см выше) сначала. Он 100% должен быть в магазинах на русском.
http://www.eskimo.com/~scs/C-faq/q11.2.html
Annotated ANSI C Вы можете купить на amazon.com за 27$ (не новый)
Драфт С стандарта можно скачать с
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n869/
Имейте в виду, что окончательный вариант может несколько (не значительно) отличаться от драфта
Удачи
— sas
>Опять же из-за аппаратного буфера не может быть 100% уверенности что после этих вызовов буфер stdin будет полностью пустым
sas, Вы не могли бы объяснить почему из-за аппаратного буфера не может быть 100% уеверенности и четакое Драфт.
Спасибо.
Вы говорили, что лучше спрашивать про алгоритм. Так вот мне нужно спросить у пользоватьеля выбор из меню, где номер пункта вводится с клавиатуры, принимается ввод scanf. Далее мне нужно запросить у пользователя строку — имя родственика (так требует HomeWork). А fgets проскакивает из-за того-что буфер stdin не пуст.
Можно конечно вызавать fgets 2 раза (первый раз для очистки потока, второй для запроса) или в функции menu, которя приводится ниже, но в этом есть недостатки. Надо дополнительно выделять память. В моем случае выделять память нужно (т.к. массив уже существует для ввода строки и можно использовать его). Но хотелось бы функцию menu сделать самостоятельной, чтобы она самостоятельно очищала поток, и в тоже время не хочется заводить массив внутри этой функции (тем более неизвестно какой длины он понадобится). Ваш метод со scanf не помогает.
Подскажите, пожалуйста как в этой ситуации быть. Как чистить поток после scanf? Хотелось бы это провернуть со стандартными функциями.
#include
#include
int menu(char*str, …);
int main(void){
char s[N];
int test;
test=menu(«Menu»,«One»,«Two»,«Three»);
fgets(s,N,stdin);
return 0;
}
int menu(char*str, …){
int n,i,test,try=-5;
char*s;
va_list p;
clearscr();
do{
i=0;
va_start(p,str);
puts(str);
puts(«0. Exit»);
while(NULL!=(s=va_arg(p,char*)))
printf(«%d. %s\n»,++i,s);
printf(«Enter number 0-%d »,i);
test=scanf(«%d»,&n);
scanf(«%*[^\n]»);
scanf(«%\n»);
clearscr();
if(test==1&&n>=0&&n<=i)break;
puts(«You’re wrong! Try it again, please …»);
}while(++try);
va_end(p);
clearscr();
if(!try)return -1;
else return n;
}
Сценарий:
1) Символы появились в «нижележащих» буферах и еще не были перемещены в буфер stdin
2) Мы прозвели очистку stdin буфера
3) До нашего чтения из stdin буфера символы были положены в stdin буфер
Драфт — это промежуточный вариант, предназначенный для обсуждения.
Посмотрите на код ниже. Он более модульный чем Ваш. Если понадобятся объяснения почему что-то сделано именно так, а не иначе, то задавайте вопросы.
WARNING: Код не тестирован и может содержать ошибки
Удачи
— sas
Красиво у вас все расписано, сразу бросается в глаза ваш опыт.
Объясните, пожалуйтса:
1.Зачем stdin проверять на конец файла?
2. Хорошо ли так дробить программу на модули? Скорость работы уменьшается.
3. У меня функция menu была написано с переменным числом параметров для более широкого применения, можно так оставить или лучше сделать как у вас?
Ваша ошибка:
У вас в функции ask_relatives_name проверяется указатель p_name на ноль . Для переносимости это не верно, так делать нельзя. Потому, что указатель проверятеся с нулем типа int. Но не навсех машинах sizeof(int)==sizeof(char*). Поэтому указатель следует проверять NULL!=p_name.
Я проверил вашу программу — олично работатет! It’s great! Спасибо!
Я еще забыл спросить, зачем писать:
scanf(«%*[^\n]»);
scanf(«%*c»);
Когда достаточно одной строки:
scanf(«%*c»);
Спасибо :O)
stdin, stdout, stderr могут быть подключены к различным типам оборудования. Для каждого типа оборудования EOF может возникнуть по разным причинам. Например в DOS если нажать Ctrl+Z во время выполнения любой консольной программы, то консоль так и будет в EOF пока какая либо другая программа не напишет что-нибудь в stdin/stderr. Это означает, что все программы сразу же читающие с stdin, и запущенные после этого, ничего прочитать с него не смогут пока консоль не будет выведена из этого состояния.
1. Это не модули, а функции
2. Да вызов функции медленнее :) , НО
3. Консоль — само по себе устройство медленное. Что мы достигнем?
4. Такой код легче
а) Писать
б) Понимать и сопровождать
в) Использовать повторно
…
я) И поверьте еще много аргументов «ЗА»
Опять же, читайте белую книгу, «Искусство программирования» и т.д.
Можно, но зачем?
В функции с переменным числом параметров Вам необходимо явно передавать все Ваши пункты меню. Это очень не удобно, не читабельно.
Представьте себе что Вы написали программу, и после пары недель работы у Вас меню меняется. Вам надо найти то место где ВЫЗЫВАЕТСЯ Ваша функция (а вызываться она может в конце или середине большого файла) и менять там вызов. Обычно такого рода данные лучше «хранить» или описывать в самом начале, чтобы сразу видно было.
Или Вам теперь надо хранить меню во внешнем текстовом файле, чтобы его было возможно изменять без перекомпиляции. С массивом эти проблемы решать легче, хотя и тут существуют ограничения.
:O)
Нет, это НЕ ОШИБКА. Это вид записи, ведь сравнение здесь с NULL указателем. Некоторые говорят, что эта форма записи плохо читается , а некоторые наоборот. Я использую и то и то по настроению.
А теперь объяснение:
ПО СТАНДАРТУ С NULL pointer:
<<
guaranteed to compare unequal to a pointer to any object or function
>>
Как это происходит:
*) fgets в случае ошибки возвращает NULL указатель.
*) В процессе компиляции компилятор развернет if ( !ptr ) в if ( ptr == 0 )
*) Кроме того компилятор знает, что он работает с указателем в этом контексте и поэтому он неявно приведет и ptr и 0 к NULL указателям.
т.е. главное здесь контекст. Кстати если контекст известен, то NULL — это только для повышения читабельности кода. И еще одно кстати: в С++ NULL рекомендуется не использовать (просто 0).
100% правда :) и размеры данных на которые указываем тут значения не имеют.
Kernigan & Ritchie «The C programming language» Second Edition A7.4.7 p. 204
ANSI Secs. 3.3.3.3; 3.3.9; 3.3.13; 3.3.14; 3.3.15; 3.6.5
ISO Secs. 6.3.3.3; 6.3.9; 6.3.13; 6.3.14; 6.3.15; 6.6.5
Более того, все это даже не зависит от того, как представляется 0 на конкретной машине.
Успехов
— sas
Потестируйте с нечисловым (ошибочным) вводом и сами ответьте на свой вопрос. Вы уже знаете достаточно о том как работает scanf
Удачи
— sas
Я тестировал и именно поэтому я спросил. Если делать с циклом запроса, то разницы нет, если пользователь в конце концов ввел правильно. Но если число попыток ограничено и пользователь ввел не првильно — тогда сбой fgets’а. Но почему так scanf(«%*c»); — читает любые символы, зачем добавлять перед ним scanf(«%*[^\n]»); На практике видно, что неработает без scanf(«%*[^\n]»);, но в теории это непонятно.
Определение:
«ПРОБЕЛЬНЫЕ» символы — ' '; '\t’; '\n' или иначе говоря разделители
Сценарий 1 только scanf( «%*c» );
============================
Мы ввели » \tqwerty» тогда в stdin буфере у нас » \tqwerty\n»
1) scanf( «%d», &n ); Сначала мы читаем и убираем из буфера все разделители. Буфер стал: «qwerty\n». Теперь scanf видит, что 'q' не цифра и он буфер больше не меняет и выходит
2) Вызывается scanf( «%*c» ) (его смысл: убрать 1 символ. Разделитель не считается символом). Он убирает из буфера 'q' и выходит. У нас осталось в stdin буфере «werty\n»
3) Так как мы находимся в цикле, то опять scanf( «%d», &n ); Он видит, что 'w' не цифра и он буфер больше не меняет и выходит
4) опять scanf( «%*c» ) После него осталось «erty\n»
и тд
==========================================
СЦЕНАРИЙ 2 со scanf( «%*[^\n]» ) и scanf( «%*c» )
Мы ввели » \tqwerty» тогда в stdin буфере у нас » \tqwerty\n»
1) scanf( «%d», &n ); Сначала мы читаем и убираем из буфера все разделители. Буфер стал: «qwerty\n». Теперь scanf видит, что 'q' не цифра и он буфер больше не меняет и выходит
2) Вызывается scanf( «%*[^\n]» ) (его смысл: убрать (пропустить) ВСЕ кроме '\n’. ). Он убирает из буфера 'qwerty' и выходит. У нас осталось в stdin буфере «\n»
3) Вызывается scanf( «%*c» ). Как мы помним он хочет найти первый же символ, не являющийся разделителем и забрать его из буфера. '\n' (а их может быть больше чем 1 вообще говоря) — разделитель, поэтому scanf его/их удаляет из буфера пока тот пустым не окажется или какой либо символ не появится. Вот и все наш буфер «опустел».
Удачи
— sas
Большое спасибо! Разобрался.