itmo.ru aco.ifmo.ru c-visionlab.ru
вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


6.2. Перегрузка операторов

Перегрузка операторов позволяет использовать для абстрактных типов данных привычные операторы, например + для сложения или объединения двух объектов, = для присвоения одного экземпляра объекта другому и т.д. Комплексная арифметика, матричная алгебра, символьные строки - это лишь немногие примеры, где удобно использовать перегруженные операторы.

Например, для комплексных чисел (типа данных Complex) можно перегрузить следующие операторы:

  • * – комплексное умножение
  • + – комплексное сложение
  • ~ – комплексное сопряжение
Complex x,y,z; 
z=x*y;
z=x.operator*(y); // явный вызов оператора

class Complex
{
   …
   Complex operator*(const Complex& other) const;
   …
}

Исходя из этой записи, нетрудно догадаться, как осуществляется перегрузка операторов для абстрактных типов данных. Просто нужно определить функцию-член с именем operator* или любой другой оператор.

Рассмотрим пример класса Complex, а затем разберем подробно перегрузку некоторых типовых операторов.

6.2.1. Пример 6.1 (класс Complex (комплексное число))

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 6.1. Класс Комплексное число
// complex.h
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
// проверка на повторное подключение файла
#if !defined COMPLEX_H
#define COMPLEX_H 

#include <iostream>
using namespace std;
/////////////////////////////////////////////////////////////////////////////
// класс Комплексное число
class Complex
{
private:
    // вещественная и мнимая часть комплексного числа
    double m_re, m_im;

public:
    // конструкторы
    Complex();
    Complex(double re, double im=0);
    Complex(const Complex& other); 

    // получение параметров комплексного числа
    double GetRe() const;
    double GetIm() const;
    // изменение параметров комплексного числа
    void Set(double re, double im=0.);

    // оператор умножения
    Complex operator*(const Complex& other) const;
    // оператор умножения на число
    Complex operator*(const double& other) const;
    // оператор умножения с присваиванием
    Complex& operator*=(const Complex& other);
    // оператор присваивания
    Complex& operator=(const Complex& other);
    // оператор равенства
    bool operator== (const Complex& other) const;
    // оператор сопряжения комплексного числа
    Complex operator~() const;
    // унарный минус
    Complex operator-() const;

    // ввод/вывод комплексного числа
    friend ostream& operator<< (ostream& out, const Complex& x);
    friend istream& operator>> (istream& out, Complex& x);

    // преобразование типа Complex в double
    operator double() const;
};
/////////////////////////////////////////////////////////////////////////////
// получение вещественной части комплексного числа 
inline double Complex::GetRe() const
{
    return m_re;
}
/////////////////////////////////////////////////////////////////////////////
// получение мнимой части комплексного числа
inline double Complex::GetIm() const
{
    return m_im;
}
/////////////////////////////////////////////////////////////////////////////
// изменение параметров комплексного числа
inline void Complex::Set(double re, double im)
{
    m_re=re;
    m_im=im;
}
/////////////////////////////////////////////////////////////////////////////
#endif //defined COMPLEX_H

 

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 6.1. Класс Комплексное число
// complex.cpp
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
// подключение описания класса
#include "complex.h"

/////////////////////////////////////////////////////////////////////////////
// конструктор по умолчанию
Complex::Complex()
: m_re(0.)
, m_im(0.)
{    
}
/////////////////////////////////////////////////////////////////////////////
// полный конструктор
Complex::Complex(double re, double im)
: m_re(re)
, m_im(im)
{
}
/////////////////////////////////////////////////////////////////////////////
// конструктор копирования
Complex::Complex(const Complex& x) 
: m_re(x.m_re)
, m_im(x.m_im)
{
}
/////////////////////////////////////////////////////////////////////////////
// оператор умножения
Complex Complex::operator*(const Complex& other) const
{
    return Complex(m_re*other.m_re-m_im*other.m_im,  
                   m_re*other.m_im-m_im*other.m_re);
}
/////////////////////////////////////////////////////////////////////////////
// оператор умножения на число
Complex Complex::operator*(const double& other) const
{
    return Complex(m_re*other, m_im*other);
} 
/////////////////////////////////////////////////////////////////////////////
// оператор умножения с присваиванием
Complex& Complex::operator*=(const Complex& other)
{
    Complex temp(*this);
    m_re=temp.m_re*other.m_re - temp.m_im*other.m_im;
    m_im=temp.m_re*other.m_im + temp.m_im*other.m_re;
    return (*this);
} 
/////////////////////////////////////////////////////////////////////////////
// унарный минус
Complex Complex::operator-() const
{
    return Complex(-m_re, -m_im);
}
/////////////////////////////////////////////////////////////////////////////
// оператор сопряжения комплексного числа
Complex Complex::operator~() const
{
    return Complex(m_re, -m_im);
} 
/////////////////////////////////////////////////////////////////////////////
// оператор равенства
bool Complex::operator== (const Complex& other) const
{
    return (m_re == other.m_re && m_im == other.m_im);
} 
/////////////////////////////////////////////////////////////////////////////
// оператор присваивания
Complex& Complex::operator=(const Complex& other)
{
    if(this != &other)
    {
        m_re=other.m_re;
        m_im=other.m_im;
    }
    return *this;
}
/////////////////////////////////////////////////////////////////////////////
// преобразование типа Complex в double
Complex::operator double() const
{
    return (m_re*m_re-m_im*m_im);
}
/////////////////////////////////////////////////////////////////////////////
// вывод комплексного числа на экран
ostream& operator<< (ostream& out, const Complex& x)
{
    return (out<<"("<<x.m_re<<","<<x.m_im<<")");
}
/////////////////////////////////////////////////////////////////////////////
// ввод комплексного числа с клавиатуры
istream& operator>> (istream& in, Complex& x) 
{
    return (in>>x.m_re>>x.m_im);
}
/////////////////////////////////////////////////////////////////////////////

 

/////////////////////////////////////////////////////////////////////////////
// Программирование на языке высокого уровня. Основы языка С++
// Пример 6.1. Класс Комплексное число
// test_complex.cpp
// 
// http://aco.ifmo.ru/el_books/programming
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
#include <iostream>
using namespace std;

// подключение описания класса
#include "complex.h"
/////////////////////////////////////////////////////////////////////////////
// пример использования класса Complex
void main()
{
    Complex x(1,1), y(2,2), res1, res2, res3; 
    Complex x1; 

    // тестирование преобразования типов    
    // преобразование вещественного числа в комплексное при помощи конструктора
    res1=Complex(3.14); 
    cout<<"Complex(3.14): "<<res1<<endl;
    // преобразование комплекcного числа в вещественное при помощи перегруженного оператора double
    double c=double(res1);
    cout<<"double(res1):  "<<c<<endl<<endl;

    // тестирование арифметических операторов
    res1=x*y;            // перемножение двух комплексных чисел
    res1=x.operator*(y); // то же самое, явный вызов оператора
    res2=-x;     // унарный минус
    res3=~x;     // комлексное сопряжение
    x*=y;        // умножение с присваиванием
    cout<<"  x*y="<<res1<<endl;
    cout<<"   -x="<<res2<<endl;
    cout<<"   ~x="<<res3<<endl;
    cout<<"x*=y: "<<x<<endl<<endl;

    // тестирование оператора равенства
    if(x==y)
        cout<<x<<" = "<<y<<endl<<endl;
    else
        cout<<x<<" != "<<y<<endl<<endl;

    // тестирование операторов присваивания
    res3=res2=res1=1;
    cout<<res1<<"="<<res2<<"="<<res3<<endl;
}
/////////////////////////////////////////////////////////////////////////////

6.2.2. Перегрузка бинарных операторов

// оператор умножения
Complex Complex::operator*(const Complex& other) const
{
   return Complex(m_re*other.m_re-m_im*other.m_im,  
                  m_re*other.m_im-m_im*other.m_re);
}
// оператор умножения на число
Complex Complex::operator*(const double& other) const
{
   return Complex(m_re*other, m_im*other);
} 

// пример использования
Complex x, z;
double y;
z=x*y;

6.2.3. Перегрузка унарных операторов

// унарный минус
Complex Complex::operator-() const
{
   return Complex(-m_re, -m_im);
}
// оператор сопряжения комплексного числа
Complex Complex::operator~() const
{
   return Complex(m_re, -m_im);
} 

// пример использования
Complex x, y;
y=-x;
y=~x;

6.2.4. Перегрузка логических операторов

// оператор равенства
bool Complex::operator== (const Complex& other) const
{
   return (m_re == other.m_re && m_im == other.m_im);
} 

// пример использования
Complex x, y;
if(x==y)
{
   …
}

6.2.5. Перегрузка оператора присваивания

Перегрузку оператора присваивания следует выделить особо. Мы уже рассматривали функцию, которая позволяет инициализировать один экземпляр класса значениями переменных членов хранящихся в другом экземпляре. Это конструктор копирования. Оператор присваивания отличается тем, что новый класс при его выполнении не создаётся, а значения членов одного экземпляра присваиваются членам другого экземпляра. При перегрузке оператора присваивания необходимо руководствоваться следующими правилами:

  1. Аргументом перегруженного оператора присваивания должна быть неизменяемая ссылка на экземпляр данного класса (чтобы случайно не испортить экземпляр).
  2. Перед осуществлением присваивания необходимо осуществить проверку на присваивание самому себе (чтобы не выполнять лишних действий).
  3. Оператор должен осуществлять поэлементное присваивание (то есть должно быть выполнено последовательное присваивание каждой переменной).
  4. Оператор должен возвращать ссылку на самого себя (чтобы оно было похоже на присваивание встроенных типов данных, тогда будет возможна запись x=y=z=1).
// оператор присваивания
Complex& Complex::operator=(const Complex& other)
{
   if(this != &other)
   {
      m_re=other.m_re;
      m_im=other.m_im;
   }
   return *this;
}

// пример использования
Complex x, y, z;
x=y=z=1;

6.2.6. Перегрузка операторов с присваиванием

Перегрузка операторов с присваиванием (+=, *= и т.д.) осуществляется по следующим правилам:

  1. Аргументом перегруженного оператора с присваиванием должна быть неизменяемая ссылка на экземпляр данного класса (чтобы случайно не испортить экземпляр).
  2. Перегруженный оператор с присваиванием должен возвращать ссылку на самого себя.
// оператор умножения с присваиванием
Complex& Complex::operator*=(const Complex& other)
{
   Complex temp(*this);
   m_re=temp.m_re*other.m_re - temp.m_im*other.m_im;
   m_im=temp.m_re*other.m_im + temp.m_im*other.m_re;
   return (*this);
} 
 
// пример использования
Complex x, y;
x*=y;

6.2.7. Перегрузка преобразования типов

Преобразовать встроенный тип данных к нашему абстрактному типу можно при помощи конструктора (см. раздел 6.1.2). А чтобы преобразовать абстрактный тип данных к встроенному типу, необходимо перегрузить оператор:

// преобразование типа Complex в double
Complex::operator double() const
{
   return (m_re*m_re-m_im*m_im);
}
 
// пример использования
Complex x;
double y;
y=double(x);

6.2.8. Перегрузка оператора доступа по индексу

Для некоторых классов, которые хранят массивы (например, матрица) для удобного доступа к элементу массива по его индексу можно перегрузить оператор ():

// оператор доступа по индексу
double& matrix::operator() (int i, int j)
{
    return (p[i][j]); // или p[i*size+j];
} 

// пример использования
matrix x;
double y;
y=matrix(1,1); // доступ к элементу (1,1)

6.2.9. Перегрузка операторов ввода/вывода

Перегрузку операторов ввода/вывода приходится оформлять в виде дружественных функций класса.

Это происходит из-за того, что при использовании оператора ввода или вывода слева от него должен находиться экземпляр потока ввода/вывода, то есть перегруженные операторы ввода/вывода являются членами потоков, а не других классов. Вмешаться во внутреннюю реализацию потоков мы не можем, но можем использовать функции, которые не являются членами классов, но позволяют получить доступ к их скрытым (private) членам.

// oписание:
friend ostream& operator<< (ostream& out, const Complex& x);
friend istream& operator>> (istream& out, Complex& x);


// вывод комплексного числа на экран
ostream& operator<< (ostream& out, const Complex& x)
{
   return (out<<"("<<x.m_re<<","<<x.m_im<<")");
}
// ввод комплексного числа с клавиатуры
istream& operator>> (istream& in, Complex& x) 
{
   return (in>>x.m_re>>x.m_im);
}
 
// пример использования
Complex x;
cout<<x<<endl; 

6.2.10. Неперегружаемые операторы

Не могут быть перегружены следующие операторы:

  • Оператор:: (левый и правый операнд являются не значениями, а именем)
  • Оператор. (правый операнд является именем)
  • Оператор.* (правый операнд является именем)
  • Оператор? : (арифметический оператор имеет специфическую семантику)
  • Оператор new (операнд является именем, кроме того выполняет небезопасную процедуру)
  • Оператор delete (не используется без new, кроме того выполняет небезопасную процедуру)

Кроме того, с помощью механизма перегрузки можно переопределить только существующие операторы. Новые операторы определить невозможно.