Объявление и вызов функций
Урок объясняет, как устроены функции в C: определение с параметрами и типом возврата, разница между объявлением и определением, зачем нужны прототипы.
Функция в C — это именованный блок кода с чётким контрактом: какие типы принимает и какой тип возвращает. Компилятор обязан знать этот контракт ДО первого вызова, иначе будет ошибка.
Функция позволяет дать имя куску логики и переиспользовать его. Определение состоит из типа возврата, имени, списка параметров и тела:
// тип имя параметры
int sum(int a, int b) {
return a + b; // возвращаем результат
}
int main(void) {
int result = sum(3, 4); // вызов
printf("%d\n", result); // 7
return 0;
}
Тип перед именем — это тип возвращаемого значения. Если функция ничего не возвращает, пишут void. Параметры в скобках — это локальные переменные, которые получают значения при вызове.
В C важен порядок: функцию нужно объявить до её использования. Если main идёт первым, а sum — после, компилятор, дойдя до вызова, ещё не знает о sum. Решение — прототип: объявление функции без тела в начале файла.
int sum(int a, int b); // прототип: контракт известен заранее
int main(void) {
printf("%d\n", sum(3, 4));
return 0;
}
int sum(int a, int b) { // определение ниже — уже не проблема
return a + b;
}
Как работает под капотом
При вызове функции происходит несколько вещей. Аргументы копируются в параметры, управление передаётся в тело, а место возврата запоминается, чтобы продолжить после вызова.
main() sum(a, b) | | | result = sum(3, 4) | | копируем 3 -> a | | копируем 4 -> b ------> a=3, b=4 | (запоминаем, куда | | вернуться) | return a + b (=7) | <------ | | result = 7 | v (функция завершилась) продолжаем после вызова
Прототип нужен именно для того, чтобы компилятор в точке вызова знал: sum принимает два int и возвращает int. Тогда он сможет проверить корректность вызова и правильно подготовить аргументы.
Частые ошибки
- Вызов функции до объявления без прототипа. Старые компиляторы «угадывали» тип, новые выдают ошибку. Всегда объявляйте заранее.
- Несовпадение типа возврата и return. Функция
int, а вы забылиreturn— поведение не определено. - Пустые скобки вместо void.
int f()в старом C значит «параметры не проверяются», аint f(void)— «параметров нет». Пишитеvoid. - Слишком длинные функции. Функция на 200 строк нечитаема и плохо тестируется.
Best practices
- Выносите прототипы функций в заголовочный файл (
.h), а определения — в.c. Это основа модульности в C. - Одна функция — одна задача. Если не можете описать функцию одним предложением, её стоит разбить.
- Давайте функциям глаголы в именах:
calculate_average,print_table— код читается как фразы.
Идея «функция — это контракт вход/выход» одинакова во всех языках, и логику легко проверить на Python.
# Та же функция sum и её использование
def c_sum(a, b):
return a + b
result = c_sum(3, 4)
print("sum(3, 4) =", result)
# Композиция функций — типично и для C
def average(a, b, c):
return c_sum(c_sum(a, b), c) / 3
print("Среднее (10,20,30):", average(10, 20, 30))
Та же логика на Python ▶ — разница в том, что в Python нет прототипов: интерпретатор узнаёт о функции в момент вызова. В C компилятор должен знать контракт заранее.
Заголовочные файлы и модульность
В настоящих проектах функции не живут в одном файле. Их прототипы выносят в заголовочный файл (.h), а определения — в файл реализации (.c). Другие модули подключают заголовок через #include \"mymodule.h\" и получают доступ к функциям, не зная их внутренностей. Это и есть модульность C: заголовок — публичный контракт, .c-файл — скрытая реализация. Чтобы один и тот же заголовок, подключённый дважды, не вызвал ошибку повторного объявления, его оборачивают в защиту от повторного включения — конструкцию #ifndef / #define / #endif или прагму #pragma once. Понимание этого механизма — порог между «пишу один файл» и «работаю над реальным проектом из многих файлов».
Итоги
Функция в C — это контракт: тип возврата, имя, типы параметров. Компилятор должен знать этот контракт до первого вызова, поэтому функции либо определяют выше места использования, либо объявляют прототипом. Параметры — локальные переменные, получающие копии аргументов. Хорошая практика — прототипы в .h, определения в .c, и одна задача на функцию.