
- •Часть 2. Дискретная математика. Структуры данных. Алгоритмы. Информатика.
- •4.1. Ограниченно-детерминированные функции
- •Ограниченно-детерминированные функции
- •Открытое хеширование
- •Закрытое хеширование
- •Метод остатков от деления
- •Метод функции середины квадрата
- •Метод свертки
- •Семафорные примитивы Дейкстры
- •4.6 Семафорные примитивы Дейкстры.
- •Концепция
- •Задача “Поставщик - Потребитель”.
- •Мониторы Хоара
- •[Править]Тип 0 — неограниченные
- •[Править]Тип 1 — контекстно-зависимые
- •[Править]Тип 2 — контекстно-свободные
- •[Править]Тип 3 — регулярные
- •Синтаксический анализ
- •Условия использования метода рекурсивного спуска
- •Устранение левой рекурсии
- •[Править]Устранение непосредственной левой рекурсии
- •[Править]Пример
- •[Править]Алгоритм устранения произвольной левой рекурсии
- •[Править]Асимптотика
- •[Править]Пример
- •[Править]Варианты реализации [править]Предсказывающий парсер
- •[Править]Парсер с возвратом
- •Виртуальные и динамические методы
- •Динамические методы
- •Символьные массивы (строки)
- •Создание функции
- •Формальные и фактические параметры
- •Очередность вызова и рекурсия
- •Способы передачи параметров в функцию
- •Перегрузка функций
- •Абстракция данных
- •Ключевые черты ооп
- •Наследование
- •Private-наследование
- •Protected-наследование
- •Public-наследование
- •Статические члены класса
- •Указатель this
- •Перегрузка операторов
- •Примеры некоторых классов Класс комплексных чисел
- •Аналитическая модель поверхности
- •Векторная полигональная модель
- •Воксельная модель
- •Равномерная сетка
Формальные и фактические параметры
Формальные параметры существуют в прототипе и теле определения функции. Они задаются некоторыми уникальными именами и внутри функции доступны как локальные переменные.
Фактические параметры существуют в основной программе. Они указываются при вызове функции на месте формальных.
В момент вызова функции значения фактических параметров присваиваются формальным. Соответственно, имена формальных и фактических параметров могут совпадать, это не вызовет конфликта.
Пример:
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;
}
Основной недостаток рекурсии — повторные вычисления одних и тех же значений.
Пример: чтоб вычислить пятое число Фибоначчи по рекурсивному алгоритму, надо вычислить четвёртое и третье числа Фибоначчи. Чтобы вычислить четвёртое — надо вычислить второе и третье, хотя третье мы уже считали для вычисления пятого.
Кроме того, в общем случае, в один момент времени может выполняться только одна функция, пока она не вернёт результат — не начнётся выполнение следующей функции. Управление от основной программы передаётся в функцию, пока функция не завершит свою работу, управление не вернётся к основной программе. В частности: ошибка в функции остановит всю программу.
Способы передачи параметров в функцию
Ранее мы рассматривали только такую ситуацию, когда при вызове функции значение фактического параметра копируется в локальную переменную, доступную как формальный параметр внутри функции.
Подобный способ передачи параметров по значению имеет следующие ограничения:
Из тела функции нельзя обратиться к какому-либо объекту, если он не является глобальным по отношению к функции или если его имя перекрыто одноимённой локальной переменной.
При передаче больших объектов происходит их копирование и часто память расходуется напрасно.
Для снятия таких ограничений существует возможность передачи параметра по ссылке.
В этих случая в функцию передаётся адрес объекта и, соответственно, работа внутри функции происходит не с копией, а с оригиналом объекта.
Чтобы параметр передавался по ссылке, достаточно в прототипе функции поставить знак & после типа параметра.
Пример:
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);
Тогда внутри функции мы не могли бы сместить указатель на соседний элемент.