Попробуйте часть кода до слова main заменить на #include "stdafx.h" //1 #include <iostream> //2 using namespace std; //3 |
Функции и их аргументы
7.9. Рекурсивные функции
Рекурсивные определения и рекуррентные вычислительные схемы довольно часто используются в математике. Например:
n! = n*(n-1)! //рекурсивное определение факториала yn+1=0.5*(yn+x/yn) //итерационная формула метода Ньютона
По аналогии с такими подходами в программировании появились рекурсивные функции, которые вызывают сами себя. Кроме такой непосредственной ( прямой ) рекурсии возможна и косвенная рекурсия. Она имеет место, когда в цепочке функций, последовательно вызывающих друг друга, последняя функция обращается к первой. Чтобы избежать бесконечного зацикливания, в таких рекурсивных цепочках должно быть обязательно предусмотрено условие выхода.
Наиболее часто цитируемым примером рекурсивной программы является вычисление факториала:
long fact(int n) { if (n<2) return 1; return (n*fact(n-1)); }
Еще один пример, демонстрирующий вычисление n-го элемента в последовательности чисел Фибоначчи: Fn=Fn-2+Fn-1
int Fib(int n) { if(n<3) return 1; return Fib(n-2)+Fib(n-1); }
Пример с числами Фибоначчи представляет собой крайне неэффективную по производительности программу. Дело в том, что каждый вызов функции связан с выделением участка стека, где должны храниться локальные переменные и промежуточные результаты вычислений. А функция Fib содержит в своем теле два вызова самой себя. В большинстве случаев вместо рекурсивной функции не так уж и сложно написать функцию, не содержащую рекурсии, которая и работает быстрее, и более экономно использует оперативную память. Два выше приведенных примера легко поддаются такой переделке:
long fact(int n) { long f=1; for(int j=2; j<=n; j++) f=f*j; return f; } //------------------------------- int Fib(int n) { int j,f,f1=1,f2=1; if(n<3) return 1; for(j=3; j<=n; j++) { f=f1+f2; f1=f2; f2=f; } return f; }
Однако некоторые рекурсивные программы смотрятся очень неплохо. Например, функция нахождения наибольшего общего делителя двух чисел по алгоритму Евклида. Идея ее построения базируется на трех очевидных фактах:
- НОД(n1,n2)=НОД(n2,n1)
- НОД(0,n2)=n2
- НОД(n1,n2)=НОД(n2,n3), где n3=n1(mod n2)
//Рекурсивный вариант нахождения НОД int nod(int n1,int n2) { if(n1==0) return n2; return nod(n2%n1,n1); }
Эту функцию тоже несложно преобразовать в программу без рекурсии:
int nod(int n1,int n2) { int t; m: if(n2<n1) {t=n1; n1=n2; n2=t; } if(n1==0) return n2; n2=n1%n2; goto m; }
7.10. Указатели на функцию и передача их в качестве параметров
Многие обрабатывающие процедуры нуждаются в получении информации о функциях, к которым они должны обратиться за недостающими данными.
Одним из таких примеров является функция вычисления определенного интеграла – кроме параметров-значений, задающих пределы интегрирования, ей необходимо сообщить имя подпрограммы, вычисляющей значения подынтегральной функции. В такой ситуации обычно используют указатель на функцию.
Объявление указателя pf на функцию f(x), аргумент которой и возвращаемое значение имеют тип double, выглядит следующим образом:
double (*pf)(double x);
Оно напоминает прототип функции, в котором имя функции заменено именем указателя, заключенным в круглые скобки.
Простейшая функция, вычисляющая определенный интеграл методом прямоугольников, может быть организована следующим образом:
double int_rect(double a, double b, double (*f)(double x)) { int i, n=100; double s=0,h=(b-a)/n; for(i=0; i<=n; i++) s += f(a+i*h); return s*h; }
Последним аргументом процедуры int_rect является указатель на функцию. Поэтому в качестве фактического параметра мы должны подставить имя подынтегральной функции. Таковым, в частности, может быть имя любой стандартной функции из библиотеки math.h:
cout << int_rect(0,M_PI,sin) << endl; //результат= 1.99984 cout << int_rect(0,M_PI,cos) << endl; //результат=-4.18544e-17
В качестве второго примера рассмотрим программу нахождения корня уравнения y=f(x), если известно, что на интервале [ x1, x2 ] эта функция меняет знак. Алгоритм базируется на делении отрезка пополам. В точке xmid=(x1+x2)/2 смотрим знак функции f, который совпадет либо со знаком f(x1), либо со знаком f(x2). Выбираем ту половину отрезка, на концах которой функция принимает разные знаки. Затем исследуем его середину и т.д. Как только длина очередного отрезка станет достаточно малой или значение функции в центре отрезка окажется меньше заданной точности, процесс поиска корня прекращается.
#include <iostream.h> #include <conio.h> #include <math.h> double y(double x) //функция f(x)=x2-4 { return x*x-4; } double root(double x1,double x2,double eps,double(*f)(double x)) { double f12,f1,f2,xmid; f1=f(x1); f2=f(x2); if(f1*f2>0) { cerr<<"Error: sign(f1)=sign(f2)"; getch(); exit(0); } while(x2-x1 > eps) { xmid=(x1+x2)/2.; f12=f(xmid); if(fabs(f12) < eps) return xmid; if(f12*f1>0) { x1=xmid; f1=f12; } else {x2=xmid; f2=f12; } } return (x1+x2)/2.; }void main() { cout<<root(0,10,1e-4,y); getch(); } //=== Результат работы === 2.00001
7.11. "Левые" функции
В документации по системам программирования и в сообщениях об ошибках иногда можно встретить термины lvalue и rvalue. Они обозначают, соответственно, величины, которые могут находиться слева ( lvalue = left value ) от знака равенства в операторе присваивания или справа ( rvalue = right value ).
Как правило, функции, возвращающие значение, используются в правой части оператора присваивания. Однако функции в качестве своего значения могут возвращать указатели и ссылки. А по указателям и ссылкам возможна запись. Именно такие функции называют "левыми".
Приведем в качестве примера функцию, возвращающую ссылку на максимум из двух своих аргументов:
double& max(double &x, double &y) { return (x>y)? x : y; }
Ее обычное использование:
double r=max(a,b);
Использование с учетом "левизны":
double a=5,b=6; max(a,b)=10; //эквивалентно b=10;
Аналогичный вариант, когда функция max возвращает указатель:
double* max(double *x, double *y) { return (*x>*y)?*x:*y; } ......................... double a=5,b=6; *max(&a,&b)=10; //эквивалентно b=10;
Левая функция, возвращающая ссылку на максимальный элемент массива:
int& Mmax(int a[],int n) { int im=0; //индекс максимального элемента for(int j=1;j<n;j++) im=(a[im]>a[j])? im : j; return a[im]; }
Левая функция, возвращающая указатель на максимальный элемент массива:
int* Mmax(int a[],int n) { int im=0; //индекс максимального элемента for(int j=1;j<n;j++) im=(a[im]>a[j])? im : j; return &a[im]; }
Для запрета левого использования функции, возвращающей указатель или ссылку, достаточно разместить в начале ее заголовка спецификатор const:
const double& max(double &x, double &y) { return (x>y)? x : y); }