Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

chast1

.pdf
Скачиваний:
16
Добавлен:
10.06.2015
Размер:
499.82 Кб
Скачать

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

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

Именно поэтому через параметр передаваемой позначению, нельзя отправить во внешний мир результат работы функции.

передача параметров по адресу.

Пусть теперь значение данного параметра(внашейфункции r)является адресом. Тогда мы имеем возможность так организовать работу с этим значением-адресом:

в процессе работы операторов в теле функции мы этот адрес можем разименовать (так и делали в строках 20-21), а значит все действия,предусмотренные операторами будут реально выполняться не над ячейкой stec а, а над ячейкой на которую ссылается этот адрес. В нашем примере такой внешней по отношениюк f1 ячейкой памяти будет область памяти, на которую ссылается r1 и которую создает оператор new.(9)

S=f1(...,r1,...);

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

Именно поэтому данный способ передачи параметров целесообразноиспользовать, если параметр предназначен для отправки результата работы функции во внешний мир.

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

Передача параметров по ссылке.

Это фактически разновидность передачи по адресу. Чем0то более удобный,чем передача по адресу.

Содержательно ссылка - это другое имя уже существующего объекта.

Формат определения ссылки. <тип> &<имя ссылки><инициализатор> Тип - это тип данных с++ за исключением void.

Другие ограничения:

-ссылки нельзя применять оператор new

-нельзя определить ссылку на другую ссылку

-нельзя создать указатель на ссылку

-нельзя создать массив ссылок

инициализатор обязателен и он может иметь любую из двух стандартных форм

Пример.

duable x=5,y=55;//(25) определили и инициализировали обычные переменные duable&ref_x=x;//(26) значением ссылки ref_x будетадрес переменной х.Перед символом х нет & операции взятия адреса.

duable&Ref_y(y);//(27) ref_y получит адрес переменной y. По определению & не является частью<типа>,т.е. ref_x имеет тип duable,а значит этот идентификатор должен восприниматься в выражениях,как переменная типа duable(не адрес). Хотя в результате инициализации(26) значением ссылки становится адрес. Соответственно ссылка применяется в выражениях подобно обычной переменной т.е. без операции разыменования. Хотя в выражении указывается имя ссылки вроде адрес, реально обрабатывается та область памяти с которой ссылка связана инициализацией.

Например

ref_x=6;//(28) это оператор изменяет значение переменной х. ref_y=7;//(29) здесь меняется значение переменной y

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

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

ссылки более удобны,чем адреса т.к. к ним не надо применять операции разыменования (см. строка 17, 22, 8,10, 13). роме того , поскольку ссылка не может поменять значение, уменьшается опасность порчи памяти, ног такая опасность высока когда работаешь с указателем.

Еще одно важное преимущество передачи параметра в функцию по адресу или по ссылки такова:

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

Параметры константы.

Если алгоритм запрограммированный в теле функции предполагает, что какой-то параметр не должен меняться в процессе выполнения функции,то его логично объявить с модификатором const.

int f2(const int a,int b,int c)...

Тогда если вы по ошибке попытаетесь написать такой оператор (а=b+c) то компелятор сообщит об ошибке. Таким образом применение модификатора const это хорошее средство повышения безопасности кода.

__________________________________________________________________________________

33.Глобальные переменные.

__________________________________________________________________________________

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

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

Целесообразнл исходить из следующего требования:

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

___________________________________________________________________________________

34.Фукции и массивы.

___________________________________________________________________________________

Массивы могут применяться и в качестве параметра и в качествевозвращаемого результата функции. Однако и в том и в другом случае они должны передаваться по адресу.

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

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

Рассмотрим эту основную проблему на примерах задач разной сложности:

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

Пример.

Определим функции расчетадлины строки в стиле с. int len(char*str)//(1)

{int n=0;//(2) стартовое значение переменной предназначенноедля подсчета длины строки while(str[n++]);//(3) считать n пока не упремся в "\0"

return(n-1);//(4)

}

char stroka[]="учиться полезно для здоровья"; cout<<"длина строки "<<len(stroka)<<endl;

Обратим внимание на параметр массива в заголовке функции (1),этот же параметр можно былобы записать иначе.

int len(char str[])//(1')

Если до определения функции определить константу,то допустимо: int const n=7;

int len(char str[n]);//(1'')

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

_________________________________________________________________________________

Передача в функцию одномерного массива.

_________________________________________________________________________________

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

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

Опишем задачуна обычном языке:

Рассматривается одномерный массив mass из n элементов. Два фрагмента этого массив с индексами от p до q и от (q+1) до r по отдельности уже отсортированы.Для определенности будем полагать, что сортировка осуществляется в порядке неубывания.Необходимо получить массив у которого отсортирован

весь непрерывный фрагмент с индексами от p до q. Т.е. надо слить два фрагмента в единую отсортированную последовательность.

В функцию реализующую алгоритм решения этой задачи необходимо передать не только длинну массива

(n), но и параметры (p,q,r).

void merge (double*mass, int n,int p, int q, int r) //(8) {int k,i,j,j1,j2,m1,m2 //(9)

double*t=new double[n]; //(10) j1=p; j2=q+1, k=p-1; //(11) while ((j1<=q)||(j2<=r)) //(12) {k=k+1; m1=j1; m2=j2; //(13) if ((m1<=q)&&(m2<=r)); //(14) if (mas[j1]<mas[j2]) {t[k]=mas[j1]; j1=j1+1} //(15) else

{t[k]=mas[j2]; j2=j2+1;} //(16) if((m1<=q)&&(m2>r)) {t[k]=mass[j1]; j1=j1+1;} if((m1>q)&&(m2<r)) {t[k]=mass[j2]; j1=j2+1}}//(18) for|(int i=p; i<=r;i++) mass[i]=t[i]; //(20)

delete []t;} //(21)

Встроке 9 определены локальные вспомогательные переменные.

В10 строке локально определен вспомогательныйдинамический массив. Обычный массив здесь нельзя объявить поскольку длину массива приходится задавать переменным параметром а не константным выражением.

Слияние 2х массивов в единую отсортированную последовательность осуществляется в цикле while, а результат сохраняется во вспомогательный динамический массив.

Вцикле for данные сохраненные во вспомогательном массиве передаются в основной массив.

Пример использует функцию merge

merge (A, 7, 2, 3, 5); //(22) здесь полагаем что А уже ранее определенный инициализированный массив. cout<<"Массив А после слияния фрагментов"; //(23)

i=0; //(24) while (i<7) {cout<<A[i]<<" "; i++; //(25)

}

cout<<endl; //(26)

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

void merge(double*mass[],int n, int p, int q, int r); //(8')

Третий вариант заголовка рассмотренного в предыдущем пункте. (1'') здесь так же формально возможен, однако неуместен по характеру задачи. В любом случае независимо от формы задачи (ст 18).

__________________________________________________________________________________

Функция возвращающая указатель на массив.

__________________________________________________________________________________

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

double*MasMax(double*mas1, double*mas2,int n) //(27)

{double*t=new double[n]; //(28) определен динамический массив для сохранения результата работы.

int i=0; //(29) while(i<n) //(30)

{t[i]=mas1[i]>mas2[i] ? mas1[i] : mas2[i]; //(31) i++} //(32)

return t; //(33) } //(34)

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

double M1[]={6,5,4,3,2,1}; //(35) double M2[]={1,2,3,4,5,6}; //(36)

double*tvn; //(37) функция для массива результата

tvn=MasMax(M1,M2,6); //(38) функция asMax сформирует новый массив и возвратит его адрес который будет присвоен внешнему указателю tvn.

i=0; //(39)

while)i<6){cout<<tvn[i]<<" ";i++;} //(40) cout<<tndl; //(41)

delete[]tvn; //(42) теперь если массив нам нужен его можно уничтожить.

__________________________________________________________________________________

Передача в функцию многомерного массива.

__________________________________________________________________________________

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

В С++ многомерный массив это всегда одномерныймассив у кророго элементами являются массивы (массив массивов).

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

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

массив,однако это рассмотрим на практических занятиях.

Вначале отметимчто попытка решить проблему используя синтаксис входного параметра в форме 1''не верна.

double mas_dbl(double mass[][],int n, int m) (неверно)

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

-использование массивов указателей на массивы.

-применение классов для представления многомерных массивов.

Рассмотрим только второй вариант. Пример

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

Определим функцию для оценки максимального элемента вещественного двухмерного массива размером m*n.

double max_dbl2(double**mas, const int m, const int n) //(43) первый параметр это указателя на указатель на данные типа double

{int i,j; //(44)

double x=-DBL_MAX; //(45) определяем вещественную переменную и передаем ейнаименьшее вещественное значение типа double

cout<<"Наименьшее вещественное значение = "<<x<<endl; //(46) for(i=0; i<m; i++) //(47)

for(j=0; j<n; j++) //(48)

if(x<mas[i][j]) x=mass[i][j]; //(49) с помощью указателя на указатель обращаемся к элементам двухмерного массива.

return x; //(50) } //(51)

Замечание:

Вместо строки 45 можно написать double x=max[0][0]; //(45')

В функции (45 строка) использовали одну из констант придельных вещественных значений, а именно DBL_MAX это максимальное число с плавающей точкой пита double. Для получения доступа к таким константам необходима директива include <cfloat>.

Поиск наибольшего элемента массива осуществляется с помощью вложенных циклов for, т.е. второй for является телом первого.

Хотя входной параметр это указатель на указатель, внутри функции (в теле) мы обращаемся к элементам массива обычным способом,а именно по индексам (49 строка).

Пример обращения к функции max_dbl2 int mst2=3, nstlb=4; //(52)

double**AM; //(53) определяем внешний по отношению к функции указатель на указатели

AM=new double*[mstr]; //(54) формализуем динамический одномерный массив указателей на данные типа double и ссылку на первый элемент массива передаем указателю AM.

for(i=0; i<mstr; i++) //(55)

AM[i]=new double [nstlb]; //(56) каждому элементумассива указателей сопоставляем одномерный динамический массив данных типа double (строку матрицы)

for(i=0; i<mstr; i++) //(57) for(j=0; j<nstbl; j++) //(58)

AM[i][j]=i*j; //(59) заполняем массив данными double max_el=maxdbl2(AM,mstr, nstlb); //(60)

cout<<"Максимальный элемент двухмерного массива = "; //(61) cout<<max_el<<endl(62)

__________________________________________________________________________________

35.Указатели на функции.Передача имен функций в качестве параметров.

__________________________________________________________________________________

Напомним что в С++ функции рассматриваются как разновидность объектов т.е. областей памяти в которых могут храниться значения. В данном случае таким значением являются коты функции. Такие объекты как и объекты других видов характеризуются типом.

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

-Спецификация параметров Поскольку в С++ любой указатель должен быть указателем на объект определенного типа, то и указатель

на функцию должен соответствовать её типу.Следовательно формат указателя на функцию должен быть таким:

<тип>(*<имя указателя>)(<спецификация параметров>) Пример

double(*ptr_fun)(int); //(1) здесь определен указатель с именем ptr_fun на функции с параметром типаint, возвращабщее значение типа double.

Наличае первой пары() является обязательным,если эти скобки опустить то возникнет правильная конструкция,но совершенно другая по смыслу.

double*ptr_fun (int); //(2)здесь дано объявление прототипа функции с именем ptr_fun и параметром типа int которая возвращает указатель на данные типа double.

Обрати внимание на то что имя функции не является атрибутом её типа.

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

Пример Далее определим 3 функции имеющие один и тотже тип,но возвращающие разные значения.

double f1(double x) //(3) {return (x-3)}; //(4) double f2(double x) //(5) {return (x*x-3)}; //(6) double f3(double x) //(7) {return (x*x*x-3)}; //(8)

double (*otr)(double); //(9)определили указатель совместный по типу с функциями f1,f2,f3. int i=3; //(10)

Далее с помощью переключателя настраиваем указатель ptr на одну из функция. switch (i) //(11)

{case1:ptr=f1; break; //(12) case2:ptr=f2; break; //(13) case3:ptr=f3; //(14)

} //(15)

Далее по указателю ptr вызываем выбранную функцию double x=5; //(16)

cout<<"вариант"<<i<<"возвратил"<<ptr(x)<<endl; //(17)

В строке 17 можно было бы использовать другой эквивалентный по семантике сиснаксис вызова (*ptr)(x). Указатели на функцию могут объединяться в массивы. Иногда это позволяет делать программу более компактной.

Можно объявить именованный тип указатель на функцию. typedef double(*type_ptr)(double); //(18)

Здесь определен не указатель,т.е. переменная,а тип указателя совместимого с функциями f1,f2, f3. Имя этого типа type_ptr.

type_ptr ptr; //(19) Здесь определен нужный указатель с именем ptr.

Иногда использование именованного типа "указатель на функцию" позволяет сделать текст на С++ более наглядным.Наибольший практический эффект указатели на функцию дают когда внутри данной функции необходимо передать адрес другой функции, которую предполагается вызвать в теле данной функциидля организации вычислительного процесса.

Смысл поясним примером:

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

Такой алгоритм можно оформить в виде функции с именем Root_Eqution, а конкретную функцию f(x) которую мы хотели бы проанализировать в данномконкретном сеансе работы программы передаватьво внутрьфункции Root_Eqution по указателю.

Приведем псевдокод соответствующей конструкции.

double Root_Eqution (type_ptr f, double A, doublr B,doublr Eps); //(20)

{/*здесь нужно написать алгоритм поиска корня уравнения f(x)=0 на заданном интервале [A,B] при условии что погрешность определения корня не превышает Eps. Первым параметром функции Root_Eqution является указатель на функции type_ptr, следовательно функцию Root_Eqution можно использовать для поиска корня уравнения f(x)=0, где в качестве исследуемой функции f(x) может использоваться любая функция типа type_ptr. Например это могут быть функции f1, f2,f3 (см строки 3-8).

Пример обращения

x=Root_Eqution(f2, 0, 6, 1E-3); //(21) переменная х получает значение корня укавнения f2(x)=0 на отрезке [0,6] с погрешностью не более 10^-3.

____________________________________________________________________________________

36.Рекурсивная функция, реализующая алгоритмвставки нового элемента в дерево (INSERT)- общее описание и реализация на с++.

________________________________________________________________________________

Возможный вариант функции INSERT.

int INSERT (const ElementType x, BinTree*B) //(8) функция возвратит ноль, если элемент успешно размещен в дереве и 1,если элемент уже был в дереве и повторное размещение не проведено.

{if((*B)==0)//(5) если адрес дерева ==0

{(*B)=new NodeType://(10) раз дерево было пусто,то создаем новый узел, который станет корнем дерева. (**B).element=x;//(11)

(*B)->leftchild=0;//(12)

(*B)->rhitchild=0;//(13) в 11-13 демонстрируются разные способы доступа к полям структуры return 0;}//(14) элемент размещен в дереве

els if (x<(*B)->element)INSERT(x,&((**B).leftchild));//(15) els if (x>(*B)->element)INSERT(x,&((**B).rhitchild));//(16) els return 1;//(17) элемент х уже есть в дереве

}//(18) конец функции INSERT

Внутренние вызовыINSERT (15-16).

В рекурсивных вызовах мы постепенно обращаясь к поддеревеьям можем добраться до указателя 0 и тогда при очередном вызове INSERT сработают операторы 11-13 по вставке нового элемента в дерево. А иначе , если 0 не достигнут, сработает return из строки 17.

Пример обращения к функции INSERT ElementType y[10]={1,0.5,0.2,0.7,5,3,8,9,0.6,4,};//(19)

BinTree A=0;//(20) выше мы создали массив данныхтипа ElementType дляпредстоящего заполнения дерева и пустое дерево А.

for(int i=0; i<10; i++) INSERT(y[i],&A;//(21) указатель В на бинарное дерево передается не позначению, апо адресу (строка 8,15, 16,21)

_________________________________________________________________________________

Рекурсивная функция,реализующая алгоритм сортировки в бинарном дереве поиска - общиее описание симметричного (внутреннего) обхода дерева и реализация алгоритма на с++.

________________________________________________________________________________

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

Для простоты будем полагать,что элементы хранилища - это вещественные числа и надо их расставитьв порядке неубывания.

Можно показать,что для такого упорядочивания элементов,хранящихся в бинарном дереве поиска, достаточно совершить обход узлов дерева в симметричном (внутреннем ) порядке. В процессе обхода будет составляется список элементов, который и окажется списком с нужной сортировкой. Для общности рассмотрим произвольное (не бинарное) дерево. Симметричный порядок обхода рекурсивно можно определить так (k-арность дерева):

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

-если дерево Т состоит из 1 узла,то в список обхода заносится элемент из этого узла -если дерево Т состоит из узла n и поддеревьев T1,Т2...Тk

рис7.3 то сначала также в симметричном порядке посещаются (рекурсия) все узлы поддерева Т1 далее

посещается корень n,а далее последовательно в симметричном порядке посещаются поддеревья Т2...Тk.

Рассмотрим пример рекурсивной функции, реализующий этот алгоритм:

void SIMM_OBXOD(BinTree B)//(22) обратим внимание, что в данном случае входной параметр допустимо передавать по значению,поскольку дерево не меняется.

{if(B==0){cout<<"\" дерево пусто\""<<endl;return;}//(23) if(((*B).leftchild==0)&&((*B).rhitchild==0));//(24) если это так,, тогда cout<<(*B).element<<endl;//(25) добрались до листа и значение элемента отправили в список els{if((*B).leftchild!=0)SIMM_OBXOD((*B).leftchild);//(26)

cout<<B->element<<endl;//(27) if((*B).rhitchild!=0)SIMM_OBXOD((*B).rightchild);}//(27) }//(29)

В 27 строке добрались до n. Если к функции обратиться так:

SIMM_OBXOD(A);//(30)A- дерево, сформированноеоператорами из строк 19-21, то в консольное окно будет выведен элемент из строки 19, но расположенныхв порядке возрастания.

_________________________________________________________________________________

37.перегрузка функции.

________________________________________________________________________________

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

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

В данном разделе рассмотрим 1 вид.

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

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

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

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

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

Пример. Необходимо решить задачу поиска максимального элемента в массиве чисел. При этом перегружаемые функции должны обеспечивать решение задач, как для целых,так и для вещественных массивов. Определим 2 функции с одинаковым числом параметров, нотипв параметров будут разные. int max_el(int n,int Ar[])//(1)

{int v=Ar[0];//(2) for(int i=1; i<n; i++)//(3) v=v>Ar[i] ? v : Ar[i];//(4) return v;//(5)

}//(6) конец функции max_el с целым параметром значений

Далее идут строки 7-12 точно такие же,но место типа int для параметра Ar и локальной переменной v используют тип double.

Пример обращения к этим функциям. int x[]={1,4,9,4,6,7,2,8};//(13)

double y[]={0.1,0.4,0.9,0.4,0.6,0.7,0.2,0.8};//(14) cout<<"наибольшее целое= "<<max_el(8,x)<<endl;//(15) cout<<"наибольшее вещественное= "<<max_el(8,y)<<endl;//(16)

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

Функции не могутбыть перегруженными, если объявление их параметров различаются только использованием модификатора const. Также если различие 2-х функций сводится только к тому, что объявление одного и того же параметра различается только по наличию или отсутствию знака ссылки. Например в одной функции указан типint а в другой ссылка на int&. Источником неоднозначности может быть использование параметров по умолчанию. Рассмотрим пример и в нем проиллюстрируем случай, когда функции различаются количеством параметров.

double multy(double x){return x*x*x;}//(17)

double multy(double x, double y){return x*y*y;}//(18)

double multy(double x, double y, double z){return x*y*z;}//(19)

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

double multy(double x=1, double y=1,double z=1, double t=10){return x*y*z*t;}//(20) то получим программу,

где легко получить ошибку. Например,если мы попытаемся:

х=multy(0.3,0.2);//(21) то на этапе компеляции получим сообщение об ошибке "неоднозначный вызов перегруженнойфункции". Это будет так поскольку этот вызов можно рассматривать двояко: либо как правильный вызов функции из строки 18, либо как правильный правильный,но в этих условиях недопустимый вызов функции из строки 20. Коварство этой конструкции в том, что не всякая попытка вызова такой функции будетнеправильной. например вызов:

x=multy(0.1,0.8,0.5,0.3);//(22) будет обработан правильно,,будет вызвана 4-я из одноименных функций.

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