Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Програм-е на ЯВУ / Программирование на языках выского уровня, алгоритмические языки.doc
Скачиваний:
63
Добавлен:
11.04.2014
Размер:
1.74 Mб
Скачать

Функции

Функции используются для наведения

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

Б. Страуструп

Функции как средство борьбы со сложностью программ

Естественным способом борьбы со сложностью любой задачи является ее разбиение на части. В языке Си такими «частями» программы являются функции. Любая программа на языке Си состоит из функций, одна из которых должна иметь имя main (с нее начинается выполнение программы).

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

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

float min(float x, float y, float z)

{

float Min;

if ((x <= y) && (x <= z) Min= x;

else if((y <= x) && (y <= z) Min= y;

else Min= z;

return Min;

}

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

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

// Определить пересечение двух массивов A(N) и B(M) по их

// содержимому. Результат поместить в массив C(K)

void main(void)

{

int A[100], N; // исходный массив и его фактический размер

int B[100], M; // исходный массив и его фактический размер

int C[100], K; // массив пересечения и его фактический размер

int i; // индекс эл-та массива A

int l; // индекс эл-та массива B или C

bool IsBelongTo; // признак принадлежности значения массиву

// Вводим эл-ты массивов A и B

... ... ... ... ... ... ...

// Эхо-печать массивов A и B

... ... ... ... ... ... ...

// Находим пересечение С массивов A и B

K= 0; // общих элементов пока не обнаружено

for(i= 0; i < N; i++)

{

// Ищем эл-т A[i] в массиве B

l= 0; // начинаем просматривать массив B c начала

IsBelongTo= false; // совпадений пока нет

while(!IsBelongTo && (l < M)) // ищем совпадение пока его не

// нашли и не закончился массив B

{

IsBelongTo= (Value == B[l]);

l++; // переходим к след. эл-ту массива B

}

// Ищем эл-т A[i] в массиве C, если нашли его в массиве B

if(IsBelongTo)

{

l= 0; // начинаем просматривать массив C c начала

IsBelongTo= false; // совпадений пока нет

while(!IsBelongTo && (l < K)) // ищем совпадение пока его не

// нашли и не закончился массив C

{

IsBelongTo= (Value == С[l]);

l++; // переходим к след. эл-ту массива С

}

}

// Записываем эл-т A[i] в массив C, если он имеется в массиве B

// и еще ни разу не встречался, т.е. его нет в массиве C

if(IsBelongTo)

{

K++; // увеличиваем кол-во общих эл-тов

C[K-1]= A[i]; // добавляем в массив C общий элемент

}

}

// Выводим массив C

... ... ... ... ... ... ...

}

// Функция определяет, принадлежит ли значение Value массиву Mass,

// размерность которого MassSize

bool ValueBelongToMass(int Value, int Mass[100], int MassSize);

// Определить пересечение двух массивов A(N) и B(M) по их

// содержимому. Результат поместить в массив C(K)

void main(void)

{

int A[100], N; // исходный массив и его фактический размер

int B[100], M; // исходный массив и его фактический размер

int C[100], K; // массив пересечения и его фактический размер

int i; // индекс эл-та массива A

// Вводим эл-ты массивов A и B

... ... ... ... ... ... ...

// Эхо-печать массивов A и B

... ... ... ... ... ... ...

// Находим пересечение С массивов A и B

K= 0; // массив C начинаем заполнять с начала

for(i= 0; i < N; i++)

{

// Записываем эл-т A[i] в массив C, если он имеется в массиве B

// и еще ни разу не встречался (его нет в массиве C)

if(ValueBelongToMass(A[i], B, M) && !ValueBelongToMass(A[i], C, K))

{

K++; // увеличиваем кол-во общих эл-тов

C[K-1]= A[i]; // добавляем в массив C общий элемент

}

}

// Выводим массив C

... ... ... ... ... ... ...

}

// Функция определяет, принадлежит ли значение Value массиву Mass,

// размерность которого MassSize

bool ValueBelongToMass(int Value, int Mass[100], int MassSize)

{

bool IsBelongTo; // признак принадлежности значения массиву

int l; // индекс эл-та массива Mass

// Ищем совпадение Value с одним из эл-тов массива

l= 0; // начинаем просматривать массив c начала

IsBelongTo= false; // совпадений пока нет

while(!IsBelongTo && (l < MassSize)) // ищем совпадение пока его не

// нашли и не закончился массив

{

IsBelongTo= (Value == Mass[l]);

l++; // переходим к след. эл-ту массива

}

return IsBelongTo; // возвращаем полученный признак

}

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

II способ борьбы со сложностью программ. Функция скрывает детали своей реализации. Для использования любой функции (например, функции printf()) не нужно знать, как она реализована, достаточно знать ее интерфейс, т.е. ее имя, передаваемые параметры, возвращаемое значение и назначение. Инкапсуляция (скрытие деталей реализации) позволяет программисту сосредоточиться на решении основной задачи и не отвлекаться на частные задачи. После решения основной задачи программист может перейти к решению других задач. Например, в предыдущем примере сначала решалась задача пересечения массивов (функция main()) в предположении, что существует функция ValueBelongToMass(). После решения основной задачи (реализации функции main()) решалась задача нахождения некоторого значения в заданном массиве, т.е. осуществлялась реализация функции ValueBelongToMass().

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

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

Окончание занятия №18 (практика)

Интерфейс и реализация функций, использование функций

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

<тип возвращаемого значения> <имя функции>(<список параметров>)

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

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

В рассмотренном выше примере функция ValueBelongToMass() имеет следующий интерфейс

bool ValueBelongToMass(int Value, int Mass[100], int MassSize)

Соответственно возвращаемое значение имеет булевой тип, а список параметров составляют целочисленное значение Value, целочисленный массив Mass из 100 элементов, в котором осуществляется поиск значения Value и фактический размер массива MassSize (также целое число).

Реализация функции задается ее определением следующего вида

<тип возвращаемого значения> <имя функции>(<список параметров>)

{

<тело функции>

}

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

// Функция определяет, принадлежит ли значение Value массиву Mass,

// размерность которого MassSize

bool ValueBelongToMass(int Value, int Mass[100], int MassSize)

{

bool IsBelongTo; // признак принадлежности значения массиву

int l; // индекс эл-та массива Mass

// Ищем совпадение Value с одним из эл-тов массива

l= 0; // начинаем просматривать массив c начала

IsBelongTo= false; // совпадений пока нет

while(!IsBelongTo && (l < MassSize)) // ищем совпадение пока его не

// нашли и не закончился массив

{

IsBelongTo= (Value == Mass[l]);

l++; // переходим к след. эл-ту массива

}

return IsBelongTo; // возвращаем полученный признак

}

Использование функции происходит путем ее вызова. Для вызова функции необходимо указать ее имя, за которым в круглых скобках через запятую перечисляются передаваемые аргументы (переменные, константы или выражения). Вызов функции может находиться в любом месте программы, где по синтаксису допустимо выражение того типа, который возвращает функция. В нашем примере функция ValueBelongToMass() возвращает значение типа bool, а потому может располагаться в условном операторе if else:

... ... ... ... ...

if(ValueBelongToMass(A[i], B, M) && !ValueBelongToMass(A[i], C, K))

{

K++; // увеличиваем кол-во общих эл-тов

C[K-1]= A[i]; // добавляем в массив C общий элемент

}

... ... ... ... ...

Рассмотрим еще варианты вызова функций на примере рассмотренной ранее функции min():

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

float min(float x, float y, float z)

{

float Min;

if ((x <= y) && (x <= z) Min= x;

else if((y <= x) && (y <= z) Min= y;

else Min= z;

return Min;

}

// Использование функции min()

void main(void)

{

float x, y, z; // значения, задаваемые пользователем

float Min; // минимальное значение

// Вводим три значения через запятую

scanf(“%f, %f, %f”, &x, &y, &z); // предположим ввели 10, 3, 5

// Первый вариант использования функции min()

Min= min(x, y, z); // 3 = min(10, 3, 5)

printf(“\n%f”, Min);

// Второй вариант использования функции min()

Min= min(14, y+3, exp(z-4)); // 2.72 = min(14, 6, 2.72)

printf(“\n%f”, Min);

// Третий вариант использования функции min()

printf(“\n%f”, min(11, 12, min(23, 2, 11))); // 2 = min(11, 12, 2)

}

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

// Объявление и определение функции f1

void f1(void)

{

f2(); // вызов функции f2()

}

// Объявление и определение функции f2

void f2(void)

{

f1(); // вызов функции f1()

}

Разрешить данную проблему можно, используя предварительное объявление функции:

// Предварительное объявление функции f1 – прототип функции f1

void f1(void);

// Предварительное объявление функции f2 – прототип функции f2

void f2(void);

// Объявление и определение функции f1

void f1(void)

{

f2(); // вызов функции f2()

}

// Объявление и определение функции f2

void f2(void)

{

f1(); // вызов функции f1()

}

Предварительное объявление функции называется прототипом функции. Рекомендуется использовать прототипы функций всегда, а не только в ситуации, приведенной выше, т.к. это дает следующие преимущества:

– не нужно следить за порядком вызова функций;

– формируется перечень реализуемых вами функций.

Рассмотрим пример использования прототипа для функции min():

// Прототип функции, возвращающей минимальное значение трех чисел

float min(float x, float y, float z);

// Использование функции min()

void main(void)

{

printf(“\n%f”, min(11, 12, min(23, 2, 11))); // 2 = min(11, 12, 2)

}

// Описание функции, возвращающей минимальное значение трех чисел

float min(float x, float y, float z);

{

float Min;

if ((x <= y) && (x <= z) Min= x;

else if((y <= x) && (y <= z) Min= y;

else Min= z;

return Min;

}

Окончание занятия №19 (лекция)

Локальные и глобальные переменные

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

Рассмотрим пример использования глобальных и локальных переменных:

int GlobalValue; // глобальная переменная

// Функция, увеличивающая значения переменных на 1

void Increment();

void main(void)

{

int LocalValueMain= 0; // локальная переменная функции main()

// Обращение к глобальной переменной

GlobalValue= 6;

GlobalValue++;

printf(“%d”, GlobalValue); // результат 7

// Увеличиваем значения переменных

Increment();

// Выводим результаты

printf(“%d”, GlobalValue); // результат 8

printf(“%d”, LocalValueMain); // результат 0

printf(“%d”, LocalValueInc); // синтаксическая ошибка, переменная

// не определена

}

// Функция, увеличивающая значения переменных на 1

void Increment()

{

int LocalValueInc= 0; // локальная переменная функции Increment()

GlobalValue++; // ошибки нет, значение переменной увеличилось

LocalValueMain++; // синтаксическая ошибка, переменная не определена

LocalValueInc++; // ошибки нет, значение переменной увеличилось

}

Локальные переменные хранятся в так называемом стеке памяти. Стек имеет «вершину» и над ним возможно выполнение двух операций: поместить значение в вершину стека и извлечь значение из вершины стека.

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

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

Рассмотрим это на нашем примере:

Изначально стек программы пустой.

...

...

При запуске программы выделяется память (не в стеке) для глобальной переменной GlobalValue (строка 1). Далее запускается функция main() (строки 6-23), соответственно в стек помещается локальная переменная LocalValueMain.

...

LocalValueMain

main()

Вызов функции Increment() (строка 16) приводит к ее выполнению (строки 26-33) и помещению в стек локальной переменной LocalValueInc.

LocalValueInc

Increment()

LocalValueMain

main()

По завершению функции Increment() (строка 33) стек очищается от локальных переменных этой функции.

...

LocalValueMain

main()

Когда завершается функция main() (строка 23) стек очищаетсяполностью.

...

...

В последнюю очередь освобождается память, занимаемая глобальной переменной GlobalValue.

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

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

Локальные переменные

Локальные переменные, принадлежащие функции main()

Область объявления

вне функций

в функции

в функции main()

Область видимости

вся программа

функция, в которой переменная объявлена

функция main()

Время жизни

время жизни программы

время выполнения функции

время жизни программы

Первоначальное значение

нулевое

произвольное

произвольное

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

Возвращаемое значение

Формально функция может возвращать только одно значение. Для возвращения значения из функции и одновременного завершения функции используется оператор return. Напомню, что возвращаемое значение может иметь любой тип данных, кроме массива (тип возвращаемого значения указывается в объявлении функции).

Рассмотрим еще раз функцию min():

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

float min(float x, float y, float z) // функция возвращает вещественное число

{

float Min;

if ((x <= y) && (x <= z) Min= x;

else if((y <= x) && (y <= z) Min= y;

else Min= z;

return Min; // возвращение значения и выход из функции

}

В дополнение рассмотрим две реализации одной и той же функции, возвращающей значение π:

// Функция, возвращающая значение Pi

double Pi()

{

return 3.14159265358979;

}

// Функция, возвращающая значение Pi

double Pi()

{

return 2*asin(1.0);

}

Окончание занятия №20 (лекция)

Параметры функции

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

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

float min(float x, float y, float z);

// Использование функции min()

void main(void)

{

float x, y, z; // значения, задаваемые пользователем

float Min; // минимальное значение

// Вводим три значения через запятую

scanf(“%f, %f, %f”, &x, &y, &z); // предположим ввели 10, 3, 5

// Первый вариант использования функции min()

Min= min(x, y, z); // 3 = min(10, 3, 5)

printf(“\n%f”, Min);

// Второй вариант использования функции min()

Min= min(14, y+3, exp(z-4)); // 2.72 = min(14, 6, 2.72)

printf(“\n%f”, Min);

// Третий вариант использования функции min()

printf(“\n%f”, min(11, 12, min(23, 2, 11))); // 2 = min(11, 12, 2)

}

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

float min(float x, float y, float z)

{

float Min;

if ((x <= y) && (x <= z) Min= x;

else if((y <= x) && (y <= z) Min= y;

else Min= z;

return Min;

}

Ниже приведена последовательность состояний стека программы

Строки 5-8

Строка 11

Строка 14 (26-28)

Min

×

main()

z

×

y

×

x

×

Min

×

main()

z

5

y

3

x

10

Min

×

min()

z

5

y

3

x

10

Min

×

main()

z

5

y

3

x

10

Строка 14(30-32)

Строка 14(34)

Строка 18 (26-28)

Min

3

min()

z

5

y

3

x

10

Min

×

main()

z

5

y

3

x

10

Min

3

main()

z

5

y

3

x

10

Min

×

min()

z

2.72

y

6

x

14

Min

3

main()

z

5

y

3

x

10

Строка 18 (30-32)

Строка 18(34)

Строка 22(26-28)

Min

2.72

min()

z

2.72

y

6

x

14

Min

3

main()

z

5

y

3

x

10

Min

2.72

main()

z

5

y

3

x

10

Min

×

min()

z

11

y

2

x

23

Min

2.72

main()

z

5

y

3

x

10

Строка 22 (30-32)

Строка 22(34),

Строка 22 (26-28)

Строка 22(30-32)

Min

2

min()

z

11

y

2

x

23

Min

2.72

main()

z

5

y

3

x

10

Min

×

min()

z

2

y

12

x

11

Min

2.72

main()

z

5

y

3

x

10

Min

2

min()

z

2

y

12

x

11

Min

2.72

main()

z

5

y

3

x

10

Строка 22(34)

Строка 23

Min

2

main()

z

5

y

3

x

10

...

...

...

...

...

...

...

...

Выделяют формальные и фактические параметры функции. Параметры, перечисленные в описании функции, называются формальными, а записанные в операторе вызова функции – фактическими. В данном примере формальными параметрами функции min() являются ее локальные переменные x, y и z (строка 26), а фактическими – переменные, константы и выражения, принадлежащие функции main():

– x, y, z (строка 7);

– 14, y+3, exp(z-4) (строка 18);

– 11, 12, min(23, 2, 11) (строка 22);

– 23, 2, 11 (строка 22).

В рассмотренном выше примере параметры передаются по значению, т.е. создаются формальные параметры, которые являются копиями фактических параметров, следовательно, у функции нет возможности изменить фактические параметры. Рассмотрим классический пример функции swap(), которая меняет значения двух переменных.

#include <stdio.h>

// Обмен значениями

void swap(int A, int B);

void main(void)

{

int x= 3, y= 5;

// Пробуем поменять значения переменных x и y

swap(x, y); // значения переменных не изменились

}

// Обмен значениями

void swap(int A, int B)

{

int Buffer;

Buffer= A;

A= B;

B= Buffer;

}

Функция swap() не может поменять значения двух переменных, используя передачу параметров по значению. Для того чтобы понять причину этого проанализируем, как изменялись значения переменных.

Строка 8

Строка 11 (15-17)

y

5

main()

x

3

Buffer

×

swap()

B

5

A

3

y

5

main()

x

3

Строка 11 (19-21)

Строка 11(22)

Buffer

3

swap()

B

3

A

5

y

5

main()

x

3

y

5

main()

x

3

Окончание занятия №21 (практика)

Данную проблему можно было решить, если функция swap() возвращала бы два значения, однако в языке Си такое невозможно. Тем не менее, выход имеется – параметры необходимо передавать не по значению, а по адресу. При передаче по адресу функция работает с фактическими параметрами через их адреса, следовательно, она может изменять значения фактических параметров. Выглядит это следующим образом:

#include <stdio.h>

// Обмен значениями

void swap(int *A, int *B);

void main(void)

{

int x= 3, y= 5;

// Пробуем поменять значения переменных x и y

swap(&x, &y); // значения переменных изменились

}

// Обмен значениями

void swap(int *A, int *B)

{

int Buffer;

Buffer= *A;

*A= *B;

*B= Buffer;

}

Строка 8

Строка 11 (15-17)

0x2348

y

5

main()

0x2344

x

3

0x2354

Buffer

×

swap()

0x2350

B

0x2348

0x234С

A

0x2344

0x2348

y

5

main()

0x2344

x

3

Строка 11 (19-21)

Строка 11 (22)

0x2354

Buffer

3

swap()

0x2350

B

0x2348

0x234С

A

0x2344

0x2348

y

5

main()

0x2344

x

3

0x2348

y

5

main()

0x2344

x

3

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

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

Передача массивов в качестве параметров

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

#include <stdio.h>

// Увеличение значений элементов массива. Не универсальный способ

// передачи массива

void Increment_1(int Mass[5]);

// Увеличение значений элементов массива. Универсальный способ

// передачи массива

void Increment_2(int *Mass, int ElementCount);

void main(void)

{

int Mass[5]= {1, 2, 3, 4, 5};

Increment_1(Mass);

Increment_2(Mass, 5);

}

// Увеличение значений элементов массива. Не универсальный способ

// передачи массива

void Increment_1(int Mass[5])

{

int i;

for(i= 0; i <= 4, i++)

{ Mass[i]++; }

}

// Увеличение значений элементов массива. Универсальный способ

// передачи массива

void Increment_2(int *Mass, int ElementCount)

{

int i;

for(i= 0; i <= ElementCount-1, i++)

{ Mass[i]++; }

}

Иногда имеется необходимость в передаче массивов не по адресу, а по значению (например, в рекурсивных алгоритмах, см. далее, для того, чтобы изменять не исходный массив, а его копию). Это можно сделать, если массив объявить как часть структуры:

struct TMass

{

int Elements[100]; // элементы массива

int Count; // фактический размер массива

};

// Печать массива. Массив передается по значению - внутри функции

// работаем с копией массива

void PrintMass(struct TMass Mass)

{

int i;

for(i= 0; i <= Mass.Count-1, i++)

{ printf(“%d”, Mass.Elements[i]); }

}

void main(void)

{

struct TMass Array;

int i;

scanf(“%d”, &Array.Count);

for(i= 0; i < Array.Count; i++)

{ scanf(“%d”, &Array.Elements[i]); }

PrintMass(Array);

}

Рекурсия и варианты ее использования

Рекурсия – это вызов внутри функции самой себя.

Рассмотрим использование рекурсии на примере расчета факториала. Рекурсивная формула расчета факториала выглядит следующим образом:

N! = N * (N-1)! при условии, что 1! = 1

// Вычисление факториала рекурсивно

int factorial(int N)

{

int Result;

if(N > 1) { Result= N * factorial(N-1); }

else { Result= 1; }

return Result;

}

void main(void)

{

int F;

F= factorial(4); // 4! = 24

}

Строка 14

Строка 16 (2-4)

Строка 6 (2-4)

F

×

main()

Result

×

factorial()

1-й вызов

N

4

F

×

main()

Result

×

factorial()

2-й вызов

N

3

Result

×

factorial()

1-й вызов

N

4

F

×

main()

Строка 6(2-4)

Строка 6(2-4)

Строка 7

Result

×

factorial()

3-й вызов

N

2

Result

×

factorial()

2-й вызов

N

3

Result

×

factorial()

1-й вызов

N

4

F

×

main()

Result

×

factorial()

4-й вызов

N

1

Result

×

factorial()

3-й вызов

N

2

Result

×

factorial()

2-й вызов

N

3

Result

×

factorial()

1-й вызов

N

4

F

×

main()

Result

1

factorial()

4-й вызов

N

1

Result

×

factorial()

3-й вызов

N

2

Result

×

factorial()

2-й вызов

N

3

Result

×

factorial()

1-й вызов

N

4

F

×

main()

Строка 9(6)

Строка 9(6)

Строка 9(6)

Result

2

factorial()

3-й вызов

N

2

Result

×

factorial()

2-й вызов

N

3

Result

×

factorial()

1-й вызов

N

4

F

×

main()

Result

6

factorial()

2-й вызов

N

3

Result

×

factorial()

1-й вызов

N

4

F

×

main()

Result

24

factorial()

1-й вызов

N

4

F

×

main()

Строка 9(16)

F

24

main()

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

Замечание. Не следует использовать рекурсивный алгоритм при расчете факториала, т.к. он не эффективен по скорости выполнения и труден для понимания; предпочтителен алгоритм, использующий цикл.

// Вычисление факториала в цикле

int factorial(int N)

{

int Result;

int i;

Result= 1;

for(i= 1; i <= N; i++)

{ Result*= i; }

return Result;

}

Окончание занятия №22 (лекция)

Пример

Окончание занятия №23 (лекция)