연산자 오버로딩 기초
연산자 오버로딩의 개념과 목적
연산자 오버로딩은 C++에서 제공하는 강력한 기능으로, 사용자 정의 타입에 대해 기존 연산자의 동작을 재정의할 수 있게 해줍니다.
이를 통해 사용자 정의 타입을 기본 타입처럼 직관적으로 사용할 수 있게 됩니다.
주요 목적
- 코드의 가독성 향상
- 직관적인 인터페이스 제공
- 타입의 자연스러운 확장
연산자 오버로딩의 기본 문법
연산자 오버로딩은 두 가지 방식으로 구현할 수 있습니다.
- 멤버 함수로 구현
class MyClass {
public:
ReturnType operator+(const MyClass& other) const {
// 구현
}
};
- 전역 함수로 구현
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];
}
};
연산자 오버로딩 주의사항
- 연산자의 기본적인 의미를 유지해야 합니다.
- 모든 연산자를 오버로딩할 수 있는 것은 아닙니다. (예 :
.
,::
,?:
,sizeof
) - 새로운 연산자를 만들 수 없습니다.
- 연산자의 우선순위와 결합법칙은 변경할 수 없습니다.
- 단항 연산자는 멤버 함수로, 이항 연산자는 전역 함수로 구현하는 것이 일반적입니다.
- 입출력 연산자와 같이 왼쪽 피연산자를 수정해야 하는 경우는 반드시 전역 함수로 구현해야 합니다.
실습 : 유리수 클래스 구현
다음 요구사항을 만족하는 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;
}
연습 문제
String
클래스를 구현하고,+
연산자를 오버로딩하여 문자열 연결 기능을 구현하세요. 또한,[]
연산자를 오버로딩하여 개별 문자에 접근할 수 있도록 하세요.Vector
클래스를 구현하고, 벡터 간의 덧셈, 뺄셈, 스칼라 곱을 위한 연산자를 오버로딩하세요. 또한, 내적을 계산하는*
연산자를 구현하세요.Matrix
클래스를 구현하고, 행렬 간의 덧셈, 뺄셈, 곱셈을 위한 연산자를 오버로딩하세요.()
연산자를 오버로딩하여 특정 원소에 접근할 수 있도록 하세요.
참고자료
- Stroustrup, Bjarne. "The C++ Programming Language (4th Edition)"
- Meyers, Scott. "Effective C++ : 55 Specific Ways to Improve Your Programs and Designs"
- Josuttis, Nicolai M. "The C++ Standard Library: A Tutorial and Reference (2nd Edition)"
- C++ Reference : Operator overloading
- ISO C++ Core Guidelines : C.160 : Define operators primarily to mimic conventional usage