Переопределение операторов

Вспомним еще раз весь список операторов в C++

  • Арифметические:
    • Унарные: (префиксные/постфиксные) + - ++ –
    • Бинарные: + - * / % += -= /= %=
  • Битовые:
    • Унарные: ~
    • Бинарные: & | ^ &= |= ^= » «
  • Логические:
    • Унарные: !
    • Бинарные: & | && ||
    • Сравнения: == != > < >= <=
  • Присваивания: =
  • Тернарный: ?:
  • Специальные: , . ::
  • Скобки: [] ()
  • Оператор приведения: (type)
  • Доступа: ->

Все операторы, за исключением ?:, ., :: можно перегружать. Операторы ||, &&, , не рекомендуется перегружать так как для них стандарт предусматривает порядок вычисления операндов (слева направо), а для первых двух еще и так называемую семантику быстрых вычислений (short-circuit evaluation), но для перегруженных операторов это уже не гарантируется или просто бессмысленно.

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

Рассмотри на примере простого класс Vector:

struct Vector {
    double x;
    double y;
};

Операторы можно перегружать в двух вариантах: как функцию внутри класса и как свободную функцию. Четыре оператора можно перегрузить только как функцию внутри класса — это =, ->, [], (). Для перечислений операторы можно перегружать только как свободные функции.

Для того, чтобы перегрузить оператор как функцию внутри класса необходимо объявить нестатическую функцию с именем operator@, где @ символ(ы) оператора. В случае перегрузки унарного оператора эта функция не должна иметь параметров, а в случае бинарного должна иметь ровно один параметр. В случае перегрузки оператора () эта функция может иметь произвольное число параметров.

Для того, чтобы перегрузить оператор как свободную функцию, необходимо объявить функцию с именем operator@, где @ символ(ы) оператора. В случае перегрузки унарного оператора, эта функция должна иметь один параметр, а в случае бинарного должна иметь два параметра. В случае перегрузки бинарного оператора — по крайней мере один из двух параметров, а в случае унарного единственный параметр должен быть того же типа (или типа ссылки), что и тип, для которого реализуется перегрузка. Так же эта функция должна находится в том же пространстве имен, что и тип, для которого реализуется перегрузка Пример свободных функций:

// унарный минус
Vector operator-(Vector const& v) {
    return Vector(-v.x, -v.y);
}
// бинарный плюс
Vector operator+(Vector const& v, Vector const& w) {
	return Vector(v.x + w.x, v.y + w.y);
}
// перегрузка умножение на число справа
Vector operator*(Vector const& v, double d) {
	return Vector(v.x * d, v.y * d);
}
// перегрузка умножение на число слева
Vector operator*(double d, Vector const& v) {
	return v * d;
}

Аналогично, часть операторов можно объявить и внутри класса:

struct Vector {
    double x;
    double y;

    Vector operator-() const { return Vector (-x, -y); }

    // бинарный минус. Аргумент всегда справа
    Vector operator-(Vector const& v) const {
		return Vector(x  v.x, y  v.y);
	}

	Vector& operator*=(double d) {
		x *= d;
		y *=d;
		return *this;
	}

    double operator[] (int i) const {
		return i == 0 ? x : y;
	}

    // операторы имитирующие вызов функций. Например:
    // Vector sample(5,6);
    // sample(3);
    // sample(4, 5);
	bool operator()(double d) const 	{ /*… */ }
	void operator()(double a, double b) 	{ /*… */ }
};

Перегрузка инкремента / декремента

Оператор инкремента (++) и дикремента (--) имеют префиксную и постфиксную запись. Напомню, на этом примере:

int k = 5;
int l = k++;
cout << k << " " << l; // 6 5
int m = ++k;
cout << k << " " << m; // 7 7

Чтобы задать форму (префиксного/постфиксного) данных операторов необходимо задать флаговое обозначение (int) в объявлении перегрузки:

struct Counter {
	Counter& operator++() { // prefix
		//...
		return *this;
	}
	Counter operator++(int) { //postfix
		Counter temp(*this);
		++(*this);
		return temp;
	}
};

Переопределение операторов ввода-вывода

#include <iostream>
struct Vector { /*...*/ };
istream& operator>>(istream& is, Vector &p) {
	is >> p.x >> p.y;
	return is;
}
ostream& operator<<(ostream& os, Vector const& p) {
	os << p.x << " " << p.y;
	return os;
}

Переопределние оператора приведения

struct Vector {
    double x;
    double y;

	operator double() const {
		return sqrt(x * x + y * y);
	}
};
//...
Vector v(2, 3);
cout << (double)v;  // 4

Операторы сравнения

bool operator==(Vector const& x, Vector const& b) { return  }
bool operator!=(Vector const& x, Vector const& b) { return ! (a == b); }
bool operator<(Vector const& x, Vector const& b) { return  }
bool operator>(Vector const& x, Vector const& b) { return b < a; }
bool operator<=(Vector const& x, Vector const& b) { return !(b < a); }
bool operator>=(Vector const& x, Vector const& b) { return !(a < b); }