1 Введение
C++ позволяет переопределить действие большинства операций для манипулирования с объектами конкретных типов. Это оказывается очень удобно, поскольку привычные символы обозначения операций могут быть использованы в разных смыслах, в зависимости от типов.
Можно перегружать любые операции, существующие в С++, за исключением:
. .* ?: :: sizeof typedef
Перегрузка операций осуществляется с помощью методов специального вида и подчиняется следующим правилам:
- При перегрузке сохраняются количество аргументов, приоритеты операций и правила ассоциаций (справа налево или слева направо), используемые в стандартных типах данных.
- Для стандартных типов перегружать операции нельзя.
- Функции-операторы не могут иметь аргументов по умолчанию.
- Функции-операторы могут наследоваться (за исключением =).
- Функции-операторы не могут быть статическими.
- Для одного и того же оператора можно объявить несколько перегруженных операторов - функций. Они должны отличаться по типу и количеству аргументов.
2 Бинарные операции
Можно определить в виде:
- нестатической функции-члена с одним аргументом,
- функции-не-члена с двумя аргументами.
Выражение aa@bb интерпретируется:
- aa.operator@(bb)
- operator@(aa, bb)
Бинарные операции. Примеры
class Coord {
int x, y, z;
public:
Coord operator+ (Coord);
Coord& operator= (Coord);
Coord& operator*= (int);
};
Coord operator*(Coord, int);
Coord operator*(Coord c,int m)
{
Coord temp = c;
return temp*=m;
}
Coord Coord::operator+(Coord t) {
Coord temp;
temp.x = x + t.x;
temp.y = y + t.y;
temp.z = z + t.z;
return temp;
}
Coord& Coord::operator=(Coord t) {
x = t.x;
y = t.y;
z = t.z;
return *this;
}
Члены и не-члены классов
class Coord {
public:
Coord& operator+=(const Coord&);
//...
};
Coord operator+(const Coord&, const Coord&);
Coord operator+(const Coord& c1, const Coord& c2)
{
Coord temp = c1;
return temp+=c2;
}
Coord& operator+=(const Coord& c)
{
x += c.x;
y += y.x;
z += z.x;
return *this;
}
class Coord {
public:
bool operator==(const Coord& c) const;
//...
};
bool operator!=(const Coord& c1, const Coord& c2);
bool operator!=(const Coord& c1, const Coord& c2)
{
return !(c1==c2);
}
3 Унарные операции
Можно определить в виде:
- нестатической функции-члена без аргументов,
- функции-не-члена с одним аргументом.
Выражение @aa интерпретируется как:
- aa.operator@ ()
- operator@ (aa)
Унарные постфиксные операции
Для любого постфиксного оператора выражение aa@ интерпретируется как:
- aa.operator@ (int)
- operator@ (aa, int)
- Аргумент int используется для указания на постфиксную форму
- Аргумент является фиктивным
Примеры
class Coord {
int x, y, z;
public:
Coord& operator- ();
Coord& operator++ ();
}
Coord& Coord::operator++() {
++x;
++y;
++z;
return *this;
}
Coord& Coord::operator- () {
x = -x;
y = -y;
z = -z;
return *this;
}
Coord operator++(Coord& c, int i)
{
Coord temp = c;
++c;
return temp;
}
4 Предопределенный смысл
Предопределенный смысл операций
- Оператор может быть объявлен только с синтаксисом, существующем для него в грамматике
- operator=, operator[ ], operator-> должны быть нестатическими функциями-членами
- Некоторые операторы определены так, что они равны комбинации других операторов, например ++a означает a+=1 или a=a+1. Компилятор сам об этом не заботится.
- Операторы = и & имеют предопределенный смысл для объектов класса, если они закрытые, этот смысл может стать недоступным.
- Операторная функция может быть либо членом, либо иметь аргумент типа, определяемого пользователем.
- Операторная функция, у которой первый операнд принадлежит к встроенному типу, не может являться членом.
aa+2
aa.operator+(2)
2+aa
2.operator+(aa)
5 Особые случаи
Перегрузка присваивания
Операция присваивания определена в любом классе по умолчанию как поэлементное копирование. Если класс содержит поля, память под которые выделяется динамически, необходимо определить собственную операцию присваивания. Чтобы сохранить семантику присваивания, функция должна возвращать ссылку на объект, для которого она вызвана и принимать в качестве параметра ссылку на присваиваемый объект.
Оператор = можно перегружать только как ФЧ, а не функцию - друг ФД.
class Complex
{
int re,im;
public:
Complex(Complex& c) { // конструктор копирования
re=c.re; im=c.im;
}
Complex(int re, int im) { // конструктор-инициализатор
this->re=re; this->im=im;
}
Complex& operator=(Complex& c) { // оператор присваивания
re=c.re; im=c.im;
return *this;
}
...
};
int main() {
Complex c1(1,2),c2(3,4),c3(0,0);
c3=c1+c2;
c3.print();
return 0;
}
Перегрузка потоков
Замечательной способностью потоков является то, что операции ввода и вывода могут быть перегружены для пользовательских типов. Функции operator<< и operator>> должны быть друзьями класса, описывающего пользовательский тип. Функции принимают в качестве аргументов ссылку на входной (выходной) поток и ссылку на объект класса, данные из которого необходимо обработать. В качестве возвращаемых значений необходимо также указывать ссылки на поток ввода или вывода (в этом случае можно конструировать сложные потоковые конструкции).
class Complex
{
...
friend ostream& operator<<(ostream& os, Complex& c);
friend istream& operator>>(istream& is, Complex& c);
...
};
ostream& operator<<(ostream& os, Complex& c) {
return os<<'('<<c.re<<','<<c.im<<')';
}
istream& operator>>(istream& is, Complex& c) {
return is>>c.re>>c.im;
}
int main() {
Complex c(0,0);
cin>>c; // 23,45
cout<<c; // (23,45)
}
Перегрузка new delete
Существует возможность перегрузить операторы new, delete для более эффективного распределения динамической памяти. Всего существует 4 формы операторов выделения и освобождения памяти
- new - работа с одиночными объектами
- delete - работа с одиночными объектами
- delete[] работа с массивами объектов
Приперегрузки операторов для работы с динамической памятью нужно соблюдать следующие правила:
- операторам не требуется передавать параметр типа класс
- первым параметром для new должен передаваться размер объекта size_t
- возвращаемые значения new должны иметь тип void*
- возвращаемые значения delete, delete[] должны иметь тип void
- первый аргумент delete, delete[] должен иметь тип void*
- данные операции являются статическими членами класса
class Foo {
...
static void* operator new(size_t size);
static void operator delete(void * obj, size_t size);
...