Урок 102. Перегрузка функций

   ⁄ 

 Обновлено 24 Дек 2017  ⁄ 

⁄   1757

Перегрузка функций — это особенность C++, которая позволяет создать несколько функций с одним и тем же именем, но с разными параметрами. Например:

Здесь мы отнимаем целые числа. Однако, что, если нам нужно использовать числа типа с плавающей запятой? Эта функция совсем не подходит, так как любые параметры типа double будут конвертироваться в int, в результате чего будет теряться дробная часть значений.

Одним из способов решения этой проблемы является определение двух функций с разными именами и параметрами:

Но есть и лучшее решение – перегрузка функции. С её использованием, мы можем просто объявить еще одну функцию subtract(), которая принимает параметры double:

Теперь у нас есть две версии subtract():

Хотя может показаться, что произойдет конфликт имен, но это не так. Компилятор может определить сам, какую версию subtract() следует вызывать на основе аргументов, используемых в вызове функции. Если параметрами будут переменные типа int, то C++ поймет, что мы имеем в виду вызов subtract(int, int). Если мы предоставим два значения типа с плавающей запятой, то C++ поймет, что мы хотим вызвать subtract(double, double). Фактически, мы можем определить столько перегруженных функций subtract(), сколько хотим, до тех пор, пока каждая из них будет иметь свои уникальные параметры.

Следовательно, можно определить функцию subtract() и с большим количеством параметров:

Хотя здесь subtract() имеет 3 параметра вместо 2, но поскольку эти параметры отличаются от параметров других версий subtract(), то это допустимо и не считается ошибкой.

Типы возврата в перегрузке функций

Обратите внимание, тип возврата функции НЕ учитывается при перегрузке функции. Рассмотрим случай: вы хотите написать функцию, которая возвращает случайное число, но вам нужна одна версия, которая возвращает int, и вторая, которая возвращает double. У вас может возникнуть соблазн сделать следующее:

Здесь компилятор выдаст ошибку. Эти две функции имеют одинаковые параметры (вернее, их отсутствие), и, следовательно, второй getRandomValue() будет рассматриваться как ошибочное переопределение первого. Имена функций нужно будет изменить.

Псевдонимы типов в перегрузке функций

Поскольку объявление typedef (псевдонима типа) не создает новый тип, то следующие два объявления print() считаются идентичными:

Как вызовы функций сопоставляются с перегрузкой функций

Выполнение вызова перегруженной функции приводит к одному из трех возможных результатов:



  Совпадение найдено. Вызов разрешен для соответствующей перегруженной функции.

  Совпадения не найдено. Аргументы не соответствуют любой из перегруженных функций.

  Найдены несколько совпадений. Аргументы соответствуют более чем одной перегруженной функции.

При вызове перегруженной функции, C++ выполняет следующие шаги для определения того, какую версию функции следует вызывать:

1. Во-первых, C++ пытается найти точное совпадение. Это тот случай, когда фактический аргумент точно соответствует типу параметра одной из перегруженных функций. Например:

Хотя 0 может технически соответствовать и print(char *) (как нулевой указатель), но он точно соответствует print(int) (для соответствия char * требуется неявное преобразование). Таким образом, print(int) является лучшим (точным) совпадением.

2. Если точного совпадения не найдено, то C++ пытается найти совпадение путем дальнейшего неявного преобразования типов. В уроке о неявном преобразование мы говорили о том, как определенные типы данных могут автоматически конвертироваться в другие типы данных. Если кратко, то:

  char, unsigned char и short конвертируются в int;

  unsigned short может преобразоваться в int или unsigned int, в зависимости от размера int;

  float конвертируется в double;

  enum конвертируется в int.

Например:

В этом случае, поскольку нет print(char), символ ‘b’ конвертируется в int, который затем соответствует print(int).

3. Если неявное преобразование невозможно, то C++ пытается найти соответствие посредством стандартного преобразования. В стандартном преобразовании:



  любой числовой тип будет соответствовать любому другому числовому типу, включая unsigned (например, int равно float);

  enum будет соответствовать формальному типу числового типа данных (например, enum равно float);

  нуль будет соответствовать типу указателя и числовому типу (например, 0 как char * или 0 как float);

  указатель будет соответствовать указателю типа void.

Например:

В этом случае, поскольку нет print(char) (точного совпадения) и нет print(int) (совпадения путем неявного преобразования), ‘b’ конвертируется в float и сопоставляется с print(float).

Обратите внимание, все стандартные преобразования считаются равными. Ни один из них не считается выше по приоритету остальных.

4. Наконец, C++ пытается найти соответствие путем пользовательского преобразования. Хотя мы еще не рассматривали классы, но они могут определять преобразования в другие типы данных, которые могут быть неявно применены к объектам этих классов. Например, мы можем определить класс W и в нем пользовательское преобразование в int.

Хотя value относится к типу класса W, но поскольку тот имеет пользовательское преобразование в int, то вызов print(value) будет соответствовать версии print(int).

То, как делать пользовательские преобразования в классах, мы рассмотрим в следующей главе.

Несколько совпадений

Если каждая перегруженная функция должна иметь уникальные параметры, то как могут быть возможны несколько совпадений? Поскольку все стандартные и пользовательские преобразования считаются равными, то если вызов функции соответствует нескольким кандидатам посредством стандартного или пользовательского преобразования, то результатом будет неоднозначное совпадение (несколько совпадений). Например:

В случае print('b') C++ не может найти точного совпадения. Он пытается преобразовать ‘b’ в int, но print(int) также нет. Используя стандартное преобразование, C++ может преобразовать ‘b’ как в unsigned int, так и во float. Поскольку все стандартные преобразования считаются равными, то получается два совпадения.

С print(0) всё аналогично. 0 — это int, а print(int) нет. Путем стандартного преобразования мы также получаем два совпадения.

А вот с print(3.14159) всё несколько запутаннее: большинство программистов отнесут его однозначно к print(float). Но помните, что по умолчанию все значения-литералы типа с плавающей запятой относятся к типу double, если у них нет суффикса ‘f’. 3.14159 – это тип double, а print(double) нет. Следовательно, мы получаем ту же ситуацию, что и в предыдущих случаях – неоднозначное совпадение, два кандидата.

Неоднозначное совпадение считаются ошибкой типа compile-time. Следовательно, оно должно быть устранено до того, как ваша программа скомпилируется. Есть два способа решения этой проблемы:

1. Часто наилучший способ — просто определить новую перегруженную функцию, которая принимает параметры именно того типа, который вы используете в вызове функции. Тогда C++ сможет найти точное совпадение для вызова функции.

2. Явно преобразовать с помощью оператора cast неоднозначный параметр(ы) в соответствие с типом функции, которую вы хотите вызвать. Например, чтобы вызов print(0) соответствовал print(unsigned int) вам нужно сделать следующее:

Итого

Перегрузка функций может значительно снизить сложность программы, в то же время создавая небольшой дополнительный риск. Хотя этот урок несколько длинный и может показаться сложным, но на самом деле перегрузка функций обычно работает прозрачно и без каких-либо проблем. Все неоднозначные случаи компилятор будет отмечать, и их можно будет легко решить.

Правило: Используйте перегрузку функций для упрощения ваших программ.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (14 оценок, среднее: 4,79 из 5)
Загрузка...
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО