Функции в C++: перегрузка и значения по умолчанию с нуля
Разбираем, как объявлять функции в C++, перегружать их по параметрам и задавать значения по умолчанию — с рабочими примерами и разбором частых ошибок компиляции.

К концу этого гайда вы напишете несколько функций, перегрузите одну из них по типам аргументов и научитесь задавать значения по умолчанию так, чтобы компилятор не ругался на неоднозначность. На всё уйдёт минут двадцать, если у вас уже установлен компилятор. Примеры проверены на GCC 13 и стандарте C++17 — они соберутся и на более новых версиях.
Что нужно до начала
Функция в C++ компилируется, а не выполняется на лету, как в Python. Поэтому вам нужен компилятор и умение запустить сборку.
- Компилятор: GCC (g++) 11+ или Clang 14+ на Linux/macOS, либо MSVC из Visual Studio 2022 на Windows.
- Проверка, что компилятор есть: в терминале выполните
g++ --version. Должна появиться строка видаg++ (GCC) 13.2.0. Если команда не найдена — компилятор не установлен или не в PATH. - Флаг стандарта: собирайте с
-std=c++17, иначе часть современного синтаксиса компилятор не примет. - Текстовый редактор — любой, от VS Code до Notepad++.
Всё, что ниже, кладите в один файл main.cpp и собирайте командой g++ -std=c++17 main.cpp -o app, а запускайте через ./app (или app.exe на Windows).
Шаг 1. Простейшая функция
Функция состоит из типа возвращаемого значения, имени, списка параметров в скобках и тела в фигурных скобках. Вот функция, которая складывает два целых числа:
#include <iostream>
// Возвращает сумму двух целых
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(2, 3);
std::cout << result << std::endl;
return 0;
}Соберите и запустите. В терминале должно напечататься 5. Если вместо этого компилятор выдаёт ошибку — переходите к разделу «Если не получилось».
Почему тип указывается дважды
Перед именем функции — тип того, что она вернёт (int). Перед каждым параметром — тип того, что она примет. C++ строго типизирован: компилятор должен знать типы заранее, чтобы проверить вызовы.
Шаг 2. Объявление и определение
Функцию можно сначала объявить (сообщить компилятору сигнатуру), а определить (написать тело) ниже. Это нужно, когда функция вызывается раньше, чем описана в файле.
#include <iostream>
// Объявление (прототип) — только сигнатура, точка с запятой вместо тела
int multiply(int a, int b);
int main() {
std::cout << multiply(4, 5) << std::endl;
return 0;
}
// Определение — тело функции
int multiply(int a, int b) {
return a * b;
}Напечатается 20. Обратите внимание: в объявлении имена параметров можно опустить, оставив только типы — int multiply(int, int);. Компилятору достаточно типов.
Шаг 3. Перегрузка функций
Перегрузка (overloading) — это несколько функций с одним именем, но разными списками параметров. Компилятор сам выбирает нужную по типам и количеству аргументов в месте вызова.
#include <iostream>
#include <string>
// Складываем целые
int combine(int a, int b) {
return a + b;
}
// Складываем строки — то же имя, другие типы
std::string combine(std::string a, std::string b) {
return a + b;
}
int main() {
std::cout << combine(2, 3) << std::endl; // выберет int-версию
std::cout << combine(std::string("foo"),
std::string("bar")) << std::endl; // выберет string-версию
return 0;
}Вывод: 5 на первой строке и foobar на второй. Компилятор смотрит на типы аргументов и подставляет подходящую функцию — это происходит на этапе компиляции, а не в рантайме.
Перегрузка различается по параметрам, но НЕ по типу возвращаемого значения. Две функции, отличающиеся только тем, что одна возвращаетint, а другаяdouble, при одинаковых параметрах компилятор считать разными не будет — это ошибка.
Что входит в сигнатуру для выбора перегрузки
- Количество параметров —
f(int)иf(int, int)различимы. - Типы параметров —
f(int)иf(double)различимы. - Порядок типов —
f(int, double)иf(double, int)различимы.
Шаг 4. Значения по умолчанию
Параметру можно задать значение, которое подставится, если при вызове аргумент не передали. Значение по умолчанию пишется в объявлении (или в определении, если объявления нет — но не в обоих сразу).
#include <iostream>
#include <string>
// step имеет значение по умолчанию 1
void printRange(int from, int to, int step = 1) {
for (int i = from; i <= to; i += step) {
std::cout << i << " ";
}
std::cout << std::endl;
}
int main() {
printRange(1, 5); // step возьмётся из умолчания = 1
printRange(1, 10, 2); // step = 2
return 0;
}Первый вызов напечатает 1 2 3 4 5, второй — 1 3 5 7 9. Значение по умолчанию сработало там, где третий аргумент не передали.
Правило про порядок
Параметры со значениями по умолчанию идут только в конце списка. Нельзя дать умолчание среднему параметру, оставив крайний справа без него — компилятор не поймёт, к какому аргументу относится переданное значение.
// Правильно: умолчания в хвосте
void f(int a, int b = 0, int c = 0);
// Ошибка компиляции: у b есть умолчание, у c нет
// void g(int a, int b = 0, int c);Где перегрузка и умолчания конфликтуют
Самая частая ловушка новичка — совместить перегрузку и значения по умолчанию так, что вызов становится неоднозначным.
void notify(int code);
void notify(int code, int level = 0); // опасно
// notify(42) — компилятор не знает,
// вызвать первую или вторую с умолчанием levelТакой код даст ошибку call of overloaded 'notify(int)' is ambiguous. Компилятор видит два одинаково подходящих варианта и отказывается выбирать. Решение — не смешивать: либо перегрузка, либо умолчание, но не для одной и той же сигнатуры.
| Механизм | Выбор происходит | Когда удобнее |
|---|---|---|
| Перегрузка | На этапе компиляции по типам | Разное поведение для разных типов аргументов |
| Значения по умолчанию | На этапе компиляции по числу аргументов | Один алгоритм, часть параметров опциональна |
Если не получилось
'add' was not declared in this scope— функция вызвана раньше, чем объявлена. Добавьте прототип передmainили перенесите определение выше вызова (Шаг 2).call of overloaded 'f(...)' is ambiguous— компилятор не может выбрать между перегрузками. Чаще всего виноват смешанный со значением по умолчанию вариант или неявное преобразование типов. Уберите конфликтующую перегрузку.redefinition of 'combine'— две функции с полностью одинаковой сигнатурой. Перегрузка требует разных параметров; разного возвращаемого типа недостаточно.default argument given for parameter ... more than once— значение по умолчанию указано и в объявлении, и в определении. Оставьте его только в одном месте, обычно в объявлении.error: 'cout' is not a member of 'std'— забыт#include <iostream>в начале файла.
Что дальше
Вы умеете объявлять функции, перегружать их и давать параметрам значения по умолчанию. Следующие темы, которые логично освоить в этом порядке: передача аргументов по ссылке (int& a) и по константной ссылке, чтобы не копировать большие объекты; затем — шаблоны функций (template), которые заменяют десяток перегрузок одной обобщённой функцией. Раздел про перегрузку операторов пока откладывайте: он опирается на классы, к которым стоит подойти отдельно. Точный набор правил разрешения перегрузок описан в справочнике cppreference — держите его под рукой, когда компилятор жалуется на неоднозначность.
Разбор научной статьи: Сложный текст — понятным языком, с терминами
Спросить за 12 ₽Источники: cppreference — Default arguments, cppreference — Overload resolution
Частые вопросы
У меня Visual Studio, а не g++. Примеры соберутся?
Можно ли перегрузить функцию только по возвращаемому типу?
Почему нельзя дать значение по умолчанию первому параметру, а второй оставить обычным?
Пример не собирается, ошибка про 'ambiguous'. Что делать?
Где именно писать значение по умолчанию — в объявлении или в определении?
В какой версии стандарта работают эти примеры?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.