LINUX.ORG.RU

Очистка потока ввода с помощью readln в Pascal

 ,


0

1

Добрый день!

В процессе изучения учебника Столярова «Азы программирования» в главе 2.5.2 «Посимвольный ввод информации» возник вопрос.

В тексте книги описывается программа для перемножения двух заданных чисел. Предварительно правильность написания чисел проверяется с помощью процедуры ReadLongint

program MultReadLongint;

procedure ReadLongint(var ok: boolean; var EndResult: longint);
var
    c: char;
    posit: integer;
    res: longint;
begin
    res := 0;
    posit := 0;
    repeat
        read(c);
        posit := posit + 1
    until (c <> ' ') and (c <> #10);
    while (c <> ' ') and (c <> #10) do
        begin
            if (c < '0') or (c > '9') then
            begin
                writeln('Incorrect simbol ', c, ' on the position ', posit);
                readln;
                ok := false;
                exit
            end;
            res := res * 10  + ord(c) - ord('0');
            read(c);
            posit := posit + 1
        end;
    EndResult := res;
    ok := true
end;

var
    a, b: longint;
    ok: boolean;
begin
    writeln('Enter the first number: ');
    repeat
        ReadLongint(ok, a);
    until ok;
    writeln('Enter the second number: ');
    repeat
        ReadLongint(ok, b);
    until ok;
    writeln(a, ' times ', b, ' is ', a*b)
end.

Но у такой программы есть слабое место. При вводе первого числа как ‘3 5’ программа будет считать, что последовательно введены число №1 - 3 и число №2 - 5.

Причина понятна - у автора пробел прерывает цикл, но поток ввода не очищается => число 5 уже находится в потоке ввода при новом старте процедуры для числа №2.

Собственно, один вариант решения проблемы я нашёл: переписать в процедуре ReadLongint участок с

    while (c <> ' ') and (c <> #10) do

на

    while c <> #10 do

Но потом я попробовал решить проблему еще одним способом - в первоначальный вариант программы добавить в конце процедуры ReadLongint очистку потока ввода c помощью readln. т.е. с

        end;
    EndResult := res;
    ok := true
end;

поменять на

        end;
    readln;
    EndResult := res;
    ok := true
end;

Но результат получился не таким, как я ожидал. Все работает корректно если первое число равно, к примеру, ’3 ’. Если же число равно просто ‘3’, то после выполнения readln сработает классическим для себя образом - переведет строку и будет ждать ввода Enter.

Почему же тогда в месте, где его изначально использовал автор,

 if (c < '0') or (c > '9') then
            begin
                writeln('Incorrect simbol ', c, ' on the position ', posit);
                readln;
                ok := false;
                exit
            end;

readln работает всегда безошибочно не переводя строку?

ReadLn не переводит строку, он отбрасывает содержимое Input (стандартного ввода) до достижения EoLn. В псевдокоде:

  while not EoLn do Read(c);  // остаток строки
  for t:=Length(EndOfLine)-1 downto 0 do Read(c);  // маркер конца строки

bormant ★★★★★
()

Но у такой программы есть слабое место

Да, «автор» называется. Суть проблемы в том, что, вопреки заявленному названию главы, процедура read(c) не считывает ввод посимвольно, она читает один символ из буфера ввода. А в буфер данные попадают только после перевода строки. По авторскому замыслу, нажатием пробела должен заканчиваться ввод числа, но этого не происходит, потому что read всё ещё ждёт ввода и проверки пока не проверяются. Что, собственно, и приводит к замеченному тобой эффекту.

Если же число равно просто ‘3’

То первый нецифровой символ — перевод строки. Который уже прочитан read’ом и буфер теперь пуст, поэтому readln и ждёт его наполнения. Можно просто добавить условие if c <> #10 then readln; тогда readln будет вызываться только если последний символ не был переводом строки.

gremlin_the_red ★★★★★
()

Проверку правильности написания чисел можно поручить рантайму:

{$I-}
repeat
  Write('a b: '); Read(a,b);
  if IOResult=0 then Break
  else WriteLn('Wrong number. Please, repeat.')
until False;

bormant ★★★★★
()
Последнее исправление: bormant (всего исправлений: 1)
Ответ на: комментарий от bormant

Лол. Код из стартопоста у Столярова появился, как ответ на твой — дескать, можно и так ошибки отлавливать, но неинформативно, давайте юзеру конкретное место ошибки покажем.

gremlin_the_red ★★★★★
()
Ответ на: комментарий от gremlin_the_red

давайте юзеру конкретное место ошибки покажем

Упрощенно: читаем строку, зовём Val(s,a,code). Если code<>0, то это позиция ошибки. Правда тут есть некоторые особенности поведения Val в разных диалектах, что для тривиального примера скорее вредно, чем полезно ;)
За скобками остались особенности перевода строки, вариантов тоже несколько больше одного:
1) положиться на рантайм (использовать EoLn + ReadLn)
2) явно сказать, какой конец строки обрабатываем, какие нет, извлекаем конец строки без ReadLn
3) извлекаем сами без ReadLn распространенные варианты LF, CRLF, CR, возможно, LFCR (он кривой, но кто-то такое пишет, попадается периодически). Но для тривиального примера это тоже такое себе «счастье» не по основной теме.

Это все к тому, что «правильно» сделать подобный фрагмент несколько сложнее, чем в исходном сообщении ;) И явно задать, что правильно, тоже необходимо ;)

И еще про конкретное место — есть #9, что выглядит часто как более чем одна позиция, есть #8, который вообще не выглядит ;) Есть в управляющих символах еще некоторые, которые делают задачу «давайте покажем позицию» слегка бессмысленной ;)

PS. И главное, за что не люблю вот эту вот мнимую пользу с «повторите ввод» — она не дает делать элементарное:

$ echo 2 2 | ./test

bormant ★★★★★
()
Последнее исправление: bormant (всего исправлений: 3)
Ответ на: комментарий от gremlin_the_red

Спасибо большое!

Я почему-то думал, что символ перевода строки в буфер ввода не попадает. Теперь разобрался.

KOMMUNIST90
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.