В C++ определено несколько способов передачи параметров функции и получения результатов вычисления функции, вызывающей средой. Существует четыре специфики передачи параметров функции:
- вызов функции с передачей значений;
- вызов функции с передачей адресов переменных;
- вызов функции с использованием механизма ссылок при передаче параметров;
- посредством глобальных параметров.
Вызов функции с передачей значений. Этот способ передачи параметров обеспечивает передачу копий переменных в стек, организуемый при активизации функции. При этом обеспечивается защита самих переменных от их изменения в функции. Пример:
#include<iostream.h>
int sum(int,int); // объявление функции
void mane(void)
{int a,b,c;
cin >> a >> b;
c=sum(a,b); //передача параметров значений
cout << c << endl;
}
// определение функции
int sum(int d, int l) // заголовок
{ // тело функции
int f;
f=d+l;
return f;// результат передаётся в точку вызова
}
Вызов функции с передачей адресов. Этот способ передачи параметров обеспечивает передачу в стек адресов передаваемых данных, что позволяет функции работать непосредственно с данными.
#include<iostream.h>
sum(int,int,int*); // объявление функции
void main()
{int a,b,c=0;
cin>>a>>b;
sum(a,b,&c); // вызов функции
cout<<c<<endl;
}
void sum(intd,intl,int*f) // определение функции
{
*f=d+l // f – указатель на c
}
Вызов функций с использованием механизма ссылок. Этот способ обеспечивает доступ к передаваемым параметрам посредством определения их альтернативного имени. Например:
#include<iostream.h>
sum(int,int,int&);
void main()
{
int a,b,c=0;
cin >> a >> b;
sum(a,b,c);
cout << c << endl;
}
//void
sum(int d,int l,int &f)
{
f=d+l; // f- ссылка на c
}
Вызов функции с передачей данных посредством глобальных параметров. Этот способ передачи исходных данных в вызываемую функцию и возвращения результата вычислений путём использования глобальных параметров. Например:
#include <iostream.h>
int a,b,c;
sum(); // объявление функции
main()
{
cin >> a >> b;
sum(); //вызов функции
cout<<c<<endl;
}
sum() // определение функции
{c=a+b; //a,b,c- глобальные переменные
}
Вызов функции с передачей аргументов по умолчанию. В языке С++ начиная с версии 3.11 и выше, определена возможность передачи значений аргументов функции по умолчанию. Этот способ передачи значений параметров используется в том случае, когда необходимо обеспечить передачу только части значений параметров, а не всех.
Объявление значений функции по умолчанию производится путём указания значений аргументов в прототипе функции посредством оператора присваивания.
#include<iostream.h>
float ur(float x,float a=0.,float b=0.,float c=0.);
int main()
{float a=1.,b=2.,c=3.,x=0.5,y;
y=ur(x,a,b,c);
cout<<"введены все аргументы"<<"\n";
cout<<y<<"\n";
y=ur(x,a,b);
cout<<"введены x,a и b"<<"\n";
cout<<y<<"\n";
y=ur(x);
cout<<"введен x"<<"\n";
cout<<y<<"\n";
cin>>a;
}
float ur(float x,float a,float b,float c)
{
return a*x*x+b*x+c;}
На экране дисплея мы получим следующие результаты работы вышеприведенной программы.
Введены все аргументы
4.25
введены x,a и b
1.25
введен x
0.
Рекурсивные функции.
В языке C++ допустима рекурсия. Рекурсия это способ организации вычислительного процесса, при котором процедура или функция может обращаться сама к себе. Покажем рекурсивную реализацию метода быстрой сортировки. В методе используется процедура половинного разделения, применяемая на 1-ом шаге ко всему массиву, а на следующих шагах – к его фрагменту. На каждом шаге образуются две половинки текущего фрагмента, к которым снова применяется процедура разделения. Если массив сортируется по возрастанию, то в левую половинку записываются меньшие значения, а в правую – большие (если по убыванию, то наоборот).
Одну из возможных версий программы покажем на примере:
#include<iostream.h>
void quicksort(float* arr, int left,int right);
void main()
{const int n = 10;
float ar[n];
int i, l, r;
//cputs(«введите данные о значениях исходного массива»);
for(i=0; i<n; i++){
cout<<”введите а[“<<k<<”] исходного массива \n”;
cin>>ar[i];
}
l = 0; r = n – 1; //левая и правая границы начального фрагмента
quicksort(ar, l, n); // вызов функции
for(i=0; i<n; i++)
printf(“ar[ %d ]= %d \n”, i, ar[i]); // cout<<ar[i]<<” “;
}
void quicksort(float * arr, int left, int right)
{int i = left, j = right; //левая и правая границы фрагмента
float middle = arr[(left + right) / 2];
float temp;
while (i < j) {
while (arr[i] < middle) i++;
while (middle < arr[j]) j--;
if (i <= j) { // замена значений
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
if(i < right)quicksort(arr, i, right); /* вызов функции для
сортировки правой половины фрагмента массива */
if(left < j)quicksort(arr, left, j); /* для сортировки левой
половины фрагмента */
}
Процедура разделения реализована в виде рекурсивно вызываемой функции quicksort(), в теле которой есть два обращения к самой себе: для сортировки левой половинки теккущего фрагмента и сортроки его правой половинки.
Однако, у рекурсии есть недостатки:
- такую программу труднее отлаживать, поскольку требуется контролировать глубину рекурсивного обращения;
- при большой глубине стек может переполниться;
- использование рекурсии повышает накладные расходы (в данном случае в стеке сохраняются не два числа, представляющие собой границы фрагмента, а гораздо больше, не говоря уже о затратах, связанных с вызовом функции).
Поэтому рекурсию следует применять с осторожностью.
Перегрузка функций.
Часто бывает удобно, чтобы функции, реализующие один и тот же алгоритм для различных типов данных, имели одно и то же имя. Использование нескольких функций с одним и тем же именем, но с различными типами параметров, называется перегрузкой функций (перегружаемыми функциями).
Компилятор определяет, какую именно функцию требуется вызвать, по типу фактических параметров. Этот процесс называется разрешением перегрузки. Тип возвращаемого функцией значения в разрешении не участвует.
int sum(int a, int b)
{return (a+b);}
double sum(double a, double b)
{return(a+b);}
double sum(double a, double b, double c)
{return(a+b+c);}
Приведенные выше функции отличаются друг от друга следующим образом: первая от второй типом формальных параметров и типом возвращаемого результата; первая от третьей количеством и типом формальных параметров и типом возвращаемого результата; вторая от третьей количеством формальных параметров.
Если точного соответствия списков параметров объявления функции и в ее вызове не найдено, выполняются преобразования типов в соответствии с общими правилами, например, bool и char в int, float в double и т.п. Далее выполняются стандартные преобразования типов, например, int в double или указателей в void*. Следующим шагом является выполнение преобразований типа, заданных пользователем, а также поиск соответствий за счет переменного числа аргументов функций. Если соответствие на одном и том же этапе может быть получено более чем одним способом, вызов считается неоднозначным и выдается сообщение об ошибке.
Существует ряд правил описания перегруженных функций:
- перегруженные функции должны находиться в одной области видимости, иначе произойдет сокрытие аналогично одинаковым именам переменных во вложенных блоках (доступ осущствляется только к одной из них);
- перегруженные функции могут иметь параметры по умолчанию, при этом значения одного и того же параметра в разных функциях должны совпадать. В различных вариантах перегруженных функций может быть различное число параметров по умолчанию;
- функции не могут быть перегружены, если описание их параметров соовпадает или отличается только модификатором const или использованием ссылки (например, int и const int, int и int&).
Шаблоны функций.
Многие алгоритмы не зависят от типов данных,. с которыми они работают (классический пример – сортировка). Естественно желание параметризовать алгоритм таким образом, чтобы его можно было использовать для различных типов данных.
В С++ есть мощное средство параметризации – шаблоны. Существуют шаблоны функций и шаблоны классов. С помощью шаблона функции можно определить алгоритм, который будет применяться к данным различных типов, а конкретный тип данных передается функции в виде параметра на этапе компиляции. Компилятор автоматически геренирует правильный код, соответствующий переданному типу. Таким образом, создается функция, которая автоматически перегружает сама себя и при этом не содержит накладных расходов, связанных с параметризацией.
Формат простейшей функции – шаблона:
template <class Тype>