Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы, часть 2.doc
Скачиваний:
0
Добавлен:
12.01.2020
Размер:
80.41 Mб
Скачать

Формальные и фактические параметры

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

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

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

Пример:

int n = -25; // глобальная переменная

int modul (int n) { // n - формальный параметр

if(n<0) n = -1 * n; // n будет перекрывать глобальную переменную с именем n

return n;

}

int main(void) {

cout << modul(n); // 25, значение глобальной переменной n будет передано в функцию

cout << n; // -25, но работа внутри функции пойдёт с локальной переменной n

return 0;

}

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

Очередность вызова и рекурсия

Одна функция может вызываться внутри другой.

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

Возможна и сложная (вложенная) рекурсия когда, например, функция А вызывает функцию В, а функция В — функцию А.

Пример простой рекурсии (функция вызывает саму себя):

int fib(int n) {

if(n == 1 || n == 2) {

return 1;

} else {

return fib(n-1) + fib(n-2);

}

}

int main(void) {

cout << fib(10); // 55

return 0;

}

Основной недостаток рекурсии — повторные вычисления одних и тех же значений.

Пример: чтоб вычислить пятое число Фибоначчи по рекурсивному алгоритму, надо вычислить четвёртое и третье числа Фибоначчи. Чтобы вычислить четвёртое — надо вычислить второе и третье, хотя третье мы уже считали для вычисления пятого.

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

Способы передачи параметров в функцию

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

Подобный способ передачи параметров по значению имеет следующие ограничения:

  1. Из тела функции нельзя обратиться к какому-либо объекту, если он не является глобальным по отношению к функции или если его имя перекрыто одноимённой локальной переменной.

  2. При передаче больших объектов происходит их копирование и часто память расходуется напрасно.

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

В этих случая в функцию передаётся адрес объекта и, соответственно, работа внутри функции происходит не с копией, а с оригиналом объекта.

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

Пример:

void func1(int val, int& ref) {

val++;

ref++;

}

...

int a = 10, b = 10;

func1(a,b);

cout << a << endl; // 10, значение будет увеличено, но внутри функции, как локальное

cout << b << endl; // 11, будет увеличено значение внешней переменной b

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

void pow2(int &a) {

a = a*a; // тут не создаётся никаких объектов, кроме хранилища адреса

}

...

int a = 10;

pow2(a);

cout << a << endl; // 100

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

void decr(const int& n) {

cout << n; // читать можно

n--; // изменять нельзя, тут будет ошибка компиляции

}

В функцию можно передавать и указатели.

void prarr(int* a, const int& n) { // массив не копируется

for(int i = 0; i<n; i++) {

cout << a[i] << ' ';

}

*a = 100; // изменит первый элемент в глобальном массиве

a++; // не вызовет ошибки, ведь указатель не константный

}

...

int a[] = {0, 5, 10, 15, 20};

prarr(a, 5); // 0 5 10 15 20

cout << *a << endl; // 100

a++; // вызовет ошибку

В теле функции указатель a — это локальный объект, но он хранит адрес первого элемента глобального массива после входа в функцию.

Массив можно было передать в функцию и по-другом. Пример прототипа:

void prarr(int a[], const int& n);

При этом указатель a внутри функции не изменит своих свойств (будет локальной переменной, хранящей адрес глобального объекта).

Корректнее параметр, по которому будет передаваться указатель на первый элемент массива, объявлять как константный:

void prarr(int* const a, const int& n);

Тогда внутри функции мы не могли бы сместить указатель на соседний элемент.

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