Изучаем C++. Часть 5. Функции и процедуры

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

Это пятая часть из серии статей «Глубокое погружение в 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?»? Тогда придётся поменять код в шести местах. А такие сообщения могут выводиться сто или даже тысячу раз.

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

Евгений Кучерявый

Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.


Как создать функцию в 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:

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

На практике применение этой особенности можно увидеть при рекурсивном удалении папок. Функция удаления должна работать так:

  1. Сканирует указанную папку.
  2. Удаляет все файлы из этой папки.
  3. Сканирует папки внутри этой папки и выполняет предыдущие шаги для каждой из них.

Рекомендации по созданию функций

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

  1. Называйте их так, чтобы было сразу понятно, что они делают. При этом лучше использовать повелительное наклонение, то есть напечатайФакториал (), а не печатьФакториала ().
  2. Делайте функции как можно меньше и универсальнее.
  3. Если есть вероятность, что часть кода может повториться несколько раз, то сразу выносите её в отдельную функцию.

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

Ну а если вы хотите углубить свои знания, набраться опыта на реальных проектах и получать обратную связь по своему коду от опытных разработчиков, то записывайтесь на наш курс по программированию на C++.

Курс

Профессия
Разработчик на C++ с нуля


Вы пройдёте полный курс по С++ и прикладной курс по Unreal Engine 4. Вы научитесь работать с многопоточностью, использовать инструменты и средства разработки: Git, GCC, GDB. Вам будет проще найти работу программиста в геймдеве.

Хочешь получать крутые статьи по программированию?
Подпишись на рассылку Skillbox