Перегрузка операций
Для удобства чтения и написания кода разрешается перегрузка некоторых операций. Оператор перегрузки записывается с помощью ключевого слова operator. Разрешена перегрузка следующих операций:
- бинарные +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^;
- унарные +,-,++,--,!,~;
- оператор присваивания =;
- оператор индексации [].
Перегрузка операций позволяет использовать операционную нотацию (запись в виде простых выражений) к сложным объектам - структурам и классам. Запись выражений с использованием перегруженных операций упрощает восприятие исходного кода, так как более сложная реализация сокрыта.
Для примера рассмотрим широко применяемые в математике комплексные числа, которые состоят из действительной и мнимой части. В языке MQL4 нет типа данных для представления комплексных чисел, но есть возможность создать новый тип данных в виде структуры или класса. Объявим структуру complex и определим в ней четыре метода, реализующие четыре арифметические операции:
//+------------------------------------------------------------------+
//| Структура для операций с комплексными числами |
//+------------------------------------------------------------------+
struct complex
{
double re; // действительная часть
double im; // мнимая часть
//--- конструкторы
complex():re(0.0),im(0.0) { }
complex(const double r):re(r),im(0.0) { }
complex(const double r,const double i):re(r),im(i) { }
complex(const complex &o):re(o.re),im(o.im) { }
//--- арифметические операции
complex Add(const complex &l,const complex &r) const; // сложение
complex Sub(const complex &l,const complex &r) const; // вычитание
complex Mul(const complex &l,const complex &r) const; // умножение
complex Div(const complex &l,const complex &r) const; // деление
}; |
Теперь мы можем объявлять в своем коде переменные, представляющие комплексные числа, и работать с ними.
Например так:
void OnStart()
{
//--- объявим и инициализируем переменные комплексного типа
complex a(2,4),b(-4,-2);
PrintFormat("a=%.2f+i*%.2f, b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- сложим два числа
complex z;
z=a.Add(a,b);
PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- умножим два числа
z=a.Mul(a,b);
PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- разделим два числа
z=a.Div(a,b);
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
} |
Но было бы удобнее для привычных арифметических операций с комплексными числами использовать привычные операторы "+","-","*" и "/".
Ключевое слово operator используется для того чтобы определить функцию-член, осуществляющую преобразование типа. Унарные и бинарные операции для переменных-объектов класса могут быть перегружены как нестатические функции-члены. Они неявно действуют на объект класса.
Большинство бинарных операций можно перегружать как обычные функции, принимающие один или оба аргумента в виде переменной класса или в виде указателя на объект данного класса. Для нашего типа complex перегрузка в объявлении будет выглядеть так:
//--- операторы
complex operator+(const complex &r) const { return(Add(this,r)); }
complex operator-(const complex &r) const { return(Sub(this,r)); }
complex operator*(const complex &r) const { return(Mul(this,r)); }
complex operator/(const complex &r) const { return(Div(this,r)); } |
Полный пример скрипта:
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- объявим и инициализируем переменные комплексного типа
complex a(2,4),b(-4,-2);
PrintFormat("a=%.2f+i*%.2f, b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//a.re=5;
//a.im=1;
//b.re=-1;
//b.im=-5;
//--- сложим два числа
complex z=a+b;
PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- умножим два числа
z=a*b;
PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- разделим два числа
z=a/b;
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
}
//+------------------------------------------------------------------+
//| Структура для операций с комплексными числами |
//+------------------------------------------------------------------+
struct complex
{
double re; // действительная часть
double im; // мнимая часть
//--- конструкторы
complex():re(0.0),im(0.0) { }
complex(const double r):re(r),im(0.0) { }
complex(const double r,const double i):re(r),im(i) { }
complex(const complex &o):re(o.re),im(o.im) { }
//--- арифметические операции
complex Add(const complex &l,const complex &r) const; // сложение
complex Sub(const complex &l,const complex &r) const; // вычитание
complex Mul(const complex &l,const complex &r) const; // умножение
complex Div(const complex &l,const complex &r) const; // деление
//--- бинарные операторы
complex operator+(const complex &r) const { return(Add(this,r)); }
complex operator-(const complex &r) const { return(Sub(this,r)); }
complex operator*(const complex &r) const { return(Mul(this,r)); }
complex operator/(const complex &r) const { return(Div(this,r)); }
};
//+------------------------------------------------------------------+
//| Сложение |
//+------------------------------------------------------------------+
complex complex::Add(const complex &l,const complex &r) const
{
complex res;
//---
res.re=l.re+r.re;
res.im=l.im+r.im;
//--- результат
return res;
}
//+------------------------------------------------------------------+
//| Вычитание |
//+------------------------------------------------------------------+
complex complex::Sub(const complex &l,const complex &r) const
{
complex res;
//---
res.re=l.re-r.re;
res.im=l.im-r.im;
//--- результат
return res;
}
//+------------------------------------------------------------------+
//| Умножение |
//+------------------------------------------------------------------+
complex complex::Mul(const complex &l,const complex &r) const
{
complex res;
//---
res.re=l.re*r.re-l.im*r.im;
res.im=l.re*r.im+l.im*r.re;
//--- результат
return res;
}
//+------------------------------------------------------------------+
//| Деление |
//+------------------------------------------------------------------+
complex complex::Div(const complex &l,const complex &r) const
{
//--- пустое комплексное число
complex res(EMPTY_VALUE,EMPTY_VALUE);
//--- проверка на ноль
if(r.re==0 && r.im==0)
{
Print(__FUNCTION__+": number is zero");
return(res);
}
//--- вспомогательные переменные
double e;
double f;
//--- выбор варианта вычисления
if(MathAbs(r.im)<MathAbs(r.re))
{
e = r.im/r.re;
f = r.re+r.im*e;
res.re=(l.re+l.im*e)/f;
res.im=(l.im-l.re*e)/f;
}
else
{
e = r.re/r.im;
f = r.im+r.re*e;
res.re=(l.im+l.re*e)/f;
res.im=(-l.re+l.im*e)/f;
}
//--- результат
return res;
} |
Большинство унарных операций для классов можно перегружать как обычные функции, принимающие единственный аргумент-объект класса или указатель на него. Добавим перегрузку унарных операций "-" и "!".
//+------------------------------------------------------------------+
//| Структура для операций с комплексными числами |
//+------------------------------------------------------------------+
struct complex
{
double re; // действительная часть
double im; // мнимая часть
...
//--- унарные операторы
complex operator-() const; // унарный минус
bool operator!() const; // отрицание
};
...
//+------------------------------------------------------------------+
//| Перегрузка оператора "унарный минус" |
//+------------------------------------------------------------------+
complex complex::operator-() const
{
complex res;
//---
res.re=-re;
res.im=-im;
//--- результат
return res;
}
//+------------------------------------------------------------------+
//| Перегрузка оператора "логическое отрицание" |
//+------------------------------------------------------------------+
bool complex::operator!() const
{
//--- действительная и мнимая часть комплексного числа равны нулю?
return (re!=0 && im!=0);
} |
Теперь мы можем проверять значение комплексного числа на ноль и получать отрицательное значение:
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- объявим и инициализируем переменные комплексного типа
complex a(2,4),b(-4,-2);
PrintFormat("a=%.2f+i*%.2f, b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- разделим два числа
complex z=a/b;
PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- комплексное число по умолчанию равно нулю (в конструкторе по умолчанию re==0 и im==0)
complex zero;
Print("!zero=",!zero);
//--- присвоим отрицательное значение
zero=-z;
PrintFormat("z=%.2f+i*%.2f, zero=%.2f+i*%.2f",z.re,z.im, zero.re,zero.im);
PrintFormat("-zero=%.2f+i*%.2f",-zero.re,-zero.im);
//--- еще раз проверим на равенство нулю
Print("!zero=",!zero);
//---
} |
Обратите внимание, что нам не пришлось в данном случае перегружать операцию присваивания "=", так как структуры простых типов можно копировать друг в друга напрямую. Таким образом, теперь мы можем писать код для расчетов с участием комплексных чисел в привычной манере.
Перегрузка оператора индексирования позволяет получать значения массивов, заключенных в объект, более простым и привычным способом, и это также способствует лучшей читаемости и пониманию исходного кода программ. Например, нам необходимо обеспечить доступ к символу в строке по указанной позиции. Строка в языке MQL4 является отдельным типом string, который не является массивом символов, но с помощью перегруженной операции индексации в созданном классе CString мы можем обеспечить простую и прозрачную работу:
//+------------------------------------------------------------------+
//| Класс для доступа к символам в строке как в массиве символов |
//+------------------------------------------------------------------+
class CString
{
string m_string;
public:
CString(string str=NULL):m_string(str) { }
ushort operator[] (int x) { return(StringGetCharacter(m_string,x)); }
};
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- массив для получения символов из строки
int x[]={ 19,4,18,19,27,14,15,4,17,0,19,14,17,27,26,28,27,5,14,
17,27,2,11,0,18,18,27,29,30,19,17,8,13,6 };
CString str("abcdefghijklmnopqrstuvwxyz[ ]CS");
string res;
//--- составим фразу,набрав символы из переменной str
for(int i=0,n=ArraySize(x);i<n;i++)
{
res+=ShortToString(str[x[i]]);
}
//--- выведем результат
Print(res);
} |
Другой пример перегрузки операции индексирования - работа с матрицами. Матрица представляет собою двумерный динамический массив, размеры массивов заранее неопределены. Поэтому нельзя объявить массив вида array[][] без указания размера второго измерения и затем передавать этот массив в качестве параметра. Выходом может служить специальный класс CMatrix, который содержит в себе массив объектов класса CRow.
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- операции сложения и умножения матриц
CMatrix A(3),B(3),C();
//--- готовим массивы под строки
double a1[3]={1,2,3}, a2[3]={2,3,1}, a3[3]={3,1,2};
double b1[3]={3,2,1}, b2[3]={1,3,2}, b3[3]={2,1,3};
//--- заполняем матрицы
A[0]=a1; A[1]=a2; A[2]=a3;
B[0]=b1; B[1]=b2; B[2]=b3;
//--- выведем матрицы в журнал "Эксперты"
Print("---- элементы матрицы A");
Print(A.String());
Print("---- элементы матрицы B");
Print(B.String());
//--- сложение матриц
Print("---- сложение матриц A и B");
C=A+B;
//--- вывод форматированного строкового представления
Print(C.String());
//--- умножение матриц
Print("---- произведение матриц A и B");
C=A*B;
Print(C.String());
//--- а теперь покажем как получать значения в стиле динамических массивов matrix[i][j]
Print("Выводим значения матрицы C поэлементно");
//--- перебираем в цикле строки матрицы - объекты CRow
for(int i=0;i<3;i++)
{
string com="| ";
//--- формируем для значения строки из матрицы
for(int j=0;j<3;j++)
{
//--- получим элемент матрицы по номерам строки и столбца
double element=C[i][j];// [i] - доступ к CRow в массиве m_rows[] ,
// [j] - перегруженный оператор индексации в CRow
com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
}
com+="|";
//--- выводим значения строки
Print(com);
}
}
//+------------------------------------------------------------------+
//| Класс "Строка" |
//+------------------------------------------------------------------+
class CRow
{
private:
double m_array[];
public:
//--- конструкторы и деструктор
CRow(void) { ArrayResize(m_array,0); }
CRow(const CRow &r) { this=r; }
CRow(const double &array[]);
~CRow(void){};
//--- количество элементов в строке
int Size(void) const { return(ArraySize(m_array));}
//--- возвращает строку со значениями
string String(void) const;
//--- оператор индексации
double operator[](int i) const { return(m_array[i]); }
//--- операторы присваивания
void operator=(const double &array[]); // массив
void operator=(const CRow & r); // другой объект CRow
double operator*(const CRow &o); // объект CRow для умножения
};
//+------------------------------------------------------------------+
//| Конструктор для инициализации строки массивом |
//+------------------------------------------------------------------+
void CRow::CRow(const double &array[])
{
int size=ArraySize(array);
//--- если массив не пустой
if(size>0)
{
ArrayResize(m_array,size);
//--- заполним значениями
for(int i=0;i<size;i++)
m_array[i]=array[i];
}
//---
}
//+------------------------------------------------------------------+
//| Операция присваивания для массива |
//+------------------------------------------------------------------+
void CRow::operator=(const double &array[])
{
int size=ArraySize(array);
if(size==0) return;
//--- заполняем массив значениями
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=array[i];
//---
}
//+------------------------------------------------------------------+
//| Операция присваивания для СRow |
//+------------------------------------------------------------------+
void CRow::operator=(const CRow &r)
{
int size=r.Size();
if(size==0) return;
//--- заполняем массив значениями
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=r[i];
//---
}
//+------------------------------------------------------------------+
//| Оператор умножения на другую строку |
//+------------------------------------------------------------------+
double CRow::operator*(const CRow &o)
{
double res=0;
//--- проверки
int size=Size();
if(size!=o.Size() || size==0)
{
Print(__FUNCSIG__,": Ошибка умножения двух матриц, не совпадают размеры");
return(res);
}
//--- умножим поэлементно массивы и сложим произведения
for(int i=0;i<size;i++)
res+=m_array[i]*o[i];
//--- результат
return(res);
}
//+------------------------------------------------------------------+
//| Возвращает форматированное строковое представление |
//+------------------------------------------------------------------+
string CRow::String(void) const
{
string out="";
//--- если размер массива больше нуля
int size=ArraySize(m_array);
//--- работаем только при ненулевом кол-ве элементов в массиве
if(size>0)
{
out="{";
for(int i=0;i<size;i++)
{
//--- собираем значения в строку
out+=StringFormat(" %G;",m_array[i]);
}
out+=" }";
}
//--- результат
return(out);
}
//+------------------------------------------------------------------+
//| Класс "Матрица" |
//+------------------------------------------------------------------+
class CMatrix
{
private:
CRow m_rows[];
public:
//--- конструкторы и деструктор
CMatrix(void);
CMatrix(int rows) { ArrayResize(m_rows,rows); }
~CMatrix(void){};
//--- получение размеров матрицы
int Rows() const { return(ArraySize(m_rows)); }
int Cols() const { return(Rows()>0? m_rows[0].Size():0); }
//--- возвращает значения столбца в виде строки CRow
CRow GetColumnAsRow(const int col_index) const;
//--- возвращает строку со значениями матрицы
string String(void) const;
//--- оператор индексации возвращает строку по ее номеру
CRow *operator[](int i) const { return(GetPointer(m_rows[i])); }
//--- оператор сложения
CMatrix operator+(const CMatrix &m);
//--- оператор умножения
CMatrix operator*(const CMatrix &m);
//--- оператор присваивания
CMatrix *operator=(const CMatrix &m);
};
//+------------------------------------------------------------------+
//| Конструктор по умолчанию, создает массив строк нулевого размера |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
{
//--- нулевое количество строк в матрице
ArrayResize(m_rows,0);
//---
}
//+------------------------------------------------------------------+
//| Возвращает значения столбца в виде строки CRow |
//+------------------------------------------------------------------+
CRow CMatrix::GetColumnAsRow(const int col_index) const
{
//--- переменная для получения значений из столбца
CRow row();
//--- количество строк в матрице
int rows=Rows();
//--- если количество строк больше нуля, выполняем операцию
if(rows>0)
{
//--- массив для получения значений столбца с индексом col_index
double array[];
ArrayResize(array,rows);
//--- заполнение массива
for(int i=0;i<rows;i++)
{
//--- проверка номера столбца для i-ой строки на выход за пределы массива
if(col_index>=this[i].Size())
{
Print(__FUNCSIG__,": Ошибка! Номер столбца ",col_index,"> размера строки ",i);
break; // row останется неинициализированным объектом
}
array[i]=this[i][col_index];
}
//--- создадим строку CRow на основе значений массива
row=array;
}
//--- результат
return(row);
}
//+------------------------------------------------------------------+
//| Сложение двух матриц |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
{
//--- количество строк и столбцов в переданной матрице
int cols=m.Cols();
int rows=m.Rows();
//--- матрица для получения результата сложения
CMatrix res(rows);
//--- размеры матрицы должны совпадать
if(cols!=Cols() || rows!=Rows())
{
//--- нельзя произвести сложение
Print(__FUNCSIG__,": Ошибка сложения двух матриц, не совпадают размеры");
return(res);
}
//--- вспомогательный массив
double arr[];
ArrayResize(arr,cols);
//--- перебираем строки для сложения
for(int i=0;i<rows;i++)
{
//--- запишем результаты сложений строк матриц в массив
for(int k=0;k<cols;k++)
{
arr[k]=this[i][k]+m[i][k];
}
//--- поместим массив в строку матрицы
res[i]=arr;
}
//--- вернем результат сложения матриц
return(res);
}
//+------------------------------------------------------------------+
//| Умножение двух матриц |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
{
//--- кол-во столбцов первой матрицы кол-во строк в переданной матрице
int cols1=Cols();
int rows2=m.Rows();
int rows1=Rows();
int cols2=m.Cols();
//--- матрица для получения результата сложения
CMatrix res(rows1);
//--- матрицы должны быть согласованы
if(cols1!=rows2)
{
//--- нельзя произвести умножение
Print(__FUNCSIG__,": Ошибка умножения двух матриц, формат не согласован "
"- число столбцов в первом сомножителе должно быть равно числу строк во втором");
return(res);
}
//--- вспомогательный массив
double arr[];
ArrayResize(arr,cols1);
//--- заполняем строки в матрице произведения
for(int i=0;i<rows1;i++)// перебираем строки
{
//--- обнулим массив-приемник
ArrayInitialize(arr,0);
//--- перебираем элементы в строке
for(int k=0;k<cols1;k++)
{
//--- возьмем из матрицы m значения к-го столбца в виде строки Crow
CRow column=m.GetColumnAsRow(k);
//--- перемножим две строки и запишем результат скалярного умножения векторов в i-ый элемент
arr[k]=this[i]*column;
}
//--- поместим массив arr[] в i-ую строку матрицы
res[i]=arr;
}
//--- вернем произведение двух матриц
return(res);
}
//+------------------------------------------------------------------+
//| Операция присваивания |
//+------------------------------------------------------------------+
CMatrix *CMatrix::operator=(const CMatrix &m)
{
//--- узнаем и зададим количество строк
int rows=m.Rows();
ArrayResize(m_rows,rows);
//--- заполним наши строки значениями строк переданной матрицы
for(int i=0;i<rows;i++) this[i]=m[i];
//---
return(GetPointer(this));
}
//+------------------------------------------------------------------+
//| Строковое представление матрицы |
//+------------------------------------------------------------------+
string CMatrix::String(void) const
{
string out="";
int rows=Rows();
//--- формируем построчно
for(int i=0;i<rows;i++)
{
out=out+this[i].String()+"\r\n";
}
//--- результат
return(out);
} |
Смотри также
Перегрузка, Арифметические операции, Перегрузка функций, Приоритеты и порядок операций
|