B. OPP 3대 요소
1. 상속성 (inheritance)
a. 상속성이란?
🌟 부모 class로부터 매소드와 변수를 상속받는 것
👉 코드의 재사용성 때문에 매우 중요하다
b. 상속의 기본 형태
⏳ 예제
#include <iostream>
using namespace std;
class Player {
public:
void Move() { }
void Die() { }
public:
int _hp;
int _attack;
int _defence;
};
class Mage : public Player {
public :
int _mp;
};
int main()
{
Mage m1;
m1._hp = 10; //Player의 hp를 상속받아 사용
}
🎨 도식화
c. Is-A vs Has-A
🌟 상속 구조인지 아닌지 판단할 때 사용하기 좋다 (Is-A 구조가 상속구조)
⏳ 예제
class Night : public Player //Player는 Night 이다 가 성립하기에 상속구조 OK
class Night : public Inventory //Night는 Inventory다가 성립 X 따라서 상속 구조가 X
👉 이 구조를 잘 생각해서 코드를 작성해야 함
d. 포인터와 상속
⏳ 예제 : 플레이어가 전투하는 경우
void Fight(Player* p1, Player* p2) {
p1->_attack -= p2->_defence;
}
int main()
{
Knight k1;
Mage m1;
Fight(&k1, &m1); //형변환이 이루어짐
}
위와 같이 플레이어가 전투하는 경우를 예를 들 수 있음
기사와 법사가 싸우는 경우 등 모든 직업의 경우의 수를 따지고 함수를 작성하게 되면 너무 비효율적
👉 따라서 플레이어의 클래스를 주소값으로 받아 형변환 하고 안에 함수를 작성함
💥 대신 아래와 같이 법사와 기사의 고유 멤버 변수는 사용할 수 없음
💥 플레이어는 기사입니까?
이런 코드는 생기지 않음 (애초에 오류)
메모리 구조를 잘 생각하면서 인스턴스를 생성해야 함!!
e. 상속 계층의 종류
플레이어가 전투하는 경우 이외에도
아래와 같이 다양한 상속 관계에서 포인터를 활용한 함수를 작성할 수 있음
대표적인 클래스의 상속구조는 아래와 같음! (참조해서 코드를 작성하도록 하자)
f. 상속 접근 지정자
다음 세대한테 부모님의 유산을 어떻게 물려줄지 결정
1) public : 모두 물려줌
2) protected : 내 상속을 보호받는 형태로 물려줌 (public -> protected)
3) private : 나까지만 상속 (public, protected -> private)
⏳ 예제 : 상속 접근 지정자
class Car {
public:
void Move() {}
protected:
void Dance() {}
private:
void Drive() {}
//멤버 변수도 동일하게 멤버 제어 지시자 사용 가능
public:
int _hp;
int _attack;
int _defence;
};
class SuperCar : private Car {
public:
int Test() {
Move();
Dance();
}
};
class UltraCar : private SuperCar {
public:
int Test() {
Move(); //에러
Dance(); //에러
}
};
📝 예제 설명
SuperCar에서 Car에 대한 상속을 private으로 받아
SuperCar를 상속하는 UltraCar는 더이상 상속을 사용할 수 없음
📌 사용하는 일은 거의 없으니 알아두기만 하자!
g. 다중 상속 (인터페이스)
여러가지 문제 때문에 아래와 같이 여러개의 상속을 하지는 않음
그대신 '인터페이스'를 이용해 다중 상속을 지원함
똑같이 class를 이용하지만 클래스 명 앞에 I를 붙이고, 가상 함수로 생성해 메소드의 오류를 방지함
2. 은닉성 (data hiding)
a. 은닉성이란?
캡슐화(encapsulation) 이라고도 함
🌟 클래스를 정의할 때 데이터와 해당 데이터를 조작하는 함수(메서드)를 하나의 단위로 묶어서 외부로부터 숨기는 개념
데이터를 보호하고 클래스의 내부 구현을 감춤으로써 객체의 상태를 안전하게 유지하고 객체 간의 상호 작용을 제어하는 데 도움
ex) 자동차로 예를 들면 엔진, 전기선 등..
은닉성을 구현하는 주요 기능은 접근 제어 지시자와 Getter / Setter 메서드가 있음
a. 접근 제어 지시자 : public
공개적, 어떤 부분에서든 멤버에 접근 가능
b. 접근 제어 지시자 : private
개인적, 클래스 내부에서만 접근 가능, 외부에서 직접 접근 X
c. 접근 제어 지시자 : protected
클래스 내부 및 파생 클래스에서 접근 가능, 외부에서 직접 접근 X
👉 즉, public과 private의 중간. 우리 class만 이용 가능하다!!
⏳ 예제 : 접근 제어 지시자 사용
class Car {
public:
void Move() {}
protected:
void Dance() {}
private:
void Drive() {}
//멤버 변수도 동일하게 멤버 제어 지시자 사용 가능
public:
int _hp;
int _attack;
int _defence;
};
class SuperCar : public Car {
public:
int Test(){
Move();
Dance();
Drive(); //private이라 접근 X
}
};
d. Setter / Getter
멤버 변수와 함수 모두에 접근하고 조작할 수 있는 접근 제어 지시자와는 다르게
🌟 오로지 멤버 변수만 접근하고 조작하기 위해 public 메서드 사용
- Getter : 값의 설정 / Setter : 값의 제어
🧡 멤버 변수를 직접 수정하지 않는 이유
만일 v1.hp += 10이라고 직접 수정을 하게 되면
1) 코드가 방대해 졌을 때 코드의 흐름을 알기 어렵고
2) hp값이 수정되었을 때 일어날 수 있는 이벤트를 한꺼번에 조작하기 어렵다
3. 다형성 (polymorphism)
a. 다형성이란?
🌟 다양한 객체가 동일한 인터페이스를 공유하면서 다른 구현을 가질 수 있는 능력
- 코드의 재사용성과 유지 보수성을 높이는 데 도움
- 프로그램의 유연성을 향상
- 객체 지향 프로그래밍의 핵심 원칙 중 하나인 "인터페이스에 의한 프로그래밍"을 실현하는 데 도움
다형성은 가상함수와 오버로딩 두 가지 방법으로 구현
b. 가상 함수(Virtual Functions)
🌟 기본 클래스(부모 클래스)에서 선언되고 파생 클래스(자식 클래스)에서 재정의(오버라이딩)될 수 있는 함수
- 가상 함수를 사용하면 기본 클래스의 포인터 또는 참조를 통해 파생 클래스의 객체에 접근할 때 실제로 호출되는 함수가 파생 클래스에 정의된 함수로 결정
- 다형성을 이용하여 동일한 인터페이스를 가진 객체들이 다른 동작을 할 수 있게함
⏳ 예제 : 가상 함수 (오버라이딩)
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
// 기본 도형 그리기 로직
cout << "Shape" << endl;
}
};
class Circle : public Shape {
public:
virtual void draw() override {
// 원 그리기 로직
cout << "Circle" << endl;
}
};
class Rectangle : public Shape {
public:
virtual void draw() override {
// 사각형 그리기 로직
cout << "Rectangle" << endl;
}
};
int main()
{
Shape* shape1 = new Circle();
Shape* shape2 = new Rectangle();
shape1->draw(); // Circle 클래스의 draw()가 호출됨
shape2->draw(); // Rectangle 클래스의 draw()가 호출됨
delete shape1;
delete shape2;
return 0;
}
✨ 실행결과
Circle
Rectangle
💙 override
함수의 상속 여부를 알기 위해 사용하는 키워드 (있으면 상속받은 것)
1) 정적 바인딩(static binding)
🌟 컴파일 시점에 결정. 일반 함수가 이용하는 방식
위 예제에서 아래 코드를 아래와 같이 수정한다면
virtual void draw() ➡ void draw()
정적 바인딩의 결과에 따라 Circle과 Rec으로 생성했음에도 불구하고
최종적으로 변한 Shape의 값으로 단순하게 생성됨
2) 동적 바인딩
🌟 런타임 (실행 시점) 에 결정. 가상 함수가 이용하는 방식
👉 즉 virtual 키워드를 붙이면 실질적으로 어떤 타입을 만들었는지 따라서 원본 대상의 함수를 호출함
🎨 도식화
💙 소멸자도 무조건 Virtual!
만약 자식 클래스가 부모 클래스로 형 변환 후 소멸하게 되어 소멸자를 불러오는 경우
자식 클래스에 남아있던 멤버 변수의 데이터가 남아 메모리 누수가 발생하게 된다
💥 따라서 각 클래스의 소멸자를 만들어주기 위해 Virtual을 사용해야 함!!
c. 함수 오버로딩(Function Overloading)
🌟 동일한 이름의 함수가 다른 매개변수 목록을 가질 수 있도록 허용 = 함수 이름의 재사용
- 호출 할 때 인수의 유형 및 개수에 따라 호출 함수가 결정
- 이를 통해 다른 시그니처를 가진 함수를 하나의 이름으로 그룹화 해 사용할 수 있으므로 다형성의 한 형태임
⏳ 예제
void print(int value) {
std::cout << "정수: " << value << std::endl;
}
void print(double value) {
std::cout << "실수: " << value << std::endl;
}
int main() {
print(5); // int를 인수로 받는 함수가 호출됨
print(3.14); // double을 인수로 받는 함수가 호출됨
return 0;
}
d. 순수 가상 함수를 사용하는 추상 클래스
사실 위 예제에서 살펴보듯, Shape 클래스나 Player 클래스처럼 독단적으로 사용할 일이 없는 부모 클래스가 있음
🌟 이런 부모 클래스의 인스턴스 생성을 막기 위해 추상 클래스를 사용함 (추상클래스는 순수 가상함수로 생성)
- 자식 클래스에서 가상 함수를 꼭 구현해줘야 함
- 💥 가상 클래스를 선언해주면 부모클래스 인스턴스를 생성할 수 없음!
📘 문법
class 부모클래스 {
public:
virtual void 메소드() = 0; //순수 가상 함수 ➡ 이거사용
virtual void 메소드() abstract; //옛날버전
}
'프로그래밍 언어 > C++' 카테고리의 다른 글
[게임 프로그래머 입문 올인원] 객체지향 : 연산자 오버로딩 (32강) (0) | 2023.09.12 |
---|---|
[게임 프로그래머 입문 올인원] 객체지향 : 멤버 변수 초기화 (31강) (0) | 2023.09.11 |
[게임 프로그래머 입문 올인원] 객체지향 : 객체지향 개론 (27강) (2) | 2023.09.11 |
C++ 문자열 총정리 (0) | 2023.09.09 |
[자료형(data type)] C++ 파생형 포인터 총정리 (0) | 2023.09.09 |