{lang: ‘ru’}

перегрузка операций, перегрузка операторов c, операции перегрузки в сиПерегрузка операций или, по другому, перегрузка операторов c++ – это одна из самых захватывающих возможностей ООП. Она может превратить сложный и малопонятный листинг программы в интуитивно понятный. Например, строки

d3.addobjects ( dl, d2 );

похожие, но одинаково непонятные

d3 = dl.addobjects ( d2 );

можно заменить на более читаемую

d3 = dl + d2;

Довольно непривлекательный термин «перегрузка операций» дается обычным операциям C++, таким, как +, *, <= или +=, в случае их применения с определенными пользователем типами данных. Обычно

а = b + с;

работают только с основными типами данных, такими, как int или float, и попытка использования обычных операторов, когда a, b и с являются объектами определенного пользователем класса, приведет к протестам компилятора. Однако, используя перегрузку, вы можете сделать эту строку правильной даже в том случае, если a, b и с являются объектами определенного пользователем класса. На самом деле перегрузка операций дает вам возможность переопределить язык C++. Если вы считаете, что ограничены стандартными возможностями операций, то вы можете назначить их выполнять нужные вам действия. Используя классы для создания новых видов переменных и перегрузку для определения новых операций, вы можете, расширив C++, создать новый, свой собственный язык.

Перегрузка унарных операций

Давайте начнем с перегрузки унарных операций. Унарные операции имеют только один операнд (операнд — это переменная, на которую действует операция). Примером унарной операции могут служить операции уменьшения и увеличения ++ и — и унарный минус, например -33.

Допустим мы создали класс Counter для хранения значения счетчика. Объект этого класса мы увеличиваем вызовом метода:

cl.inc_count ( );

Он выполнял свою работу, но листинг программы станет более понятным, если мы применим вместо этой записи операцию увеличения ++:

++cl;

Давайте перепишем программу, чтобы сделать это действие возможным. Приведем листинг программы.

#include <iostream>
using namespace std;
///////////////////////////////////////////////////////////
class Counter
{
private:
	unsigned int count; // значение счетчика
public:
	Counter ( ) : count ( 0 ) // конструктор
	{ }
	unsigned int get_count ( ) // получить значение
	{ return count; }
	void operator++ ( ) // увеличить значение
	{ ++count; }
};
///////////////////////////////////////////////////////////
int main ( )
{
	Counter cl, c2; // определяем переменные
	cout << "ncl = " << cl.get_count ( ); // выводим на экран
	cout << "nc2 = " << c2.get_count ( );
	++cl; // увеличиваем cl
	++c2; // увеличиваем c2
	++c2; // увеличиваем c2
	cout << "ncl = " << cl.get_count ( ); // снова показываем значения
	cout << "nc2 = " << c2.get_count ( ) << endl;
	return 0;
}

В этой программе мы создали два объекта класса Counter: cl и с2. Значения счетчиков выведены на дисплей, сначала они равны 0. Затем, используя перегрузку операции ++, мы увеличиваем cl на единицу, а с2 на два и выводим полученные значения. Результат работы программы:

cl = 0 - изначально обе переменные равны нулю
с2 = 0
cl = 1 - после однократного увеличения
с2 = 2 - после двукратного увеличения

Выражения, соответствующие этим действиям:

++cl;
++с2;
++с2;

Операция ++ применена к cl и дважды применена к с2. В этом примере мы использовали префиксную версию операции ++, постфиксную мы объясним позднее.

Ключевое слово operator

Как использовать обычные операции с определенными пользователями типами? В этом объявлении использовано ключевое слово operator для перегрузки операции ++:

void operator++ ( )

Сначала пишут возвращаемый тип (в нашем случае void), затем ключевое слово operator, затем саму операцию (++) и наконец список аргументов, заключенный в скобки (здесь он пуст). Такой синтаксис говорит компилятору о том, что если операнд принадлежит классу Counter, то нужно вызывать функцию с таким именем, встретив в тексте программы операцию ++. Компилятор различает перегружаемые функции по типу данных и количеству их аргументов. Перегружаемые операции компилятор отличает по типу данных их операндов. Если операнд имеет базовый тип, такой, как int в ++intvar то компилятор будет использовать свою встроенную процедуру для увеличения переменной типа int. Но если операнд является объектом  класса Counter, то компилятор будет использовать написанную программистом функцию operator++().

Аргументы операции

В функции main() операция ++ применена к объекту, как в выражении ++cl. До сих пор функция operator++() не имела аргументов. Что же увеличивает эта операция? Она увеличивает переменную count объекта, к которому применяется. Так как методы класса всегда имеют доступ к объектам класса, для которых они были вызваны, то для этой операции не требуются аргументы. Это показано на рис. 1.

перегрузка операций, перегрузка операторов c, операции перегрузки в си

Рис. 1. Перегрузка унарной операции без аргументов

Значения, возвращаемые операцией

Функция operator++() программы имеет небольшой дефект. Вы можете его выявить, если используете в функции main() строку, похожую на эту:

сl = ++с2;

Компилятор будет протестовать. Почему? Просто потому, что мы определили тип void для возвращаемого значения функции operator++(). А в нашем выражении присваивания будет запрошена переменная типа Counter. То есть компилятор запросит значение переменной с2, после того как она будет обработана операцией ++, и присвоит ее значение переменной cl. Но,
при данном нами определении мы не можем использовать ++ для увеличения объекта Counter в выражении присваивания: с таким операндом может быть использована только операция ++. Конечно, обыкновенная операция ++, примененная к данным таких типов, как int, не столкнется с этими проблемами.

Для того чтобы иметь возможность использовать написанный нами operator++() в выражениях присваивания, нам необходимо правильно определить тип его возвращаемого значения. Это сделано в следующей нашей программе.

#include <iostream>
using namespace std;
///////////////////////////////////////////////////////////
class Counter
{
private:
	unsigned int count;
public:
	Counter ( ) : count ( 0 )
	{ }
	unsigned int get_count ( )
	{ return count; }
	Counter operator++ ( )
	{
		++count;
		Counter temp;
		temp.count = count;
		return temp;
	}
}
///////////////////////////////////////////////////////////
int main ( )
{
	Counter cl, c2; // определяем переменные
	cout << "ncl = " << cl.get_count ( ); // выводим на экран
	cout << "nc2 = " << c2.get_count ( );
	++cl; // увеличиваем cl
	c2 = ++cl; // cl=2, c2=2
	cout << "ncl = " << cl.get_count ( ); // снова показываем значения
	cout << "nс2 = " << c2.get_count ( ) << endl;
	return 0;
}

Здесь функция operator++() создает новый объект класса Counter, названный temp, для использования его в качестве возвращаемого значения. Она сначала увеличивает переменную count в своем объекте, а затем создает объект temp и присваивает ему значение count, то же значение, что и в собственном объекте. В конце функция возвращает объект temp. Получаем ожидаемый эффект. Выражение типа

++cl;

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

с2 = ++cl;

как показано в функции main(), где значение, возвращаемое cl++, присваивается с2. Результат работы этой программы будет следующим:

cl = 0
с2 = 0
cl = 2
с2 = 2

Перегрузка бинарных операций

Бинарные операции могут быть перегружены так же, как и унарные операции. Мы рассмотрим примеры перегрузки арифметических операций, операций сравнения и операции присваивания.

Арифметические операции

Допустим у нас есть два объекта класса Distance, которые складываются с помощью метода

add_dist():
dist3.add_dist ( dist1, dist2 );

Используя перегрузку операции +, мы можем отказаться от этого неприглядного выражения и воспользоваться таким:

dist 3 = dist1 + dist2

В листинге нижеперечисленной программы реализована эта возможность:

#include <iostream>
using namespace std;
///////////////////////////////////////////////////////////
class Distance // класс английских мер длины
{
private:
	int feet;
	float inches;
public:
	// конструктор без параметров
	Distance ( ) : feet ( 0 ), inches ( 0.0 )
	{ }
	// конструктор с двумя параметрами
	Distance ( int ft, float in ) : feet ( ft ), inches ( in )
	{ }
	// получение информации or пользователя
	void getdist ( )
	{
		cout << "nВведите футы: "; cin >> feet;
		cout << "Введите дюймы: "; cin >> inches;
	}
	// показ информации
	void showdist ( )
	{
		cout << feet << "'-" << inches << "'""; }
	// сложение двух длин
	Distance operator+ ( Distance );
};
///////////////////////////////////////////////////////////
// сложение двух длин
Distance Distance::operator+ ( Distance d2 )
{
	int f = feet + d2.feet; // складываем футы
	float i = inches + d2.inches; // складываем дюймы
	if ( i >= 12.0 ) // если дюймов стало больше 12
	{
		i -= 12.0; // то уменьшаем дюймы на 12
		f++; // и увеличиваем футы на 1
	}
	return Distance (f, i); // создаем и возвращаем временную переменную
}
///////////////////////////////////////////////////////////
int main ( )
{
	Distance dist1, dist3, dist4; // определяем переменные
	dist1.getdist ( ); // получаем информацию
	Distance dist2 ( 11, 6.25 ); // определяем переменную со значением
	dist3 = dist1 + dist2; // складываем две переменные
	dist4 = dist1 + dist2 + dist3; // складываем несколько переменных
	// показываем, что же у нас получилось
	cout << "dist1 = "; dist1.showdist ( ); cout << endl;
	cout << "dist2 = "; dist2.showdist ( ); cout << endl;
	cout << "dist3= "; dist3.showdist ( ); cout << endl;
	cout << "dist4 = "; dist4.showdist ( ); cout << endl;
	return 0;
}

Для того чтобы показать, что результат сложения может быть использован в других операциях, мы выполнили в функции main() следующие действия: сложили dist1, dist2 и dist3, чтобы получить dist4 (значение которой представляет собой удвоенное значение переменной dist3), в строке:

dist4 = dist1 + dist2 + dist3

Вот результат работы программы:

Введите футы: 10
Введите дюймы: 6.5
dist1 = 10'-6.5" - ввод пользователя
dist2 = 11'-6.25" - предопределено в программе
dist3 = 22'-0.75" - dist1+dist2
dist4 - 44'-1.5" - dist1+dist2+dist3

Объявление метода operator+() в классе Distance выглядит следующим образом:

Distance operator+ ( Distance )

Эта операция возвращает значение типа Distance и принимает один аргумент типа Distance.

В выражении

dist3 = dist1 + dist2;

важно понимать, к каким объектам будут относиться аргументы и возвращаемые значения. Когда компилятор встречает это выражение, то он просматривает типы аргументов. Обнаружив только аргументы типа Distance, он выполняет операции выражения, используя метод класса Distance operator+(). Но какой из объектов используется в качестве аргумента
этой операции — dist1 или dist2? И не нужно ли нам использовать два аргумента, так как мы складываем два объекта?

Существует правило: объект, стоящий с левой стороны операции (в нашем случае dist1), вызывает функцию оператора. Объект, стоящий справа от знака операции, должен быть передан в функцию в качестве аргумента.
Операция возвращает значение, которое мы затем используем для своих нужд. В нашем случае мы присваиваем его объекту dist3. Рисунок 2 иллюстрирует все эти действия.

перегрузка операций, перегрузка операторов c, операции перегрузки в си

Рис. 2. Перегрузка бинарной операции с одним аргументом

В функции operator+() к левому операнду мы имеем прямой доступ, используя feet и inches, так как это объект, вызывающий функцию. К правому операнду мы имеем доступ как к аргументу функции, то есть как d2.feet и d2.inches.

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

Для вычисления значения функции operator+() мы сначала складываем значения feet и inches обоих операндов (корректируя результат, если это необходимо). Полученные значения f и i мы затем используем при инициализации безымянного объекта класса Distance, который затем будет возвращен в выражение

return Distance( f, i );

Похожие методы вы сможете создать в классе Distance для перегрузки других операций. Тогда вы сможете выполнять вычитание, умножение и деление с объектами этого класса, используя обычную запись выражени.

Множественная перегрузка

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

Операции сравнения

Давайте рассмотрим перегрузку операций другого типа: операций сравнения.

Сравнение объектов класса Distance

В нашем примере мы перегрузим операцию «меньше чем» (<) в классе Distance для того, чтобы иметь возможность сравнивать объекты этого класса. Приведем листинг программы ENGLESS.

#include <iostream>
using namespace std;
///////////////////////////////////////////////////////////
class Distance // класс английских мер длины
{
private:
	int feet;
	float inches;
public:
	// конструктор без параметров
	Distance ( ) : feet ( 0 ), inches ( 0.0 )
	{ }
	// конструктор с двумя параметрами
	Distance ( int ft, float in ) : feet ( ft ), inches ( in )
	{ }
	// получение информации от пользователя
	void getdist ( )
	{
		cout << "nВведите футы: "; cin >> feet;
		cout << "Введите дюймы: "; cin >> inches;
	}
	// показ информации
	void showdist ( )
	{
		cout << feet << "’-" << inches << "'"";
	}
	// сравнение двух длин
	bool operator< ( Distance );
};
///////////////////////////////////////////////////////////
// сравнение двух длин
bool Distance::operator< ( Distance d2 )
{
	float bfl = feet + inches /12;
	float bf2 = d2.feet + d2.inches / 12;
	return ( bfl < bf2 ) ? true : false;
}
///////////////////////////////////////////////7///////////
int main ( )
{
	Distance dist1; // определяем переменную
	dist1.getdist ( ); // получаем длину от пользователя
	Distance dist2 ( 6, 2.5 ); // определяем и инициализируем переменную
	// показываем длины
	cout << "ndist1 - "; dist1.showdist ( );
	cout << "ndist2 - "; dist2.showdist ( );
	// используем наш оператор
	if ( dist1 < dist2 )
		cout << "ndist1 меньше чем dist2";
	else
		cout << "ndist1 больше или равно чем dist2";
	cout << endl;
	return 0;
}

Эта программа сравнивает интервал, заданный пользователем, с интервалом 6′-2.5″, определенным в программе. В зависимости от полученного результата затем выводится одно из двух предложений. Пример возможного вывода:

Введите футы: 5
Введите дюймы: 11.5
dist1 = 5"-11.5"
dist2 = 6'-2.5"
dist1 меньше чем dist2

Здесь функция operator<() имеет возвращаемое значение типа bool. Возвращаемым значением может быть false или true, в зависимости от результата сравнения двух интервалов. При сравнении мы конвертируем оба интервала в значения футов с плавающей точкой и затем сравниваем их, используя обычную операцию <. Помним, что здесь использована операция
условия:

return ( bfl < bf2 ) ? true : false

это то же самое, что

if ( bfl < bf2 )
    return true;
else
    return false


Получайте новые статьи блога прямо себе на почту