Глиф Учебники

Урок 6. Функции

Курс «C» · урок 6 из 10 · 4 минуты чтения

Функция — именованный кусок программы, который можно вызывать много раз. printf и scanf — тоже функции, только написанные заранее. Свои функции нужны, чтобы не повторять один и тот же код и чтобы разбить большую задачу на понятные части.

C
#include <stdio.h>

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

int main(void) {
    printf("%d\n", max2(3, 8));
    printf("%d\n", max2(-1, -5));
    int m = max2(max2(4, 9), 7);
    printf("%d\n", m);
    return 0;
}
Вывод
8
-1
9

Из чего состоит функция:

main — вызов printf("%d\n", max2(3, 8)); 8 вызов заменяется на на экране: 8 функция int max2(int a, int b) a 3 b 8 if (a > b) 3 > 8 — ложь return b; отдаёт 8 3 и 8 вернёт 8
При вызове max2(3, 8) аргументы 3 и 8 попадают в параметры a и b, условие a > b ложно, и return b отдаёт 8 — вызов в printf заменяется этим числом, и на экран выводится 8.

Функции без результата: void

Если функция ничего не возвращает, а только что-то делает, её тип — void:

C
#include <stdio.h>

void line(int n, char c) {
    for (int i = 0; i < n; i++) {
        printf("%c", c);
    }
    printf("\n");
}

int main(void) {
    line(10, '=');
    printf("Отчёт\n");
    line(10, '-');
    return 0;
}
Вывод
==========
Отчёт
----------

В void-функции return можно не писать или писать без значения: return; — чтобы выйти раньше.

Порядок в файле: объявление до вызова

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

C
#include <stdio.h>

int square(int x);

int main(void) {
    printf("%d\n", square(12));
    return 0;
}

int square(int x) {
    return x * x;
}
Вывод
144

Если не сделать ни того, ни другого, gcc напишет implicit declaration of function 'square'. Разбор этой ошибки — в справке implicit declaration of function.

У функции без параметров в C пишут (void): int read_number(void). Пустые скобки int f() в C17 означают «число параметров не указано» — компилятор не сможет проверить вызовы. В C++ и в новом стандарте C23 пустые скобки значат то же, что (void), но в C17 — нет.

Параметры передаются по значению

Функция получает копии значений. Изменение параметра внутри функции не меняет переменную, которую передали:

C
#include <stdio.h>

void add_one(int x) {
    x = x + 1;
    printf("Внутри: %d\n", x);
}

int main(void) {
    int n = 5;
    add_one(n);
    printf("Снаружи: %d\n", n);
    return 0;
}
Вывод
Внутри: 6
Снаружи: 5
main int n = 5; n 5 add_one(n); Снаружи: 5 n не изменилась add_one — своя переменная x void add_one(int x) x 5 6 x = x + 1; Внутри: 6 копия 5
При вызове add_one(n) в параметр x копируется значение 5; x = x + 1 меняет только копию — внутри функции x становится 6, а n в main остаётся равной 5.

Чтобы функция могла изменить переменную вызывающего, ей передают адрес переменной — так работает scanf. Это тема следующего урока, Указатели.

Массив в параметре

С массивами иначе: функция получает не копию массива, а адрес его начала. Поэтому функция может менять элементы. Размер массива она не знает — его передают отдельным параметром:

C
#include <stdio.h>

int sum(int a[], int n) {
    int s = 0;
    for (int i = 0; i < n; i++) {
        s += a[i];
    }
    return s;
}

void double_all(int a[], int n) {
    for (int i = 0; i < n; i++) {
        a[i] *= 2;
    }
}

int main(void) {
    int a[4] = {1, 2, 3, 4};
    printf("Сумма: %d\n", sum(a, 4));
    double_all(a, 4);
    printf("После удвоения: %d %d %d %d\n", a[0], a[1], a[2], a[3]);
    return 0;
}
Вывод
Сумма: 10
После удвоения: 2 4 6 8

Внутри sum запись sizeof(a) даст размер адреса (8 байт в 64-битной программе), а не массива; gcc предупреждает об этом: 'sizeof' on array function parameter 'a' will return size of 'int *'.

Локальные и глобальные переменные

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

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

Рекурсия

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

C
#include <stdio.h>

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

int gcd(int a, int b) {
    if (b == 0) {
        return a;
    }
    return gcd(b, a % b);
}

int main(void) {
    printf("10! = %lld\n", factorial(10));
    printf("НОД(84, 36) = %d\n", gcd(84, 36));
    return 0;
}
Вывод
10! = 3628800
НОД(84, 36) = 12

gcd — алгоритм Евклида: наибольший общий делитель a и b равен НОД b и остатка a % b.

каждый вызов — отдельная копия функции со своими a и b gcd(84, 36) a = 84, b = 36 b == 0 — нет 84 % 36 = 12 gcd(36, 12) a = 36, b = 12 b == 0 — нет 36 % 12 = 0 gcd(12, 0) a = 12, b = 0 b == 0 — да return a; база: отвечает сама 12 12 в main: НОД(84, 36) = 12
gcd(84, 36) вызывает gcd(36, 12), та — gcd(12, 0); в ней b == 0, это база рекурсии: она возвращает 12, и это число по цепочке возвращается каждым вызовом обратно в main.

Математические функции

В math.h — готовые функции: sqrt(x) — квадратный корень, pow(x, y) — x в степени y, fabs(x) — модуль дробного числа, floor, ceil, round — округления. Все они принимают и возвращают double. Модуль целого числа — abs из stdlib.h.

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

int main(void) {
    double a = 3, b = 4;
    double c = sqrt(a * a + b * b);
    printf("Гипотенуза: %.1f\n", c);
    printf("2 в степени 10: %.0f\n", pow(2, 10));
    printf("Модуль: %.2f\n", fabs(-7.25));
    return 0;
}
Вывод
Гипотенуза: 5.0
2 в степени 10: 1024
Модуль: 7.25

В Linux при сборке программы с math.h добавляйте в конец команды ключ -lm — подключить математическую библиотеку: gcc prog.c -o prog -lm. Без него сборка может закончиться ошибкой undefined reference to 'sqrt'. В Windows (MinGW) и macOS ключ не нужен, но и не мешает.

Константы M_PI в стандарте C нет. При сборке с -std=c17 gcc в Linux её не видит — объявите число сами: const double PI = 3.14159265358979;.

Коротко

Проверьте себя

1. Что выведет программа?
C
#include <stdio.h>

void change(int x) {
    x = 100;
}

int main(void) {
    int a = 1;
    change(a);
    printf("%d\n", a);
    return 0;
}
2. Что такое прототип функции?
3. Что случится, если у рекурсивной функции нет базы?
4. Может ли функция изменить элементы массива, переданного ей параметром?