C++ поддерживает перегрузку операторов (operator overloading). За небольшими исключениями большинство операторов C++ могут быть перегружены, в результате чего они получат специальное значение по отношению к определенным классам. Например, класс, определяющий связанный список, может использовать оператор + для того, чтобы добавлять объект к списку. Другой класс может использовать оператор + совершенно иным способом. Когда оператор перегружен, ни одно из его исходных значений не теряет смысла. Просто для определенного класса объектов определен новый оператор. Поэтому перегрузка оператора + для того, чтобы обрабатывать связанный список, не изменяет его действия по отношению к целым числам.
Операторные функции обычно являются либо членами, либо друзьями того класса, для которого они используются. Несмотря на большое сходство, имеется определенное различие между способами, которыми перегружаются операторные функции-члены и операторные функции-друзья.
Для того чтобы перегрузить оператор, необходимо определить, что именно означает оператор по отношению к тому классу, к которому он применяется. Для этого определяется функция-оператор, задающая действие оператора. Общая форма записи функции-оператора для случая, когда она является членом класса, имеет вид:
тип имя_класса::operator#(список_аргументов)
{
//действия, определенные применительно к классу
}
Здесь перегруженный оператор подставляется вместо символа #, а тип задает тип значений, возвращаемых оператором. Для того, чтобы упростить использование перегруженного оператора в сложных выражениях, в качестве возвращаемого значения часто выбирают тот же самый тип, что и класс, для которого перегружается оператор. Характер списка аргументов определяется несколькими факторами, как будет видно ниже.
Чтобы увидеть, как работает перегрузка операторов, начнем с простого примера. В нем создается класс point, представляющий собой точку в трехмерном пространстве. Следующая программа перегружает операторы + и = для класса point:
#include <iostream.h>
class point
{
double x, y, z; // трехмерные координаты
public:
point operator+(point t);
point operator=( point t);
void show();
void set(int mx, int my, int mz);
};
point point::operator+(point t)
{
point temp;
temp.x = x+t.x; temp.у = y+t.y; temp.z = z+t.z;
return temp;
}
point point::operator=(point t)
{
x = t.x; y = t.y; z = t.z;
return *this;
}
void point::show()
{
cout << x << ", " << у << ", " << z << "\n";
}
void point::set(int mx, int my, int mz)
{
x = mx; y = my; z = mz;
}
int main()
{
point a, b, c;
a.set(1, 2, 3); b.set(10, 10, 10);
а.show(); b.show();
с = a+b; // сложение а и b
с.shоw();
с = a+b+c; // сложение a, b и с
с.show();
c = b = a; // демонстрация множественного присваивания
с.show(); b.show();
return 0;
}
Эта программа выводит на экран следующие данные:
1, 2, 3
10, 10, 10
11, 12, 13
22, 24, 26
1, 2, 3
1, 2, 3
Если рассмотреть эту программу внимательно, может вызвать удивление, что обе функции-оператора имеют только по одному параметру, несмотря на то, что они перегружают бинарный оператор. Это связано с тем, что при перегрузке бинарного оператора с использованием функции-члена ей передается явным образом только один аргумент. Вторым аргументом служит указатель this, который передается ей неявно. Так, в строке
temp.х = х + t.х;
х соответствует this->x, где х ассоциировано с объектом, который вызывает функцию-оператор. Во всех случаях именно объект слева от знака операции вызывает функцию-оператор. Объект, стоящий справа от знака операции, передается функции.
При перегрузке унарной операции функция-оператор не имеет параметров, а при перегрузке бинарной операции функция-оператор имеет один параметр. (Нельзя перегрузить тернарный оператор ?:.) Во всех случаях объект, активизирующий функцию-оператор, передается неявным образом с помощью указателя this.
Чтобы понять, как работает перегрузка операторов, тщательно проанализируем, как работает предыдущая программа, начиная с перегруженного оператора +. Когда два объекта типа point подвергаются воздействию оператора +, значения их соответствующих координат складываются, как это показано в функции operator+(), ассоциированной с данным классом. Обратим, однако, внимание, что функция не модифицирует значений операндов. Вместо этого она возвращает объект типа point, содержащий результат выполнения операции. Чтобы понять, почему оператор + не изменяет содержимого объектов, можно представить себе стандартный арифметический оператор +, примененный следующим образом: 10 + 12. Результатом этой операции является 22, однако ни 10 ни 12 от этого не изменились. Хотя не существует правила о том, что перегруженный оператор не может изменять значений своих операндов, обычно имеет смысл следовать ему. Если вернуться к данному примеру, то нежелательно, чтобы оператор + изменял содержание операндов.
Другим ключевым моментом перегрузки оператора сложения служит то, что он возвращает объект типа point. Хотя функция может иметь в качестве значения любой допустимый тип языка C++, тот факт, что она возвращает объект типа point, позволяет использовать оператор + в более сложных выражениях, таких, как а + b + с. Здесь а + b создает результат типа point. Это значение затем прибавляется к с. Если бы значением суммы а + b было значение другого типа, то мы не могли бы затем прибавить его к с.
В противоположность оператору +, оператор присваивания модифицирует свои аргументы. (В этом и заключается смысл присваивания.) Поскольку функция operator=() вызывается объектом, стоящим слева от знака равенства, то именно этот объект модифицируется при выполнении операции присваивания. Однако даже оператор присваивания обязан возвращать значение, поскольку как в C++, так и в С оператор присваивания порождает величину, стоящую с правой стороны равенства. Так, для того, чтобы выражение следующего вида
а = b = с = d;
было допустимым, необходимо, чтобы оператор operator=() возвращал объект, на который указывает указатель this и который будет объектом, стоящим с левой стороны оператора присваивания. Если следовать этому правилу, то можно выполнять множественное присваивание.
Можно перегрузить унарные операторы, такие как ++ или --. Как уже говорилось ранее, при перегрузке унарного оператора с использованием функции-члена, эта функция-член не имеет аргументов. Вместо этого операция выполняется над объектом, осуществляющим вызов функции-оператора путем неявной передачи указателя this. В качестве примера ниже рассмотрена модификация предыдущей программы, в которой определяется оператор-инкремент для объекта типа point:
#include <iostream.h>
class point
{
double x, y, z; // трехмерные координаты
public:
point operator+(point t);
point operator=( point t);
point operator++(); //оператор инкремент
void show();
void set(int mx, int my, int mz);
};
point point::operator++()
{
x++; у++; z++;
return *this;
}
//остальные функции остались прежними
int main()
{
point a;
a.set(1, 2, 3);
a.show();
++a; // увеличение a
a.show();
return 0;
}
В ранних версиях C++ было невозможно определить, предшествует или следует за операндом перегруженный оператор ++ или --. Например, для объекта a следующие две инструкции были идентичными:
a++; ++a;
Однако более поздние версии C++ позволяют различать префиксную и постфиксную форму операторов инкремента и декремента. Для этого программа должна определить две версии функции operator++(). Одна их них должна быть такой же, как показано в предыдущей программе. Другая объявляется следующим образом:
point operator++(int x);
Если ++ предшествует операнду, то вызывается функция operator++(). Если же ++ следует за операндом, то тогда вызывается функция operator++(int x), где х принимает значение 0.
Действие перегруженного оператора по отношению к тому классу, для которого он определен, не обязательно должно соответствовать каким-либо образом действию этого оператора для встроенных типов C++. Например, операторы << и >> применительно к cout и cin имеют мало общего с их действием на переменные целого типа. Однако, исходя из стремления сделать код более легко читаемым и хорошо структурированным, желательно, чтобы перегруженные операторы соответствовали, там, где это возможно, смыслу исходных операторов. Например, оператор + по отношению к классу point концептуально сходен с оператором + для переменных целого типа. Мало пользы, например, можно ожидать от такого оператора +, действие которого на соответствующий класс будет напоминать действие оператора ||. Хотя можно придать перегруженному оператору любой смысл по своему выбору, но для ясности его применения желательно, чтобы его новое значение соотносилось с исходным значением.
Имеются некоторые ограничения на перегрузку операторов. Во-первых, нельзя изменить приоритет оператора. Во-вторых, нельзя изменить число операндов оператора. Наконец, за исключением оператора присваивания, перегруженные операторы наследуются любым производным классом. Каждый класс обязан определить явным образом свой собственный перегруженный оператор =, если он требуется для каких-либо целей. Разумеется, производные классы могут перегрузить любой оператор, включая и тот, который был перегружен базовым классом. Следующие операторы не могут быть перегружены: . :: ? .* # ##