C++에서 상속은 기존 클래스의 기능을 이어받아 새로운 클래스를 만드는 중요한 개념입니다. 마치 부모로부터 자식이 유전적인 특징을 물려받는 것처럼, 상속을 통해 기존 코드를 재사용하여 효율적인 개발이 가능합니다. 상속은 코드 재사용성을 높이고 유지보수를 용이하게 하여 프로그램을 더 간결하고 관리하기 쉽게 만듭니다.
상속의 장점
- 코드 재사용성:
- 부모 클래스에서 정의한 코드를 다시 작성하지 않고 재사용할 수 있음.
- 유지보수성:
- 공통된 기능은 부모 클래스에만 수정하면 됨.
- 계층적 구조 표현:
- 예: Animal → Dog, Cat처럼 공통된 특성과 행동을 공유.
- 부모 클래스(Base Class, Superclass): 다른 클래스에 의해 상속되는 클래스
- 자식 클래스(Derived Class, Subclass): 부모 클래스의 멤버 변수와 메소드를 물려받는 클래스
기본구조
class 부모클래스 {
// 부모 클래스의 멤버
};
class 자식클래스 : 접근제한자 부모클래스 {
// 자식 클래스의 멤버
};
ex)
class Animal {
public:
void speak() {
std::cout << "동물들은 울음소리가 있습니다." << std::endl;
}
};
class Dog : public Animal {
public:
void bark() {
std::cout << "멍멍" << std::endl;
}
};
class Cat : public Animal {
public:
void bark() {
std::cout << "냐옹" << std::endl;
}
};
Dog myDog;
myDog.speak(); // 출력: 동물들은 울음소리가 있습니다.
myDog.bark(); // 출력: 멍멍
Cat myCat;
myCat.speak(); // 출력: 동물들은 울음소리가 있습니다.
myCat.bark(); // 출력: 냐옹
접근 제어
부모 클래스의 멤버가 자식 클래스에서 어떻게 보이는지는 접근 제어 지정자에 따라 다릅니다.
- public 멤버: 어디서든 접근 가능
- protected 멤버: 부모 클래스와 자식 클래스 내부에서만 접근 가능
- private 멤버: 부모 클래스 내부에서만 접근 가능, 자식 클래스에서는 접근 불가능
접근제한자부모의 public 멤버부모의 protected 멤버부모의 private 멤버
public | 자식에게 public | 자식에게 protected | 접근 불가 |
protected | 자식에게 protected | 자식에게 protected | 접근 불가 |
private | 자식에게 private | 자식에게 private | 접근 불가 |
ex)
class Animal {
public:
void SetName(std::string n) {
name = n;
}
private:
std::string name;
};
Dog myDog;
myDog.SetName("Buddy"); // 가능
myDog.name = "Buddy"; // 오류: name은 private 멤버이기 때문에 직접 접근 불가능
public 상속
부모 클래스의 public 멤버는 자식 클래스에서도 public으로 사용됩니다.
#include <iostream>
using namespace std;
class Animal {
public:
void eat() {
cout << "This animal eats food." << endl;
}
};
class Dog : public Animal { // public 상속
public:
void bark() {
cout << "The dog barks." << endl;
}
};
int main() {
Dog myDog;
myDog.eat(); // 부모 클래스의 메서드 사용
myDog.bark(); // 자식 클래스의 메서드 사용
return 0;
}
protected 상속
부모 클래스의 public 멤버가 자식 클래스에서는 protected로 바뀝니다.
#include <iostream>
using namespace std;
class Animal {
public:
void eat() {
cout << "This animal eats food." << endl;
}
};
class Dog : protected Animal { // protected 상속
public:
void bark() {
eat(); // 자식 클래스 내부에서 호출 가능
cout << "The dog barks." << endl;
}
};
int main() {
Dog myDog;
// myDog.eat(); // 오류: eat()은 protected로 변경됨
myDog.bark(); // bark()를 통해서만 eat() 호출 가능
return 0;
}
private 상속
- 부모 클래스의 public과 protected 멤버가 자식 클래스에서 private로 변경됩니다.
- 자식 클래스의 내부에서만 접근 가능하며, 손자 클래스에서는 사용할 수 없습니다.
#include <iostream>
using namespace std;
class Parent {
protected:
int value;
public:
Parent(int v) : value(v) {}
};
class Child : private Parent { // private 상속
public:
Child(int v) : Parent(v) {}
void showValue() {
cout << "Value: " << value << endl; // 자식 클래스에서 접근 가능
}
};
class GrandChild : public Child { // 손자 클래스
public:
void test() {
// cout << value; // 오류: value는 private로 변경되어 접근 불가
}
};
int main() {
Child c(42);
c.showValue(); // 자식 클래스에서 접근 가능
GrandChild gc;
// gc.showValue(); // 오류: showValue()도 private처럼 작동
return 0;
}
메소드 오버라이딩?
오버라이딩(Overriding)은 자식 클래스가 부모 클래스의 메소드를 재정의하는 것을 의미합니다. 이를 통해 부모의 동작을 자식 클래스에 맞게 변경할 수 있습니다.
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() { // 가상 함수로 선언
cout << "This animal makes a sound." << endl;
}
};
class Dog : public Animal {
public:
void sound() override { // 부모 클래스의 sound()를 재정의
cout << "The dog barks." << endl;
}
};
int main() {
Animal* myAnimal = new Dog(); // 부모 클래스 포인터로 자식 객체를 참조
myAnimal->sound(); // 자식 클래스의 sound() 호출
delete myAnimal;
return 0;
}
위 예시와 같이 virtual 키워드를 사용해 부모 메소드를 가상 함수로 선언하면, 부모 타입의 포인터로 자식 클래스의 메소드를 호출할 수 있습니다.
new와 가상 함수(virtual)의 관계
왜 가상 함수(virtual)가 중요할까?
비유:
- 부모 클래스는 기본 설계도(예: 기본 자동차 설계도)입니다.
- 설계도가 "이 버튼을 누르면 엔진이 작동한다"라고 정의.
- 자식 클래스는 설계도를 기반으로 만든 고급 자동차입니다.
- 고급 자동차는 같은 버튼으로 "엔진을 작동하면서 더 효율적인 방식으로 작동"하도록 설계.
가상 함수를 사용하면 부모 클래스의 설계도를 따라가더라도 **자식 클래스에서 기능을 재정의(오버라이딩)**할 수 있습니다.
가상 함수가 없으면 어떻게 될까?
가상 함수가 없으면 부모 클래스의 메서드가 호출됩니다
#include <iostream>
using namespace std;
class Parent {
public:
void sayHello() { // 가상 함수 아님
cout << "Hello from Parent!" << endl;
}
};
class Child : public Parent {
public:
void sayHello() { // 오버라이딩
cout << "Hello from Child!" << endl;
}
};
int main() {
Parent* obj = new Child(); // 부모 포인터로 자식 객체 생성
obj->sayHello(); // 부모 클래스 메서드 호출
delete obj; // 동적 메모리 해제
return 0;
}
출력: Hello from Parent!
요약
- new의 역할:
- 객체를 동적으로 생성하고, 생성된 객체의 주소를 반환.
- 메모리를 힙(Heap)에 할당하므로, 사용 후 delete로 해제해야 함.
- 가상 함수와 함께 사용 시:
- 부모 포인터로 자식 객체를 생성할 때 가상 함수가 있으면 자식 클래스의 메서드가 호출됨.
- 가상 함수가 없으면 부모 클래스의 메서드만 호출.
- 비유로 이해:
- new는 "필요할 때 공장에서 물건(객체)을 새로 만드는 것".
- 가상 함수는 부모 설계도만 가지고 있어도, 실제 자식의 기능이 동작하도록 해주는 "스마트 기능"입니다.
다중상속이 가능한가?
C++에서는 하나의 자식 클래스가 여러 부모 클래스로부터 상속받을 수 있습니다.
class Animal {
public:
void eat() {
std::cout << "동물은 음식을 먹습니다." << std::endl;
}
};
class Mammal {
public:
void walk() {
std::cout << "포유류는 걸을 수 있습니다." << std::endl;
}
};
class Dog : public Animal, public Mammal {
public:
void bark() {
std::cout << "멍멍" << std::endl;
}
};
Dog myDog;
myDog.eat(); // Animal 클래스의 메소드 호출
myDog.walk(); // Mammal 클래스의 메소드 호출
myDog.bark(); // Dog 클래스의 메소드 호출
'언리얼(Unreal) > 엔진' 카테고리의 다른 글
24.12.07 언리얼(스마트 포인터에 대하여) (0) | 2024.12.07 |
---|---|
24.12.06 언리얼 (템플릿에 대하여) (1) | 2024.12.06 |
24.12.06 언리얼(Cpp 멤버변수,메소드 등) 사용법 (0) | 2024.12.06 |
24.12.06 언리얼(포인터에 대하여) (0) | 2024.12.06 |
24.12.05 언리얼(Cpp 기본개념중 헷갈릴만한 것만) (0) | 2024.12.05 |