icon안동민 개발노트

연산자 오버로딩 기초


연산자 오버로딩의 개념과 목적

 연산자 오버로딩은 C++에서 제공하는 강력한 기능으로, 사용자 정의 타입에 대해 기존 연산자의 동작을 재정의할 수 있게 해줍니다.

 이를 통해 사용자 정의 타입을 기본 타입처럼 직관적으로 사용할 수 있게 됩니다.

 주요 목적

  1. 코드의 가독성 향상
  2. 직관적인 인터페이스 제공
  3. 타입의 자연스러운 확장

연산자 오버로딩의 기본 문법

 연산자 오버로딩은 두 가지 방식으로 구현할 수 있습니다.

  1. 멤버 함수로 구현
class MyClass {
public:
    ReturnType operator+(const MyClass& other) const {
        // 구현
    }
};
  1. 전역 함수로 구현
ReturnType operator+(const MyClass& lhs, const MyClass& rhs) {
    // 구현
}

주요 연산자 오버로딩 예제

 산술 연산자 오버로딩

class Complex {
private:
    double real, imag;
 
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
 
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
 
    Complex operator-(const Complex& other) const {
        return Complex(real - other.real, imag - other.imag);
    }
 
    Complex operator*(const Complex& other) const {
        return Complex(real * other.real - imag * other.imag,
                       real * other.imag + imag * other.real);
    }
 
    Complex operator/(const Complex& other) const {
        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);
    }
 
    // 단항 연산자 (부호 변경)
    Complex operator-() const {
        return Complex(-real, -imag);
    }
};

 입출력 연산자 오버로딩

#include <iostream>
 
class Point {
private:
    int x, y;
 
public:
    Point(int x = 0, int y = 0) : x(x), y(y) {}
 
    friend std::ostream& operator<<(std::ostream& os, const Point& p) {
        os << "(" << p.x << ", " << p.y << ")";
        return os;
    }
 
    friend std::istream& operator>>(std::istream& is, Point& p) {
        char ch;
        is >> ch >> p.x >> ch >> p.y >> ch;
        return is;
    }
};

 비교 연산자 오버로딩

class Person {
private:
    std::string name;
    int age;
 
public:
    Person(const std::string& n, int a) : name(n), age(a) {}
 
    bool operator==(const Person& other) const {
        return (name == other.name) && (age == other.age);
    }
 
    bool operator!=(const Person& other) const {
        return !(*this == other);
    }
 
    bool operator<(const Person& other) const {
        return (age < other.age) || ((age == other.age) && (name < other.name));
    }
 
    bool operator>(const Person& other) const {
        return other < *this;
    }
 
    bool operator<=(const Person& other) const {
        return !(other < *this);
    }
 
    bool operator>=(const Person& other) const {
        return !(*this < other);
    }
};

 증감 연산자 오버로딩

class Counter {
private:
    int count;
 
public:
    Counter(int c = 0) : count(c) {}
 
    // 전위 증가
    Counter& operator++() {
        ++count;
        return *this;
    }
 
    // 후위 증가
    Counter operator++(int) {
        Counter temp = *this;
        ++count;
        return temp;
    }
 
    // 전위 감소
    Counter& operator--() {
        --count;
        return *this;
    }
 
    // 후위 감소
    Counter operator--(int) {
        Counter temp = *this;
        --count;
        return temp;
    }
 
    int getCount() const { return count; }
};

 첨자 연산자 오버로딩

class Array {
private:
    int* data;
    size_t size;
 
public:
    Array(size_t s) : size(s), data(new int[s]) {}
    ~Array() { delete[] data; }
 
    int& operator[](size_t index) {
        if (index >= size) throw std::out_of_range("Index out of bounds");
        return data[index];
    }
 
    const int& operator[](size_t index) const {
        if (index >= size) throw std::out_of_range("Index out of bounds");
        return data[index];
    }
};

연산자 오버로딩 주의사항

  1. 연산자의 기본적인 의미를 유지해야 합니다.
  2. 모든 연산자를 오버로딩할 수 있는 것은 아닙니다. (예 : ., ::, ?:, sizeof)
  3. 새로운 연산자를 만들 수 없습니다.
  4. 연산자의 우선순위와 결합법칙은 변경할 수 없습니다.
  5. 단항 연산자는 멤버 함수로, 이항 연산자는 전역 함수로 구현하는 것이 일반적입니다.
  6. 입출력 연산자와 같이 왼쪽 피연산자를 수정해야 하는 경우는 반드시 전역 함수로 구현해야 합니다.

실습 : 유리수 클래스 구현

 다음 요구사항을 만족하는 Rational 클래스를 구현해보세요.

  • 분자와 분모를 표현하는 멤버 변수
  • 사칙연산 (+, -, *, /) 연산자 오버로딩
  • 비교 연산자 (==, !=, <, >, <=, >=) 오버로딩
  • 입출력 연산자 (<<, >>) 오버로딩
  • 단항 연산자 (+, -) 오버로딩
  • 복합 대입 연산자 (+=, -=, *=, /=) 오버로딩
#include <iostream>
#include <numeric>  // for std::gcd
 
class Rational {
private:
    int numerator;
    int denominator;
 
    void simplify() {
        int gcd = std::gcd(numerator, denominator);
        numerator /= gcd;
        denominator /= gcd;
        if (denominator < 0) {
            numerator = -numerator;
            denominator = -denominator;
        }
    }
 
public:
    Rational(int num = 0, int denom = 1) : numerator(num), denominator(denom) {
        if (denominator == 0) throw std::invalid_argument("Denominator cannot be zero");
        simplify();
    }
 
    // 사칙연산
    Rational operator+(const Rational& other) const {
        return Rational(numerator * other.denominator + other.numerator * denominator,
                        denominator * other.denominator);
    }
 
    Rational operator-(const Rational& other) const {
        return Rational(numerator * other.denominator - other.numerator * denominator,
                        denominator * other.denominator);
    }
 
    Rational operator*(const Rational& other) const {
        return Rational(numerator * other.numerator, denominator * other.denominator);
    }
 
    Rational operator/(const Rational& other) const {
        if (other.numerator == 0) throw std::invalid_argument("Division by zero");
        return Rational(numerator * other.denominator, denominator * other.numerator);
    }
 
    // 단항 연산자
    Rational operator+() const {
        return *this;
    }
 
    Rational operator-() const {
        return Rational(-numerator, denominator);
    }
 
    // 복합 대입 연산자
    Rational& operator+=(const Rational& other) {
        *this = *this + other;
        return *this;
    }
 
    Rational& operator-=(const Rational& other) {
        *this = *this - other;
        return *this;
    }
 
    Rational& operator*=(const Rational& other) {
        *this = *this * other;
        return *this;
    }
 
    Rational& operator/=(const Rational& other) {
        *this = *this / other;
        return *this;
    }
 
    // 비교 연산자
    bool operator==(const Rational& other) const {
        return (numerator == other.numerator) && (denominator == other.denominator);
    }
 
    bool operator!=(const Rational& other) const {
        return !(*this == other);
    }
 
    bool operator<(const Rational& other) const {
        return (numerator * other.denominator) < (other.numerator * denominator);
    }
 
    bool operator>(const Rational& other) const {
        return other < *this;
    }
 
    bool operator<=(const Rational& other) const {
        return !(other < *this);
    }
 
    bool operator>=(const Rational& other) const {
        return !(*this < other);
    }
 
    // 입출력 연산자
    friend std::ostream& operator<<(std::ostream& os, const Rational& r) {
        os << r.numerator;
        if (r.denominator != 1) os << "/" << r.denominator;
        return os;
    }
 
    friend std::istream& operator>>(std::istream& is, Rational& r) {
        char slash;
        is >> r.numerator >> slash >> r.denominator;
        if (r.denominator == 0) throw std::invalid_argument("Denominator cannot be zero");
        r.simplify();
        return is;
    }
};
 
int main() {
    Rational r1(1, 2), r2(1, 3);
    std::cout << r1 << " + " << r2 << " = " << (r1 + r2) << std::endl;
    std::cout << r1 << " - " << r2 << " = " << (r1 - r2) << std::endl;
    std::cout << r1 << " * " << r2 << " = " << (r1 * r2) << std::endl;
    std::cout << r1 << " / " << r2 << " = " << (r1 / r2) << std::endl;
 
    r1 += r2;
    std::cout << "After r1 += r2, r1 = " << r1 << std::endl;
 
    std::cout << "Enter a rational number (e.g., 3/4): ";
    Rational r3;
    std::cin >> r3;
    std::cout << "You entered: " << r3 << std::endl;
 
    return 0;
}

연습 문제

  1. String 클래스를 구현하고, + 연산자를 오버로딩하여 문자열 연결 기능을 구현하세요. 또한, [] 연산자를 오버로딩하여 개별 문자에 접근할 수 있도록 하세요.
  2. Vector 클래스를 구현하고, 벡터 간의 덧셈, 뺄셈, 스칼라 곱을 위한 연산자를 오버로딩하세요. 또한, 내적을 계산하는 * 연산자를 구현하세요.
  3. Matrix 클래스를 구현하고, 행렬 간의 덧셈, 뺄셈, 곱셈을 위한 연산자를 오버로딩하세요. () 연산자를 오버로딩하여 특정 원소에 접근할 수 있도록 하세요.

 참고자료