클래스 심화
클래스 심화¶
1. 연산자 오버로딩¶
클래스에 대해 연산자의 동작을 정의할 수 있습니다.
기본 구문¶
반환타입 operator연산자(매개변수) {
// 구현
}
산술 연산자 오버로딩¶
#include <iostream>
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// + 연산자 (멤버 함수)
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
// - 연산자
Vector2D operator-(const Vector2D& other) const {
return Vector2D(x - other.x, y - other.y);
}
// * 연산자 (스칼라 곱)
Vector2D operator*(double scalar) const {
return Vector2D(x * scalar, y * scalar);
}
void print() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};
int main() {
Vector2D v1(3, 4);
Vector2D v2(1, 2);
Vector2D v3 = v1 + v2; // operator+ 호출
v3.print(); // (4, 6)
Vector2D v4 = v1 - v2;
v4.print(); // (2, 2)
Vector2D v5 = v1 * 2;
v5.print(); // (6, 8)
return 0;
}
비교 연산자 오버로딩¶
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
Person(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;
}
};
int main() {
Person p1("Alice", 25);
Person p2("Alice", 25);
Person p3("Bob", 30);
std::cout << std::boolalpha;
std::cout << (p1 == p2) << std::endl; // true
std::cout << (p1 != p3) << std::endl; // true
std::cout << (p1 < p3) << std::endl; // true
return 0;
}
복합 대입 연산자¶
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// += 연산자
Vector2D& operator+=(const Vector2D& other) {
x += other.x;
y += other.y;
return *this;
}
// -= 연산자
Vector2D& operator-=(const Vector2D& other) {
x -= other.x;
y -= other.y;
return *this;
}
};
증감 연산자¶
#include <iostream>
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// 전위 증가 (++c)
Counter& operator++() {
++value;
return *this;
}
// 후위 증가 (c++)
Counter operator++(int) { // int는 구분용 더미
Counter temp = *this;
++value;
return temp;
}
int getValue() const { return value; }
};
int main() {
Counter c(5);
std::cout << (++c).getValue() << std::endl; // 6
std::cout << (c++).getValue() << std::endl; // 6
std::cout << c.getValue() << std::endl; // 7
return 0;
}
입출력 연산자 (friend)¶
#include <iostream>
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// << 연산자 (friend 함수)
friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
// >> 연산자
friend std::istream& operator>>(std::istream& is, Vector2D& v) {
is >> v.x >> v.y;
return is;
}
};
int main() {
Vector2D v(3, 4);
std::cout << "벡터: " << v << std::endl; // 벡터: (3, 4)
Vector2D v2;
std::cout << "x y 입력: ";
std::cin >> v2;
std::cout << "입력: " << v2 << std::endl;
return 0;
}
함수 호출 연산자 ()¶
#include <iostream>
class Adder {
private:
int base;
public:
Adder(int b) : base(b) {}
// () 연산자 - 함수처럼 호출 가능
int operator()(int x) const {
return base + x;
}
int operator()(int x, int y) const {
return base + x + y;
}
};
int main() {
Adder add10(10);
std::cout << add10(5) << std::endl; // 15
std::cout << add10(5, 3) << std::endl; // 18
return 0;
}
첨자 연산자 []¶
#include <iostream>
#include <stdexcept>
class SafeArray {
private:
int* data;
int size;
public:
SafeArray(int s) : size(s) {
data = new int[size]();
}
~SafeArray() {
delete[] data;
}
// [] 연산자 (읽기/쓰기)
int& operator[](int index) {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
// const 버전 (읽기 전용)
const int& operator[](int index) const {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
};
int main() {
SafeArray arr(5);
arr[0] = 10;
arr[1] = 20;
std::cout << arr[0] << std::endl; // 10
std::cout << arr[1] << std::endl; // 20
// arr[10] = 100; // 예외 발생!
return 0;
}
2. 복사 생성자¶
객체를 복사할 때 호출됩니다.
기본 복사¶
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
Person(std::string n, int a) : name(n), age(a) {
std::cout << "일반 생성자" << std::endl;
}
// 복사 생성자
Person(const Person& other) : name(other.name), age(other.age) {
std::cout << "복사 생성자" << std::endl;
}
};
int main() {
Person p1("Alice", 25); // 일반 생성자
Person p2(p1); // 복사 생성자
Person p3 = p1; // 복사 생성자
return 0;
}
얕은 복사 vs 깊은 복사¶
#include <iostream>
#include <cstring>
class String {
private:
char* data;
int length;
public:
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 깊은 복사 생성자
String(const String& other) {
length = other.length;
data = new char[length + 1]; // 새 메모리 할당
strcpy(data, other.data); // 내용 복사
std::cout << "깊은 복사" << std::endl;
}
~String() {
delete[] data;
}
void print() const {
std::cout << data << std::endl;
}
};
int main() {
String s1("Hello");
String s2 = s1; // 깊은 복사
s1.print(); // Hello
s2.print(); // Hello
return 0;
}
3. 복사 대입 연산자¶
이미 존재하는 객체에 다른 객체를 대입할 때 호출됩니다.
#include <iostream>
#include <cstring>
class String {
private:
char* data;
int length;
public:
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// 복사 대입 연산자
String& operator=(const String& other) {
if (this != &other) { // 자기 대입 체크
delete[] data; // 기존 메모리 해제
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this;
}
~String() {
delete[] data;
}
void print() const {
std::cout << data << std::endl;
}
};
int main() {
String s1("Hello");
String s2("World");
s2 = s1; // 복사 대입 연산자
s1.print(); // Hello
s2.print(); // Hello
return 0;
}
4. 이동 시맨틱 (C++11)¶
임시 객체의 리소스를 "이동"하여 불필요한 복사를 방지합니다.
이동 생성자¶
#include <iostream>
#include <cstring>
#include <utility> // std::move
class String {
private:
char* data;
int length;
public:
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
std::cout << "일반 생성자" << std::endl;
}
// 복사 생성자
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
std::cout << "복사 생성자" << std::endl;
}
// 이동 생성자
String(String&& other) noexcept {
data = other.data; // 포인터만 복사
length = other.length;
other.data = nullptr; // 원본 무효화
other.length = 0;
std::cout << "이동 생성자" << std::endl;
}
~String() {
delete[] data;
}
void print() const {
if (data) std::cout << data << std::endl;
else std::cout << "(empty)" << std::endl;
}
};
int main() {
String s1("Hello"); // 일반 생성자
String s2 = s1; // 복사 생성자
String s3 = std::move(s1); // 이동 생성자
s1.print(); // (empty) - 이동됨
s2.print(); // Hello
s3.print(); // Hello
return 0;
}
이동 대입 연산자¶
// 이동 대입 연산자
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data; // 기존 메모리 해제
data = other.data; // 포인터 이동
length = other.length;
other.data = nullptr; // 원본 무효화
other.length = 0;
}
std::cout << "이동 대입" << std::endl;
return *this;
}
Rule of Five¶
리소스를 관리하는 클래스는 5가지를 모두 정의해야 합니다:
- 소멸자
- 복사 생성자
- 복사 대입 연산자
- 이동 생성자
- 이동 대입 연산자
class Resource {
public:
Resource(); // 생성자
~Resource(); // 1. 소멸자
Resource(const Resource& other); // 2. 복사 생성자
Resource& operator=(const Resource& other); // 3. 복사 대입
Resource(Resource&& other) noexcept; // 4. 이동 생성자
Resource& operator=(Resource&& other) noexcept; // 5. 이동 대입
};
5. static 멤버¶
클래스의 모든 객체가 공유하는 멤버입니다.
static 멤버 변수¶
#include <iostream>
class Counter {
private:
static int count; // 선언
public:
Counter() {
count++;
}
~Counter() {
count--;
}
static int getCount() { // static 멤버 함수
return count;
}
};
// 정의 (클래스 외부)
int Counter::count = 0;
int main() {
std::cout << "개수: " << Counter::getCount() << std::endl; // 0
Counter c1;
Counter c2;
std::cout << "개수: " << Counter::getCount() << std::endl; // 2
{
Counter c3;
std::cout << "개수: " << Counter::getCount() << std::endl; // 3
}
std::cout << "개수: " << Counter::getCount() << std::endl; // 2
return 0;
}
static 멤버 함수¶
#include <iostream>
class Math {
public:
static int add(int a, int b) {
return a + b;
}
static int multiply(int a, int b) {
return a * b;
}
static const double PI;
};
const double Math::PI = 3.14159;
int main() {
// 객체 없이 호출
std::cout << Math::add(3, 5) << std::endl; // 8
std::cout << Math::multiply(3, 5) << std::endl; // 15
std::cout << Math::PI << std::endl; // 3.14159
return 0;
}
6. friend¶
클래스의 private 멤버에 접근할 수 있는 외부 함수나 클래스입니다.
friend 함수¶
#include <iostream>
class Box {
private:
double width;
public:
Box(double w) : width(w) {}
// friend 함수 선언
friend void printWidth(const Box& b);
friend double addWidths(const Box& a, const Box& b);
};
// friend 함수 정의
void printWidth(const Box& b) {
std::cout << "Width: " << b.width << std::endl; // private 접근 가능
}
double addWidths(const Box& a, const Box& b) {
return a.width + b.width;
}
int main() {
Box b1(10), b2(20);
printWidth(b1); // Width: 10
std::cout << "합: " << addWidths(b1, b2) << std::endl; // 합: 30
return 0;
}
friend 클래스¶
#include <iostream>
class Engine {
private:
int horsepower;
public:
Engine(int hp) : horsepower(hp) {}
friend class Car; // Car가 Engine의 private에 접근 가능
};
class Car {
private:
Engine engine;
public:
Car(int hp) : engine(hp) {}
void showHorsepower() const {
std::cout << "마력: " << engine.horsepower << std::endl;
}
};
int main() {
Car car(300);
car.showHorsepower(); // 마력: 300
return 0;
}
7. explicit¶
암시적 형변환을 방지합니다.
#include <iostream>
class Fraction {
private:
int numerator;
int denominator;
public:
// explicit 없으면 Fraction f = 5; 가능
explicit Fraction(int n, int d = 1) : numerator(n), denominator(d) {}
void print() const {
std::cout << numerator << "/" << denominator << std::endl;
}
};
void printFraction(const Fraction& f) {
f.print();
}
int main() {
Fraction f1(3, 4);
f1.print(); // 3/4
Fraction f2(5); // 명시적 호출 OK
f2.print(); // 5/1
// Fraction f3 = 5; // 에러! explicit
// printFraction(10); // 에러! 암시적 변환 불가
printFraction(Fraction(10)); // OK: 명시적 변환
return 0;
}
8. 실습 예제: 완전한 String 클래스¶
#include <iostream>
#include <cstring>
#include <utility>
class String {
private:
char* data;
size_t length;
public:
// 기본 생성자
String() : data(nullptr), length(0) {
data = new char[1];
data[0] = '\0';
}
// 문자열 생성자
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 복사 생성자
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
// 이동 생성자
String(String&& other) noexcept
: data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
}
// 소멸자
~String() {
delete[] data;
}
// 복사 대입
String& operator=(const String& other) {
if (this != &other) {
delete[] data;
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this;
}
// 이동 대입
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
}
return *this;
}
// + 연산자
String operator+(const String& other) const {
char* newData = new char[length + other.length + 1];
strcpy(newData, data);
strcat(newData, other.data);
String result(newData);
delete[] newData;
return result;
}
// == 연산자
bool operator==(const String& other) const {
return strcmp(data, other.data) == 0;
}
// [] 연산자
char& operator[](size_t index) {
return data[index];
}
const char& operator[](size_t index) const {
return data[index];
}
// << 연산자
friend std::ostream& operator<<(std::ostream& os, const String& s) {
return os << s.data;
}
size_t size() const { return length; }
const char* c_str() const { return data; }
};
int main() {
String s1("Hello");
String s2(" World");
String s3 = s1 + s2;
std::cout << s3 << std::endl; // Hello World
std::cout << "길이: " << s3.size() << std::endl; // 11
return 0;
}
9. 요약¶
| 개념 | 설명 |
|---|---|
| 연산자 오버로딩 | 클래스에 연산자 정의 |
| 복사 생성자 | T(const T&) |
| 복사 대입 | T& operator=(const T&) |
| 이동 생성자 | T(T&&) |
| 이동 대입 | T& operator=(T&&) |
static |
클래스 공유 멤버 |
friend |
private 접근 허용 |
explicit |
암시적 변환 방지 |
다음 단계¶
09_Inheritance_and_Polymorphism.md에서 상속과 다형성을 배워봅시다!