Понимание функций C: параметры, возвращаемые значения и область видимости

5

Большинство языков программирования позволяют разбить громоздкий, долго выполняющийся код на аккуратные именованные фрагменты. Эти фрагменты называются функциями. Они позволяют повторно использовать код вместо того, чтобы переписывать одну и ту же логику снова и снова. Функция принимает входные данные, известные как параметры, и выдает результат. В C вы можете передавать столько параметров, сколько необходимо. Компилятору не важен порядок объявления функций в исходном файле, главное — чтобы он знал имя функции до того, как попытается её выполнить.

Мы кратко затрагивали эту тему с функцией rand. Это самая простая возможная функция. Она ничего не принимает и возвращает целое число.

Строка int rand() сообщает компилятору, что rand — это функция. Она не принимает параметров и возвращает целое число. В данной конкретной версии нет локальных переменных. Если бы они были, они располагались бы сразу после открывающей фигурной скобки {. В C разрешено объявлять переменные в любом месте после {. Они существуют до тех пор, пока не будет достигнута соответствующая закрывающая скобка }. Затем они исчезают. Пока они «живы», локальные переменные хранятся в системном стеке.

Обратите внимание на отсутствие точки с запятой после rand(). Если вы случайно поставите её, компилятор выдаст огромное количество бессмысленных сообщений об ошибках. Даже если параметров нет, скобки обязательны. Без них вы не объявляете функцию, а просто объявляете целое число.

Оператор return и несколько точек выхода

Оператор return критически важен для любой функции, которая возвращает результат. Он устанавливает возвращаемое значение и немедленно завершает выполнение функции. Вы можете разместить несколько операторов return в одной функции, чтобы создать несколько точек выхода.

Если вы забудете оператор return в функции, которая должна возвращать значение, функция завершится при достижении закрывающей скобки. Она вернет случайное, «мусорное» значение. Большинство современных компиляторов предупредят вас, если вы забудете вернуть конкретное значение. C поддерживает возврат любого типа: int, float, char, struct и других.

Существует несколько правильных способов вызова функции rand. Самый распространенный — присваивание:

Здесь переменная x получает значение, возвращаемое rand. Вы должны включить скобки при вызове, даже если параметров нет. Без них x получит адрес памяти функции rand. Это редко является тем, что вы намеревались сделать.

Вы также можете вызвать её внутри условия:

Или просто проигнорировать результат:

В последнем случае функция выполняется, но возвращаемое значение отбрасывается. Возможно, вам не стоит делать это с rand, но многие функции используют возвращаемое значение как код ошибки. Если вы знаете, что ошибка невозможна, отбрасывание кода допустимо.

Обработка void-возвратов

Иногда вам не нужно ничего возвращать. Для этого используется тип возврата void.

Эта функция выводит текст, но не возвращает никакого значения. Вы вызываете её следующим образом:

Снова включите скобки. Если вы их опустите, функция не будет вызвана. Она скомпилируется корректно на многих системах, но ничего не сделает.

Параметры и устаревший стиль кода на C

Функции C могут принимать параметры любого типа. Вот функция, вычисляющая факториал числа:

Параметр i передается как целое число. Чтобы передать несколько параметров, разделяйте их запятыми.

Язык C эволюционировал. Вы время от времени будете встречать старый код, написанный в «старом стиле». Он выглядит так:

Вы должны уметь читать эту старую нотацию. Она выполняется абсолютно так же. Это просто другой синтаксис. Вы должны использовать «новый стиль», известный как ANSI C, где типы объявляются в списке параметров. Используйте старый стиль только если вы знаете, что отправляете код кому-то с не-ANSI компилятором.

Почему это важно для обычных пользователей

Вы можете не писать функции на C ежедневно. Но понимание того, как они работают, меняет ваше представление о программном обеспечении. Каждый раз, когда вы нажимаете кнопку в веб-приложении, функция, скорее всего, обрабатывает этот клик, проверяет параметры и возвращает статус. Если вы когда-либо видели «runtime error» (ошибку времени выполнения) или «stack overflow» (переполнение стека), вы сталкивались с пределами этих механизмов.

Различие между передачей значения и передачей ссылки (адреса памяти) является распространенным источником ошибок. В высокоуровневых языках это часто скрыто. В C это явно. Знание того, что rand без скобок возвращает адрес, а не число, экономит часы отладки.

Дискуссия о старом стиле против нового стиля в основном урегулирована для новых проектов. ANSI C является стандартом. Но устаревшие кодовые базы все еще существуют. Умение читать int add(i,j) int i; int j; предотвращает застревание на коде, который выглядит неправильно, но работает идеально. Это вопрос грамотности.

Действительно ли стек имеет для вас значение? Если ваша программа неожиданно падает, трассировка стека — это ваша единственная подсказка. Исчезновение локальных переменных при достижении закрывающей скобки — это не баг. Это особенность. Она делает использование памяти предсказуемым.

Не существует идеального способа написания кода. Есть только способ, который компилируется, и способ, который не компилируется. Сохраняйте скобки. Следите за операторами return. И не предполагайте, что компилятор спасет вас от собственных синтаксических ошибок. Он не спасет.