Бел Амор дата публикации 27-06-2011 08:10 КАТЕГОРИЯ | | КОМПИЛЯТОР.Переменная Result типа string может иметь непустое начальное значение | ПРОДУКТ | | Delphi | ПЛАТФОРМА | | Windows |
Все привыкли, что переменные типа string, в том числе локальные, всегда инициализируются пустым значением. Это может навести на мысль, что переменная Result в функциях, возвращающих значение типа string, также должна быть инициализирована пустым значением. Однако, это не так. Возможны условия, при которых переменная Result при входе в функцию имеет непустое значение. В частности, такие условия возникают при нескольких последовательных вызовах такой функции в пределах одного блока. В этом случае при входе в функцию переменная Result сохраняет значение, которое было возвращено этой функцией в
результате предыдущего вызова.
Пример:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Memo1: TMemo;
GroupBox1: TGroupBox;
btnTestWrong: TButton;
btnTestRight: TButton;
procedure btnTestWrong_Click(Sender: TObject);
procedure btnTestRight_Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TNumSpeller = function (Num: Integer): String;
procedure FillStrings(ALines: TStrings; ASpellNum: TNumSpeller); forward;
function SpellNum_Wrong(Num: Integer): String; forward;
function SpellNum_Right(Num: Integer): String; forward;
const
Names: array [0..2] of String = ('ноль', 'один', 'два');
{--- Обработчики ---}
procedure TForm1.btnTestWrong_Click(Sender: TObject);
begin
FillStrings(Memo1.Lines, SpellNum_Wrong);
end;
procedure TForm1.btnTestRight_Click(Sender: TObject);
begin
FillStrings(Memo1.Lines, SpellNum_Right);
end;
{--- Заполнение строк ---}
procedure FillStrings(ALines: TStrings; ASpellNum: TNumSpeller);
var
i: Integer;
begin
ALines.Clear;
for i := -High(Names) to High(Names) do
ALines.Add(Format('%2d (%s)', [i, ASpellNum(i)]));
end;
{--- С ошибкой ---}
function SpellNum_Wrong(Num: Integer): String;
begin
if Num<0 then // при невыполнении условия
Result := 'минус ';
Result := Result + Names[Abs(Num)]; // начальное значение влияет на результат
end;
{--- Без ошибки ---}
function SpellNum_Right(Num: Integer): String;
begin
Result := Names[Abs(Num)]; // безусловное присвоение, начальное значение не влияет
if Num<0 then
Result := 'минус ' + Result;
end;
end.
В этом примере при нажатии на кнопку btnTestWrong результат, выводимый в Memo1, будет отличаться от ожидаемого.
Ожидаемый результат:
-2 (минус два)
-1 (минус один)
0 (ноль)
1 (один)
2 (два)
Получаемый результат:
-2 (минус два)
-1 (минус один)
0 (минус одинноль)
1 (минус одиннольодин)
2 (минус одиннольодиндва)
При нажатии на кнопку btnTestRight результат будет совпадать с ожидаемым.
Любым удобным способом исключить влияние начального значения переменной Result на возвращаемое значение. В частности, к вышеприведённому примеру (с разной степенью "красивости") применимы следующие варианты:
- Перенести в начало функции часть кода, выполняющего безусловное присвоение переменной Result значения, не зависящего от начального.
- В начале функции явным образом очистить переменную Result.
- Для оператора if предусмотреть вариант else, в котором выполняется присвоение переменной Result какого-либо значения, например, пустого.
- Использовать алгоритм, в котором не используется последовательная
модификация переменной Result.
Скачать пример: StoneTest_117.zip
Эта особенность работы компилятора не является ошибкой. Так сделано by design. Дело в том, что результат функции, если тип его не помещается в регистр, или это любой динамические тип, в т.ч. строка, не принадлежит самой функции. Он фактически является неявным var-параметром. Вызывающая процедура передает
указатель вызываемой функции, где она должна разместить результат. Часто это место оказывается локальной переменной (явной или неявной), но может быть и глобальной, и частью любой структуры в любой области памяти.
Знание этого факта позволит не только избегать нежелательного побочного эффекта, но и использовать его в своих целях. Если мы строковой переменной присваиваем результат вызова функции, можем считать, что эта переменная передается как var-параметр в эту функцию, со всеми вытекающими последствиями.
[Строки] [Параметры вызова процедур и функций]
Обсуждение материала [ 26-08-2011 14:48 ] 13 сообщений |