🔹 정의
복잡한 내부 구현은 감추고, 중요한 정보나 기능만 외부에 제공하는 것
🔹 예시 (C++ 코드)
class RemoteControl {
public:
void turnOnTV(); // 외부에 보여주는 동작 (인터페이스)
void turnOffTV();
};
🔹 정의
🔹 예시 (C++ 코드)
class BankAccount {
private:
int balance; // 외부에서 직접 접근 불가
public:
void deposit(int amount) {
if (amount > 0) balance += amount;
}
int getBalance() const {
return balance;
}
};
• balance는 외부에서 직접 접근 불가
• 대신 공식 함수(deposit, getBalance)를 통해서만 접근 가능
• → 데이터 무결성 유지, 오류 방지
🔹 정의
객체가 생성될 때 자동으로 호출되는 특수한 함수. 객체의 초기화를 담당.
class MyClass {
public:
MyClass(); // 생성자
};
🔹 생성자의 역할
🔹 생성자의 종류
기본 생성자
인자 없음 (또는 모두 기본값). MyClass();
매개변수 생성자
인자를 받아 초기화. MyClass(int x);
복사 생성자
다른 객체로부터 복사 MyClass(const MyClass& other);
이동 생성자 (C++11~)
자원을 이동 MyClass(MyClass&& other);
위임 생성자 (C++11~)
다른 생성자 호출 MyClass() : MyClass(0) {}
위임 생성자와 약간 성격은 다르지만 부모 생성자를 호출하여 값을 초기화하는 경우도 존재함.
🔹 정의
객체가 수명 종료 시 자동으로 호출되는 특수한 함수.
객체가 차지한 리소스를 해제하는 역할
class MyClass {
public:
~MyClass(); // 소멸자
};
🔹 소멸자의 역할
생성자는 “객체가 태어날 때 자동 호출 → 초기화 담당”
소멸자는 “객체가 죽을 때 자동 호출 → 정리 및 리소스 해제”
✔️ 생성자는 여러 개 정의 가능(오버로딩),
✔️ 소멸자는 반드시 하나만 존재
생성자의 본문이 실행되기 전에 멤버 변수들을 초기화하는 특별한 문법
class MyClass {
private:
int x;
public:
MyClass(int value) : x(value) {} // ← 이 부분이 초기화 리스트!
};
위 코드에서 : x(value) 부분이 바로 초기화 리스트입니다.
✅ 초기화 리스트를 쓰는 이유
class A {
const int x;
int& ref;
public:
A(int v) : x(v), ref(r) {} // ✅ 가능
// A(int v) { x = v; ref = r} // ❌ 컴파일 에러
};
초기화 리스트는 생성자 본문이 실행되기 전에 실행됩니다.
✅ 예시 1: 일반 변수 초기화
class Point {
private:
int x, y;
public:
Point(int x_, int y_) : x(x_), y(y_) {}
};
✅ 예시 2: const 멤버 변수 초기화
class Circle {
private:
const double pi;
public:
Circle() : pi(3.14159) {} // const는 무조건 초기화 리스트로!
};
✅ 예시 3: 참조형 멤버 변수 초기화
class Wrapper {
private:
int& ref;
public:
Wrapper(int& r) : ref(r) {}
};
✅ 예시 4: 클래스형 멤버의 생성자 호출
class Engine {
public:
Engine(int power) {
std::cout << "Engine(power=" << power << ") 생성됨\n";
}
};
class Car {
private:
Engine engine;
public:
Car() : engine(150) { // Engine 생성자 호출
std::cout << "Car 생성됨\n";
}
};
✅ 멤버 초기화 순서 주의!
❗️ 초기화 리스트에서 적어준 순서대로가 아닌,
→ 클래스 내 멤버 변수 정의 순서대로 초기화됨!
class Weird {
private:
int x;
int y;
public:
Weird(int a, int b) : y(b), x(a) {} // x가 먼저 초기화됨 (정의 순서!)
};
따라서 정의 순서와 초기화 리스트 순서를 일치시키는 것이 좋습니다 (버그 방지)
💡 언제 쓰나?
클래스 내에 static으로 선언된 멤버는 “객체마다 따로 존재하지 않고, 클래스 전체에서 공유되는 멤버”입니다.
즉, 클래스 이름을 통해 접근하고, 객체를 생성하지 않아도 사용할 수 있는 멤버가 됩니다.
✅ static 멤버 변수 (정적 멤버 변수)
🔹 특징
클래스이름::변수명으로 접근 가능🔹 선언 & 정의 예시
class Counter {
public:
static int count; // 선언만 함
};
int Counter::count = 0; // 클래스 외부에서 정의 & 초기화
🔹 특징
🔹 예시
class Math {
public:
static int add(int a, int b) {
return a + b;
}
};
int result = Math::add(3, 4); // 객체 없이 호출 가능
✅ 예시: 객체 수 카운트
#include <iostream>
class Person {
private:
static int count; // 객체 수 카운트용
public:
Person() {
count++;
}
~Person() {
count--;
}
static int getCount() {
return count;
}
};
int Person::count = 0; // 반드시 클래스 밖에서 정의!
int main() {
Person p1, p2;
std::cout << "현재 객체 수: " << Person::getCount() << '\n';
}
✅ 출력: 현재 객체 수: 2
✅ static 멤버의 사용 목적
✅ C++17부터는 inline static 가능
class Config {
public:
inline static int version = 1; // C++17 이상부터 선언과 동시에 초기화 가능
};
✅ 주의할 점
💬 보너스
자신과 같은 타입의 객체를 인자로 받아, 자기 자신을 그 객체의 복사본으로 초기화하는 생성자
🔹 문법
ClassName(const ClassName& other);
✅ 언제 호출되나? (복사 생성자 호출 시점)
Class a = b; 또는 Class a(b);func(obj);return obj;✅ 기본 복사 생성자 (컴파일러가 자동 생성)
class A {
public:
int x;
};
A a1;
A a2 = a1; // 복사 생성자 자동 호출됨
✅ 사용자 정의 복사 생성자가 필요한 경우
주로 동적 메모리를 사용하는 클래스에서 직접 구현해야 함
예시: 얕은 복사 문제
class MyArray {
private:
int* data;
public:
MyArray(int size) {
data = new int[size];
}
~MyArray() {
delete[] data;
}
};
MyArray a1(5);
MyArray a2 = a1; // ❌ 얕은 복사 → data 포인터 주소만 복사됨 → double delete 오류 발생 가능
→ a1과 a2가 같은 data를 가리키게 되어, 소멸자에서 중복 해제 문제 발생
✅ 사용자 정의 복사 생성자 예시 (깊은 복사)
class MyArray {
private:
int* data;
int size;
public:
MyArray(int s) : size(s) {
data = new int[size];
}
// 복사 생성자 (깊은 복사)
MyArray(const MyArray& other) : size(other.size) {
data = new int[size];
for (int i = 0; i < size; ++i)
data[i] = other.data[i];
}
~MyArray() {
delete[] data;
}
};
this 포인터는 클래스의 멤버 함수 내부에서 암묵적으로 사용 가능한 포인터로, 자기 자신 객체의 주소를 가리킵니다.
즉, 멤버 함수가 호출된 객체 자신을 가리켜요.
✅ 예시
class MyClass {
private:
int value;
public:
void setValue(int value)
{
this->value = value; // 멤버변수 value ← 매개변수 value 구분
}
};
🔸 여기서 this->value는 멤버 변수, value는 매개변수
✅ 언제 사용하나?
return *this; ← 자기 객체 리턴return *this; 해서 a.set().do().log(); 가능연산자 오버로딩은 C++에서 기호(+, -, == 등)를 클래스에 맞게 재정의하는 기능입니다.
즉, 객체도 일반 타입처럼 연산자 사용이 가능하도록 만들어줍니다.
✅ 예시: + 연산자 오버로딩
class Point {
private:
int x, y;
public:
Point(int a, int b) : x(a), y(b) {}
// + 연산자 오버로딩
Point operator+(const Point& other) const {
return Point(x + other.x, y + other.y);
}
void show() const {
std::cout << "(" << x << ", " << y << ")\n";
}
};
int main() {
Point p1(1, 2), p2(3, 4);
Point p3 = p1 + p2;
p3.show(); // 출력: (4, 6)
}
✅ 연산자 오버로딩 문법
리턴타입 클래스이름 :: operator기호 (매개변수)
예: Point operator+(const Point&);
this는 오버로딩 함수 내부에서 자기 객체의 멤버에 접근하거나,
자기 자신을 리턴할 때 매우 자주 사용됩니다.
class MyClass {
public:
MyClass& operator=(const MyClass& other) {
// 자기 자신에 대한 할당 방지
if (this != &other) {
// 멤버 복사
}
return *this; // 자기 자신을 리턴 → 연속 대입 가능
}
};
*this를 리턴하면 a = b = c; 같은 연속 대입이 가능해집니다.
C++에서 friend는 객체지향의 정보 은닉(encapsulation)을 선택적으로 해제할 수 있는 예외적인 키워드예요.
즉, 특정 클래스나 함수에 “내 private/protected 멤버에 접근해도 돼!” 라고 허용해주는 역할을 합니다.
클래스의 멤버가 아님에도 불구하고, 그 클래스의 private/protected 멤버에 접근할 수 있게 허용하는 키워드입니다.
사용할 수 있는 대상:
- 비멤버 함수
- 다른 클래스
- 멤버 함수 (다른 클래스의)
✅ 1. friend 함수 예시 (입출력 연산자 오버로딩)
#include <iostream>
class Point {
private:
int x, y;
public:
Point(int x_, int y_) : x(x_), y(y_) {}
// friend 함수 선언
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
// friend 함수 정의 (클래스 밖)
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
✅ operator<<는 Point의 멤버가 아니지만, friend로 선언되었기 때문에 private 멤버인 x, y에 접근 가능
✅ 2. friend 클래스 예시
class Secret {
private:
int code = 1234;
friend class Hacker; // 🔓 Hacker는 Secret의 private 멤버에 접근 가능
};
class Hacker {
public:
void steal(const Secret& s) {
std::cout << "훔친 코드: " << s.code << '\n';
}
};
⚠️ 사용 시 주의점
기존 클래스(부모, 기반 클래스)의 속성과 기능(멤버 변수, 함수)을
새로운 클래스(자식, 파생 클래스)가 물려받아 재사용하는 객체지향 개념입니다.
상속을 통해 중복을 줄이고, 공통 기능은 부모 클래스에,
세부 기능은 자식 클래스에 넣어서 코드를 효율적으로 관리할 수 있어요.
✅ 기본 문법
class 부모클래스 {
// 공통 멤버들
};
class 자식클래스 : [접근지정자] 부모클래스 {
// 자식만의 멤버들
};
예:
class Animal {
public:
void eat() { std::cout << "냠냠\n"; }
};
class Dog : public Animal {
public:
void bark() { std::cout << "멍멍!\n"; }
};
→ Dog는 Animal의 기능을 상속받음
→ Dog 객체는 eat()도, bark()도 사용 가능!
✅ 상속의 접근 지정자 (public / protected / private 상속)
👉 이건 **“부모의 멤버들이 자식 클래스에서 어떤 수준으로 보이느냐”**를 결정합니다.
🔹 1. public 상속 → 가장 일반적이고 권장
class A {
public:
void show() {}
protected:
void protect() {}
private:
void hide() {}
};
class B : public A {
// A의 public → 그대로 public
// A의 protected → 그대로 protected
// A의 private → 접근 불가
};
🔹 2. protected 상속 → 외부엔 숨기되, 자식과 손자, 그 아래 모두 접근 가능
class B : protected A {
// A의 public → protected로 바뀜
// A의 protected → 그대로 protected
// A의 private → 접근 불가
};
🔹 3. private 상속 → 거의 캡슐화 목적
class B : private A {
// A의 public → private으로 바뀜
// A의 protected → private으로 바뀜
// A의 private → 접근 불가
};