Зачем это нужно?
Очень часто при работе с типами данных, определенных пользователями, не хватает возможности работать с этими типами, как со встроенными в язык, т.е., например для сложения матриц не использовать вызов процедуры
MatrixAdd(C, A, B);
, а записать эту операцию в виде
C := A + B;
Или для сравнения матрицы с нулевой (или единичной) использовать не
If isEqual(mx, mxO) or isEqual(mx, mxE)
, а
If (mx = mxO) or (mx = mxE)...
В решении этой задачи могут помочь перегруженные операции.
Как это сделать?
Прежде всего - чтобы перегрузка была доступна, программа (или модуль, если это делается в модуле) должна компилироваться с ключом {$mode objfpc}. При использовании модулей директива {$mode objfpc} обязательна только для модуля, содержащего реализацию перегрузки, вызывающая программа может такой директивы не иметь...
Ну, и второе: для того, чтобы использовать в программе перегруженные операции, их нужно сначала определить. Это делается так:
Operator {перегружаемая операция} ({список параметров}) {возвращаемое значение};
где:
{перегружаемая операция} - одна из следующих:
- арифметическая (+, -, *, /, **, mod, div)
- сравнения (=, <, <=, >, >=)
- присваивания (:=)
- логическая (or, and, xor, not)
{возвращаемое значение} - то, что вернется в вызывающую программу как результат выполнения операции. Например, при операции сложения 2-х матриц, результатом также является матрица:
Operator + (Const m1, m2: TMatrix) R: TMatrix;
Begin
{ Заполняем матрицу R нужными значениями, и она вернется как результат операции }
End;
После этого описания в программе можно делать так:
Var A, B, C: TMatrix;
Begin
...
C := A + B; { матрица - сумма присвоится переменной C }
...
End.
Перегрузка функций
Кроме перегрузки операций (переопределения операций для пользовательских типов) в FPC введена еще и перегрузка функций, т.е. в программе может быть несколько процедур/функций с одинаковыми именами (сюда же относятся и Operator-ы), при условии, что их списки параметров неодинаковы.
Т.е. можно сделать так:
function test(a: integer): integer;
begin
result := 0;
end;
{ Разрешено - список параметров отличается от списка предыдущей функции }
function test(a: double): integer;
begin
result := 0;
end;
{ Ошибка: несмотря на то, что тип результата другой, списки параметров одинаковы, а это недопустимо }
function test(a: integer): double;
begin
result := 0.0;
end;
Эта возможность поможет, например, в таком случае.
Допустим, была переопределена операция + для матриц:
operator + (const mx1, mx2: TMatrix) r: TMatrix;
var i, j: integer;
begin
for i := 1 to n do
for j := 1 to n do
r[i, j] := mx1[i, j] + mx2[i, j];
end;
но иногда модет понадобиться и операция сложения матрицы с числом, т.е. увеличение каждого элемента матрицы на какую-то величину. Без перегрузки функций здесь не обойтись:
operator + (const mx: TMatrix; const X: integer) r: TMatrix;
var i, j: integer;
begin
for i := 1 to n do
for j := 1 to n do
r[i, j] := mx[i, j] + X;
end;
(параметры первой и второй Operator + различны, следовательно ошибки не будет)
Теперь можно использовать как сложение матриц, так и сложение матрицы с числом:
Var A, B, C: TMatrix;
Begin
A := A + 2;
C := A + B;
End.
Внимание: перегруженные операции не являются коммутативными, если типы переменных слева и справа от знака операции различны, т.е. если можно записать
A := A + 2;
это совсем не значит, что можно сделать и
A := 2 + A;
Компилятор сразу сообщит об ошибке: "Operator is not overloaded" ("Операция не перегружена")... Для того, чтобы иметь возможность использовать оба варианта сложения матрицы с числом, придется определить еще одну операцию:
operator + (const X: integer; const mx: TMatrix) r: TMatrix;
begin
r := mx + X; { Пользуемся уже определенным сложением с другим порядком операндов }
end;
Примеры использования:
Здесь:
Как вычислить заданный многочлен от матрицы A приведен пример вычисления заданного многочлена от матрицы A ...
С использованием перегрузки операторов та же программа будет выглядеть вот так:
{$mode objfpc}
const
size = 4;
type
TMatrix = array[1 .. size, 1 .. size] of double;
operator * (const a, b: TMatrix) m: TMatrix;
var i, j, k: integer;
begin
for i := 1 to size do
for j := 1 to size do begin
m[i, j] := 0;
for k := 1 to size do
m[i, j] := m[i, j] + a[i, k] * b[k, j]
end;
end;
operator * (const a: TMatrix; const f: double) m: TMatrix;
var i, j: integer;
begin
for i := 1 to size do
for j := 1 to size do
m[i, j] := f * a[i, j]
end;
operator + (const a, b: TMatrix) m: TMatrix;
var i, j: integer;
begin
for i := 1 to size do
for j := 1 to size do
m[i, j] := a[i, j] + b[i, j]
end;
{ Возведение матрицы в степень }
operator ** (const a: TMatrix; const pow: integer) m: TMatrix;
var i, j: Integer;
begin
if pow = 0 then begin
for i := 1 to size do
for j := 1 to size do
m[i, j] := Byte(i = j);
exit
end;
m := a;
for i := 1 to pred(pow) do
m := m * a;
end;
procedure matrixPrint(a: TMatrix);
var i, j: integer;
begin
for i := 1 to size do begin
for j := 1 to size do
write(a[i, j]:9:2);
writeln
end
end;
const
n = 3;
p: array[1 .. n] of double = (1.0, -2.0, 3.0);
const
a: TMatrix =
(
(10, 11, 14, 16),
(12, 17, 10, 16),
( 8, 12, 12, 7),
( 8, 5, 17, 1)
);
var
Res: TMatrix;
i: Integer;
begin
matrixPrint(a);
for i := 1 to n do
res := res + (a ** (n - i)) * p[i]; { Все вычисление записывается в одну строчку }
matrixPrint(Res)
end.
**********
Еще одно полезное применение перегрузки операций - операции с большими числами... Вот так это может выглядеть:
