А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Сама по себе задача преобразования строки в вещественное число достаточно сложна, и чтобы не отвлекаться на ее решение, мы воспользуемся функцией StrToFloat
из модуля SysUtils
. Когда функция Number
выделит из строки последовательность символов, являющуюся числом, эта последовательность передается функции StrToFloat
, и преобразованием занимается она. Здесь следует учесть два момента. Во-первых, в нашей грамматике разделителем целой и дробной части является точка, a StrToFloat
использует системные настройки, т.е. разделителем может быть и запятая. Чтобы обойти эту проблему, слегка изменим синтаксис и будем сравнивать аргумент функции IsSeparator
не с символом ".", а с DecimalSeparator
(таким образом, наш калькулятор тоже станет чувствителен к системным настройкам). Во-вторых, не всякое выражение, соответствующее нашей грамматике, будет допустимым числом с точки зрения StrToFloat
, т.к. эта функция учитывает диапазон типа Extended
. Например, синтаксически верное выражение "2е5000" даст исключение EConvertError
, т.к. данное число выходит за пределы этого диапазона. Но пока мы остаемся в рамках типа Extended
, мы вынуждены мириться с этим.
Новые функции приведены в листинге 4.3.
// Выделение из строки подстроки, соответствующей
// определению , и вычисление этого числа
// S — строка, из которой выделяется подстрока
// Р — номер позиции в строке, с которой должно
// начинаться число. После завершения работы функции
// этот параметр содержит номер первого после числа
function Number(const S: string; var P: Integer): Extended;
var
InitPos: Integer;
begin
// InitPos нам понадобится для выделения подстроки,
// которая будет передана в StrToFloat
InitPos := Р;
if (Р <= Length(S)) and IsSign(S[P]) then Inc(P);
if (P > Length(S)) or not IsDigit(S[P]) then
raise ESyntaxError.Create(
'Ожидается цифра в позиции ' + IntToStr(Р));
repeat
Inc(P);
until (P > Length(S)) or not IsDigit(S[P]);
if (P <= Length(S)) and IsSeparator(S[P]) then begin
Inc(P);
if (P > Length(S)) or not IsDigit(S[P]) then
raise ESyntaxError.Create(
'Ожидается цифра в позиции ' + IntToStr(Р));
repeat
Inc(P);
until (P > Length(S)) or not IsDigit(S[P]);
end;
if (P <= Length(S)) and IsExponent(S[P]) then
begin
Inc(P);
if Р > Length(S) then
raise ESyntaxError.Create('Неожиданный конец строки');
if IsSign(S[P]) then Inc(P);
if (P > Length(S)) or not IsDigit(S[P]) then
raise ESyntaxError.Create(
'Ожидается цифра в позиции ' + IntToStr(Р));
repeat
Inc(P);
until (P > Length(S)) or not IsDigit(S[P]);
end;
Result := StrToFloat(Copy(S, InitPos, P - InitPos));
end;
// Проверка символа на соответствие
function IsOperator(Ch: Char): Boolean;
begin
Result := Ch in ['+', '-', '*', '/'];
end;
// Проверка строки на соответствие
// и вычисление выражения
function Expr(const S: string): Extended;
var
P: Integer;
OpSymb: Char;
begin
P := 1;
Result := Number(S, P);
while (P <= Length(S)) and IsOperator(S[P]) do
begin
OpSymb := S[P];
Inc(P);
case OpSymb of
'+': Result := Result + Number(S, P);
'-': Result := Result - Number(S, P);
'*': Result := Result * Number(S, P);
'/': Result := Result / Number(S, P);
end;
end;
if P <= Length(S) then
raise ESyntaxError.Create(
'Heкорректный символ в позиции ' + IntToStr(Р));
end;
Код приведен практически без комментариев, т.к. он очень простой, и все моменты, заслуживающие упоминания, мы уже разобрали в тексте. На прилагаемом компакт-диске находится программа SimpleCalcSample, которая демонстрирует работу нашего калькулятора. Калькулятор выполняет действия над числами слева направо, без учета приоритета операций, т.е. вычисление выражения "2+2*2" даст 8.
Грамматика выражения является простой для разбора, т.к. разбор выражения идет слева направо, и для соотнесения очередной части строки с тем или иным нетерминальным символом на любом этапе анализа достаточно знать только следующий символ. Такие грамматики называются LR(1)-грамматиками (в более общем случае требуется не один символ, а одна лексема). Класс этих грамматик был исследован Кнутом.
Грамматика Паскаля не относится к классу LR(1)-грамматик из-за уже упоминавшейся проблемы отнесения else
к тому или иному if
. Чтобы решить эту проблему, приходится вводить два нетерминальных символа — завершенной формы оператора if
(с else
) и незавершенной (без else
). Таким образом, встретив в тексте программы лексему if
, синтаксический анализатор не может сразу отнести ее к одному из этих символов, пока не продвинется вперед и не натолкнется на наличие или отсутствие else
. А поскольку оператор if
может быть оператором в циклах for
, while
или в операторе with
, для них также приходится вводить завершенную и незавершенную форму. Именно из-за этой проблемы Вирт (разработчик Паскаля) отказался от идеи составного оператора и модифицировал синтаксис в своем новом языке Оберон таким образом, чтобы проблема else
не возникала.
Другое достоинство нашей простой грамматики — ее однозначность. Любая синтаксически верная строка не допускает неоднозначной трактовки. Неоднозначность могла бы возникнуть, например), если бы какая-то операция обозначалась символом "." (точка). Тогда было бы непонятно, должно ли выражение "1.5" трактоваться как число "одна целая пять десятых" или как выполнение операции над числами 1 и 5. Этот пример выглядит несколько надуманным, но неоднозначные грамматики, тем не менее, иногда встречаются на практике. Например, если запятая служит для отделения дробной части числа от целой и для разделения значений в списке параметров функций, то выражение f(1,5)
может, с одной стороны, трактоваться как вызов функции f
с одним аргументом 1.5, а с другой — как вызов ее с двумя аргументами 1 и 5. Правила решения неоднозначных ситуаций не описываются в виде БНФ, их приходится объяснять "на словах", что затрудняет разбор соответствующих выражений. Другой пример неоднозначной грамматики — грамматика языков C/C++. В них оператор инкремента, записывающийся как "++",
имеет две формы записи — префиксную (перед увеличиваемой переменной) и постфиксную (после переменной). Кроме того, этот оператор возвращает значение, поэтому его можно использовать в выражениях. Синтаксически допустимо, например, выражение а+++b
, но грамматика не дает ответа, следует ли это трактовать как (а++)+b
или как а+(++b)
. Кроме того, т.к. существует операция "унарный плюс", возможно и третье толкование — а+(+(+b))
.
Интервал:
Закладка: