Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lesson_9 встраивание_ перегрузка_ шаблоны функц...doc
Скачиваний:
3
Добавлен:
01.07.2025
Размер:
129 Кб
Скачать
☆
  1. Перегрузка функций.

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

Ключевую роль в перегрузке функций играет список аргументов, который также называется сигнатурой функции. Если две функции используют одно и то же количество аргументов с теми же самыми типами в одном и том же порядке, то функции имеют одинаковые сигнатуры; при этом имена переменных во внимание не принимаются. Язык C++ позволяет определить две функции с одним и тем же именем при условии, что эти функции обладают разными сигнатурами. Сигнатуры могут различаться по количеству аргументов или по их типам, либо по тому и другому. Например, можно определить набор функций print () со следующими прототипами:

void print (const char * str, int width); // #1

void print (double d, int width); // #2

void print (long 1, int width); // #3

void print (int i, int width); // #4

void print(const char *str); // #5

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

print("Pancakes", 15); // используется #1

print("Syrup"); // используется #5

print(1999.0, 10); // используется #2

print(1999, 12); // используется #4

print (1999L, 15); // используется #3

Например, в вызове print ("Pancakes", 15) передается строка и целое число в качестве аргументов, что соответствует прототипу #1.

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

unsigned int year =3210;

print(year, 6); // неоднозначный вызов

Некоторые сигнатуры, которые выглядят как отличающиеся друг от друга, тем не менее, не могут сосуществовать. Для примера рассмотрим следующие два прототипа:

double cube(double х) ;

double cube(double & x) ;

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

В ходе сопоставления функций учитываются различия между переменными с квалификатором const и без него. Рассмотрим следующие прототипы:

void dribble(char * bits); // перегружена

void dribble (const char *cbits); // перегружена

void dabble(char * bits); // не перегружена

void drivel(const char * bits); // не перегружена

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

long gronk(int n, float m); // одинаковые сигнатуры, поэтому

double gronk(int n, float m); // объявления не допускаются

В C++ нельзя перегружать функцию gronk () подобным образом. Можно иметь различные возвращаемые типы, но только при условии, что сигнатуры функций отличаются:

long gronk(int n, float m) ; // различные сигнатуры, поэтому

double gronk(float n, float m) ; // объявления допустимы

Когда целесообразно использовать перегрузку функций

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

char * left (const char * str, unsigned n) ; // два аргумента

char * left (const char * str); // один аргумент.

Тем не менее, использование единственной функции с аргументом по умолчанию будет проще. Прежде всего, понадобится написать только одну функцию, а не две, к тому же программе потребуется меньше памяти. Если впоследствии нужно будет внести изменения, достаточно отредактировать только одну функцию. Однако если необходимо применять аргументы разного типа, то аргументы по умолчанию здесь не помогут, и придется использовать перегрузку функций.

Аргументы по умолчанию

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

Например, если функция void wow (int n) определена так, что n по умолчанию имеет значение 1, то вызов функции wow () означает то же самое, что и wow (1).

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

char * left (const char * str, int n = 1) ;

Эта функция должна возвращать новую строку, поэтому ее типом будет char*, т.е. указатель на char. Если нужно сохранить исходную строку неизменной, следует использовать квалификатор const для первого аргумента. А чтобы аргумент п имел значение по умолчанию 1, присвоим это значение аргументу п. Принимаемое по умолчанию значение аргумента — это значение, заданное при инициализации. Таким образом, данный прототип инициализирует п значением 1. Если аргумент п опускается, для него принимается значение 1, но если данный аргумент передается, то новое значение переопределяет значение 1.

В функции со списком аргументов значения по умолчанию должны добавляться в конце. Другими словами, нельзя предоставить значение по умолчанию некоторому аргументу до тех пор, пока не будут предоставлены значения по умолчанию для всех аргументов, размещенных справа от него:

int harpo(int n, int m = 4, int j = 5) ; // ПРАВИЛЬНО

int chico(int n, int m = 6, int j); // НЕПРАВИЛЬНО

int groucho(int к = 1, int m = 2, int n = 3) ; // ПРАВИЛЬНО

Например, прототип функции harpo () допускает реализацию вызова функции с одним, двумя или тремя аргументами:

beeps = harpo (2); //то же, что и harpo (2, 4, 5)

beeps = harpo(1,8) ; //тоже, что и harpo(1,8,5)

beeps = harpo (8,7,6); // аргументы по умолчанию не используются

Значения фактических аргументов присваиваются соответствующим формальным аргументам в направлении слева направо; пропускать аргументы нельзя. Таким образом, следующее выражение является недопустимым:

beeps = harpo (3, ,8); // неправильно, m не устанавливается в 4

Аргументы по умолчанию не являются выдающимся достижением в программировании — они предназначены лишь для удобства. Когда вы начнете работать с классами, то убедитесь в том, что этот прием позволяет сократить количество конструкторов, методов и перегрузок методов, подлежащих определению.

Пример использования аргументов по умолчанию приведен в листинге. Обратите внимание, что значения по умолчанию отражает только прототип. Определение функции будет таким же, как и без аргументов по умолчанию.

_____________________________

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

Например, может потребоваться функция, возвращающая максимальное из значений элементов одномерного массива, передаваемого ей в качестве параметра. Массивы, используемые как фактические параметры, могут содержать элементы разных типов, но пользователь функции не должен беспокоиться о типе результата. Функция всегда должна возвращать значение того же типа, что и тип массива - фактического параметра.

Для реализации перегрузки функций необходимо для каждого имени определить, сколько разных функций связано с ним, т.е. сколько вариантов вызовов допустимы при обращении к ним. Предположим, что функция выбора максимального значения элемента из массива должна работать для массивов типа int, long, float, double. В этом случае придется написать четыре разных варианта функции с одним и тем же именем. В нашем примере эта задача решена следующим образом:

#include <iostream>

using namespace std;

long max_element(int n, int array[])

// Функция для массивов с элементами типа int.

{

int value = array[0];

for (int i = 1; i < n; i++)

value = value > array [i] ? value : array [i] ;

cout << "\nFor (int) : ";

return long(value);

}

long max_element(int n, long array[])

// Функция для массивов с элементами типа long.

{

long value = array[0];

for (int i = 1; i < n; i++)

value = value > array[i] ? value : array[i];

cout << "\nFor (long) : ";

return value;

}

double max_element(int n, float array[])

// Функция для массивов с элементами типа float.

{

float value = array[0];

for (int i = 1; i < n; i++)

value = value > array[i] ? value : array[i];

cout << "\nFor (float) : ";

return double(value);

}

double max_element(int n, double array[])

// Функция для массивов с элементами типа double.

{

double value = array[0];

for (int i = 1; i < n; i++)

value = value > array[i] ? value : array[i];

cout << "\nFor (double) : ";

return value;

}

void main()

{

int x[] = { 10, 20, 30, 40, 50, 60 };

long f[] = { 12L, 44L, 5L, 22L, 37L, 30L };

float y[] = { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6 };

double z[] = { 0.01, 0.02, 0.03, 0.04, 0.05, 0.06 };

cout << "max_elem(6,x) = " << max_element(6,x);

cout << "max_elem(6,f) = " << max_element(6,f);

cout << "max_elem(6,y) = " << max_element(6,y);

cout << "max elem(6,z) = " << max_element(6,z);

}

1. В программе мы показали независимость перегруженных функций от типа возвращаемого значения. Две функции, обрабатывающие целые массивы (int, long), возвращают значение одного типа long. Две функции, обрабатывающие вещественные массивы (double, float), обе возвращают значение типа double.

2. Распознавание перегруженных функций при вызове выполняется по их параметрам. Перегруженные функции должны иметь одинаковые имена, но спецификации их параметров должны различаться по количеству и (или) по типам, и (или) по расположению.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]