246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 20
Текст из файла (страница 20)
Следует иметь в виду, что при создании двух функций с одинаковым именем иодинаковым списком параметров, но с различными типами возвращаемых значений, будетсгенерированаошибкакомпиляции.Перегрузка функций также называется полиморфизмом функций. Поли (гр. poly) означаетмного, морфе (гр. morphe) — форма, т.е. полиморфическая функция — это функция,отличающаясямногообразиемформ.Под полиморфизмом функции понимают существование в программе несколькихперегруженных версий функции, имеющих разные назначения. Изменяя количество или типпараметров, можно присвоить двум или нескольким функциям одно и то же имя. При этомникакой путаницы при вызове функций не будет, поскольку нужная функция определяется посовпадению используемых параметров.
Это позволяет создать функцию, которая сможет,например,усреднятьцелочисленныезначения,значениятипаdoubleилизначениядругихтиповбез необходимости создавать отдельные имена для каждой функции — AverageInts(),AverageDoubles()ит.д.Предположим, вы пишете функцию, которая удваивает любое передаваемое ей значение.При этом вы бы хотели иметь возможность передавать ей значения типа int, long, float илиdouble.Безперегрузкифункцийвамбыпришлосьсоздаватьчетыреразныефункции:intDoubleInt(int);longDoubleLong(long);floatDoubleFloat(float);doubleDoubleDouble(double);Спомощьюперегрузкифункцийможноиспользоватьследующиеобъявления:intDouble(int);longDouble(long);floatDouble(float);doubleDouble(double);Благодаря использованию перегруженных функций не нужно беспокоиться о вызове впрограмме нужной функции, отвечающей типу передаваемых переменных.
При вызовеперегруженнойфункциикомпиляторавтоматическиопределит,какойименновариантфункцииследуетиспользовать.Перегрузкафункциипоказанавлистинге5.8.Листинг5.8.Полиморфизмфункций1://Листинг5.8.Пример2://полиморфизмафункций3:4:#include<iostream.h>5:6:intDouble(int);7:longDouble(long);8:floatDouble(float);9:doubleDouble(double);10:11:intmain()12:{13:intmyInt=6500;14:longmyLong=65000;15:floatmyFloat=6.5F;16:doublemyDouble=6.5e20;17:18:intdoubledInt;19:longdoubledLong;20:floatdoubledFloat;21:doubledoubledDouble;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:return0;39:}40:41:intDouble(intoriginal)42:{43:cout<<"InDouble(int)\n";44:return2*original;45:}46:47:longDouble(longoriginal)48:{49:cout<<"InDouble(long)\n";50:return2*original;51:}52:53:floatDouble(floatoriginal)54:{55:cout<<"InDouble(float)\n";56:return2*original;57:}58:59:doubleDouble(doubleoriginal)60:{61:cout<<"InDouble(double)\n";62:return2*original;63:}Результат:myInt:6500myLong:65000myFloat:6.5myDouble:6.5e+20InDouble(int)InDouble(long)InDouble(float)InDouble(double)DoubledInt:13000DoubledLong130000DoubledFLoat:13DoubledDouble: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:inlinemtDouble(int);6:7:intmain()8:{9:inttarget;10:11:cout<<"Enterаnumbertoworkwith: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:return0;25:}26:27:intDouble(inttarget)28:{29:return2'target;20:}Результат:Enteranumbertoworkwith:20Target:40Target:80Target: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).