Overloading Operators in C++

Overload Basics

So, let's start with a few simple but important rules that will help you not get confused in this process:

  1. Don't overload everything. You can only overload those operators where it really makes sense. For example, you won't be able to overload the operator . (member access) or ?: (ternary operator).

  2. Follow your intuition. If you overload the operator +your colleagues will expect him to add rather than, say, subtract.

  3. Avoid ambiguity. Nobody wants to encounter code that behaves strangely. Therefore, try to create unambiguous behavior for overloaded operators.

The syntax for operator overloading looks like this:

return_type operator op (parameters);

Where return_type is the return type op is the operator you are overloading, and parameters – these are the parameters that the operator accepts.

Now let's see how we can overload arithmetic operators for a custom class. Let's imagine that there is a class Complexwhich describes complex numbers.

#include 

class Complex {
private:
    double real; // Действительная часть
    double imag; // Мнимая часть

public:
    // Конструктор
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // Перегрузка оператора сложения
    Complex operator+(const Complex& other) {
        return Complex(real + other.real, imag + other.imag);
    }

    // Перегрузка оператора вычитания
    Complex operator-(const Complex& other) {
        return Complex(real - other.real, imag - other.imag);
    }

    // Перегрузка оператора умножения
    Complex operator*(const Complex& other) {
        return Complex(real * other.real - imag * other.imag,
                       real * other.imag + imag * other.real);
    }

    // Перегрузка оператора деления
    Complex operator/(const Complex& other) {
        double denominator = other.real * other.real + other.imag * other.imag;
        return Complex((real * other.real + imag * other.imag) / denominator,
                       (imag * other.real - real * other.imag) / denominator);
    }

    // Метод для вывода комплексного числа
    void display() const {
        std::cout << real << " + " << imag << "i" << std::endl;
    }
};

int main() {
    Complex c1(3, 2);
    Complex c2(1, 7);
    
    Complex sum = c1 + c2;
    sum.display(); // 4 + 9i

    Complex diff = c1 - c2;
    diff.display(); // 2 - 5i

    Complex prod = c1 * c2;
    prod.display(); // -11 + 23i

    Complex quot = c1 / c2;
    quot.display(); // 0.47 - 0.29i

    return 0;
}

Also don't forget about special features like operator<<which make your life much easier.

If your class uses dynamic memory allocation, be sure to implement a copy constructor and assignment operator. This will help avoid double free issues that can happen if you forget about these mechanisms.

An example of a simple copy constructor:

Vector(const Vector& other) : data(other.data) {}

And an example assignment operator:

Vector& operator=(const Vector& other) {
    if (this != &other) {
        data = other.data; // Правильное копирование
    }
    return *this;
}

Deep overload

Now let's look at operator overloading for working with vectors and matrices. A vector is simply an ordered set of values, while a matrix is ​​a two-dimensional array. Let's create a class for working with vectors and overload operators for addition and subtraction.

Here is an example class Vector:

#include 
#include 

class Vector {
private:
    std::vector data;

public:
    Vector(int size) : data(size) {}

    // Перегрузка оператора сложения
    Vector operator+(const Vector& other) {
        if (data.size() != other.data.size()) {
            throw std::invalid_argument("Vectors must be of the same size");
        }
        Vector result(data.size());
        for (size_t i = 0; i < data.size(); ++i) {
            result.data[i] = data[i] + other.data[i];
        }
        return result;
    }

    // Перегрузка оператора вычитания
    Vector operator-(const Vector& other) {
        if (data.size() != other.data.size()) {
            throw std::invalid_argument("Vectors must be of the same size");
        }
        Vector result(data.size());
        for (size_t i = 0; i < data.size(); ++i) {
            result.data[i] = data[i] - other.data[i];
        }
        return result;
    }

    // Метод для вывода вектора
    void display() const {
        for (const auto& val : data) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    Vector v1(3);
    Vector v2(3);
    
    v1 = Vector({1.0, 2.0, 3.0});
    v2 = Vector({4.0, 5.0, 6.0});
    
    Vector sum = v1 + v2;
    sum.display(); // 5.0 7.0 9.0

    Vector diff = v1 - v2;
    diff.display(); // -3.0 -3.0 -3.0

    return 0;
}

Operator overloading << And >> is one of the most useful features in C++. It allows us to display classes on the screen and read them from a stream. Let's add these overloads to our class Vector.

Here's how you can implement it:

#include 

std::ostream& operator<<(std::ostream& os, const Vector& v) {
    os << "[ ";
    for (const auto& val : v.data) {
        os << val << " ";
    }
    os << "]";
    return os;
}

std::istream& operator>>(std::istream& is, Vector& v) {
    for (auto& val : v.data) {
        is >> val;
    }
    return is;
}

Now you can input and output vectors as easily as built-in data types:

Vector v(3);
std::cin >> v; // Ввод значений в вектор
std::cout << v; // Вывод вектора

Conclusion

Experiment with operator overloading in your projects and you will see how it greatly improves the structure of your code.


In conclusion, let me remind you about the open lessons that will be held in October as part of the “C++ Developer. Professional” course:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *