Перейти к содержимому
Теория · Функции · урок 1 из 13

Объявление и определение

Чему вы научитесь

Чему вы научитесь:

  • вызывать функцию до её определения и понимать, кто сообщает об ошибке;
  • выбирать, получит функция копию аргумента или саму переменную;
  • перегружать функции и задавать аргументы по умолчанию;
  • записывать короткие функции лямбдами и захватывать в них переменные.
Автомат в разрезе с панелью twice(int): фишка 21 входит в прорезь, механизм внутри, фишка 42 выпадает в лоток

Представьте автомат, который удваивает числа. На его передней панели написано имя twice(int). Ниже есть прорезь, куда кладут фишку с целым числом, и лоток, откуда выпадает результат. Внутри стоит механизм, который и делает работу.

Чтобы пользоваться автоматом, механизм видеть не нужно: по панели понятно, как он называется, что в него класть и что из него выйдет. Функция в C++ устроена так же:

  • панель — это первая строка функции: тип результата, имя и параметры;
  • механизм — это тело в фигурных скобках.

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

C++
#include <iostream>

int main() {
    std::cout << twice(21) << '\n';  // ✗
}

int twice(int x) {
    return x * 2;
}
TEXT
error: use of undeclared identifier 'twice'

У twice один параметр — переменная x из объявления функции, которая получает значение при вызове. Аргумент — значение, которое передают в вызове: в twice(21) аргумент — 21, а параметр — x. На автомате параметр — это прорезь, а аргумент — фишка, которую в неё кладут.

Можно перенести twice выше main, и ошибка исчезнет. Но если функция определена в другом файле, переносить нечего — о ней нужно сообщить компилятору иначе.

Объявление

Объявление сообщает компилятору имя функции, типы её параметров и тип результата. Это панель автомата без механизма: тела у объявления нет, вместо него стоит точка с запятой.

C++
int twice(int x);

Имена параметров в объявлении можно не писать: int twice(int); значит то же самое. Компилятору нужны только типы, а имя x подсказывает читателю, что передаётся в функцию. В определении имя тоже можно не писать, если тело параметр не использует.

Определение — это объявление вместе с телом в фигурных скобках, поэтому функцию, определённую выше main, отдельно объявлять не нужно. Объявить функцию можно несколько раз, а определить — только один.

Вызов до определения

Объявление выше main решает задачу: вызов компилируется, а тело может стоять в конце файла.

C++
#include <iostream>

int twice(int x);

int main() {
    std::cout << twice(21) << '\n';  // 42
}

int twice(int x) {
    return x * 2;
}

Чтобы проверить вызов twice(21), компилятору хватает объявления: функция принимает int и возвращает int. Если тело лежит в другом .cpp-файле, компоновщик свяжет вызов с этим телом. На этом основаны заголовочные файлы: в них пишут объявления, а определения — в .cpp-файлах. Так устроен и #include <iostream>: он вставляет в файл объявления из стандартной библиотеки.

Частая ошибка

Функцию объявили, а определить забыли. Компилятор ошибки не видит, и сборка останавливается на следующем шаге:

C++
int twice(int x);

int main() {
    return twice(21);  // ✗
}
TEXT
undefined reference to `twice(int)'

Это сообщение компоновщика, а не компилятора: объявления хватило, чтобы проверить вызов, но тела twice нет ни в одном файле программы. Здесь аналогия с автоматом хромает: настоящую панель без механизма видно сразу, а программа с таким объявлением компилируется, и пустоту замечает только компоновщик. В задачах курса то же сообщение выглядит иначе: undefined symbol: twice(int).

Та же ошибка появится, если определение не совпадёт с объявлением по типам параметров. int twice(long x) — уже другая функция, и объявленная twice(int) снова останется без тела.

Главное

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

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом