Вернёмся к рассмотрению класса TPerson. Этот класс содержит два поля и два метода. Представим, что имеется 100 объектов класса TPerson. Так как каждый объект конкретизируется значениями своих полей, то в памяти должно содержаться 100 наборов полей класса. Означает ли это, что и код методов класса будет продублирован 100 раз? Определённо, нет. Код методов класса содержится в памяти в единственном числе, как и код любой подпрограммы. Однако как метод определяет, с полями какого объекта он работает?
var A, B: TPerson;
...
A.SetAge(10); // SetAge работает с A.fAge
B.SetAge(40); // SetAge работает с B.fAge
Для выявления конкретного объекта, с которым происходит работа, любому методу передаётся скрытый параметр self. Этот параметр указывает на объект, вызывающий метод. Тип параметра self совпадает с типом класса. Например, на уровне компилятора описание метода SetAge и работу с ним можно представить следующим образом:
procedure TPerson.SetAge(Age: Integer; self: TPerson);
begin
if Age > 0 then self.fAge:= Age
end;
...
TPerson.SetAge(10, A);
TPerson.SetAge(40, B);
Подчеркнём два важных момента:
|
|
1. Параметр self передаётся в любой метод;
2. Методы можно воспринимать как обыкновенные подпрограммы, которые принимают дополнительный параметр self.
Практически всегда в явном использовании self нет необходимости. Одно из исключений – использование одинаковых идентификаторов для полей класса и параметров метода. Предположим, что класс TPerson содержит поле с идентификатором Age, а не fAge. Тогда корректная реализация метода TPerson.SetAge должна выглядеть следующим образом:
procedure TPerson.SetAge(Age: Integer);
begin
// Age – параметр метода, self.Age – поле объекта
if Age > 0 then self.Age:= Age
end;
Следующий код показывает нетривиальный пример использования self. Перепишем метод GetWeight и деструктор Destroy из класса TBTree:
function TBTree.GetWeight;
begin
if self = nil then Result:= 0
else Result:= Weight + Left.GetWeight + Right.GetWeight
end;
destructor TBTree.Destroy;
begin
if self <> nil then begin
Left.Destroy;
Right.Destroy;
end
end;
В деструкторе TBTree.Destroy при помощи self проверяется, является ли объект инициализированным. Если объект инициализирован, вызываются деструкторы для левого и правого поддеревьев.