В некоторых языках программирования перегрузка функций или перегрузка методов — это возможность создания нескольких функций с одинаковым именем и разными реализациями. Вызовы перегруженной функции будут запускать определенную реализацию этой функции, соответствующую контексту вызова, позволяя одному вызову функции выполнять разные задачи в зависимости от контекста.
Например, doTask() и doTask(object o) являются перегруженными функциями. Для вызова последней необходимо передать объект в качестве параметра , тогда как первая не требует параметра и вызывается с пустым полем параметра. Распространенной ошибкой будет присвоение значения по умолчанию объекту во второй функции, что приведет к неоднозначной ошибке вызова , поскольку компилятор не будет знать, какой из двух методов использовать.
Другим примером является функция Print(object o), которая выполняет различные действия в зависимости от того, печатает ли она текст или фотографии. Две разные функции могут быть перегружены как Print(text_object T); Print(image_object P) . Если мы напишем перегруженные функции печати для всех объектов, наша программа будет «печатать», нам никогда не придется беспокоиться о типе объекта, и снова правильный вызов функции, вызов всегда: Print(something) .
Языки, поддерживающие перегрузку функций, включают, помимо прочего, следующие:
Перегрузка функций обычно связана со статически типизированными языками программирования, которые обеспечивают проверку типов при вызовах функций . Перегруженная функция — это набор различных функций, которые могут быть вызваны с одним и тем же именем. Для любого конкретного вызова компилятор определяет, какую перегруженную функцию использовать, и решает это во время компиляции . Это справедливо для таких языков программирования, как Java. [10]
Перегрузка функций отличается от форм полиморфизма , где выбор делается во время выполнения, например, посредством виртуальных функций , а не статически.
Пример: перегрузка функций в C++
#include <iostream> int Volume ( int s ) { // Объем куба. return s * s * s ; } double Volume ( double r , int h ) { // Объем цилиндра. return 3.1415926 * r * r * static_cast < double > ( h ); } long Volume ( long l , int b , int h ) { // Объем прямоугольного параллелепипеда. return l * b * h ; } int main () { std :: cout << Объем ( 10 ); std :: cout << Объем ( 2.5 , 8 ); std :: cout << Объем ( 100 л , 75 , 15 ); }
В приведенном выше примере объем каждого компонента рассчитывается с использованием одной из трех функций под названием «объем», при этом выбор основывается на различном количестве и типе фактических параметров.
Конструкторы , используемые для создания экземпляров объекта, также могут быть перегружены в некоторых объектно-ориентированных языках программирования . Поскольку во многих языках имя конструктора предопределено именем класса, может показаться, что может быть только один конструктор. Всякий раз, когда требуется несколько конструкторов, они должны быть реализованы как перегруженные функции. В C++ конструкторы по умолчанию не принимают параметров, создавая экземпляры членов объекта с их соответствующими значениями по умолчанию, «которые обычно равны нулю для числовых полей и пустой строке для строковых полей». [11] Например, конструктор по умолчанию для объекта счета в ресторане, написанный на C++, может установить чаевые в размере 15%:
Счет () : чаевые ( 0,15 ), // процент всего ( 0,0 ) { }
Недостатком этого является то, что для изменения значения созданного объекта Bill требуется два шага. Ниже показано создание и изменение значений в основной программе:
Счет кафе ; кафе . чаевые = 0,10 ; кафе . всего = 4,00 ;
Перегружая конструктор, можно передать чаевые и общую сумму в качестве параметров при создании. Здесь показан перегруженный конструктор с двумя параметрами. Этот перегруженный конструктор помещается в класс, как и исходный конструктор, который мы использовали ранее. Какой из них будет использоваться, зависит от количества параметров, предоставленных при создании нового объекта Bill (ни одного или два):
Счет ( двойные чаевые , двойная сумма ) : чаевые ( чаевые ), общая сумма ( общая сумма ) { }
Теперь функция, которая создает новый объект Bill, может передавать два значения в конструктор и устанавливать элементы данных за один шаг. Ниже показано создание и установка значений:
Кафе Билл ( 0,10 , 4,00 );
Это может быть полезно для повышения эффективности программы и сокращения длины кода.
Другой причиной перегрузки конструктора может быть необходимость принудительного применения обязательных членов данных. В этом случае конструктор по умолчанию объявляется закрытым или защищенным (или, что предпочтительнее, удаленным с C++11 ), чтобы сделать его недоступным извне. Для приведенного выше Bill total может быть единственным параметром конструктора, поскольку Bill не имеет разумного значения по умолчанию для total, тогда как tip по умолчанию равен 0,15.
С перегрузкой функций связаны и усложняют ее две проблемы: маскировка имен (из-за области действия ) и неявное преобразование типов .
Если функция объявлена в одной области видимости, а затем другая функция с тем же именем объявлена во внутренней области видимости, есть два естественных возможных поведения перегрузки: внутреннее объявление маскирует внешнее объявление (независимо от сигнатуры), или и внутреннее объявление, и внешнее объявление включены в перегрузку, причем внутреннее объявление маскирует внешнее объявление только в том случае, если сигнатура совпадает. Первое взято из C++: «в C++ нет перегрузки между областями видимости». [12] В результате, чтобы получить набор перегрузки с функциями, объявленными в разных областях видимости, нужно явно импортировать функции из внешней области видимости во внутреннюю область видимости с помощью using
ключевого слова .
Неявное преобразование типов усложняет перегрузку функций, поскольку, если типы параметров не полностью соответствуют сигнатуре одной из перегруженных функций, но могут совпадать после преобразования типов, разрешение зависит от того, какое преобразование типов выбрано.
Они могут объединяться запутанными способами: например, неточное совпадение, объявленное во внутренней области, может маскировать точное совпадение, объявленное во внешней области. [12]
Например, чтобы создать производный класс с перегруженной функцией, принимающей a double
или an int
, используя функцию, принимающую an int
из базового класса, в C++ можно написать:
класс B { public : void F ( int i ); }; класс D : public B { public : using B :: F ; void F ( double d ); };
Отсутствие включения using
результатов в int
параметр, переданный F
в производном классе, преобразуется в double и соответствует функции в производном классе, а не в базовом классе; включение using
приводит к перегрузке в производном классе и, таким образом, к соответствию функции в базовом классе.
Если метод разработан с избыточным количеством перегрузок, разработчикам может быть сложно различить, какая перегрузка вызывается, просто прочитав код. Это особенно верно, если некоторые из перегруженных параметров имеют типы, которые являются унаследованными типами других возможных параметров (например, «object»). IDE может выполнить разрешение перегрузки и отобразить (или перейти к) правильной перегрузке.
Перегрузка на основе типов также может затруднять обслуживание кода, поскольку обновления кода могут случайно изменить то, какой метод перегрузки выбирается компилятором. [13]