Перегрузка операций мне давалась тяжело. Проблема с объяснениями, как мне кажется, налицо. Все друг у друга списывают и никто разъяснить понятно не может. Я тут рискнуть решил, попробовать истолковать понятнее кого бы то ни было. Если удастся, то это будет здорово.
Перегрузка — это дополнительные варианты обработки, пригружаемые довеском на одну и ту же сущность (перегрузка функций, перегрузка операций)
В С++ перегружать операции для встроенных типов нельзя, можно только для наших собственных типов (т. е. перегрузка возможна при наличии классов/структур/объединений). Есть два подхода к перегрузке: перегружать операцию внутри класса и перегружать операцию вне класса. Эти подходы незначительно различаются в написании кода.
Первое, что мы сейчас перегрузим — операция + — перегрузим эту операцию для структуры.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Листинг #1.1 Перегрузка сложения Для структуры Вне класса clang
#include <iostream>
usingstd::cout;
structMyStruct{
intvalue;
};
intmain(){
MyStructx;
MyStructy;
MyStruct result;
x.value=100;
y.value=100;
result=x+y;//<-- нельзя, потому что тип MyStruct не знает об операции +
//надо учить
cout<<result.value<<'\n';
}
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//Листинг #1.2 Перегрузка сложения Для структуры Вне класса clang
#include <iostream>
usingstd::cout;
structMyStruct{
intvalue;
};
/* УЧИМ СТРУКТУРУ ОПЕРАЦИИ + */
MyStruct operator+(MyStructx,MyStructy){
x.value=x.value+y.value;;
returnx;
}
/*научили структуру*/
intmain(){
MyStructx;
MyStructy;
MyStruct result;
x.value=100;
y.value=100;
result=x+y;//<-- теперь работает
cout<<result.value<<'\n';
}
Операция сложения — это операция бинарная. Нужно два слагаемых. Поэтому когда мы учим структуру вне класса, нужно передавать два аргумента. Сама перегрузка почти что самая обычная функция, в принципе, она и есть самая обычная функция, но отличает её наличие ключевого оператора operator и обозначение операции, в нашем случае значка сложения. На месте значка сложения могла бы быть любая другая бинарная операция: вычитание, умножение, деление…
Из-за того, что на один символ возложено много разного рода функциональности (функциональность зависит от типа или типов, которые в деле), называют перегрузку перегрузкой. Живёт себе символ спокойно, умеет что-то делать с данными, а тут прилетают новые умения, и постоянно довесок уменями происходит. Грузится символ, грузится, грузится, грузится… Вот поэтому перегрузка.
Объекты сложных типов в функции правильно передавать по ссылке, а те, которые не меняются внутри функций, по константной ссылке, поэтому гораздо приятнее наш код будет выглядет вот в таком виде:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//Листинг #1.3 Перегрузка сложения Для структуры Вне класса clang
#include <iostream>
usingstd::cout;
structMyStruct{
intvalue;
};
/* УЧИМ СТРУКТУРУ ОПЕРАЦИИ + */
MyStruct operator+(MyStruct&x, const MyStruct &y){ //x внутри изменяется, y - нет
x.value = x.value + y.value;;
returnx;
}
/*научили структуру*/
intmain(){
MyStructx;
MyStructy;
MyStruct result;
x.value=100;
y.value=100;
result=x+y;//<-- теперь работает
cout<<result.value<<'\n';
}
Вы постоянно будете натыкаться на примеры с константными ссылками и просто ссылками, просто потому что именно с ними правильно. Поэтому я привёл в пример #1.3
Перед тем, как идти дальше, вы должны освоить хотя бы пример #1.1. Он очень простой, но без понимания его дальше двигаться будет сложно.
Сложность может возникнуть с возвращаемым значением и типами, указываемыми для параметров. Типы в скобках — это типы уходящих в функцию аргументов. Возвращаемое значение должно совпадать с типом конечной для операции сущности. В показанном примере все типы были представлены одним классом, позднее будет показано для трёх классов.
На возвращаемом значении предлагаю пока что не останавливаться надолго, а изучать тему дальше. Синтаксис для перегрузки операции вне класса и внутри класса отличается. Как по мне, так одной из проблем сложного восприятия темы является порядок выдачи примеров: сначала дают пример с перегрузкой внутри класса, потом или вообще ничего не дают, или всё-таки дают нехотя пример с перегрузкой операции вне класса. По листингам #1. должно быть понятно: бинарная операция, два параметра для перегрузки. С случае с перегрузкой операции внутри класса, один из двух параметров явно передавать не нужно, не нужно передавать тот параметр, о котором известно классу.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Листинг #2.1 Перегрузка сложения Для структуры В классе clang
#include <iostream>
usingstd::cout;
structMyStruct{
intvalue;
/* УЧИМ СТРУКТУРУ ОПЕРАЦИИ + */
MyStruct operator+(MyStructy){
value=value+y.value;;
return*this;
}
/*научили структуру*/
};
intmain(){
MyStructx;
MyStructy;
MyStruct result;
x.value=100;
y.value=100;
result=x+y;//<-- теперь работает
cout<<result.value<<'\n';
}
При перегрузке бинарной операции в классе, в параметрах должен остаться только один. Вот этот момент меня дико тормозил. Первый параметр представляет из себя уже известный классу объект: это тот объект, к которому будет применена перегружаемая операция, поэтому в параметрах его задавать — излишек. А вот второй параметр классу узнать не от куда, поэтому классу в перегрузке операции нужно подсказать, что то будет за параметр. Фактически у нас при A+B к A применяется плюс, а поскольку плюс применяется к А, то это А будет известно внутри класса, т. е. и перегрузка затачивается под это А, а вот B надо подсказывать. Кто первый слева — тот известен, а следующий — неизвестен. Неизвестный делаем известным с помощью передачи значения в параметр.
Сейчас будет показан, возможно, сложный пример для новичка. Но буду надеяться, что все этот пример понять смогут. Ключ к пониманию — понимание типов, даваемых под возвращаемые значения функций. Суть: если у нас три независимых класса с порождёнными от них объектами и два объекта нужно сложить между собой и вывести результат в третий объект, то делается это вот так (пример вне класса):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//Листинг #3.1 Перегрузка сложения Для структуры В классе clang
#include <iostream>
usingstd::cout;
structMan{
intsalary;
};
structFeman{
intsalary;
};
structResult_Salary{
intsalary;
};
/* УЧИМ СТРУКТУРУ ОПЕРАЦИИ + */
Result_Salary operator+(Man man,Feman feman){
Result_Salary result;
result_salary=man.salary+feman.salary;////<-- не перепутайте регистр!!!
returnresult;
}
/*научили структуру*/
intmain(){
Manx;
Femany;
Result_Salary result;
x.salary=100;
y.salary=100;
result=x+y;
cout<<result.salary<<'\n';
}
В листинге #3.1 нам не пришлось перегружать операцию = только по той причине, что мы свели сложение типов Man + Feman к результату типа Result_Salary. Сведение это получилось благодаря возвращаемому из функции, перегружающей плюс, применяемый к типам Man и Feman. Поскольку мы свели сложение к типу Result_Salary и присваиваем само сложение в объект типа Result_Salary, дополнительных перегрузок (перегрузки операции =) нам не понадобилось. Объекты одного и того же класса можно присваивать друг в друга напрямую. Но ситуация вполне может сложиться и так, что операцию присваивания перегрузить придётся. Операция присваивания бинарная, перегружается так же как плюс, только надо правильно научать классы поведению по применении к ним операции.
Перегрузкой можно давать любой смысл операции. Из значка минуса можно сделать операцию умножения, из операции сложения операцию деления. Устроить можно кавардак. Если вы проникнитесь, то возможно появление адски-сильного желания перегружать что попало как попало, перегружать всё и везде. Но постарайтесь не усложнять код нелогичными вариантами. Перегрузка операций прежде всего нужна для того, чтобы код упрощать. Если перегрузка код усложняет, то это плохо.
В этой теме не рассмотрена перегрузка унарных операций, потому что там есть некоторые особенности, подробное описание из-за чего может затянуться, лучше опишу отдельной статьёй.
По теме:
Перегрузка операций обозначает, что почти любой из известных компилятору операций можно назначить свое поведение.
Перегрузка операций требует минимум одного параметра пользовательского типа (структура, класс…). Это предотвращает перегрузку операций, работающих со стандартными типами.
Нельзя задавать свои операции.
Нельзя перегружать операции способом, ведущим к нарушению синтаксиса исходной операции:
C++
1
2
3
4
intx;
Time shiva;
%х;//недопускаетсядляоперациивзятиямодуля
%shiva;//не допускается для перегруженной операции
Не допускается изменение приоритетов операций.
Не допускается перегрузка некоторых встроенных операций.
Перегрузка операторов — это изменение смысла оператора. Плюсом можно не только складывать, но и делить и умножать и даже описывать решение сложных формул. Чтобы плюс делал нечто подобное, его поведение описывают в том же виде, как описывают функции.
Это не самый удачный пример получился, к сожалению, смысл этой задачки только в том, чтобы понять что поведение операторов можно описать удобным для нас способом.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
usingnamespacestd;
classMyClass
{
intx;
doubley;
public:
MyClass(inta,doubleb)
{
x=a;
y=b;
}
doubleoperator/(MyClass obj1);
};
doubleMyClass::operator/(MyClass obj){
return((x-obj.x)*(y-obj.y));
}
intmain()
{
MyClassA(20,30.5),B(7,10);
cout<<A/B<<"\n";//<---------------- Что и требовалось, отдаст 266,5
«(20-7)/(30.5-10)»- из этого уравнения у меня получилось(0,634146). А На экране 266,5-это что?
У меня получился вот такой код и выводит 0,634146. Откуда вы вообще взяли 266.5?
thanks ^^!
Почему мы 13 и 20.5 умножае ?если по вашей формуле нужно делить.Вы показываете умножение,а перегрузка оператора не нужна?
Вы мне подскажите,может я что-то неправильно понимаю.
Перегрузка операторов — это изменение смысла оператора. Плюсом можно не только складывать, но и делить и умножать и даже описывать решение сложных формул. Чтобы плюс делал нечто подобное, его поведение описывают в том же виде, как описывают функции.
Это не самый удачный пример получился, к сожалению, смысл этой задачки только в том, чтобы понять что поведение операторов можно описать удобным для нас способом.
Спасибо за ответ.