Перегрузкой функции называется ситуация, когда в программе определены несколько функций с одинаковыми именами, но с различными параметрами. Такие функции называются перегруженными. Тип или количество параметров каждой перегруженной функции должны быть уникальными. Например, перегрузка функции вычисления квадрата числа:
int sqr(int value);
float sqr(float value);
Теперь функцию sqr можно использовать как с целым, так и с вещественным аргументом:
int x = 2;
cout << sqr(-5.4);
cout << sqr(x+5);
Обычно перегрузку используют именно с этой целью – объединить под одним именем сразу несколько функций, работающих с аргументами различных типов. По этому принципу реализованы многие стандартные функции, например математические. К примеру, функция вычисления модуля (abs) в заголовочном файле math.h имеет следующие объявления:
int abs(int _X);
inline long abs(long _X);
inline float abs(float _X);
inline double abs(double _X);
inline long double abs(long double _X);
Это позволяет использовать функцию abs с аргументами любых встроенных типов.
Хороший стиль программирования предполагает, что перегруженные функции будут выполнять однотипные, схожие действия. Не рекомендуется использовать перегрузку для функций, назначение которых никак между собой не связано.
Рассмотрим подробнее каким образом компилятор выбирает конкретную функцию, которую он будет вызывать в зависимости от значений фактических аргументов.
Пусть объявлены следующие функции:
int func(int, int);
int func(char, double);
int func(long, double);
int func(float, ...); // Функция с неопределённым числом аргументов.
int func(char*, int);
При вызове функции с именем func и некоторым списком аргументов первое, что будет делать компилятор – это пытаться найти функцию, формальные аргументы которой соответствуют фактическим без всяких преобразований типов или с использованием только неизбежных преобразований – например, имени массива к указателю или значения переменной к константе или наоборот.
char string[] = "Строка - это массив символов";
long l = 142;
func(string, 13); // func(char*, int);
func(l, 36.6); // func(long, double);
Если на первом этапе подходящая функция не найдена, то на втором этапе совершается попытка подобрать такую функцию, чтобы для соответствия формальных и фактических аргументов достаточно было использовать только такие стандартные преобразования, которые не влекут за собой преобразований целых типов к плавающим и наоборот. При этом подбирается функция, для которой число таких преобразований было бы минимально.
Пусть обращение к функции выглядит так:
float a = 36.6;
func('a', a);
Применяя указанные стандартные преобразования, найдём, что будет вызвана функция с прототипом func(char, double) и аргумент а будет преобразован к типу double.
Третьим этапом является подбор такой функции, для вызова которой достаточно осуществить любые стандартные преобразования аргументов (и опять так, чтобы этих преобразований было как можно меньше).
Так, в инструкции
func("ГОД:", 2010.3);
будет вызвана функция func (char*, int), фактический аргумент типа double которой будет преобразован к int с отбрасыванием дробной части числа.
На четвёртом этапе подбираются функции, для которых аргументы можно получить с помощью всех преобразований, рассмотренных до этого, а также преобразований типов, определённых самим программистом.
Если и в этом случае единственная нужная функция не найдена, то на последнем, пятом этапе, компилятор пробует найти соответствие с учётом списка неопределённых аргументов.
Так, при вызове func (1, 2, 3); подходит лишь одна функция func(float, ...).
При следующем обращении компилятор не найдёт ни одной подходящей функции и выдаст сообщение об ошибке.
int i, j;
func(&i, &j);
Если компилятор не смог выбрать между двумя и более функциями, т.к. они требуют одинакового преобразования аргументов, то такая ситуация считается неоднозначной (ambiguous) и компилятор сгенерирует сообщение об ошибке. Например, при таком вызове функции
double c;
func(5,c);
компилятор Visual Studio (msvc8) сообщит, что 4 перегруженные функции могут быть вызваны с однотипными преобразованиями.