ПЕРЕГРУЗКА ОПЕРАТОРОВ (operator overloading) применяется как альтернатива функциям для пользовательских типов, которым в предметной области свойственен понятный однозначный синтаксис операций. Например, для сложения и умножения матриц вполне естественна и читабельна операторная форма выражений, хотя за этими элементарными на вид операциями скрываются алгоритмы с двойной и тройной вложенностью циклов соответственно:
D = A + B * C
Арифметические, логические и прочие операторы предоставляются языком С++ лишь для встроенных типов данных. Компилятор не в состоянии определить семантику операторов для пользовательских типов самостоятельно - нельзя просто складывать или умножать объекты любых классов одинаковым универсальным способом. В зависимости от смысла абстракции, заложенного автором класса, алгоритм вычисления оператора может иметь сугубо индивидуальный способ реализации, что делает автоматическую реализацию бессмысленной и невозможной.
Применение перегрузки операторов является необязательным, и для любого класса перегрузка операторов может быть заменена использованием обычных функций-членов. Если для реализуемого класса в предметной области операторный стиль является настолько естественным, как для матриц, перегрузка операторов существенно облегчает написание и чтение программ, использующих данный класс. Программист может предоставить собственную функцию, которая будет неявно вызываться при использовании оператора с объектами класса.
Однако, многие программисты злоупотребляют перегрузкой операторов для неподходящих задач, и в результате получают обратный эффект в виде снижения читабельности кода. Когда семантика конкретного оператора не очевидна из контекста, и глядя на операцию над объектами в коде программы не понятно, какие действия скрываются за оператором, то следует вместо перегруженных операторов предпочесть обычные функции-члены, поскольку их смысл легко донести через название.
Нельзя перегружать операторы:
● “.” - доступ к члену структуры/класса;
● “.*” - доступ к члену структуры/класса через указатель на член;
● “::” - оператор разрешения области видимости;
● “?:” - тернарный условный оператор.
Перегрузка вышеуказанных операторов не допускается, поскольку обратное могло бы привести к недопустимо сложной неоднозначности интерпретации синтаксиса.
//пример перегрузки унарного оператора “!”
bool CoffeeMachine::operator! ()
{
if(GetGrainWeidhtLeft()<4)
return 0;
return 1;
}
13. Внутриклассовые и глобальные перегруженные операторы. Перегрузка операторов сдвига. Применение перегрузки сдвига для взаимодействия с потоками ввода/вывода.
● Внутриклассовый оператор ==:
class Date
{ // … без изменений …
public:
// Объявление внутриклассового оператора сравнения на равенство дат
bool operator == (const Date & d) const;
};
// Реализация внутриклассового оператора сравнения на равенство дат
inline bool Date::operator == (const Date & d) const
{
// Даты равны, когда все их компоненты равны между собой
return m_Year == d.GetYear() &&
m_Month == d.GetMonth() &&
m_Day == d.GetDay();
}
● Глобальный оператор ==:
// Реализация глобального оператора сравнения на равенство дат
inline bool operator == (const Date & d1, const Date & d2)
{
return d1.GetYear() == d2.GetYear() &&
d1.GetMonth() == d2.GetMonth() &&
d1.GetDay() == d2.GetDay();
}
Выбор между внутриклассовой и глобальной перегрузкой операторов часто является стилистической конвенцией. Общая рекомендация состоит в ограничении количества функций, имеющих непосредственный доступ к private-части класса, соответственно, где это возможно и рационально, операторы должны быть глобальными функциями и работать с объектами классов через открытый общедоступный интерфейс. Однако, если операторов немного, и получение прямого доступа к закрытой части класса позволяет реализовать семантику оператора эффективно, внутриклассовая перегрузка всячески приветствуется.
Перегрузка операторов сдвига <<, >>
Как и любые другие операторы, операторы сдвига можно перегрузить в их оригинальном числовом смысле, предусмотренном языком C, если такой семантикой обладает разрабатываемый класс. Однако гораздо чаще такой оператор перегружают для обеспечения взаимодействия объектов классов и потоков ввода-вывода стандартной библиотеки. Система потоков реализована таким удачным образом, что определив операторы сдвига однажды, можно организовать единообразное взаимодействие объектов классов с потоками любого вида - консолью, файлами, буферами памяти.
Когда появляться необходимость вывода объектов на консоль, имеет смысл перегрузить операторы сдвига для соответствующих классов, и получить согласованный синтаксис вывода.
Перегрузить оператор для вывода объектов пользовательских типов относительно легко. Такой оператор может быть только глобальным, внутриклассовый вариант исключается, поскольку первым операндом является сам объект-поток, а определение его класса уже нельзя никак расширить для пользовательского типа. Глобальные перегружаемые операторы таких проблем не имеют:
std::ostream& operator << (std::ostream& o, const Date & d)
{
o << d.GetYear() << ‘/’ << d.GetMonth() << ‘/’ << d.GetDay();
return o;
}
Возврат ссылки на поток необходим для дальнейшего каскадирования, т.е. для следующих вызовов <<, идущих в инструкции, использующей вывод для объектов классов.
Если требуется решить обратную задачу, а именно чтение объекта-даты из входного потока, нужно добавить подобные определения, однако передавать объект по ссылке с правом на запись:
std::istream & operator >> (std::istream & i, Date& d)
{ // Считываем строку до пробела
char buf[ 100 ];
i >> buf;
d = Date(buf); // используем имеющийся конструктор из строки, а затем копируем
return i;
}
int main ()
{ Date d;
std::cin >> d; //...
}