Претоварване и предефиниране на функции c. Вградени (вградени) функции. Претоварване на функциите. Аргументи със стойности по подразбиране

Претоварването на функции е дефинирането на няколко функции (две или повече) с едно и също име, но различни параметри. Наборите от параметри на претоварени функции може да се различават по ред, брой и тип. По този начин е необходимо претоварване на функции, за да се избегне дублирането на имената на функции, които изпълняват подобни действия, но с различна програмна логика. Например, разгледайте функцията areaRectangle(), която изчислява площта на правоъгълник.

Float areaRectangle(float, float) //функция, която изчислява площта на правоъгълник с два параметъра a(cm) и b(cm) (връща a * b; // умножава дължините на страните на правоъгълника и връща полученият продукт)

И така, това е функция с два параметъра тип float и аргументите, предадени на функцията, трябва да бъдат в сантиметри, върнатата стойност на типа float също е в сантиметри.

Да предположим, че нашите начални данни (страни на правоъгълник) са дадени в метри и сантиметри, например: a = 2m 35 cm; b = 1m 86 см. В този случай би било удобно да се използва функция с четири параметъра. Тоест, всяка дължина на страните на правоъгълника се предава на функцията в два параметъра: метри и сантиметри.

Float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, която изчислява площта на правоъгълник с 4 параметъра a(m) a(cm); b(m) b(cm) (връщане (a_m * 100 + a_sm) * (b_m * 100 + b_sm); )

В тялото на функцията стойностите, които са предадени в метри (a_m и b_m), се преобразуват в сантиметри и се добавят към стойностите a_sm b_sm, след което умножаваме сумите и получаваме площта на правоъгълника в см. Разбира се, беше възможно да конвертирате оригиналните данни в сантиметри и да използвате първата функция, но сега не става дума за това.

Сега най-важното е, че имаме две функции с различни сигнатури, но еднакви имена (претоварени функции). Сигнатурата е комбинация от име на функция с нейните параметри. Как да извикате тези функции? И извикването на претоварени функции не се различава от извикването на обикновени функции, например:

AreaRectangle(32, 43); // ще бъде извикана функция, която изчислява площта на правоъгълник с два параметъра a(cm) и b(cm) areaRectangle(4, 43, 2, 12); // ще бъде извикана функция, която изчислява площта на правоъгълник с 4 параметъра a(m) a(cm); b(m) b(cm)

Както можете да видите, компилаторът ще избере самостоятелно желаната функция, анализирайки само сигнатурите на претоварени функции. Заобикаляйки претоварването на функцията, човек може просто да декларира функция с различно име и тя ще свърши работата си добре. Но представете си какво ще се случи, ако имате нужда от повече от две такива функции, например 10. И за всяка трябва да измислите смислено име, а най-трудно се запомнят те. Точно затова е по-лесно и по-добре да претоварите функциите, освен ако разбира се няма нужда от това. Изходният код на програмата е показан по-долу.

#include "stdafx.h" #include << "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

// код Код::Блокове

// Dev-C++ код

#включи използване на пространство от имена std; // прототипи на претоварени функции float areaRectangle(float a, float b); float areaRectangle(float a_m, float a_sm, float b_m, float b_sm); int main() ( cout<< "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

Резултатът от програмата е показан на фигура 1.

Когато дефинирате функции във вашите програми, трябва да укажете типа на връщането на функцията, както и броя на параметрите и типа на всеки параметър. В миналото (ако сте програмирали на C), когато сте имали функция, наречена add_values​, която е работила с две цели числа, и сте искали да използвате подобна функция, за да добавите три цели числа, трябва да сте създали функция с различно име. Например можете да използвате add_two_values ​​​​и add_three_values. По същия начин, ако искате да използвате подобна функция за добавяне на плаващи стойности, тогава ще ви трябва друга функция с друго име. За да избегнете дублиране на функции, C++ ви позволява да дефинирате множество функции с едно и също име. По време на компилация C++ взема предвид броя на аргументите, използвани от всяка функция и след това извиква точно необходимата функция. Предоставянето на компилатора на избор между множество функции се нарича претоварване. В този урок ще научите как да използвате претоварени функции. До края на този урок ще сте усвоили следните основни концепции:

Претоварването на функции ви позволява да използвате едно и също име за множество функции с различни типове параметри.

За да претоварите функции, просто дефинирайте две функции с едно и също име и тип връщане, които се различават по броя на параметрите или техния тип.

Претоварването на функции е функция на езика C++, която не се среща в C. Както ще видите, претоварването на функции е доста удобно и може да подобри четливостта на вашите програми.

ПЪРВО ВЪВЕДЕНИЕ В ПРЕТОВАРВАНЕТО НА ФУНКЦИИТЕ

Претоварването на функции позволява на вашите програми да дефинират множество функции с едно и също име и тип връщане. Например следната програма претоварва функция с име add_values. Първата дефиниция на функция добавя две int стойности. Втората дефиниция на функция добавя трите стойности. По време на компилация C++ правилно определя функцията, която да се използва:

#включи

int add_values(int a,int b)

{
връщане (a+b);
)

int add_values ​​​​(int a, int b, int c)

(
връщане (a+b+c);
)

{
cout<< «200 + 801 = » << add_values(200, 801) << endl;
cout<< «100 + 201 + 700 = » << add_values(100, 201, 700) << endl;
}

Както можете да видите, програмата дефинира две функции, наречени add_values.Първата функция добавя две int стойности, докато втората добавя три стойности. Не е нужно да правите нищо конкретно, за да предупредите компилатора за претоварване, просто го използвайте. Компилаторът ще разбере коя функция да използва въз основа на опциите, които програмата предоставя.

По подобен начин следната програма MSG_OVR.CPP претоварва функцията show_message. Първата функция с име show_message показва стандартно съобщение, към нея не се подават параметри. Вторият извежда съобщението, предадено му, а третият извежда две съобщения:

#включи

void show_message(void)

{
cout<< «Стандартное сообщение: » << «Учимся программировать на C++» << endl;
}

void show_message(char *съобщение)

{
cout<< message << endl;
}

void show_message(char *първи, char *втори)

{
cout<< first << endl;
cout<< second << endl;
}

{
покажи_съобщение();
show_message("Научете се да програмирате на C++!");
show_message("В C++ няма предразсъдъци!", "Претоварването е готино!");
}

КОГАТО Е НЕОБХОДИМО ПРЕТОВАРВАНЕ

Един от най-честите случаи на употреба за претоварване е да се използва функция за получаване на конкретен резултат при различни параметри. Да предположим например, че вашата програма има функция, наречена day_of_week, която връща текущия ден от седмицата (0 за неделя, 1 за понеделник, ..., 6 за събота). Вашата програма може да претовари тази функция, така че да върне правилно деня от седмицата, ако й е даден юлиански ден като параметър или ако й е даден ден, месец и година:

int ден_от_седмицата(int юлиански_ден)

{
// Оператори
}

int ден_от_седмицата(int месец, int ден, int година)

{
// Оператори
}

Докато изследвате обектно-ориентираното програмиране в C++ в следващите уроци, ще използвате претоварване на функции, за да разширите мощността на вашите програми.

Претоварването на функциите подобрява четливостта на програмата

Претоварването на C++ функции позволява на вашите програми да дефинират множество функции с едно и също име. Претоварените функции трябва да връщат стойности от един и същи тип*, но може да се различават по броя и вида на параметрите. Преди появата на претоварването на функции в C++, C програмистите трябваше да създават множество функции с почти едно и също име. За съжаление, програмистите, желаещи да използват такива функции, трябваше да запомнят коя комбинация от параметри на коя функция съответства. От друга страна, претоварването на функциите опростява задачата на програмистите, като изисква от тях да запомнят само едно име на функция.* Претоварените функции не са задължени да връщат стойности от същия тип, тъй като компилаторът уникално идентифицира функция по нейното име и набор от аргументи. За компилатора функциите с едно и също име, но различни типове аргументи са различни функции, така че типът връщане е прерогатив на всяка функция. - Прибл. прев.

КАКВО ТРЯБВА ДА ЗНАЕТЕ

Претоварването на функция ви позволява да укажете множество дефиниции за една и съща функция. По време на компилация C++ ще определи коя функция да използва въз основа на броя и вида на предадените параметри. В този урок научихте, че е лесно да претоварвате функции. В урок 14 ще научите как препратките към C++ улесняват промяната на параметрите във функциите. Въпреки това, преди да преминете към урок 14, уверете се, че сте научили следните основни понятия:

  1. Претоварването на функции осигурява множество "изгледи" на една и съща функция във вашата програма.
  2. За да претоварите функции, просто дефинирайте множество функции с едно и също име и тип на връщане, които се различават само по броя и вида на параметрите.
  3. По време на компилация C++ ще определи коя функция да извика въз основа на броя и вида на предадените параметри.
  4. Претоварването на функциите опростява програмирането, като позволява на програмистите да работят само с едно име на функция.

Претоварване на функциите

Претоварване на операции (оператори, функции, процедури)- в програмирането - един от начините за реализиране на полиморфизъм, който се състои във възможността за едновременно съществуване в един обхват на няколко различни варианта на операция (оператор, функция или процедура), които имат едно и също име, но се различават по типовете параметри към които се прилагат.

Терминология

Терминът "претоварване" е копия на английското "претоварване", което се появява в руски преводи на книги за езици за програмиране през първата половина на 90-те години. Може би това не е най-добрият вариант за превод, тъй като думата "претоварване" на руски има установено собствено значение, коренно различно от новопредложеното, но се е вкоренило и е доста широко разпространено. В публикациите от съветската епоха подобни механизми се наричат ​​на руски "предефиниране" или "предефиниране" на операциите, но тази опция не е спорна: възникват несъответствия и объркване в преводите на английските "override", "overload" и " предефинирай”.

Причини за появата

Повечето ранни езици за програмиране имаха ограничение, че не повече от една операция с едно и също име може да бъде налична в програма по едно и също време. Съответно всички функции и процедури, видими в дадена точка от програмата, трябва да имат различни имена. Имената и обозначенията на функции, процедури и оператори, които са част от езика за програмиране, не могат да бъдат използвани от програмиста за назоваване на собствените му функции, процедури и оператори. В някои случаи програмистът може да създаде свой собствен програмен обект с името на друг вече съществуващ, но тогава новосъздаденият обект се „припокрива“ с предишния и става невъзможно да се използват и двете опции едновременно.

Тази ситуация е неудобна в някои доста често срещани случаи.

  • Понякога има нужда да се опишат и приложат операции към типове данни, създадени от програмиста, които са еквивалентни по значение на тези, които вече са налични в езика. Класически пример е библиотека за работа с комплексни числа. Те, подобно на обикновените числови типове, поддържат аритметични операции и би било естествено да се създадат за този тип операция „плюс“, „минус“, „умножение“, „деление“, обозначавайки ги със същите знаци за операция, както за други числови видове. Забраната за използване на елементи, дефинирани в езика, налага създаването на много функции с имена като ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat и т.н.
  • Когато операции с едно и също значение се прилагат към операнди от различни типове, те са принудени да бъдат именувани по различен начин. Невъзможността да се използват функции с едно и също име за различни видове функции води до необходимостта да се измислят различни имена за едно и също нещо, което създава объркване и дори може да доведе до грешки. Например в класическия език C има две версии на стандартната библиотечна функция за намиране на модула на число: abs() и fabs() - първата е предназначена за целочислен аргумент, втората за реален. Тази ситуация, комбинирана със слаба проверка на типа C, може да доведе до трудна за откриване грешка: ако програмист напише abs(x) в изчислението, където x е реална променлива, тогава някои компилатори ще генерират код без предупреждение, което ще преобразувайте x в цяло число, като изхвърлите дробните части и изчислете модула от полученото цяло число!

Отчасти проблемът се решава с помощта на обектно програмиране - когато нови типове данни се декларират като класове, операциите върху тях могат да бъдат формализирани като методи на клас, включително едноименни методи на клас (тъй като методите от различни класове не трябва да имат различни имена), но, първо, такъв начин на проектиране на операции със стойности от различни типове е неудобен, и второ, не решава проблема със създаването на нови оператори.

Самото претоварване на операторите е просто "синтактична захар", въпреки че дори като такова може да бъде полезно, защото позволява на разработчика да програмира по по-естествен начин и кара персонализираните типове да се държат повече като вградени. Ако подходим към въпроса от по-обща позиция, тогава можем да видим, че инструментите, които ви позволяват да разширите езика, да го допълните с нови операции и синтактични конструкции (и претоварването на операции е един от тези инструменти, заедно с обекти, макроси , функционали, затваряния) го превръщат вече в метаезика - средство за описване на езици, ориентирани към конкретни задачи. С негова помощ е възможно да се изгради езиково разширение за всяка конкретна задача, което е най-подходящо за нея, което ще позволи да се опише нейното решение в най-естествената, разбираема и проста форма. Например, в приложение за претоварване на операции: създаване на библиотека от сложни математически типове (вектори, матрици) и описване на операции с тях в естествена, „математична“ форма, създава „език за векторни операции“, в който сложността на изчисленията е скрито и е възможно да се опише решението на проблемите по отношение на векторни и матрични операции, като се фокусира върху същността на проблема, а не върху техниката. Именно поради тези причини такива средства някога са били включени в езика Алгол-68.

Механизъм за претоварване

Внедряване

Претоварването на оператора включва въвеждането на две взаимосвързани характеристики в езика: способността да декларирате няколко процедури или функции с едно и също име в един и същи обхват и способността да описвате вашите собствени реализации на операции (тоест знаците на операциите, обикновено написана в инфикс нотация, между операндите). По принцип тяхното изпълнение е доста просто:

  • За да се позволи съществуването на няколко операции с едно и също име, е достатъчно да се въведе правило в езика, според което дадена операция (процедура, функция или оператор) се разпознава от компилатора не само по име (нотация), но също и от видовете на техните параметри. Така че abs(i), където i е декларирано като цяло число, и abs(x), където x е декларирано като реално, са две различни операции. По принцип няма трудности при предоставянето на точно такова тълкуване.
  • За да се даде възможност за дефиниране и предефиниране на операции, е необходимо да се въведат подходящи синтактични конструкции в езика. Може да има доста опции, но всъщност те не се различават една от друга, достатъчно е да запомните, че въвеждането на формуляра “<операнд1> <знакОперации> <операнд2>» е фундаментално подобен на извикването на функцията «<знакОперации>(<операнд1>,<операнд2>)". Достатъчно е да се позволи на програмиста да опише поведението на операторите под формата на функции - и проблемът с описанието е решен.

Опции и проблеми

Претоварването на процедури и функции на ниво обща идея по правило не е трудно нито за изпълнение, нито за разбиране. Въпреки това, дори и в него има някои "подводни камъни", които трябва да се вземат предвид. Позволяването на претоварване на оператора създава много повече проблеми както за внедряващия език, така и за програмиста, работещ на този език.

Проблем с идентификацията

Първият въпрос, пред който е изправен разработчикът на езиков преводач, който позволява претоварване на процедури и функции, е: как да изберем измежду едноименните процедури тази, която да се приложи в конкретния случай? Всичко е наред, ако има вариант на процедурата, чиито типове формални параметри съвпадат точно с типовете на действителните параметри, използвани в това извикване. Въпреки това, в почти всички езици има известна степен на свобода при използването на типове, като се предполага, че компилаторът автоматично извършва безопасни за типове преобразувания в определени ситуации. Например, при аритметични операции върху реални и цели аргументи, цялото число обикновено се преобразува автоматично в реален тип и резултатът е реален. Да предположим, че има два варианта на функцията add:

int add(int a1, int a2); float add(float a1, float a2);

Как трябва компилаторът да се справи с израза y = add(x, i), където x е число с плаваща единица, а i е int? Очевидно няма точно съвпадение. Има две опции: или y=add_int((int)x,i) или като y=add_flt(x, (float)i) (тук имената add_int и add_float означават съответно първата и втората версия на функцията) .

Възниква въпросът: трябва ли компилаторът да позволява това използване на претоварени функции и ако да, на каква база ще избере конкретния използван вариант? По-специално, в примера по-горе, трябва ли преводачът да вземе предвид типа на променливата y, когато избира? Трябва да се отбележи, че горната ситуация е най-простата, възможни са много по-сложни случаи, които се утежняват от факта, че не само вградените типове могат да бъдат конвертирани според правилата на езика, но и класовете, декларирани от програмиста , ако имат роднински връзки, могат да бъдат прехвърлени един на друг. Има две решения на този проблем:

  • Изобщо забранете неточната идентификация. Изисквайте за всяка конкретна двойка типове да има точно подходящ вариант на претоварената процедура или операция. Ако няма такава опция, компилаторът трябва да изведе грешка. Програмистът в този случай трябва да приложи изрично преобразуване, за да преобразува действителните параметри в желания набор от типове. Този подход е неудобен в езици като C++, които позволяват доста свобода при работа с типове, тъй като води до значителна разлика в поведението на вградените и претоварените оператори (аритметичните операции могат да се прилагат към обикновени числа без да се замисля, но към други видове - само с изрично преобразуване) или до появата на огромен брой опции за операции.
  • Установете определени правила за избор на „най-близкото прилягане“. Обикновено в този вариант компилаторът избира тези от вариантите, чиито извиквания могат да бъдат получени от източника само чрез безопасни (без загуба на информация) преобразувания на типове и ако има няколко от тях, той може да избере въз основа на това кой вариант изисква по-малко такива преобразувания. Ако резултатът оставя повече от една възможност, компилаторът извежда грешка и изисква от програмиста изрично да посочи варианта.

Специфични съображения за претоварване на операцията

За разлика от процедурите и функциите, инфиксните операции на езиците за програмиране имат две допълнителни свойства, които значително влияят върху тяхната функционалност: приоритет и асоциативност, чието присъствие се дължи на възможността за "верижно" записване на оператори (как да разберем a + b * c: като (a + b )*c или като a+(b*c)? Изразът a-b+c е (a-b)+c или a-(b+c) ?).

Вградените в езика операции винаги имат предварително дефиниран традиционен приоритет и асоциативност. Възниква въпросът: какви приоритети и асоциативност ще имат предефинираните версии на тези операции или още повече новите операции, създадени от програмиста? Има и други тънкости, които може да изискват пояснение. Например в C има две форми на оператори за увеличаване и намаляване ++ и -- - префикс и постфикс, които се държат различно. Как трябва да се държат претоварените версии на такива оператори?

Различните езици се справят с тези проблеми по различни начини. По този начин в C++ приоритетът и асоциативността на претоварените версии на операторите се запазват същите като тези, дефинирани в езика; възможно е отделно да се претоварват префиксните и постфиксните форми на операторите за увеличаване и намаляване, като се използват специални подписи:

Така че int се използва, за да се направи разлика в подписите

Обявяване на нови операции

Още по-сложна е ситуацията с обявяването на нови операции. Включването на възможността за такава декларация в езика не е трудно, но прилагането й е изпълнено със значителни трудности. Декларирането на нова операция всъщност е създаване на нова ключова дума за език за програмиране, усложнена от факта, че операциите в текст обикновено могат да следват други токени без разделители. Когато се появят, възникват допълнителни трудности в организацията на лексикалния анализатор. Например, ако езикът вече има операциите „+“ и унарния „-“ (промяна на знака), тогава изразът a+-b може точно да се интерпретира като + (-b), но ако нова операция +- е деклариран в програмата, веднага възниква неяснота, тъй като същият израз вече може да бъде анализиран като (+-) b. Разработчикът и внедрителят на езика трябва да се справят с подобни проблеми по някакъв начин. Опциите отново могат да бъдат различни: изискване всички нови операции да бъдат с един знак, постулиране, че в случай на несъответствия се избира „най-дългата“ версия на операцията (т.е. до следващия набор от символи, прочетени от преводачът съвпада с всяка операция, продължава да се чете), опитайте се да откриете сблъсъци по време на превод и да генерирате грешки в противоречиви случаи ... По един или друг начин езиците, които позволяват декларирането на нови операции, решават тези проблеми.

Не бива да се забравя, че при новите операции стои и въпросът за определяне на асоциативност и приоритет. Вече няма готово решение под формата на стандартна езикова операция и обикновено просто трябва да зададете тези параметри с правилата на езика. Например, направете всички нови операции ляво-асоциативни и им дайте еднакъв, фиксиран приоритет, или въведете в езика средствата за уточняване и на двете.

Претоварване и полиморфни променливи

Когато се използват претоварени оператори, функции и процедури в строго типизирани езици, където всяка променлива има предварително деклариран тип, зависи от компилатора коя версия на претоварения оператор да използва във всеки отделен случай, без значение колко е сложен . Това означава, че за компилираните езици използването на претоварване на оператори не води до влошаване на производителността - във всеки случай има добре дефинирана операция или извикване на функция в обектния код на програмата. Ситуацията е различна, когато е възможно да се използват полиморфни променливи в езика, тоест променливи, които могат да съдържат стойности от различни типове по различно време.

Тъй като типът на стойността, към която ще се приложи претоварената операция, не е известен по време на превода на кода, компилаторът е лишен от възможността да избере правилната опция предварително. В този случай е принуден да вгради фрагмент в обектния код, който непосредствено преди извършването на тази операция ще определи типовете на стойностите в аргументите и динамично ще избере вариант, съответстващ на този набор от типове. Освен това такава дефиниция трябва да се прави при всяко изпълнение на операцията, тъй като дори един и същ код, извикан втори път, може да бъде изпълнен по различен начин.

По този начин използването на претоварване на оператора в комбинация с полиморфни променливи прави неизбежно динамичното определяне кой код да се извика.

Критика

Използването на претоварване не се счита за благоприятно от всички експерти. Ако претоварването на функциите и процедурите обикновено не предизвиква възражения (отчасти защото не води до някои типични "операторски" проблеми, отчасти защото е по-малко изкушаващо да се използва погрешно), тогава претоварването на оператори по принцип е, а в специфични езикови реализации, е подложен на доста сериозна критика от много теоретици и практици по програмиране.

Критиците посочват, че проблемите с идентификацията, приоритета и асоциативността, описани по-горе, често правят работата с претоварени оператори или ненужно трудна, или неестествена:

  • Идентификация. Ако езикът има строги правила за идентификация, тогава програмистът е принуден да запомни за кои комбинации от типове има претоварени операции и ръчно да ги преобразува. Ако езикът позволява "приблизителна" идентификация, човек никога не може да бъде сигурен, че в някаква доста сложна ситуация ще бъде изпълнен точно този вариант на операцията, който програмистът е имал предвид.
  • Приоритетност и асоциативност. Ако те са твърдо дефинирани, това може да е неудобно и да не е подходящо за предметната област (например за операции с множества приоритетите се различават от аритметичните). Ако те могат да бъдат зададени от програмиста, това се превръща в допълнителен източник на грешки (дори само защото различните варианти на една операция се оказват с различни приоритети или дори асоциативност).

Колко удобството от използването на вашите собствени операции може да надвиши неудобството от влошаване на контролируемостта на програмата е въпрос, който няма ясен отговор.

От гледна точка на езиковата реализация същите проблеми водят до усложняване на преводачите и намаляване на тяхната ефективност и надеждност. И използването на претоварване във връзка с полиморфни променливи също е очевидно по-бавно от извикването на твърдо кодирана операция по време на компилация и предоставя по-малко възможности за оптимизиране на обектния код. Специфичните характеристики на изпълнението на претоварването на различни езици са подложени на отделна критика. Така че в C++ обектът на критика може да бъде липсата на споразумение относно вътрешното представяне на имената на претоварените функции, което поражда несъвместимост на ниво библиотеки, компилирани от различни C++ компилатори.

Някои критици се обявяват против операциите за претоварване въз основа на общите принципи на теорията за разработката на софтуер и реалната индустриална практика.

  • Привържениците на "пуританския" подход към изграждането на езика, като Wirth или Hoare, се противопоставят на претоварването на операторите, просто защото то може лесно да бъде премахнато. Според тях такива инструменти само усложняват езика и преводача, без да предоставят допълнителни функции, съответстващи на това усложнение. Според тях самата идея за създаване на ориентирано към задачи разширение на езика изглежда само привлекателна. В действителност използването на инструменти за разширение на езика прави програмата разбираема само за нейния автор - този, който е разработил това разширение. Програмата става много по-трудна за разбиране и анализиране от други програмисти, което прави поддръжката, модификацията и развитието на екипа по-трудни.
  • Отбелязва се, че самата възможност за използване на претоварване често играе провокативна роля: програмистите започват да го използват, когато е възможно, в резултат на това инструмент, предназначен да опрости и рационализира програмата, става причина за нейното усложнение и объркване.
  • Претоварените оператори може да не правят точно това, което се очаква от тях, въз основа на вида им. Например a + b обикновено (но не винаги) означава същото като b + a , но "едно" + "две" е различно от "две" + "едно" в езиците, където операторът + е претоварен за конкатенация на низове.
  • Претоварването на оператора прави програмните фрагменти по-чувствителни към контекста. Без да се знаят типовете операнди, включени в израза, е невъзможно да се разбере какво прави изразът, ако използва претоварени оператори. Например, в C++ програма, операторът<< может означать и побитовый сдвиг, и вывод в поток. Выражение a << 1 возвращает результат побитового сдвига значения a на один бит влево, если a - целая переменная, но если a является выходным потоком , то же выражение выведет в этот поток строку «1» .

Класификация

Следното е класификация на някои езици за програмиране според това дали позволяват претоварване на оператори и дали операторите са ограничени до предварително дефиниран набор:

Операции Без претоварване Има претоварване
Ограничен набор от операции
  • Обектив-C
  • Python
Възможно е да се определят нови операции
  • PostgreSQL
  • Вижте също

    Фондация Уикимедия. 2010 г.

    Вижте какво е "Претоварване на функции" в други речници:

      - (оператори, функции, процедури) в програмирането един от начините за реализиране на полиморфизъм, който се състои във възможността за едновременно съществуване в един обхват на няколко различни варианта на операция (оператор, функция или ... ... Wikipedia



Как да постигнем претоварване на функция в C? (10)

Има ли начин да се постигне претоварване на функции в C? Разглеждам прости функции, които могат да бъдат претоварени като

foo (int a) foo (char b) foo (float c, int d)

Мисля, че няма пряк начин; Търся заобиколни решения, ако има такива.

Надявам се, че кодът по-долу ще ви помогне да разберете претоварването на функциите

#включи #включи int fun(int a, ...); int main(int argc, char *argv)( fun(1,10); fun(2,"cquestionbank"); return 0; ) int fun(int a, ...)( va_list vl; va_start(vl,a ); if(a==1) printf("%d",va_arg(vl,int)); else printf("\n%s",va_arg(vl,char *)); )

Искам да кажа, искаш да кажеш - не, не можеш.

Можете да декларирате функцията va_arg като

void my_func(char* формат, ...);

Но ще трябва да подадете информация за броя на променливите и техните типове в първия аргумент - като printf() .

Да, като.

Ето вие давате пример:

void printA(int a)( printf("Здравей свят от printA: %d\n",a); ) void printB(const char *buff)( printf("Здравей свят от printB: %s\n",buff) ; ) #define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 #define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N #define _Num_ARGS_(... ) __VA_ARG_N(__VA_ARGS__) #define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) #define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t) #define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) #define print(x, args ...) \ CHECK_ARGS_MIN_LIMIT(1) printf("грешка");fflush(stdout); \ CHECK_ARGS_MAX_LIMIT(4) printf("грешка");fflush(stdout) ; \ (( \ if (__builtin_types_compatible_p (typeof (x), int)) \ printA(x, ##args); \ else \ printB (x,##args); \ )) int main(int argc, char* * argv) ( int a=0; print(a); print("hello"); return (EXIT_SUCCESS); )

Той ще изведе 0 и здравей от printA и printB.

Ако вашият компилатор е gcc и нямате нищо против да правите ръчни актуализации всеки път, когато добавяте ново претоварване, можете да направите макро маса и да получите резултата, който искате от гледна точка на извикващите, не е толкова хубаво да се пише... но възможно е

погледнете __builtin_types_compatible_p, след което го използвайте, за да дефинирате макрос, който прави нещо като

#define foo(a) \ ((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

но да, гадно, просто не

РЕДАКТИРАНЕ: C1X ще получи поддръжка за типови изрази, които изглеждат така:

#define cbrt(X) _Generic((X), long double: cbrtl, \ default: cbrt, \ float: cbrtf)(X)

Както вече беше посочено, претоварването в смисъла, който имате предвид, не се поддържа от C. Обичайният идиом за решаване на проблема е функцията да приеме маркирано обединение. Това се реализира с помощта на параметъра struct, където самата структура се състои от някакъв тип индикатор за тип, като например enum, и обединение на различни типове стойности. Пример:

#включи typedef enum (T_INT, T_FLOAT, T_CHAR,) my_type; typedef struct ( my_type type; union ( int a; float b; char c; ) my_union; ) my_struct; void set_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT: whatever->my_union.a = 1; break; case T_FLOAT: whatever->my_union.b = 2.0; break; case T_CHAR: whatever-> my_union.c = "3"; ) ) void printf_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT: printf("%d\n", whatever->my_union.a); break; case T_FLOAT : printf("%f\n", whatever->my_union.b); break; case T_CHAR: printf("%c\n", whatever->my_union.c); break; ) ) int main (int argc, char* argv) ( my_struct s; s.type=T_INT; set_overload(&s); printf_overload(&s); s.type=T_FLOAT; set_overload(&s); printf_overload(&s); s.type=T_CHAR; set_overload(&s) ; printf_overload(&s); )

Не можете ли просто да използвате C++ и да не използвате всички други функции на C++ освен тази?

Ако досега не е имало строго строг C, бих препоръчал variadic функции вместо това.

Следният подход е подобен на a2800276, но с някои C99 макроси:

// имаме нужда от `size_t` #include // типове аргументи за приемане на enum sum_arg_types ( SUM_LONG, SUM_ULONG, SUM_DOUBLE ); // структура за задържане на аргумент struct sum_arg ( enum sum_arg_types type; union ( long as_long; unsigned long as_ulong; double as_double; ) value; ); // определяме размера на масива #define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY))) // така ще се нарича нашата функция #define sum(...) _sum( count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__)) // създаване на масив от `struct sum_arg` #define sum_args(...) ((struct sum_arg )( __VA_ARGS__ )) // създаване на инициализатори за аргументите #define sum_long (VALUE) ( SUM_LONG, ( .as_long = (VALUE) ) ) #define sum_ulong(VALUE) ( SUM_ULONG, ( .as_ulong = (VALUE) ) ) #define sum_double(VALUE) ( SUM_DOUBLE, ( .as_double = (VALUE) ) ) // нашата полиморфна функция long double _sum(size_t count, struct sum_arg * args) (long double value = 0; for(size_t i = 0; i< count; ++i) { switch(args[i].type) { case SUM_LONG: value += args[i].value.as_long; break; case SUM_ULONG: value += args[i].value.as_ulong; break; case SUM_DOUBLE: value += args[i].value.as_double; break; } } return value; } // let"s see if it works #include int main() ( unsigned long foo = -1; long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10)); printf("%Le\n", value); return 0; )

За момента, _Generic, тъй като въпросът е _Generic, стандартният C (без разширения) е ефективно получениподдръжка за функции за претоварване (вместо оператори) благодарение на добавянето на думата _Generic _Generic в C11. (поддържа се в GCC от версия 4.9)

(Претоварването не е наистина „вградено“ по начина, показан във въпроса, но е лесно да се унищожи нещо, което работи по този начин.)

Generic е оператор по време на компилиране в същото семейство като sizeof и _Alignof. Описано е в стандартния раздел 6.5.1.1. Необходими са два основни параметъра: израз (който няма да бъде оценен по време на изпълнение) и списък от асоциации тип/израз, което е малко като блок за превключване. _Generic получава общия тип на израза и след това „превключва“ към него, за да избере крайния резултатен израз в списъка за неговия тип:

Generic(1, float: 2.0, char *: "2", int: 2, по подразбиране: get_two_object());

Горният израз се оценява на 2 - типът на контролния израз е int, така че той избира израза, свързан с int като стойност. Нищо от това не остава по време на изпълнение. (Клаузата по подразбиране е задължителна: ако не я посочите и типът не съвпада, това ще доведе до грешка при компилиране.)

Техника, която е полезна за претоварване на функции, е, че тя може да бъде вмъкната от C препроцесора и да избере резултатен израз въз основа на типа на аргументите, предадени на управляващия макрос. И така (пример от стандарта C):

#define cbrt(X) _Generic((X), \ long double: cbrtl, \ default: cbrt, \ float: cbrtf \)(X)

Този макрос изпълнява претоварената операция cbrt, като предава типа на аргумента на макроса, избира подходящата функция за изпълнение и след това предава оригиналния макрос на тази функция.

И така, за да приложим вашия оригинален пример, можем да направим следното:

Foo_int (int a) foo_char (char b) foo_float_int (float c, int d) #define foo(_1, ...) _Generic((_1), \ int: foo_int, \ char: foo_char, \ float: _Generic(( FIRST(__VA_ARGS__,)), \int: foo_float_int))(_1, __VA_ARGS__) #define FIRST(A, ...) A

В този случай бихме могли да използваме обвързването по подразбиране: за третия случай, но това не показва как да разширим принципа към множество аргументи. Крайният резултат е, че можете да използвате foo(...) във вашия код, без да се притеснявате (много) за типа на вашите аргументи.

За по-сложни ситуации, като например функции, които претоварват повече аргументи или променят числа, можете да използвате помощни макроси за автоматично генериране на статични структури за изпращане:

void print_ii(int a, int b) ( printf("int, int\n"); ) void print_di(double a, int b) ( printf("double, int\n"); ) void print_iii(int a, int b, int c) ( printf("int, int, int\n"); ) void print_default(void) ( printf("неизвестни аргументи\n"); ) #define print(...) OVERLOAD(print, (__VA_ARGS__), \ (print_ii, (int, int)), \ (print_di, (double, int)), \ (print_iii, (int, int, int)) \) #define OVERLOAD_ARG_TYPES (int, double) #define OVERLOAD_FUNCTIONS (print) #include "activate-overloads.h" int main(void) ( print(44, 47); // отпечатва "int, int" print(4.4, 47); // отпечатва "double, int" print (1, 2, 3); // отпечатва "int, int, int" print(""); // отпечатва "неизвестни аргументи" )

(изпълнение тук). С известно усилие можете да намалите шаблона, така че да изглежда доста подобен на език с вградена поддръжка за претоварване.

Освен това вече беше възможно да се претовари количествоаргументи (вместо тип) в C99.

Имайте предвид, че начинът, по който се оценява C, може да ви трогне. Това ще избере foo_int, ако се опитате да предадете литерал към него, например, и имате нужда от малко foo_int, ако искате вашите претоварвания да поддържат низови литерали. Въпреки това, като цяло доста готино.

Отговорът на Leushenko е наистина страхотен: само примерът foo не се компилира с GCC, което се проваля при foo(7), сблъсквайки се с ПЪРВИЯ макрос и действителното извикване на функция ((_1, __VA_ARGS__), оставайки с допълнителна запетая. Също така, срещаме проблеми, ако искаме да предоставим допълнителни претоварвания като foo(double).

Затова реших да отговоря на този въпрос по-подробно, включително да разреша празното претоварване (foo(void) - което причини някои проблеми...).

Идеята сега е: дефинирайте повече от един генеричен в различни макроси и оставете правилния да бъде избран според броя на аргументите!

Броят на аргументите е доста прост въз основа на този отговор:

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) #define CONCAT(X, Y) CONCAT_(X, Y) # дефинирайте CONCAT_(X, Y) X ## Y

Това е добре, ние решаваме или SELECT_1, или SELECT_2 (или повече аргументи, ако искате/имате нужда от тях), така че просто се нуждаем от подходящите дефиниции:

#define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char: foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double : _Generic((_2), \ int: foo_double_int \) \)

Първо, празно макро извикване (foo()) все още създава токен, но той е празен. Така че макросът за броене всъщност връща 1 вместо 0, дори ако макросът се нарича празен. Можем "лесно" да коригираме този проблем, ако __VA_ARGS__ със запетая след __VA_ARGS__ условно, в зависимост от това дали списъкът е празен или не:

#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)

Това погледналесно, но макросът COMMA е доста тежък; за щастие тази тема вече е разгледана в блога на Jens Gustedt (благодаря, Jens). Основният трик е, че функционалните макроси не се разширяват, освен ако не са последвани от скоби, вижте блога на Jens за допълнително обяснение... Просто трябва малко да модифицираме макросите за нашите нужди (ще използвам по-кратки имена и по-малко аргументи за краткост) .

#define ARGN(...) ARGN_(__VA_ARGS__) #define ARGN_(_0, _1, _2, _3, N, ...) N #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0 ) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_COMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3 # define COMMA_0000, #define COMMA_0001 #define COMMA_0010, // ... (всички останали със запетая) #define COMMA_1111,

И сега сме добре...

Пълен код в един блок:

/* * demo.c * * Създаден на: 2017-09-14 * Автор: sboehler */ #include void foo_void(void) ( puts("void"); ) void foo_int(int c) ( printf("int: %d\n", c); ) void foo_char(char c) ( printf("char: %c) \n", c); ) void foo_double(double c) ( printf("double: %.2f\n", c); ) void foo_double_int(double c, int d) ( printf("double: %.2f, int: %d\n", c, d); ) #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) # define CONCAT(X, Y) CONCAT_(X, Y) #define CONCAT_(X, Y) X ## Y #define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char : foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double: _Generic((_2), \ int: foo_double_int \) \) #define ARGN(...) ARGN_( __VA_ARGS__) #define ARGN_(_0, _1, _2, N, ...) N #define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0) #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_COMMA(SET_COMMA __VA_ARGS__) , \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 # #_1 ## _2 ## _3 #define compara_0000, № , #define COMMA_1011 , #define COMMA_1100 , #define COMMA_1101 , #define COMMA_1110 , #define COMMA_1111 , int main(int argc, char** argv) ( foo(); foo(7); foo(10.12); foo(12.10, 7); foo((char)"s"); връщане 0; )

C++ ви позволява да посочите повече от една дефиниция за функциииме или операторв същия район като функция за претоварванеИ претоварване на операторасъответно.

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

Когато се обадите на претоварен функцияили оператор, компилаторът определя най-подходящата дефиниция за използване чрез сравняване на типовете аргументи, които сте използвали за извикване на функцията или оператора с типовете параметри, посочени в дефинициите. Извиква се процесът на избор на най-подходящата претоварена функция или оператор резолюция при претоварване .

Претоварване на функции в C++

Можете да имате множество дефиниции за едно и също име на функция в един и същи обхват. Дефиницията на функцията трябва да се различава една от друга по отношение на типовете и/или броя на аргументите в списъка с аргументи. Не можете да претоварвате декларации на функции, които се различават само по типа им на връщане.

По-долу е даден пример със същата функция печат ()използвани за отпечатване на различни видове данни -

#включи използване на пространство от имена std; class printData ( public: void print(int i) ( cout<< "Printing int: " << i << endl; } void print(double f) { cout << "Printing float: " << f << endl; } void print(char* c) { cout << "Printing character: " << c << endl; } }; int main(void) { printData pd; // Call print to print integer pd.print(5); // Call print to print float pd.print(500.263); // Call print to print character pd.print("Hello C++"); return 0; }

Printing int: 5 Printing float: 500.263 Printing character: Hello C++

Претоварване на оператори в C++

Можете да замените или претоварите повечето от вградените оператори, налични в C++. По този начин програмистът може също да използва оператори с дефинирани от потребителя типове.

Претоварените оператори са функции със специални имена: ключовата дума "оператор", последвана от знака за дефинирания оператор. Както всяка друга функция, претовареният оператор има тип на връщане и списък с параметри.

Box оператор+(const Box&);

декларира оператор за добавяне, който може да се използва за допълнениядва обекта Box и връща последния обект Box. Повечето претоварени оператори могат да бъдат дефинирани като обикновени функции, които не са членове, или като функции членове на класа. В случай, че дефинираме функцията по-горе като членна функция, която не е от класа, ще трябва да предадем два аргумента за всеки операнд, както следва:

Box оператор+(const Box&, const Box&);

По-долу е даден пример, показващ концепцията за оператор, когато се зарежда с помощта на функция член. Тук обект се предава като аргумент, чиито свойства ще бъдат достъпни чрез този обект, обектът, който ще извика този оператор, може да бъде достъпен чрез товаоператор, както е описано по-долу -

#включи използване на пространство от имена std; class Box ( public: double getVolume(void) ( return length * breadth * height; ) void setLength(double len) ( length = len; ) void setBreadth(double bre) (breadth = bre; ) void setHeight(double hei) ( height = hei; ) // Overload + оператор за добавяне на два обекта Box.Box operator+(const Box& b) ( Box box; box.length = this->length + b.length; box.breadth = this->breadth + b .breadth; box.height = this->height + b.height; return box; ) private: двойна дължина; // Дължина на кутия двойна ширина; // Ширина на кутия двойна височина; // Височина на кутия ) ; // Основна функция за програмата int main() ( Box Box1; // Деклариране на Box1 от тип Box Box Box2; // Деклариране на Box2 от тип Box Box Box3; // Деклариране на Box3 от тип Box double volume = 0.0; // Store обемът на кутия тук // спецификация на кутия 1 Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // спецификация на кутия 2 Box2.setLength(12.0); ;Box2.setHeight(10.0) ); // обем на кутия 1 volume = Box1.getVolume(); cout<< "Volume of Box1: " << volume <

Когато горният код се компилира и изпълни, той произвежда следния изход:

Обем на Кутия 1: 210 Обем на Кутия 2: 1560 Обем на Кутия 3: 5400

Претоварващи/непретоварваеми оператори

По-долу е даден списък с оператори, които могат да бъдат претоварени.



Продължение на темата:
Windows

Наталия Комарова , 28.05.2009 г. (25.03.2018 г.) Когато четете форум или блог, запомняте авторите на публикациите по псевдоним и ... по снимката на потребителя, така наречения аватар ....

Нови статии
/
Популярен