Урок 6. Функции
Функция — именованный кусок программы, который можно вызывать много раз. printf и scanf — тоже функции, только написанные заранее. Свои функции нужны, чтобы не повторять один и тот же код и чтобы разбить большую задачу на понятные части.
#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Из чего состоит функция:
intперед именем — тип значения, которое функция возвращает;max2— имя функции, правила те же, что у переменных;(int a, int b)— параметры: переменные, которые получают значения при вызове. При вызовеmax2(3, 8)вaпопадёт 3, вb— 8;return— завершает функцию и отдаёт значение туда, откуда её вызвали. Команды после выполненногоreturnне выполняются.
Функции без результата: void
Если функция ничего не возвращает, а только что-то делает, её тип — void:
#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; — чтобы выйти раньше.
Порядок в файле: объявление до вызова
Компилятор читает файл сверху вниз и должен знать о функции до её первого вызова. Есть два способа:
- написать функцию целиком выше
main, как в примерах выше; - поставить выше
mainобъявление (прототип) — заголовок функции с точкой с запятой, а саму функцию написать ниже.
#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 — нет.
Параметры передаются по значению
Функция получает копии значений. Изменение параметра внутри функции не меняет переменную, которую передали:
#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Чтобы функция могла изменить переменную вызывающего, ей передают адрес переменной — так работает scanf. Это тема следующего урока, Указатели.
Массив в параметре
С массивами иначе: функция получает не копию массива, а адрес его начала. Поэтому функция может менять элементы. Размер массива она не знает — его передают отдельным параметром:
#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 *'.
Локальные и глобальные переменные
Переменная, объявленная внутри функции, — локальная: она существует только во время работы функции и не видна из других функций. Две функции могут иметь переменные с одинаковым именем — это разные переменные. Переменная, объявленная вне всех функций, — глобальная: она видна везде ниже объявления и в начале равна нулю.
Глобальные переменные удобны для больших массивов, но злоупотреблять ими не стоит: когда любую переменную может изменить любая функция, ошибку трудно найти.
Рекурсия
Функция может вызывать сама себя — это рекурсия. Нужна база — случай, когда функция отвечает без нового вызова, иначе вызовы не закончатся и программа упадёт от переполнения стека.
#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) = 12gcd — алгоритм Евклида: наибольший общий делитель a и b равен НОД b и остатка a % b.
Математические функции
В math.h — готовые функции: sqrt(x) — квадратный корень, pow(x, y) — x в степени y, fabs(x) — модуль дробного числа, floor, ceil, round — округления. Все они принимают и возвращают double. Модуль целого числа — abs из stdlib.h.
#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;.
Коротко
- Функция:
тип имя(параметры) { … return значение; }; без результата —void. - Функция должна быть написана или объявлена (прототип) выше места вызова.
- Без параметров пишут
(void), а не пустые скобки. - Параметры — копии значений; массив передаётся адресом, его размер — отдельным параметром.
- Рекурсии нужна база. Функции
math.hв Linux требуют ключа-lm.