Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Перегрузка функций (Function Overloading)
Перемузка — это возможность определить несколько функций с одинаковым именем, но разными параметрами. Компилятор выбирает нужную функцию на основе типов и количества аргументов (это часть compile-time полиморфизма).
Основной принцип
// Одна функция
void print(int x) {
std::cout << "int: " << x << "\n";
}
// Другая функция, но с тем же именем
void print(double x) {
std::cout << "double: " << x << "\n";
}
// Третья функция
void print(std::string x) {
std::cout << "string: " << x << "\n";
}
// При вызове компилятор выбирает нужную версию
print(42); // Вызовет первую (int)
print(3.14); // Вызовет вторую (double)
print("hello"); // Вызовет третью (string)
Перегрузка по количеству параметров
void func() {
std::cout << "Нет параметров\n";
}
void func(int x) {
std::cout << "Один параметр: " << x << "\n";
}
void func(int x, int y) {
std::cout << "Два параметра: " << x << ", " << y << "\n";
}
func(); // Вызовет первую
func(5); // Вызовет вторую
func(5, 10); // Вызовет третью
Перегрузка операторов
Перемузка работает и с операторами:
class Complex {
public:
double real, imag;
// Перегруженный оператор +
Complex operator+(const Complex& other) const {
return {real + other.real, imag + other.imag};
}
// Перегруженный оператор *
Complex operator*(const Complex& other) const {
return {
real * other.real - imag * other.imag,
real * other.imag + imag * other.real
};
}
// Перегруженный оператор <<
friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
};
int main() {
Complex a{3, 4};
Complex b{1, 2};
Complex sum = a + b; // Использует operator+
Complex prod = a * b; // Использует operator*
std::cout << sum << "\n"; // Использует operator<<
}
Разрешение перегрузки (Overload Resolution)
Компилятор использует правила разрешения перегрузки для выбора подходящей функции:
void foo(int x); // Функция 1
void foo(double x); // Функция 2
void foo(int x, int y); // Функция 3
foo(5); // Точное совпадение → Функция 1
foo(3.14); // Точное совпадение → Функция 2
foo(5, 10); // Точное совпадение → Функция 3
foo(5.0); // Точное совпадение → Функция 2
foo(A); // char можно конвертировать в int → Функция 1
Проблема: неоднозначность
void func(int x);
void func(double x);
func(5.5); // OK, вызовет func(double)
func(5); // OK, вызовет func(int)
// Но:
func(5L); // ОШИБКА! long может быть преобразовано и в int, и в double
// неоднозначность → компилятор не знает, какую выбрать
Перегрузка с const
class MyClass {
int value;
public:
// Non-const версия
int& getValue() {
return value; // Можно изменять
}
// Const версия
const int& getValue() const {
return value; // Нельзя изменять
}
};
MyClass obj;
const MyClass const_obj;
obj.getValue() = 100; // OK, используется non-const версия
// const_obj.getValue() = 100; // Ошибка! используется const версия
Перегрузка с шаблонами
// Обычная функция
void print(const std::string& s) {
std::cout << "String: " << s << "\n";
}
// Шаблонная функция
template<typename T>
void print(const T& x) {
std::cout << "Generic: " << x << "\n";
}
print(std::string("hello")); // Вызовет обычную (она точнее)
print(42); // Вызовет шаблонную
print(3.14); // Вызовет шаблонную
Перегрузка в иерархии классов
class Base {
public:
virtual void func(int x) {
std::cout << "Base::func(int): " << x << "\n";
}
};
class Derived : public Base {
public:
// Переопределение
void func(int x) override {
std::cout << "Derived::func(int): " << x << "\n";
}
// Перегрузка (новая функция)
void func(double x) {
std::cout << "Derived::func(double): " << x << "\n";
}
};
Derived d;
d.func(5); // Вызовет Derived::func(int)
d.func(3.14); // Вызовет Derived::func(double)
Base* p = &d;
p->func(5); // Вызовет Derived::func(int) через virtual
// p->func(3.14); // ОШИБКА! Тип указателя Base не знает про func(double)
Практические примеры
Конструкторы (очень частая перегрузка):
class Vector {
int size;
int* data;
public:
// Конструктор с размером
Vector(int s) : size(s), data(new int[s]) {}
// Конструктор копирования
Vector(const Vector& other) : size(other.size), data(new int[size]) {
std::copy(other.data, other.data + size, data);
}
// Конструктор перемещения (C++11)
Vector(Vector&& other) noexcept : size(other.size), data(other.data) {
other.data = nullptr;
}
~Vector() { delete[] data; }
};
Vector v1(10); // Первый конструктор
Vector v2 = v1; // Конструктор копирования
Vector v3 = std::move(v2); // Конструктор перемещения
Когда использовать перегрузку
Хорошо:
- Операции с разными типами (convert, parse, print)
- Конструкторы с разными параметрами
- Операторы с разными типами операндов
Плохо:
- Функции с разной логикой (используй разные имена)
- Слишком много перегруженных версий (усложняет код)
// Плохо — разная логика, одинаковое имя
void process(int x) { /* математическая обработка */ }
void process(std::string x) { /* строковая обработка */ }
// Лучше: processInt(), processString()
// Хорошо — логика одинакова, просто разные типы
void print(int x);
void print(double x);
void print(const std::string& x);
Перегрузка — мощный инструмент для создания удобных API, но требует осторожности при использовании, чтобы код оставался понятным.