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


vlada_maestro / shutterstock
Это пятая часть из серии статей «Глубокое погружение в C++». В прошлый раз мы узнали, как правильно использовать ввод данных и обрабатывать исключения. Сегодня займёмся функциями и процедурами.
Если код нужно использовать несколько раз, то лучше всего его вывести в подпрограмму — функцию или процедуру. Это позволит значительно сократить объём кода. Давайте рассмотрим вот такую программу:
int name1 = "John";
int name2 = "James";
int name3 = "Jack";
int name4 = "Ivan";
int name5 = "Igor";
int name6 = "Boris";
std::cout << "Hello, " << name1 << "!\n";
std::cout << "Hello, " << name2 << "!\n";
std::cout << "Hello, " << name3 << "!\n";
std::cout << "Hello, " << name4 << "!\n";
std::cout << "Hello, " << name5 << "!\n";
std::cout << "Hello, " << name6 << "!\n";
Программа по очереди приветствует шесть человек. Это относительно небольшой код, но что, если понадобится заменить фразу «Hello, %name%!» на «Hello, %name%! How are you?»? Тогда придётся поменять код в шести местах. А такие сообщения могут выводиться сто или даже тысячу раз.
Подпрограммы как раз и нужны, чтобы избежать таких проблем.
Как создать функцию в C++
Для удобства любые подпрограммы, в том числе и процедуры, называют функциями.
Одну функцию мы с вами уже создавали — main (). Она работает как точка входа в каждую программу. Другие функции создаются аналогичным образом:

Вот так это выглядит в коде:
#include <iostream>
//Новые функции и процедуры должны создаваться выше функции main()
//В круглых скобках указываются аргументы, которые будет принимать функция
int sum(int a, int b)
{
//Используем переданные аргументы, чтобы провести вычисления
int result = a + b;
//Оператор возврата говорит программе, что функция завершила работу
return result;
//Всё, что записано после return, не будет выполнено
}
int main()
{
//Вызываем функцию с аргументами
int result = sum(5, 6);
}
Функции могут принимать или не принимать аргументы. Но если вы указали, что аргументы всё же нужны, то попытка вызвать функцию без них приведёт к ошибке.
Если вы указываете какой-либо тип возвращаемых данных, то обязательно должен присутствовать оператор return. Если возвращать значение не нужно, создайте процедуру — укажите тип void.
#include <iostream>
void printHello(std::string name)
{
std::cout << "Hello, " << name << "!\n";
}
int main()
{
printHello("Igor");
printHello("Boris");
}
Вот что выведет такая программа:

Функция main () автоматически возвращает значение, поэтому отдельно прописывать return не обязательно. Программа вернёт 0, если она выполнилась успешно, или другое число в зависимости от ошибки.
Области видимости в C++
Всё, что происходит в функции, в ней же и остаётся. То есть функции могут работать только с теми значениями, которые в них передали:
#include <iostream>
void printHello()
{
//Переменная name объявлена в другой функции, поэтому у printHello() нет к ней доступа
//Запуск такого кода приведёт к ошибке
std::cout << "Hello, " << name << "!\n";
}
int main()
{
std::string name = "Igor";
printHello();
}
Если переменная создаётся внутри какого-либо блока {}, то она будет доступна только в этом и во всех вложенных блоках; такие переменные называются локальными.
Если вы хотите, чтобы какая-нибудь переменная была доступна везде, то её нужно объявить за пределами каких-либо блоков; такие переменные называются глобальными:
#include <iostream>
//Глобальные переменные создаются вне функций
std::string name;
void printHello()
{
std::cout << "Hello, " << name << "!\n";
}
int main()
{
name = "Igor";
printHello();
}
Однако использовать глобальные переменные не рекомендуется, потому что вам будет сложно отслеживать изменения и влиять на результат работы программы. Единственное исключение — это константы, которые нужно делать глобальными, потому что они не меняются.
Как работают аргументы
Рассмотрим, что происходит, когда вы передаёте аргумент в функцию. Допустим, есть вот такой код:
#include <iostream>
void sum(int a)
{
a = a + 500;
}
int main()
{
int a = 5;
sum(a);
std::cout << a << "\n";
}
Можно подумать, что в результате программы мы увидим число 505, но это неверно. Дело в том, что функции в качестве аргументов принимают не сами переменные, а их значения. То есть в sum () попала не ячейка памяти, а число 5, которое находилось в этой ячейке. Учитывайте это, когда пишете программы.

Это касается только примитивных типов данных. Ссылочные типы ведут себя иначе.
Рекурсия C++
Рекурсия — это ситуация, когда объект является частью самого себя (не путать с фракталом). В программировании это проявляется в одной интересной особенности функций — они могут вызывать сами себя.

Чтобы понять, что такое рекурсия, нужно понять, что такое рекурсия
Чаще всего рекурсия объясняется на примере факториалов (не путать с фракталами):
#include <iostream>
int f(int n)
{
if(n < 0)
{
return 0;
}
else if (n == 0)
{
return 1;
}
else
{
return n * f(n - 1);
}
}
void printFactorial(int n)
{
int a = f(n);
std::cout << n << "! = " << a << "\n";
}
int main()
{
printFactorial(1);
printFactorial(2);
printFactorial(3);
printFactorial(4);
printFactorial(5);
printFactorial(6);
printFactorial(7);
printFactorial(8);
printFactorial(9);
printFactorial(10);
}
Вот факториалы чисел от 1 до 10:

Важно! Прописывайте условия выхода из рекурсии, иначе программа попадёт в бесконечный цикл и сильно нагрузит компьютер.
На практике применение этой особенности можно увидеть при рекурсивном удалении папок. Функция удаления должна работать так:
- Сканирует указанную папку.
- Удаляет все файлы из этой папки.
- Сканирует папки внутри этой папки и выполняет предыдущие шаги для каждой из них.
Рекомендации по созданию функций
Разрабатывая программы, вы постоянно будете работать с функциями, поэтому важно научиться делать их максимально полезными. Вот несколько рекомендаций:
- Называйте их так, чтобы было сразу понятно, что они делают. При этом лучше использовать повелительное наклонение, то есть напечатайФакториал (), а не печатьФакториала ().
- Делайте функции как можно меньше и универсальнее.
- Если есть вероятность, что часть кода может повториться несколько раз, то сразу выносите её в отдельную функцию.
И старайтесь как можно больше практиковаться, чтобы закрепить новые знания. В этой серии статей описываются базовые возможности программирования, которые пригодятся вам на протяжении всей карьеры. В следующем материале мы разберёмся, как упростить повторяющийся код с помощью циклов.