Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Л5.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
154.11 Кб
Скачать

8.6. Рекурсия

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

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

Классический пример рекурсии - это математическое определение факториала n! :

n! = 1 при n=0;

n*(n-1)! при n>1 .

Функция, вычисляющая факториал, будет иметь следующий вид:

long fakt(int n)

{

return ( (n==1) ? 1 : n*fakt(n-1) );

}

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

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

Как правило, давать разным функциям разные имена - мысль хорошая, но когда некоторые функции выполняют одинаковую работу над объектами разных типов, может быть более удобно дать им одно и то же имя. Использование одного имени для различных действий над различными типами называется перегрузкой (overloading). Допускается перегружать как операторы, так и функции. Так, например, у сложения существует только одно имя, +, но его можно применять для сложения значений целых, плавающих и указательных типов. Эта идея легко расширяется на обработку операций, определенных пользователем, то есть, функций. Это значит, что имя может использоваться более чем для одной функции.

Например:

void vvod(int); void vvod(char*);

Что касается компилятора, единственное общее, что имеют функции с одинаковым именем, это имя. Предположительно, они в каком-то смысле похожи, но в этом язык ни стесняет программиста, ни помогает ему. Таким образом, перегруженные имена функций - это главным образом удобство записи. Это удобство значительно в случае функций с общепринятыми именами вроде sqrt, print и open. Когда имя семантически значимо, как это имеет место для операций вроде +, * и << и в случае конструкторов, это удобство становится существенным. Когда вызывается перегруженная функция f(), компилятор должен понять, к какой из функций с именем f следует обратиться. Это делается путем сравнения типов фактических параметров с типами формальных параметров всех функций с именем f. Поиск функции, которую надо вызвать, осуществляется за три отдельных шага:

1. Искать функцию соответствующую точно, и использовать ее, если она найдена.

2. Искать соответствующую функцию, используя встроенные преобразования, и использовать любую найденную функцию.

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

Например:

print(double), print(int);

void f(); {

print(1); print(1.0);

}

Правило точного соответствия гарантирует, что f напечатает 1 как целое и 1.0 как число с плавающей точкой. Ноль, char или short точно соответствуют параметру. Аналогично, float точно соответствует double.

К параметрам функций с перегруженными именами стандартные С++ правила неявного преобразования типа применяются не полностью. Преобразования, которые могут уничтожить информацию, не выполняются. Остаются int в long, int в double, ноль в long, ноль в double и преобразования указателей: преобразование ноль в указатель void*, и указатель на производный класс в указатель на базовый класс.

Вот пример, в котором преобразование необходимо:

print(double), print(long);

void f(int a); {

print(a);

}

Здесь a может быть напечатано или как double, или как long. Неоднозначность разрешается явным преобразованием типа (или print(long(a)) или print(double(a))).

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

Пример:

//недопустимая перегрузка

int sum(int a){return(a+a);}

int sum(int &a){return(a+a);}//аргументы имеют одинаковый тип

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

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

Пример:

//допустимая перегрузка

int sum(int a){return(a+a);}

int sum(char a){return(int)a;}

Пример:

//допустимая перегрузка

int sum(int a);

float sum(int a);

Допускается перегружать стандартные функции.

Пример:

#include<stdio.h>

void printf(int a){printf("%d",a);};

void printf(char a){printf("%c",a);};

void main()

{

printf(1);

printf('f');

}

Пример:

#include<stdio.h>

#include<math.h>

void sqrt(int a){printf("%d\n",a);};

void sqrt(char a){printf("%c\n",a);};

void sqrt(float a){printf("%f\n",a);};

//недопустимо описать void sqrt(double a){printf("%f\n",a);}; , т.к.

//void sqrt(double );уже существует в библиотеке math.h(является стандартной)

void main()

{

sqrt(1);

sqrt(11.0f);//вызов функции пользователя

sqrt(11.0);//вызов стандартной функции вычисления корня квадратного

sqrt('f');

}

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