Зачем нужны функции
Всё до сих пор жило внутри main. Это работает, пока программа делает одно дело, а затем сразу приходят три проблемы: один и тот же блок кода копируется в три места, main разрастается больше, чем кто-либо способен удержать в голове, и нет способа назвать, для чего нужен кусок кода.
Функция решает все три. Она даёт блоку кода имя, собственные локальные переменные, список входных данных и одно значение, которое можно вернуть.
square написана один раз и вызвана трижды, в том числе один раз на собственном результате. Вы уже вызывали функции — printf одна из них, и main тоже.
Анатомия функции
returnType name(parameterList) {
// тело
return value;
}
Четыре части:
- Тип возвращаемого значения — тип значения, которое функция отдаёт обратно.
int,double,char, тип указателя илиvoidдля «ничего». - Имя — подчиняется тем же правилам, что и имя переменной, и должно говорить, что функция делает или производит.
- Список параметров — входные данные, каждый со своим типом, через запятую.
(void)означает, что параметров нет. - Тело — инструкции в фигурных скобках, завершающиеся
return, если тип возвращаемого значения неvoid.
Обратите внимание, что pi() всё равно нужны пустые скобки в месте вызова — именно они делают это вызовом, а не упоминанием имени. А printBanner(20); — самостоятельная инструкция, потому что она не производит значения, которое можно было бы использовать.
Для функции без параметров пишите (void), а не (). В C это разные вещи, и () — более слабый вариант; подробнее об этом чуть ниже.
Вызов и возврат
Вызов передаёт управление внутрь функции, выполняет её тело и возвращается со значением, которое заменяет вызов в окружающем выражении.
return делает две вещи сразу: отдаёт значение и завершает функцию на месте. Всё, что идёт после return на том же пути, никогда не выполняется — именно это позволяет работать стилю раннего возврата в larger: else не нужен, потому что достижение последней строки уже означает, что a > b было ложно.
У функции может быть несколько return. Но за один вызов выполнится только один из них.
Функции void
Функция, которая существует ради эффекта, а не ради значения, имеет тип void: вывод, изменение чего-то через указатель, рисование.
Внутри функции void инструкция return; без значения завершает её раньше. return someValue; там — ошибка компиляции, как и использование вызова в выражении: int x = printTable(7, 5); не скомпилируется, и это система типов делает свою работу.
Декомпозиция: настоящий смысл
Функции нужны не столько для того, чтобы избежать повторений. Они нужны, чтобы превратить одну длинную процедуру в несколько именованных шагов, так что main читается как описание программы, а не как её реализация.
Теперь main — это четыре строки вывода и одно вычисление. Каждая вспомогательная функция делает одно дело, носит имя, которое говорит, какое именно, и может быть протестирована отдельно. Обратите внимание, что длина массива путешествует вторым параметром: как только массив передан в функцию, трюк с sizeof там уже не работает, потому что массив низводится до указателя.
Три практических правила разбиения работы на функции:
- Одна задача на функцию. Если в имени нужно «и», это, скорее всего, две функции.
- Берите то, что нужно, возвращайте то, что производите. Функция, читающая глобальное состояние, сложнее для рассуждений и невозможна для изолированного тестирования.
- Достаточно короткая, чтобы увидеть целиком. Волшебного числа строк нет, но функция, не помещающаяся на экран, обычно прячет внутри себя функцию поменьше.
Функции, вызывающие функции
Функция может вызывать любую уже объявленную функцию, включая написанные вами:
main вызывает report, та вызывает sumEvens, а та внутри цикла вызывает isEven. Каждый вызов получает собственный свежий набор локальных переменных, уложенный поверх переменных вызывающего, и они отбрасываются при возврате.
Определения выше идут в порядке зависимостей — isEven перед sumEvens, sumEvens перед report, — потому что C требует, чтобы функция была объявлена до вызова. Вызовите ту, что расположена ниже по файлу, и компилятор пожалуется на неявное объявление. Общее решение — прототип в начале файла, который позволяет располагать определения в любом порядке. Функция, вызывающая саму себя, тоже законна и полезна; это рекурсия.
main — это функция
main — обычная функция, которую за вас вызывает среда выполнения. Её тип int — это код завершения программы: 0 означает успех, ненулевое — неудачу, и его читают скрипты оболочки и инструменты сборки.
int main(void) {
/* ... */
return 0;
}
Начиная с C99, выход за конец main без return неявно возвращает 0 — особое правило, действующее только для main. Любая другая не-void функция, завершившаяся без возврата значения, порождает неопределённое поведение, когда вызывающий использует результат.
Другая стандартная форма — int main(int argc, char *argv[]), которая принимает аргументы командной строки. Обе корректны; void main() — нет, что бы ни говорили старые учебники.
Распространённые ошибки
- Забытый
returnв не-void функции. Вызывающий тогда читает мусорное значение.gcc -Wallпредупреждает. - Объявление внутри тела переменной с именем параметра. Она затеняет параметр, и присваивание, которое вы хотели сделать входному значению, уходит в никуда.
- Ожидание, что функция изменит свои аргументы. C передаёт всё по значению, поэтому функция получает копии.
void reset(int x) { x = 0; }ничего не меняет в месте вызова — тема следующей страницы. - Пропуск скобок при вызове.
printBanner;— законное выражение, которое вычисляет адрес функции и отбрасывает его. Оно компилируется, ничего не делает, и-Wallна него указывает. - Запись
()вместо(void). Законно, но в старых диалектах C это отключает проверку аргументов.
Часто задаваемые вопросы
Как объявить функцию в C?
Напишите тип возвращаемого значения, имя и список параметров в круглых скобках, затем тело в фигурных скобках: int add(int a, int b) { return a + b; }. Функция, которая ничего не принимает, использует (void), а та, что ничего не возвращает, имеет тип void.
Что делает return в функции C?
Он немедленно завершает функцию и передаёт значение вызывающему коду. Тип значения должен совпадать с объявленным типом возвращаемого значения. В функции void можно написать голый return;, чтобы выйти раньше, либо не писать его вовсе и дать функции закончиться на закрывающей скобке.
Почему main возвращает int в C?
Возвращаемое значение main — это код завершения программы, который могут прочитать операционная система и скрипты оболочки. return 0; означает успех, любое ненулевое значение — неудачу. Начиная с C99, достижение конца main без return неявно возвращает 0.
Может ли функция C вернуть больше одного значения?
Напрямую нет — return даёт ровно одно значение. Обычные обходные пути: вернуть структуру с несколькими полями либо передать указатели параметрами и записать дополнительные результаты через них.