Перегрузка операций
Для удобства чтения и написания кода разрешается перегрузка некоторых операций. Оператор перегрузки записывается с помощью ключевого слова operator. Разрешена перегрузка следующих операций:
- бинарные +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^;
- унарные +,-,++,--,!,~;
- оператор присваивания =;
- оператор индексации [].
Перегрузка операций позволяет использовать операционную нотацию (запись в виде простых выражений) к сложным объектам - структурам и классам. Запись выражений с использованием перегруженных операций упрощает восприятие исходного кода, так как более сложная реализация сокрыта.
Для примера рассмотрим широко применяемые в математике комплексные числа, которые состоят из действительной и мнимой части. В языке MQL5 нет типа данных для представления комплексных чисел, но есть возможность создать новый тип данных в виде структуры или класса. Объявим структуру 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); //--- } |
Обратите внимание, что нам не пришлось в данном случае перегружать операцию присваивания "=", так как структуры простых типов можно копировать друг в друга напрямую. Таким образом, теперь мы можем писать код для расчетов с участием комплексных чисел в привычной манере.
Перегрузка оператора индексирования позволяет получать значения массивов, заключенных в объект, более простым и привычным способом, и это также способствует лучшей читаемости и пониманию исходного кода программ. Например, нам необходимо обеспечить доступ к символу в строке по указанной позиции. Строка в языке MQL5 является отдельным типом 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); } |
Смотри также
Перегрузка, Арифметические операции, Перегрузка функций, Приоритеты и порядок операций