Company
교육 철학

LV01 연산자 오버로딩

C++ 연산자 오버로딩 완전 가이드

연산자 오버로딩이란?

기본 개념

*연산자 오버로딩(Operator Overloading)**은 기본 제공 연산자(+, -, *, / 등)를 사용자 정의 클래스에서 새롭게 정의하여 사용할 수 있게 하는 C++의 기능입니다.
핵심 특징:
대부분의 기본 제공 연산자 함수는 전역적으로 또는 클래스 단위로 다시 정의 가능
오버로드된 연산자는 함수로 구현됩니다
연산자 함수의 이름은 operator + 연산자 기호 형태
예시:
더하기 연산자 오버로드: operator+
더하기/대입 연산자 오버로드: operator+=
비교 연산자 오버로드: operator==

오버로드 가능한 연산자

재정의 가능한 연산자 표

연산자
이름
타입
연산자
이름
타입
,
Comma
이진
!
논리 NOT
단항
!=
같지 않음
이진
%
나머지
이진
%=
나머지 대입
이진
&
비트 AND
이진
&
주소 참조
단항
&&
논리 AND
이진
&=
비트 AND 대입
이진
( )
함수 호출
( )
캐스트 연산자
단항
*
곱하기
이진
*
포인터 역참조
단항
*=
곱하기 대입
이진
+
더하기
이진
+
단항 더하기
단항
++
증가
단항
+=
더하기 대입
이진
-
빼기
이진
-
단항 부정
단항
--
감소
단항
-=
빼기 대입
이진
->
멤버 선택
이진
->*
멤버 포인터 선택
이진
/
나누기
이진
/=
나누기 대입
이진
<
보다 작음
이진
<<
왼쪽 시프트
이진
<<=
왼쪽 시프트 대입
이진
<=
작거나 같음
이진
=
대입
이진
==
같음
이진
>
보다 큼
이진
>=
크거나 같음
이진
>>
오른쪽 시프트
이진
>>=
오른쪽 시프트 대입
이진
[ ]
배열 첨자
^
배타적 OR
이진
^=
배타적 OR 대입
이진
|
비트 OR
이진
|=
비트 OR 대입
이진
~
1의 보수
단항
delete
삭제
new
생성

오버로드 불가능한 연산자

다음 연산자들은 오버로드할 수 없습니다:
. (멤버 접근)
:: (스코프 해결)
?: (삼항 연산자)
sizeof (크기 연산자)
.* (멤버 포인터 접근)

기본적인 연산자 오버로딩 예제

복소수 클래스 예제

#include <iostream> using namespace std; class Complex { public: // 생성자 Complex(double r = 0, double i = 0) : mRe(r), mIm(i) {} // 소멸자 ~Complex() {} // 멤버 함수들 // 더하기 연산자 오버로딩 (멤버 함수 버전) Complex operator+(const Complex& other) const { return Complex(mRe + other.mRe, mIm + other.mIm); } // 빼기 연산자 오버로딩 Complex operator-(const Complex& other) const { return Complex(mRe - other.mRe, mIm - other.mIm); } // 곱하기 연산자 오버로딩 Complex operator*(const Complex& other) const { return Complex(mRe * other.mRe - mIm * other.mIm, mRe * other.mIm + mIm * other.mRe); } // 대입 연산자 오버로딩 Complex& operator=(const Complex& other) { if (this != &other) // 자기 자신 대입 방지 { mRe = other.mRe; mIm = other.mIm; } return *this; } // 복합 대입 연산자 오버로딩 Complex& operator+=(const Complex& other) { mRe += other.mRe; mIm += other.mIm; return *this; } // 비교 연산자 오버로딩 bool operator==(const Complex& other) const { return (mRe == other.mRe) && (mIm == other.mIm); } bool operator!=(const Complex& other) const { return !(*this == other); } // 단항 연산자 오버로딩 Complex operator-() const // 부호 반전 { return Complex(-mRe, -mIm); } // 전위 증가 연산자 Complex& operator++() { ++mRe; ++mIm; return *this; } // 후위 증가 연산자 (int 매개변수로 구분) Complex operator++(int) { Complex temp = *this; ++mRe; ++mIm; return temp; } // 출력 함수 void Display() const { cout << mRe; if (mIm >= 0) cout << " + "; else cout << " - "; cout << abs(mIm) << "i" << endl; } // getter 함수들 double GetReal() const { return mRe; } double GetImaginary() const { return mIm; } private: // 멤버 변수들 double mRe; // 실수부 double mIm; // 허수부 }; // 전역 함수로 연산자 오버로딩 (스트림 연산자) ostream& operator<<(ostream& os, const Complex& c) { os << c.GetReal(); if (c.GetImaginary() >= 0) os << " + "; else os << " - "; os << abs(c.GetImaginary()) << "i"; return os; } istream& operator>>(istream& is, Complex& c) { double real, imag; cout << "실수부를 입력하세요: "; is >> real; cout << "허수부를 입력하세요: "; is >> imag; c = Complex(real, imag); return is; } // 사용 예제 int main() { cout << "=== 복소수 연산자 오버로딩 예제 ===" << endl << endl; // 복소수 생성 Complex a(1.2, 3.4); Complex b(5.6, 7.8); cout << "a = " << a << endl; cout << "b = " << b << endl << endl; // 사칙연산 Complex c = a + b; cout << "a + b = " << c << endl; Complex d = a - b; cout << "a - b = " << d << endl; Complex e = a * b; cout << "a * b = " << e << endl << endl; // 복합 대입 연산 Complex f = a; f += b; cout << "f = a; f += b; f = " << f << endl << endl; // 비교 연산 cout << "a == b: " << (a == b ? "true" : "false") << endl; cout << "a != b: " << (a != b ? "true" : "false") << endl << endl; // 단항 연산 Complex g = -a; cout << "-a = " << g << endl << endl; // 증가 연산 Complex h = a; cout << "h = a = " << h << endl; cout << "++h = " << ++h << endl; cout << "h++ = " << h++ << endl; cout << "h = " << h << endl; return 0; }
C++
복사

연산자 오버로딩 구현 방법

멤버 함수 vs 전역 함수

멤버 함수로 구현:
class Vector2D { public: // 생성자 Vector2D(float x = 0, float y = 0) : mX(x), mY(y) {} // 소멸자 ~Vector2D() {} // 멤버 함수로 구현된 연산자 Vector2D operator+(const Vector2D& other) const { return Vector2D(mX + other.mX, mY + other.mY); } // 스칼라 곱셈 (멤버 함수) Vector2D operator*(float scalar) const { return Vector2D(mX * scalar, mY * scalar); } void Print() const { cout << "(" << mX << ", " << mY << ")" << endl; } private: // 멤버 변수들 float mX; float mY; }; // 전역 함수로 구현된 연산자 Vector2D operator*(float scalar, const Vector2D& vec) { return Vector2D(vec.mX * scalar, vec.mY * scalar); // private 접근 불가! }
C++
복사
전역 함수로 구현 (friend 사용):
class Vector2D { public: // 생성자 Vector2D(float x = 0, float y = 0) : mX(x), mY(y) {} // 소멸자 ~Vector2D() {} // friend 전역 함수 선언 friend Vector2D operator*(float scalar, const Vector2D& vec); friend ostream& operator<<(ostream& os, const Vector2D& vec); void Print() const { cout << "(" << mX << ", " << mY << ")" << endl; } private: // 멤버 변수들 float mX; float mY; }; // friend 전역 함수 구현 Vector2D operator*(float scalar, const Vector2D& vec) { return Vector2D(vec.mX * scalar, vec.mY * scalar); // private 접근 가능! } ostream& operator<<(ostream& os, const Vector2D& vec) { os << "(" << vec.mX << ", " << vec.mY << ")"; return os; }
C++
복사

구현 방법 선택 기준

멤버 함수로 구현해야 하는 경우:
= (대입 연산자)
[] (배열 첨자 연산자)
> (멤버 접근 연산자)
() (함수 호출 연산자)
전역 함수로 구현하는 것이 좋은 경우:
<<, >> (스트림 연산자)
교환법칙이 필요한 연산 (예: scalar * vector)
대칭적 연산 (두 피연산자가 동등한 경우)

고급 연산자 오버로딩 예제

배열 클래스 구현

#include <iostream> using namespace std; class IntArray { public: // 생성자 IntArray(int size = 10) : mSize(size) { mData = new int[mSize]; for (int i = 0; i < mSize; i++) { mData[i] = 0; } } // 복사 생성자 IntArray(const IntArray& other) : mSize(other.mSize) { mData = new int[mSize]; for (int i = 0; i < mSize; i++) { mData[i] = other.mData[i]; } } // 소멸자 ~IntArray() { delete[] mData; } // 대입 연산자 IntArray& operator=(const IntArray& other) { if (this != &other) { delete[] mData; mSize = other.mSize; mData = new int[mSize]; for (int i = 0; i < mSize; i++) { mData[i] = other.mData[i]; } } return *this; } // 배열 첨자 연산자 (읽기) const int& operator[](int index) const { if (index >= 0 && index < mSize) { return mData[index]; } // 예외 처리 (간단한 버전) cout << "Index out of bounds!" << endl; return mData[0]; } // 배열 첨자 연산자 (쓰기) int& operator[](int index) { if (index >= 0 && index < mSize) { return mData[index]; } // 예외 처리 (간단한 버전) cout << "Index out of bounds!" << endl; return mData[0]; } // 비교 연산자 bool operator==(const IntArray& other) const { if (mSize != other.mSize) return false; for (int i = 0; i < mSize; i++) { if (mData[i] != other.mData[i]) return false; } return true; } // 함수 호출 연산자 int operator()(int index) const { return (index >= 0 && index < mSize) ? mData[index] : -1; } int GetSize() const { return mSize; } // friend 함수 선언 friend ostream& operator<<(ostream& os, const IntArray& arr); private: // 멤버 변수들 int* mData; int mSize; }; // 스트림 출력 연산자 ostream& operator<<(ostream& os, const IntArray& arr) { os << "["; for (int i = 0; i < arr.mSize; i++) { os << arr.mData[i]; if (i < arr.mSize - 1) os << ", "; } os << "]"; return os; } // 사용 예제 int main() { cout << "=== 배열 클래스 연산자 오버로딩 ===" << endl << endl; IntArray arr1(5); IntArray arr2(5); // 배열 첨자 연산자 사용 for (int i = 0; i < 5; i++) { arr1[i] = i * 10; arr2[i] = i * 5; } cout << "arr1 = " << arr1 << endl; cout << "arr2 = " << arr2 << endl << endl; // 비교 연산자 사용 cout << "arr1 == arr2: " << (arr1 == arr2 ? "true" : "false") << endl; // 함수 호출 연산자 사용 cout << "arr1(2) = " << arr1(2) << endl; cout << "arr1(10) = " << arr1(10) << endl; // 범위 초과 return 0; }
C++
복사

연산자 오버로딩 설계 원칙

좋은 설계 원칙

1.
직관성: 연산자의 의미가 직관적이어야 함
2.
일관성: 관련 연산자들이 일관된 동작을 해야 함
3.
효율성: 불필요한 복사나 메모리 할당 피하기
4.
안전성: 예외 상황에 대한 적절한 처리

권장사항

const 정확성 유지:
// 좋은 예 Complex operator+(const Complex& other) const; // const 멤버 함수 bool operator==(const Complex& other) const; // const 멤버 함수 // 나쁜 예 Complex operator+(Complex other); // 불필요한 복사
C++
복사
참조 반환 활용:
// 좋은 예 - 체이닝 가능 Complex& operator+=(const Complex& other); Complex& operator=(const Complex& other); // 나쁤 예 - 체이닝 불가 void operator+=(const Complex& other);
C++
복사
자기 대입 처리:
Complex& operator=(const Complex& other) { if (this != &other) // 자기 대입 체크 { // 대입 로직 } return *this; }
C++
복사
연산자 오버로딩은 클래스를 기본 타입처럼 자연스럽게 사용할 수 있게 해주는 강력한 기능입니다. 하지만 과도한 사용은 코드의 가독성을 해칠 수 있으므로, 적절한 상황에서만 사용하는 것이 중요합니다.