← Назад к вопросам

Что такое перегрузка?

1.3 Junior🔥 181 комментариев
#ООП и проектирование#Язык C++

Комментарии (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, но требует осторожности при использовании, чтобы код оставался понятным.