Здесь A1
получит значение 4, A2
— 3, т.e. интуитивно ожидаемые. Тем не менее полагаться на интуицию все же не стоит: в более сложных случаях она может подвести. Дело в том, что стандарт языка Паскаль разрешает разработчикам конкретной реализации языка самим выбирать порядок вычисления операндов [5]. Поэтому, даже если вам удалось добиться желаемого порядка вычисления, в следующих версиях Delphi (или при переносе на другую платформу) программа может начать работать неправильно. Таким образом, разработчик не имеет права делать какие-то предположения о том, в каком порядке будут вычисляться операнды, а когда изменение этого порядка может повлиять на результат, код должен быть написан таким образом, чтобы исключить эту возможность. В частности, пример со сложением должен быть переписан так (листинг 3.50).
procedure TForm1.Button1Click(Sender: TObject);
var
A1, A2: Integer;
begin
X:= 2;
A1:= X;
Inc(A1, GetValueAndModifyX);
X:= 2;
A2:= GetValueAndModifyX;
Inc(A2, X);
Label1.Caption:= IntToStr(A1);
Label2.Caption:= IntToStr(A2);
end;
Такой код, несмотря на побочные эффекты функции GetValueAndModifyX
, даст ожидаемые значения при любом порядке вычисления операндов, т. к. здесь вычисление операндов разнесено по разным операторам, а порядок выполнения операторов четко определен.
Другие компиляторы могут использовать иной порядок вычисления операндов. Так, FreePascal вычисляет их в том порядке, в каком они встречаются в выражении, т. е. в первом примере А1
получит значение 4, А2
— 3.
3.4.2. Зацикливание обработчика
Для демонстрации этого "подводного камня" нам потребуется проект, на форме которого находится компонент TUpDown
со следующим обработчиком события OnClick
(листинг 3.51, пример UpDownDlg на компакт-диске).
OnClick
компонента UpDown1
procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
Application.MessageBox('Text', 'Caption', MB_OK);
end;
Теперь, если запустить программу и нажать на верхнюю кнопку UpDown1
, откроется окно с сообщением (при нажатии на нижнюю кнопку окно не будет открываться потому, что по умолчанию у компонент TUpDown
свойства Position
и Min
равны нулю, поэтому нажатие на нижнюю кнопку не приводит к изменению значения Position
, и событие OnClick
не возникает; если изменить значение свойства Min
или Position
, то тот же эффект будет наблюдаться и при нажатии на нижнюю кнопку). Если закрыть это окно, то щелчок мышью в любом месте формы снова приведет к срабатыванию события OnClick
и открытию окна, и так до бесконечности: любой щелчок по форме в любом ее месте будет снова и снова приводить к появлению сообщения. Эффект наблюдается и в том случае, когда вместо стандартного сообщения в обработчике показывается любая другая модальная форма. Кроме того, тот же эффект будет, и если использовать события OnChanging
или OnChangingEx
вместо OnClick
, но мы далее для определенности будем говорить только об OnClick
.
Если этот код пройти по шагам в отладчике, то никакого зацикливания не возникает: OnClick
вызывается один раз, любое последующее нажатие кнопки мыши на форме не приводит ни к каким необычным результатам.
Причина этой проблемы в том, как VCL обрабатывает сообщения, которые система помещает в очередь. При нажатии на кнопку компонента TUpDown
в очередь сообщений помещаются два сообщения: WM_LBUTTONDOWN
и WM_NOTIFY
. Компонент TUpDown
по умолчанию имеет стиль csCaptureMouse
— это означает, что при обработке WM_LBUTTONDOWN
VCL захватывает мышь в монопольное пользование для данного компонента.