Освой самостоятельно С++ за 21 день - Джесс Либерти 13 стр.


Перегрузка функций

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

int myFunction (int, int); int myFunction (long, long); int myFunction (long);

Функция myFunction() перегружена с тремя разными списками параметров. Первая и вторая версии отличаются типами параметров, а третья - их количеством.

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

Перегрузка функций также называется полиморфизмом функций. Поли (гр. poly) означает много, морфе (гр. morphe) - форма, т.е. полиморфическая функция - это функция, отличающаяся многообразием форм.

Под полиморфизмом функции понимают существование в программе нескольких перегруженных версий функции, имеющих разные назначения. Изменяя количество или тип параметров, можно присвоить двум или нескольким функциям одно и то же имя. При этом никакой путаницы при вызове функций не будет, поскольку нужная функция определяется по совпадению используемых параметров. Это позволяет создать функцию, которая сможет, например, усреднять целочисленные значения, значения типа double или значения других типов без необходимости создавать отдельные имена для каждой функции - AverageInts(), AverageDoubles() и т.д.

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

int DoubleInt(int); long DoubleLong(long); float DoubleFloat(float); double DoubleDouble(double);

С помощью перегрузки функций можно использовать следующие объявления:

int Double(int); long Double(long); float Double(float); double Double(double);

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

Листинг 5.8. Полиморфизм функций

1: // Листинг 5.8. Пример

2: // полиморфизма функций

3:

4: #include <iostream.h>

5:

6: int Double(int);

7: long Double(long);

8: float Double(float);

9: double Double(double);

10:

11: int main()

12: {

13: int myInt = 6500;

14: long myLong = 65000;

15: float myFloat = 6.5F;

16: double myDouble = 6.5e20;

17:

18: int doubledInt;

19: long doubledLong;

20: float doubledFloat;

21: double doubledDouble;

22:

23: cout << "myInt: " << myInt << "\n";

24: cout << "myLong: " << myLong << "\n";

25: cout << "myFloat: " << myFloat << "\n";

26: cout << "myDouble: " << myDouble << "\n";

27:

28: doubledInt = Double(myInt);

29: doubledLong = Double(myLong);

30: doubledFloat = Double(myFloat);

31: doubledDouble = Double(myDouble);

32:

33: cout << "doubledInt: " << doubledInt << "\n";

34: cout << "doubledLong: " << doubledLong << "\n";

35: cout << "doubledFloat: " << doubledFloat << "\n";

36: cout << "doubledDouble: " << doubledDouble << "\n";

37:

38: return 0;

39: }

40:

41: int Double(int original)

42: {

43: cout << "In Double(int)\n";

44: return 2 * original;

45: }

46:

47: long Double(long original)

48: {

49: cout << "In Double(long)\n";

50: return 2 * original;

51: }

52:

53: float Double(float original)

54: {

55: cout << "In Double(float)\n";

56: return 2 * original;

57: }

58:

59: double Double(double original)

60: {

61: cout << "In Double(double)\n";

62: return 2 * original;

63: }

Результат:

myInt: 6500

myLong: 65000

myFloat: 6.5

myDouble: 6.5e+20

In Double(int)

In Double(long)

In Double(float)

In Double(double)

DoubledInt: 13000

DoubledLong 130000

DoubledFLoat: 13

DoubledDouble: 1.3e+21

Анализ: Функция Double() перегружается для приема параметров четырех типов: int, long, float и double. Прототипы функций занимают строки 6-9, а определения - строки 41-63.

В теле основной программы объявляется восемь локальных переменных. В строках 13-16 инициализируются первые четыре переменные, а в строках 28-31 остальным четырем переменным присваиваются результаты передачи значений первых четырех переменных функции Double(). Обратите внимание, что по виду вызова эти функции ничем не отличаются друг от друга. Но удивительное дело: вы передаете аргумент - и вызывается нужная функция!

Дело в том, что компилятор определяет тип переданного аргумента, на основании которого выбирает соответствующий вариант функции Double(). А результаты работы этой программы подтверждают ожидаемую очередность вызова вариантов этой перегруженной функции.

Дополнительные сведения о функциях

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

Подставляемые inline-функции

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

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

Если функция объявлена с ключевым словом inline (т.е. подставляемая), компилятор не создает функцию в памяти компьютера, а копирует ее строки непосредственно в код программы по месту вызова. Это равносильно вписыванию в программе соответствующих блоков вместо вызовов функций.

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

Так какой же напрашивается вывод? Если в программе часто вызывается маленькая функция, состоящая из одной-двух строк, то это первый кандидат в подставляемые функции. Но если функция велика, то лучше воздержаться от ее многократного копирования в программе. Использование подставляемой функции демонстрируется в листинге 5.9.

Листинг 5.3. Использование подставляемых inline-функций

1: // Листинг 5.9. Подставляемые inline-функции

2:

3: <<include <iostгеагп.h>

4:

5: inline mt Double(int);

6:

7: int main()

8: {

9: int target;

10:

11: cout << "Enter а number to work with:

12: cin >> target;

13: cout << "\n";

14:

15: target = Double(target);

16: cout << "Target: " << target << endl.

17:

18: target = Double(target):

19: coul << "Target: " << target << endl;

20:

21:

22: target = Double(target):

23: cout << "Target: " << target << endl;

24: return 0;

25: }

26:

27: int Double(int target)

28: {

29: return 2'target;

20: }

Результат:

Enter a number to work with: 20

Target: 40

Target: 80

Target: 160

Анализ: В строке 5 объявляется подставляемая функция Double(), принимающая параметр типа int и возвращающая значение типа int. Это объявление подобно любому другому прототипу за исключением того, что прямо перед типом возвращаемого значения стоит ключевое слово inline.

Результат компиляции этого прототипа равносилен замене в программе строки:

target = 2 * target;

вызовом функции Double():

target = Double(target);

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

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

Рекурсия

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

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

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

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

Чтобы показать пример решение проблемы с помощью рекурсии, рассмотрим ряд Фибоначчи:

1,1,2,3,5,8,13,21,34...

Каждое число ряда (после второго) представляет собой сумму двух стоящих впереди чисел. Задача может состоять в том, чтобы, например, определить 12-й член ряда Фибоначчи.

Один из способов решения этой проблемы лежит в тщательном анализе этого ряда. Первые два числа равны 1. Каждое последующее число равно сумме двух предыдущих. Таким образом, семнадцатое число равно сумме шестнадцатого и пятнадцатого. В общем случае n-e число равно сумме (n-2)-го и (n-l)-го при условии, если n > 2.

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

При этом используется следующий алгоритм:

1. Предлагаем пользователю указать, какой член в ряду Фибоначчи следует рассчитать.

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

3. В функции fib() выполняется анализ аргумента (n). Если n < 3, функция возвращает значение 1; в противном случае функция fib() вызывает самое себя (рекурсивно), передавая в качестве аргумента значение n-2, затем снова вызывает самое себя, передавая в качестве аргумента значение п-1, а после этого возвращает сумму.

Если вызвать функцию fib(1), она возвратит 1. Если вызвать функцию fib(2), она также возвратит 1. Если вызвать функцию fib(3), она возвратит сумму значений, возвращаемых функциями fib(2) и fib(l). Поскольку вызов функции fib(2) возвращает значение 1 и вызов функции fib(1) возвращает значение 1,то функция fib(3) возвратит значение 2.

Если вызвать функцию fib(4), она возвратит сумму значений, возвращаемых функциями fib(3) и fib(2). Мы уже установили, что функция fib(3) возвращает значение 2 (путем вызова функций fib(2) и fib(1)) и что функция fib(2) возвращает значение 1, поэтому функция fib(4) просуммирует эти числа и возвратит значение 3, которое будет являться четвертым членом ряда Фибоначчи.

Сделаем еще один шаг. Если вызвать функцию fib(5), она вернет сумму значений, возвращаемых функциями fib(4) и fib(3). Как мы установили, функция fib(4) возвращает значение 3, а функция fib(3) - значение 2, поэтому возвращаемая сумма будет равна числу 5.

Описанный метод - не самый эффективный способ решения этой задачи (при вызове функции fib(20) функция fib() вызывается 13 529 раз!), тем не менее он работает. Однако будьте осторожны. Если задать слишком большой номер члена ряда Фибоначчи, вам может не хватить памяти. При каждом вызове функции fib() резервируется некоторая область памяти. При возвращении из функции память освобождается. Но при рекурсивных вызовах резервируются все новые области памяти, а при таком подходе системная память может исчерпаться довольно быстро. Реализация функции fib() показана в листинге 5.10.

Предупреждение: При запуске программы, представленной в листинге 6.10, задавайте небольшие номера членов ряда Фибоначчи (меньше 15). Поскольку в этой программе используется рекурсия, возможны большие затраты памяти.

Листинг 5.10. Пример использования рекурсии для нахождения члена ряда Фибоначчи

1: #include <iostream.h>

2:

3: int fib (int n);

4:

5: int main()

6: {

7:

8: int n, answer;

9: cout << "Enter number to find: "; 10: cin >> n;

10:

11: cout << "\n\n";

12:

13: answer = fib(n);

14:

15: cout << answer << " is the " << n << "th Fibonacci number\n"; 17: return 0,

16: }

17:

18: int fib (int n)

19: {

20: cout << "Processing fib(" << n << ")... "; 23:

21: if (n < 3 )

22: {

23: cout << "Return 1!\n";

24: return (1);

25: }

26: else

27: {

28: cout << "Call fib(" << n-2 << ") and fib(" << n-1 << ").\n";

29: return( fib(n-2) + fib(n-l));

30: }

31: }

Результат:

Enter number lo find: 6

Processing fib(6)... Call fib(4) and fib{S)

Processing fib(4)... Call fit>(2) and fib(3)

Processing fib(2)... Return 1!

Processing fib(3)... Call fib(l) and fiO<2)

Processing fib(D... Return 1!

Processi ng fib(2)... Return 1!

Processing fib(5)... Call fib(3) and fib{4)

Processing fib(3}... Call fib(1) and fib(2)

Processing flb(1)... Return 1!

Processi ng fib(2)... Return 1!

Processing fib(4)... Call fib(2) and fib(3)

Processing fib(2)... Return 1!

Processing fib(3)... Call fib(1) and fib(2)

Processing fib(l)... Return 1!

Processing fib(2)... Return 1!

8 is the 6th Fibonacci number

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

28: cout << "Call fib(" << (n-2) << ") and fib(" << n-1 << ").\n";

Анализ: В строке 9 программа предлагает ввести номер искомого члена ряда и присваивает его переменной n. Затем вызывается функция fib() с аргументом n. Выполнение программы переходит к функции fib(), где в строке 20 этот аргумент выводится на экран.

В строке 21 проверяется, не меньше ли аргумент числа 3, и, если это так, функция fib() возвращает значение 1. В противном случае выводится сумма значений, возвращаемых при вызове функции fib() с аргументами n-2 и п-1. Таким образом, эту программу можно представить как циклический вызов функции fib(), повторяющийся до тех пор, пока при очередном вызове этой функции не будет возвращено некоторое значение. Единственными вызовами, которые немедленно возвращают значения, являются вызовы функций fib(1) и fib(2). Рекурсивное использование функции fib() проиллюстрировано на рис. 5.4 и 5.5.

В примере, изображенном на рисунках, переменная n равна значению 6, поэтому из функции main() вызывается функция fib(6). Выполнение программы переходит в тело функции fib(), и в строке 30 значение переданного аргумента сравнивается с числом 3. Поскольку число 6 больше числа 3, функция fib(6) возврашает сумму значений, возвращаемых функциями fib(4) и fib(5):

38: return( fib(n-2) + fib(n-1));

Это означает, что выполняется обращение к функциям fib(4) и fib(5) (поскольку переменная n равначислу 6, то fib(n-2) - это то же самое, что fib(4), а fib(n-1) - то же самое, что fib(5)). После этого функция fib(6), которой в текущий момент передано управление программой, ожидает, пока сделанные вызовы не возвратят какое-нибудь значение. Дождавшись возврата значений, эта функция возвратит результат суммирования этих двух значений.

Джесс Либерти - Освой самостоятельно С++ за 21 день

Рис. 5.4. Использование рекурсии

Назад Дальше