§ 3. Перагрузка аперацый
Алгоритмические конструкции
Как вам уже известно из курса информатики, любой алгоритм может быть записан с использованием трех базовых алгоритмических конструкций: следование, цикл и ветвление (пример 3.1). Команды, составляющие алгоритмическую конструкцию следование, выполняются последовательно, друг за другом, в том порядке, в котором они записаны. Команды цикла и ветвления управляют порядком выполнения других команд в программе и относятся к командам управления (управляющим конструкциями). Алгоритмическая конструкция ветвления обеспечивает выполнение одной или другой последовательности команд в зависимости от истинности или ложности некоторого условия. Оператор ветвления — команда, реализующая алгоритмическую конструкцию ветвления на языке программирования. Алгоритмическая конструкция повторение (цикл) представляет собой последовательность действий, выполняемых многократно. Саму последовательность называют телом цикла. Оператор цикла — это команда, реализующая алгоритмическую конструкцию повторения на языке программирования. Существуют разные возможности управлять тем, сколько раз будет повторяться тело цикла. Может быть задано условие продолжения или окончания работы цикла, а также число повторений тела цикла. В зависимости от этого выделяют цикл с предусловием, цикл с постусловием и цикл с параметром. Существуют разные возможности управлять тем, сколько раз будет повторяться тело цикла. Может быть задано условие продолжения или окончания работы цикла, а также число повторений тела цикла. Выделяют следующие циклы: цикл с предусловием, цикл с постусловием и цикл с параметром. Выбор цикла зависит от задачи. Во многих случаях циклы взаимозаменяемы. При выборе цикла можно ориентироваться на следующее:
|
Пример 3.1 Блок-схемы алгоритмических конструкций. Следование Ветвление Цикл
Кроме блок-схем, для графического представления алгоритмов используют структурограммы (N — S-диаграммы, диаграммы Насси — Шнейдермана). Примеры структурограмм Команда ветвления: Команда цикла с предусловием: |
Сайт: | Профильное обучение |
Курс: | Інфарматыка. 11 клас (Павышаны ўзровень) |
Книга: | § 3. Перагрузка аперацый |
Напечатано:: | Гость |
Дата: | Суббота, 19 Апрель 2025, 12:39 |
3.1. Перагрузка арыфметычных аперацый
Перагрузка аператараў у праграмiраванні — адзін з спосабаў рэалізацыі палімарфізму. Яна дазваляе выкарыстоўваць некалькі розных варыянтаў прымянення аператара. Аператары маюць адно і тое ж імя, але адрозніваюцца тыпамі параметраў, да якіх яны ўжываюцца. У мове C++ аператары рэалізаваны ў выглядзе функцый. Выкарыстоўваючы перагрузку функцыі аператара, вы можаце вызначыць свае ўласныя версіі аператараў, якія будуць працаваць з рознымі тыпамі даных, уключаючы класы. З перагружанымі аператарамі вы ўжо працавалі. У класе string перагружаныя аператары параўнання і аператар «+». Для параўнання радкоў выкарыстоўваюцца тыя ж знакі, што і для параўнання лікаў, аднак параўнанне радкоў выконваецца не так, як параўноўваюцца лікі. Складанне радкоў таксама мае сэнс, які адрозніваецца ад складання лікаў. Большасць аперацый у С++ можна перазагрузіць (прыклад 3.1)[1]. Важна памятаць, што перагрузка пашырае магчымасці мовы, а не змяняе мову. Таму нельга перагружаць аператары для ўбудаваных тыпаў даных. Нельга мяняць прыярытэт выканання аператараў і іх асацыятыўнасць (злева направа ці справа налева). Нельга ствараць уласныя аператары і перагружаць некаторыя ўбудаваныя (прыклад 3.2). Сінтаксіс перагрузкі аперацый: тып operator @ (спiс_параметраў-аперанд) дзе @ — знак перагружанай аперацыі, тып — тып значэння, якое вяртаецца. Перагружаць можна толькі аперацыі, для якіх хоць бы адзін аргумент уяўляе тып даных, вызначаны карыстальнікам (напрыклад, клас). Функцыя для перагрузкі аперацыі павінна быць вызначана або як функцыя-член класа (прыклад 3.3), або як знешняя функцыя, але дружалюбная класу (пример 3.4). Адзін і той жа аператар можна перагрузіць некалькі разоў. Калі бінарная[2] аперацыя перагружаецца з выкарыстаннем метаду класа, то першым аперандам яна атрымлівае пераменную класа, якая няяўна перадаецца праз паказальнік this на аб'ект. Другім аперандам з'яўляецца аргумент функцыі. Такім чынам, бінарная аперацыя, перагружаная метадам класа, мае фактычна адзін параметр (правы аперанд), а левы перадаецца няяўна праз паказальнік this. Калі перагрузка бінарнай аперацыі рэалізавана з выкарыстаннем дружалюбнай функцыі, то ў спісе параметраў павінна быць два аргумента. Прыклад 3.5. Вектар на плоскасці задаецца парай каардынат. Апісаць клас Vect для прадстаўлення вектара на плоскасці. Рэалізаваць метад, які вылічае даўжыню вектара, перагрузіць аператары «+» (складанне двух вектараў), «*» (множання вектара на лік), «==» (параўнанне вектараў), «>» (параўнанне вектараў па даўжыні). Этапы выканання задання 1. Палямі класа будуць каардынаты (х, y) вектара на плоскасці. 2. Для вылічэння даўжыні вектара ў метадзе класа dl выкарыстоўваецца формула вылічэння даўжыні адрэзка. 3. Рэалізаваць перагрузку аперацый.
Любы перагружаны аператар можна выклікаць з выкарыстаннем функцыянальнай формы запісу (функцыі-аперацыі): Vect v3 = operator + (v1, v2); Выкарыстанне аперацыі — гэта ўсяго толькі скарочаны запіс яўнага выкліку функцыі аперацыі. Паколькі функцыя-аперацыя апісваецца так жа, як любая іншая функцыя, то і яна можа выклікацца аналагічна. Прамавугольную табліцу з лікамі ў матэматыцы называюць матрыцай. Памер матрыцы вызначаецца колькасцю радкоў і слупкоў ў ёй. Для матрыц вызначаны арыфметычныя аперацыі. Матрыцу можна памножыць на лік — для гэтага кожны элемент матрыцы памнажаецца на яго. Дзве матрыцы аднолькавага памеру можна скласці ці адняць. Дзеянні выконваюцца над элементамі, якія стаяць на адпаведных месцах. Прыклад 3.6. Апісаць клас Matrix. Перагрузіць аперацыі складання, аднімання і множання матрыцы на лік. Этапы выканання задання 1. Палямі класа будуць цэлыя лікі m, n — колькасць радкоў і слупкоў у матрыцы і двухмерны вектар для захоўвання самой матрыцы. 2. Рэалізаваць метад rand_data, які будзе запаўняць матрыцу выпадковымі лікамі. 3. Перагрузка аперацый аналагічная перагрузцы аперацый, якія рэалізаваны ў прыкладзе 3.4.
[1] Больш падрабязна аб перагрузцы аператараў можна пачытаць, напрыклад тут: https://habr.com/ru/post/489666/ [2] Аперацыя з'яўляецца бінарнай, калі для яе выканання неабходны два аперанда. Напрыклад, аперацыя складання (a + b). Аперацыя з адным аперандам з'яўляецца ўнарнай. Напрыклад, аперацыя змены знака ў ліку (-a). |
Неабходнасць у перагрузкi аперацый абумоўлена патрэбай апісваць і ўжываць да створаных праграмістам тыпам даных аперацыі, па сэнсе эквівалентныя ўжо іcнуючым у мове. Для рэалізацыі перагрузкі аперацый у мове праграмiравання неабходна ўвесці ў ім адпаведныя сінтаксічныя канструкцыі. Варыянтаў рэалізацыі можа быць шмат, аднак яны мала чым адрозніваюцца адзін ад аднаго. Дастаткова памятаць, што запіс выгляду <аперанд1> <знакАперацыi> <аперанд2> аналагічны выкліку функцыі <знакАперацыi>(<аперанд1>,<аперанд2>). Калі дазволіць праграмісту апісваць паводзіны аператараў у выглядзе функцый, то праблема перагрузкі аператараў будзе вырашана. Прыклад 3.1. Аперацыі, якія можна перагрузіць у С++:
Прыклад 3.2. Аперацыі, якія нельга перагрузіць у С++:
Дружалюбная функцыя — гэта функцыя, якая не з'яўляецца членам класа, але якая мае доступ да яго закрытых членаў. Ёю можа быць як звычайная функцыя, так і метад іншага класа. Для аб'явы дружалюбнай функцыі выкарыстоўваецца ключавое слова friend перад аб'явай функцыі, якая стане дружалюбнай класу. Функцыя можа быць аб'яўлена як у раздзеле public, так і раздзеле private. Прыклад 3.3. Перагрузка аперацыі «+» з прымяненнем метаду класа:
Прыклад 3.4. Перагрузка аперацыі «+» з ужываннем знешняй функцыі, дружалюбнай класу:
Прыклад 3.5. Праграма.
Вынік працы: Роб Мюрэй, у сваёй кнізе «C++ Strategies and Tactics» рэкамендаваў перагружаць унарныя аперацыі і аперацыі, сумешчаныя з прысвойваннем (+=, *= і інш.), як члены класа. Бінарныя аперацыі ён рэкамендаваў перагружаць з выкарыстаннем дружалюбных функцый.
Прыклад 3.6. Апісанне класа Matrix.
|
3.2. Лагічны тып даных
Для стандартных тыпаў даных такіх як int або double ўвод і вывад рэалізаваны праз стандартныя аператары >> і <<. Гэтыя ж аператары перагружаныя для ўводу-вываду радкоў. Для карыстальніцкіх тыпаў даных, якія маюць мноства палёў, таксама можна перагрузіць аператары ўводу-вываду. Перагрузка аператараў < < і >> нашмат спрашчае працэс вываду аб'екта на экран і атрыманне карыстальнiцкага ўводу з запісам даных у аб'ект класа. Разгледзім перагрузку аператара <<. Аператар з'яўляецца бінарным аператарам, таму яго перагрузка будзе аналагічная перагрузцы аператара «+», разгледжанай у прыкладах 3.5 і 3.6. Левым аперандам у аператара < < з'яўляецца аб'ект cout, а правым — аб'ект класа, які трэба вывесці. Аператар, які перавызначаецца, павінен вяртаць значэнне тыпу ostream, аб'ектам якога з'яўляецца cout. Параметры, апісанай функцыі, павінны быць спасылкамі (прыклады 3.7 i 3.8). Таксама спасылкай павінен быць і вынік, які вяртаецца, паколькі ostream забараняе сваё капіраванне. Перагрузка аператара ўводу адбываецца аналагічна. Адрозненне ў тым, што cin з'яўляецца аб'ектам тыпу istream (прыклады 3.9 i 3.10). Спасылка на аб'ект класа, які з'яўляецца другім параметрам, не можа быць канстантай, паколькі аб'ект змяняецца пры ўводзе. Пасля перазагрузкі аператараў уводу і вываду іх можна выкарыстоўваць таксама для чытання і запісу файлаў. |
Прыклад 3.7. Перагрузка аператара вываду << для класа Vect, вызначанага ў прыкладзе 3.5. Аб'ява:
Апісанне:
Прыклад 3.8. Перагрузка аператара вываду << для класа Matrix, вызначанага ў прыкладзе 3.6. Аб'ява:
Апісанне:
Прыклад 3.9. Перагрузка аператара вываду >> для класа Vect, вызначанага ў прыкладзе 3.5. Аб'ява:
Апісанне:
Прыклад 3.10. Перагрузка аператара вываду >> для класа Matrix, вызначанага ў прыкладзе 3.6 Аб'ява:
Апісанне:
|
Пытаннi да параграфа
![]() |
1. Для чаго выкарыстоўваецца перагрузка аперацый? 2. Якія аперацыі можна перагрузіць у С++? 3. Якія аперацыі нельга перагрузіць у С++? 4. Якімі спосабамі можна вызначыць функцыю для перагрузкі аператараў?
|
Практыкаваннi
1. Для праграмы з прыкладу 3.5 выканайце наступнае:
- Дапоўніце апісанне класа, прыведзенае ў прыкладах 3.7 і 3.9, перагрузкай аператараў уводу і вываду.
- Змяніце функцыі для перагрузкі аператараў параўнання так, каб яны былі дружалюбнымі.
- Як будзе рэагаваць праграма, калі выкарыстоўваць знак «<» для параўнання даўжынi вектараў? Чаму?
- Перагрузіце аперацыі «<» і «!=».
- Перагрузіце аператар «*» яшчэ раз, лічачы, што ён выкарыстоўваецца і для знаходжання скалярнага памнажэння двух вектараў [1].
[1] Скалярным памнажэннем двух вектараў i
з'яўляецца лік
.
2. Для праграмы з прыкладу 3.6 выканайце наступнае:
- Рэалізуйце шматфайлавы праект, падзяліце аб'яву і вызначэнне класа. Стварыце файлы Matrix.h і Matrix.cpp.
- Дапоўніце апісанне класа перагрузкай аператараў уводу і вываду, прыведзенае ў прыкладах 3.8 і 3.10.
- Змяніце функцыі для перагрузкі аператараў параўнання так, каб яны былі дружалюбнымі.
- Перагрузіце аперацыю «==», якая будзе вяртаць true, у тым выпадку, калі матрыцы маюць аднолькавыя памеры і false у адваротным выпадку.
- * Перагрузіце аператар «*» яшчэ раз, лічачы, што ён выкарыстоўваецца і для знаходжання памнажэння матрыц[1]
[1] Аб тым, як памнажаць матрыцы, можна пачытаць тут: https://www.webmath.ru/poleznoe/formules_6_6.php.
- Прадэманструйце працу метадаў і аператараў апісанага класа.
3. Змяніце апісанне класа Parallelepiped (заданне 4, §2), дадаўшы перагрузку аператараў параўнання паралелепіпедаў па аб'ёме.
4. Дадайце ў клас Rect (заданне 6, §2) перагрузку аперацый «+» для атрымання найменшага прамавугольніка, які змяшчае два зададзеных прамавугольніка, і «*» — для атрымання прамавугольніка, які з'яўляецца агульнай часткай (перасячэннем) двух прамавугольнікаў.
5. Стварыць клас Drob. Канструктар прымае два лікі: лічнік і назоўнік дробу. Перагрузіць аперацыі: складанне, множанне, дзяленне, адніманне. Рэалізаваць метады: скарачэнне (выкарыстоўваць private функцыю для знаходжання НОД па алгарытме Еўкліда), пераклад звычайнага дробу ў дзесятковы. Прадэманстраваць працу ўсіх функцый класа і перагружаных аперацый на прыкладах.
6. Стварыць базавы клас Progressii і яе наследнікаў — арыфметычную і геаметрычную прагрэсіі. Рэалізаваць метады: n-ы член прагрэсіі, праверка прагрэсіі на спаданне. Перагрузіць аперацыі: «+» (першы аперанд — пераменная тыпу прагрэсіі, другі — лік (колькасць элементаў)) для вылічэння сумы арыфметычнай прагрэсіі «*» — аналагічна для вылічэння сумы геаметрычнай прагрэсіі. Прадэманстраваць працу ўсіх функцый класа і перагружаных аперацый на прыкладах.
7. Апісаць клас Polinom для працы з мнагачленамі. Палямі класа з'яўляецца ступень мнагачлена і масіў каэфіцыентаў. Перагрузіць аперацыі «+», «-», «*», а таксама ўвод і вывад мнагачлена. Рэалізаваць метад вылічэння значэння мнагачлена для зададзенага значэння пераменнай[1]. *Выкарыстоўваючы метад двайковага дзялення, знайсці корань мнагачлена на зададзеным прамежку (корань павінен быць на гэтым прамежку адзіным).
[1] Рэкамендуецца выкарыстоўваць для вылічэння значэння мнагачлена схему Горнэра — https://intuit.ru/studies/professional_retraining/941/courses/67/lecture/1966?page=2
8*. Апісаць клас BigNumber для працы з «вялікімі» лікамі (лікі, якія не змяшчаюцца ў стандартныя тыпы). Перагрузіць аперацыі «+», «-», «*», параўнання, уводу і вываду.
Прыклад апісання некаторых членаў класа:
///Клас "вялікi лік", апісвае спосаб захоўвання вялікага ліку і складанне
class BigNumber
{
private:
vector < int > cifr;
///нармалізацыя ліку мадэлюе перанос у наступны разрад, калі лічба > 10
void norm();
public:
///Канструктар па змоўчанні ("пусты" лік)
BigNumber() {};
///Канструктар, канвертуе радок у вялікi лік
BigNumber(string str)
BigNumber operator + (const BigNumber &);
friend ostream & operator << (ostream &, const BigNumber &);
};
BigNumber::BigNumber(string str)
{
///Запісваем лічбы з канца радка
for (int i = str.size() - 1; i >= 0; i --)
cifr.push_back(str[i] - '0');
}
///Аператар +, выконвае складанне вялікіх лікаў
BigNumber BigNumber::operator + (const BigNumber &num)
{
BigNumber res;
int r1 = min(cifr.size(), num.cifr.size());
res.cifr.resize(r1);
/// складанне лічбаў
for (int i = 0; i < r1; i++)
res.cifr[i] = cifr[i] + num.cifr[i];
///калі ў адным з лікаў лічбы скончыліся
for (int i = r1; i < cifr.size(); i++)
res.cifr.push_back(cifr[i]);
for (int i = r1; i < num.cifr.size(); i++)
res.cifr.push_back(num.cifr[i]);
res.norm();
return res;
}
void BigNumber::norm()
{
if (cifr.size() > 1) {
for (int i = 0; i < cifr.size()-1; i++) {
cifr[i + 1] += cifr[i] / 10;
cifr[i] = cifr[i] % 10;
}
if (cifr[cifr.size() - 1] >= 10) {
int t = cifr[cifr.size() - 1] / 10;
cifr[cifr.size() - 1] = cifr[cifr.size() - 1] % 10;
cifr.resize(cifr.size() + 1);
cifr[cifr.size()-1] = t;
}
}
}
///Перагрузка аператара << для вываду
ostream & operator << (ostream &cout_bn, const BigNumber &num)
{
for (int i = num.cifr.size() - 1; i >= 0; i--)
cout_bn << num.cifr[i];
}
Прыклад выкарыстання:
int main() {
BigNumber n1("9999999999999999");
BigNumber n2("1");
cout << n1 << endl;
cout << n2 << endl;
BigNumber n3 = n1 + n2;
cout << n3 << endl;
return 0;