📘 Тема 1.5. Необходимый материал#
Шпаргалка по функциям, декомпозиции и рекурсии.
1. Что такое функция#
Функция — именованный блок кода, который выполняет одну задачу и может быть вызван из других частей программы.
Зачем нужны функции:
- Повторное использование. Код пишется один раз, а вызывается сколько угодно раз.
- Модульность. Программа разбивается на логические блоки, её проще читать и отлаживать.
- Одно место для изменений. Если формула поменялась, правим её в одной функции, а не в десяти местах.
Декомпозиция — разбиение большой задачи на небольшие подзадачи, каждая из которых оформляется отдельной функцией.
Термины:
| Термин | Значение |
|---|---|
| параметр | переменная в заголовке функции, через которую она получает данные (int x) |
| аргумент | конкретное значение, которое передаётся при вызове (square(5) — аргумент 5) |
| возвращаемое значение | результат, который функция отдаёт через return |
| локальная переменная | переменная, объявленная внутри функции; снаружи её не видно |
| прототип | объявление функции без тела |
Заметка
main— тоже функция. Она возвращаетint, иreturn 0;в конце сообщает системе, что программа завершилась успешно.
2. Синтаксис функции#
тип
- тип — тип возвращаемого значения (
int,long long,double,bool,string). Если функция ничего не возвращает —void. - имя — пишется с маленькой буквы по правилам имён переменных.
- параметры — входные данные через запятую, у каждого свой тип.
return— возвращает значение и сразу завершает функцию.
int
int
Совет
Функция — это «чёрный ящик»: даём ей вход, получаем выход. Результат можно вывести, сохранить в переменную или подставить в другое выражение, в том числе в вызов другой функции.
3. Виды подпрограмм#
| Вид | Что делает | Пример |
|---|---|---|
процедура (void) |
выполняет действие, ничего не возвращает | void printHello() |
| функция | вычисляет и возвращает значение | int square(int x) |
| рекурсивная функция | вызывает сама себя | int factorial(int n) |
// процедура
void
// функция
int
// рекурсия
int
4. Прототип функции#
Функцию нужно объявить до того места, где она вызывается. Если хочется написать её после main, сверху ставят прототип — объявление без тела.
using namespace std;
int ; // прототип
int
int
Предупреждение
Без прототипа такой код не скомпилируется: компилятор читает файл сверху вниз и в
mainещё не знает, что такоеsquare.
5. Передача параметров по значению#
Функция получает копию аргумента. Изменения внутри функции на оригинал не влияют.
void
int
Before: n = 4
In square: m = 16
After: n = 4
Здесь m — копия n. Изменение m не меняет n. Чтобы функция изменила оригинал, нужна передача по ссылке или указателю — к этому мы ещё вернёмся.
Значение параметра по умолчанию#
Параметру можно задать значение по умолчанию — тогда при вызове его можно не указывать.
int
; // reversed = 0
; // reversed = 5
6. Возврат указателя#
Если функция возвращает указатель, он должен указывать на память, которая жива после выхода из функции, либо быть равным nullptr.
// ❌ ошибка: a и b — локальные копии, после выхода из функции их нет
int*
// ✅ указатели ссылаются на переменные из main
int*
int
Внимание
Указатель на локальную переменную называют висячим: он показывает на память, которая уже освобождена. Программа может выдать мусор или упасть.
7. Декомпозиция: пример с рядом Тейлора#
Задача: вычислить sin(x) через ряд
sin(x) = x - x³/3! + x⁵/5! - x⁷/7! + ...
Суммируем члены, пока очередной член не станет меньше точности 10⁻⁶.
Каждый следующий член получается из предыдущего: term = -term * x * x / ((2n) * (2n + 1)). Поэтому факториал в явном виде считать не нужно.
using namespace std;
double
int
Совет
Признаки хорошей декомпозиции:
mainкороткий и читается как план решения, каждая функция делает одну вещь, каждую функцию можно проверить отдельно.
8. Рекурсия#
Рекурсия — приём, при котором функция вызывает саму себя для задачи меньшего размера.
У рекурсивной функции всегда две части:
- База рекурсии — условие, при котором функция возвращает ответ сразу, без вызова себя.
- Рекурсивный шаг — вызов функции с аргументом, который приближает к базе.
int
Как это работает для n = 5:
factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120
Каждый вызов ждёт результата следующего и хранится в стеке вызовов. Когда достигнута база, результаты начинают «возвращаться» вверх.
Внимание
Без базы (или если база никогда не достигается) функция вызывает себя бесконечно. Стек вызовов заканчивается, и программа падает — это переполнение стека.
9. Последовательности и рекуррентные формулы#
Последовательность — упорядоченный набор чисел a₁, a₂, a₃, ..., где n — номер члена.
| Последовательность | Формула n-го члена | Пример |
|---|---|---|
| арифметическая прогрессия | aₙ = a₁ + (n − 1) · d |
a₁ = 2, d = 3 → 2, 5, 8, 11, ... |
| геометрическая прогрессия | aₙ = a₁ · r^(n − 1) |
a₁ = 2, r = 3 → 2, 6, 18, 54, ... |
Рекуррентная формула выражает член последовательности через предыдущие: aₙ = f(aₙ₋₁, aₙ₋₂, ...).
| Последовательность | Начальные условия | Рекуррентная формула |
|---|---|---|
| факториал | 0! = 1 |
n! = n · (n − 1)! |
| Фибоначчи | F(0) = 0, F(1) = 1 |
F(n) = F(n − 1) + F(n − 2) |
член ряда Тейлора для sin |
T₀ = x |
Tₙ = −Tₙ₋₁ · x² / ((2n)(2n + 1)) |
| НОД (алгоритм Евклида) | gcd(a, 0) = a |
gcd(a, b) = gcd(b, a % b) |
| Ханойские башни | h(1) = 1 |
h(n) = 2 · h(n − 1) + 1 |
Как перевести последовательность в рекуррентную формулу#
- Найдите закономерность: как следующий член связан с предыдущими.
- Выразите
aₙчерезaₙ₋₁,aₙ₋₂, ... - Запишите начальные условия — они станут базой рекурсии.
- Проверьте формулу на нескольких первых членах.
Пример: 1, 1, 2, 6, 24, 120, ... — каждый член в n раз больше предыдущего, значит, aₙ = n · aₙ₋₁, a₀ = 1. Проверка: a₃ = 3 · 2 = 6 ✅.
Совет
Рекуррентная формула переводится в код почти дословно: начальные условия →
if (...) return ...;, формула →return ...;с вызовом той же функции.
10. Шаблоны рекурсивных функций#
Числа Фибоначчи#
int
Предупреждение
Такая функция работает медленно:
fibonacci(5)вызываетfibonacci(3)дважды,fibonacci(2)— трижды, и с ростомnчисло вызовов растёт экспоненциально. Дляn ≤ 30это нормально, дляn = 50— уже слишком долго. На олимпиадах в таких случаях считают циклом.
Сумма цифр числа#
int
НОД (алгоритм Евклида)#
int
// НОД трёх чисел
cout << << endl;
Переворот числа#
int
Проверка на простоту (функция с bool)#
bool
Заметка
Условие
i * i <= nравносильноi <= sqrt(n), но работает только с целыми числами и не требует<cmath>.
11. Чтение данных с разделителями#
Если данные записаны как 15:01:40, удобно считывать разделитель в переменную типа char:
int h, m, s;
char sep;
cin >> h >> sep >> m >> sep >> s; // sep получает ':'
cin прочитает число, затем один символ, затем снова число.
12. Типичные ошибки#
| ❌ Неправильно | ✅ Правильно |
|---|---|
Написали функцию, но не вызвали её в main |
вызвать функцию и использовать результат |
int f(int x) { if (x > 0) return 1; } — нет return для x <= 0 |
return в каждой ветке |
int average(int a, int b) возвращает дробный результат |
тип возвращаемого значения double |
| Вызов функции выше её определения без прототипа | прототип сверху или определение до main |
Использование переменной из main внутри функции |
передать её параметром |
| Рекурсия без базы или с базой, которая не достигается | if (...) return ...; в начале функции |
gcd(a % b, b) |
gcd(b, a % b) |
return &a; — указатель на локальную переменную |
возвращать указатель на внешние данные или nullptr |
| Одна огромная функция на всё решение | несколько небольших функций |
13. Шаблон программы#
using namespace std;
// вспомогательные функции
int