Перейти к содержанию
Educora
Средний17 мин4 / 8

Функции

Пиши собственные функции на C: прототипы, передача параметров по значению, return, функции void, стандартные математические функции и основы рекурсии.

Проверь себя
В этом уроке ты узнаешь
  • Определять и вызывать функцию с типом результата, именем и параметрами
  • Объяснять, зачем нужен прототип функции
  • Понимать, что параметры передаются по значению (как копии)
  • Писать простую рекурсивную функцию с базовым случаем

Две функции ты уже хорошо знаешь: main, с которой начинается любая программа, и printf, которая выводит текст. Большие программы состоят из сотен маленьких функций: одна считает баллы, другая печатает таблицу, третья читает файл. Каждая функция выполняет одну задачу, пишется один раз и вызывается сколько угодно раз. А найденную ошибку достаточно исправить в одном месте.

Как устроена функция

  • Тип результата — тип значения, которое возвращает функция (int, double…). Для функции, которая ничего не возвращает, пишут void.
  • Имя — название, которое говорит, что делает функция, например max или print_line.
  • Параметры — входные значения в скобках вместе с их типами: (int a, int b). Если параметров нет, пишут (void).
  • Тело — код между фигурными скобками. return возвращает результат в место вызова и сразу завершает функцию.

Компилятор C читает файл сверху вниз. Если main вызывает функцию раньше, чем она определена, компилятор должен знать о ней заранее. Для этого в начале файла пишут прототип — заголовок функции с ; в конце.

Определение
Прототип функции

Объявление, которое сообщает имя функции, тип результата и типы параметров, но не содержит тела. Например: int max(int a, int b);. Заголовочные файлы вроде stdio.h состоят в основном из прототипов.

C
#include <stdio.h>

int max(int a, int b);
double average(int a, int b, int c);

int main(void) {
    printf("Max: %d\n", max(12, 31));
    printf("Average: %.2f\n", average(5, 4, 4));
    return 0;
}

int max(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}

double average(int a, int b, int c) {
    return (a + b + c) / 3.0;
}
Ожидаемый результат
Max: 31
Average: 4.33
Благодаря прототипам main может стоять в начале файла. Деление на 3.0 даёт дробный результат: 13 / 3 ≈ 4,33.

Функции void и передача по значению

Некоторые функции ничего не возвращают, а просто выполняют работу, например рисуют линию на экране. Их тип результата — void. Программа ниже показывает и очень важное правило: в C аргументы передаются по значению. Функция получает не саму переменную, а её копию, поэтому изменение параметра не влияет на переменную вызывающей стороны.

C
#include <stdio.h>

void print_line(int n) {
    for (int i = 0; i < n; i++) {
        printf("-");
    }
    printf("\n");
}

void try_to_change(int x) {
    x = 100;
    printf("Inside: x = %d\n", x);
}

int main(void) {
    int x = 5;
    print_line(12);
    try_to_change(x);
    printf("Outside: x = %d\n", x);
    print_line(12);
    return 0;
}
Ожидаемый результат
------------
Inside: x = 100
Outside: x = 5
------------
x внутри функции — копия: она стала равна 100, а x в main осталась равной 5.

Не каждую функцию нужно писать самому. В заголовке math.h стандартной библиотеки есть готовые математические функции: sqrt (квадратный корень), pow (степень), fabs (модуль) и другие. Они принимают и возвращают double. В Linux при компиляции такой программы может понадобиться добавить в конце -lm.

C
#include <stdio.h>
#include <math.h>

int main(void) {
    printf("%.2f\n", sqrt(16.0));
    printf("%.0f\n", pow(2, 10));
    printf("%.1f\n", fabs(-7.5));
    return 0;
}
Ожидаемый результат
4.00
1024
7.5

Рекурсия: функция, вызывающая саму себя

Функция может вызывать и саму себя — это называется рекурсией. У рекурсивной функции две части: базовый случай, который решает задачу сразу, и рекурсивный шаг, который немного уменьшает задачу и снова вызывает функцию. Классический пример — факториал: n! = n · (n − 1)!, а базовый случай — 1! = 1.

C
#include <stdio.h>

long long factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main(void) {
    for (int i = 1; i <= 5; i++) {
        printf("%d! = %lld\n", i, factorial(i));
    }
    printf("20! = %lld\n", factorial(20));
    return 0;
}
Ожидаемый результат
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
20! = 2432902008176640000
Факториал растёт очень быстро, поэтому выбран тип long long (%lld) — 20! уже не помещается в int.
Как вычисляется factorial(4)?

Проследи вызов factorial(4) шаг за шагом.

Показать решение
factorial(4) = 4 · factorial(3)
factorial(3) = 3 · factorial(2)
factorial(2) = 2 · factorial(1)
factorial(1) = 1 — базовый случай, вызовы прекращаются.
Теперь результаты возвращаются обратно: 2 · 1 = 2, 3 · 2 = 6, 4 · 6 = 24.

Главное

  • У функции есть тип результата, имя, параметры и тело; return возвращает результат.
  • Если функция вызывается до своего определения, в начале файла нужен её прототип.
  • Функция, которая ничего не возвращает, имеет тип void.
  • В C аргументы передаются по значению: функция работает с копией.
  • У рекурсивной функции обязательно должен быть базовый случай, иначе стек переполнится.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что такое прототип функции?