연산자 오버로딩(Operator Overloading)
연산자 오버로딩이란?
- C++에서 기존연산자(+, -, *, / 등)의 동작을 사용자가 정의한 데이터 타입(클래스 또는 구조체)에서도 사용할 수 있도록 재정의하는 기능을 의미
- 기본적으로 연산자는 내장 타입(int, float, double 등)에서만 동작하지만, 연산자 오버로딩을 통해 사용자 정의 타입에서도 연산자 기능을 활용할 수 있다
연산자 오버로딩의 필요성
- 객체 간 연산을 직관적으로 표현 가능
- 코드의 가독성을 높인다
- 복잡한 연산을 간결하게 처리
class ClassName {
public:
ReturnType operatorOp(const ClassName& other) {
// 연산자 동작 구현
}
};
오버로딩이 가능한 연산자
- + : 덧셈 연산자
- - : 뺄셈 연산자
- * : 곱셈 연산자
- / : 나눗셈 연산자
- % : 나머지 연산자
- +=, -=, *=, /=, %= : 복합 대입 연산자
- ++, -- : 증가 및 감소 연산자
- ==, !=, <, >, <=, >= : 비교 연산자
- &&, ! : 논리 연산자
- &, ^, ~, <<, >> : 비트 연산자
- [] : 배열 인덱스 연산자
- () : 함수 호출 연산자
- -> : 멤버 접근 연산자
오버로딩이 불가능한 연산자
- . : 멤버 접근 연산자
- :: : 범위 지정 연산자 (Scope resolution)
- sizeof : 크기 연산자
- typeid : 런타임 타입 연산자
- .* :멤버 포인터 연산자
- new, delete : 메모리 할당/해제 연산자 (단, new[], delete[]는 오버로딩 가능)
프렌드 연산자(Friend Operator Overloading)
- 일반적으로 연산자 오버로딩은 클래스의 멤버 함수로 구현할 수 있지만, 두 개의 다른 객체를 비교하거나 좌항이 사용자 정의 클래스가 아닐 때는 프렌드 함수를 사용해야 한다
#include <iostream>
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// Friend function으로 연산자 오버로딩
friend Complex operator+(const Complex& c1, const Complex& c2);
void display() { std::cout << real << " + " << imag << "i\n"; }
};
Complex operator+(const Complex& c1, const Complex& c2) {
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
int main() {
Complex c1(3, 4), c2(1, 2);
Complex c3 = c1 + c2;
c3.display(); // 출력: 4 + 6i
return 0;
}
필요성
- 두 개의 객체를 비교하는 연산자를 구현할 때(==, !=, <, > 등)
- 클래스 외부에서 오버로딩된 연산자로 접근해야 할 때
삽입 연산자(Insertion Operator Overloading)
- C++에서는 << 연산자를 활용하여 객체를 std::cout을 통해 출력할 수 있다
- 하지만 기본적으로 std::cout는 사용자 정의 클래스를 출력하는 방법을 모른다
- 이를 해결하기 위해 << 연산자를 오버로딩한다
#include <iostream>
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// << 연산자 오버로딩
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
};
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
int main() {
Complex c1(3, 4);
std::cout << "Complex number: " << c1 << std::endl; // Complex number: 3 + 4i
return 0;
}
필요성
- 사용자 정의 객체를 쉽게 출력할 수 있다
- 가독성이 향상된다
- 디버깅 시 객체의 상태를 출력하기 용이하다
상속(Inheritance)
상속이란?
- 기존 클래스를 확장하여 새로운 클래스를 생성하는 객체지향 프로그래밍의 핵심 개념 중 하나
- 코드의 재사용성을 높이고 유지보수를 용이하게 할 수 있다
- 기존 클래스(부모 클래스 또는 기본 클래스)의 속성과 동작을 새로운 클래스(자식 클래스 또는 파생 클래스)에서 물려받아 사용할 수 있으며, 필요에 따라 일부 기능을 수정하거나 확장할 수 있다
상속의 장점
- 코드 재사용성 증가 : 동일한 코드 중복을 줄이고 유지보수를 쉽게 한다
- 구조화된 프로그램 : 계층적 구조를 통해 클래스를 체계적으로 설계 가능
- 확장성 제공 : 기존 클래스를 기반으로 기능을 확장할 수 있다
상속의 생성자
- 상속을 사용할 때, 부모 클래스의 생성자가 먼저 실행되고, 그 후 자식 클래스의 생성자가 실행
- 부모 클래스의 속성이 먼저 초기화되어야 자식 클래스에서 이를 사용할 수 있기 때문
#include <iostream>
using namespace std;
class Parent {
public:
Parent() { cout << "부모 생성자 실행\n"; }
};
class Child : public Parent {
public:
Child() { cout << "자식 생성자 실행\n"; }
};
int main() {
Child obj;
return 0;
}
출력 예)
부모 생성자 실행
자식 생성자 실행
isA vs hasA
- 객체지향 프로그래밍에서는 상속(Inheritance)과 구성(Composition)을 구별하는 것이 중요하다
- 일반적으로 isA관계는 상속을 사용하고, hasA관계는 멤버 변수를 활용하여 구성한다
isA 관계(상속)
- 'A는 B이다'라는 관계를 의미
- class Car : public Vehicle -> 자동차는 차량이다
- class Dog : public Animal -> 개는 동물이다
hasA 관계(구성)
- 'A는 B를 포함한다'라는 관계를 의미
- class Car { Engine engine; } -> 자동차는 엔진을 포함한다
- class House { Room room; } -> 집은 방을 포함한다
접근 제한자의 효과
- C++에서 상속을 할 때, 부모 클래스의 멤버 변수와 함수의 접근 범위는 접근 제한자에 의해 결정
- public : 모든 곳에서 접근 가능(상속 가능)
- protected : 상속받은 클래스에서 접근 가능(상속 가능)
- private : 외부 및 자식 클래스에서 접근 불가능(상속 불가능)
class Parent {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class Child : public Parent {
public:
void access() {
publicVar = 10; // 접근 가능
protectedVar = 20; // 접근 가능
// privateVar = 30; // 오류 발생: private 멤버는 접근 불가
}
};
상속 타입
- 상속의 유형은 여러가지가 있으며, 대표적인 4가지 유형이 있다
단일 상속(Simple/Single)
계층적 상속(Hierarchical)
- 하나의 부모 클래스를 여러 자식 클래스가 상속
다단계 상속(Multilevel)
다중 삭속(Multiple)
상속 방법
- 상속 방법에 따라 부모 클래스의 멤버 접근 가능 여부가 달라진다

일반화와 전문화(Generalization and Specialization)
- 상속을 설계할 때 일반화와 전문화 개념을 이해하는 것이 중요하다
일반화(상향식 접근법)
- 여러 개의 하위 클래스로부터 공통적인 특징을 추출하여 상위 클래스를 만드는 방식
- 예제 : Car, Bike, Bus 클래스에서 공통 속성을 추출하여 Vehicle 클래스를 생성
전문화(하향식 접근법)
- 기존 상위 클래스를 기반으로 세분화된 하위 클래스를 만드는 방식
- 예제 : Employee 클래스를 기반으로 Manager, Engineer 등의 하위 클래스를 생성
Base Class Pointer Derived Class Object
Base Class Pointer Derived Class Object란?
- 부모 클래스의 포인터를 사용하여 자식 클래스 객체를 가리키는 것
- 다형성과 동적 바인딩을 활용할 수 있다
- 이는 C++의 업캐스팅 개념을 활용한 것이다
#include <iostream>
using namespace std;
class Parent {
public:
void show() { cout << "부모 클래스 함수\n"; }
};
class Child : public Parent {
public:
void show() { cout << "자식 클래스 함수\n"; }
};
int main() {
Child obj;
Parent* ptr = &obj; // 부모 클래스 포인터로 자식 객체를 가리킴
ptr->show(); // 부모 클래스 함수 호출 (정적 바인딩)
return 0;
}
출력 예)
부모 클래스 함수
- Parent* ptr = &obj; 를 통해 부모 클래스 포인터가 자식 클래스 객체를 가리킨다
- 그러나 ptr -> show(); 호출 시 부모 클래스의 함수가 실행된다
- 이는 정적 바인딩 때문
가상 함수를 이용한 동적 바인딩
- 부모 클래스의 함수를 virtual로 선언하면, 자식 클래스의 함수가 실행된다
- 이를 동적 바인딩(Dynamic Binding) 또는 런타임 다형성(Runtime Polymorphism)이라고 한다
#include <iostream>
using namespace std;
class Parent {
public:
virtual void show() { cout << "부모 클래스 함수\n"; } // 가상 함수
};
class Child : public Parent {
public:
void show() override { cout << "자식 클래스 함수\n"; } // 오버라이딩
};
int main() {
Child obj;
Parent* ptr = &obj; // 부모 클래스 포인터가 자식 객체를 가리킴
ptr->show(); // 자식 클래스 함수 호출 (동적 바인딩)
return 0;
}
출력 예)
자식 클래스 함수
- virtual void show() 로 선언하면, 객체가 가리키는 실제 클래스(자식 클래스)의 함수가 실행된다
- 이를 통해 다형성을 구현할 수 있다
업캐스팅과 다운 캐스팅
업캐스팅
- 부모 클래스 포인터로 자식 클래스 객체를 가리키는 것
- 안전한 방식이며, 동적 바인딩을 활용 가능
Parent* ptr = new Child(); // Upcasting (정상 작동)
다운캐스팅
- 자식 클래스 포인터가 부모 클래스 포인터를 다시 자식으로 변환하는 것
- static_cast 또는 dynamic_cast를 사용해야 하며, 잘못된 경우 런타임 오류가 발생할 수 있다
Child* cptr = static_cast<Child*>(ptr); // Downcasting
순수 가상 함수와 추상 클래스
- 추상 클래스(Abstract Class)란 한 개 이상의 순수 가상 함수(Pure Virtual Function)를 포함하는 클래스이다
- 추상 클래스를 상속받은 클래스는 반드시 가상함수를 구현해야 한다
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() = 0; // 순수 가상 함수
};
class Dog : public Animal {
public:
void sound() override { cout << "멍멍!\n"; }
};
int main() {
Animal* ptr = new Dog();
ptr->sound(); // 멍멍!
delete ptr;
return 0;
}
- virtual void sound() = 0; 선언으로 인해 Animal 클래스는 추상 클래스가 된다
- Dog 클래스는 sound() 함수를 반드시 구현해야 한다