B. OPP 상속성
1. 파생 클래스
a. 상속(inheritance)
추상화, 캡슐화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나
사용자에게 높은 수준의 코드 재활용성을 제공하며, 클래스 간의 계층적 관계를 구성함으로써 다형성의 문법적 토대를 마련
b. 클래스 상속(class inheritance)
🌟 기존에 정의되어 있는 클래스의 모든 멤버 변수와 멤버 함수를 물려받아, 새로운 클래스를 작성하는 것을 의미
- 기존에 정의되어 있던 클래스 : 기초 클래스(base class), 부모 클래스(parent class), 상위 클래스(super class)
- 상속을 통해 새롭게 작성되는 클래스 : 파생 클래스(derived class), 자식 클래스(child class), 하위 클래스(sub class)
👍 장점
- 기존에 작성된 클래스를 재활용
- 공통적인 부분은 기초 클래스에 미리 작성하여, 파생 클래스에서 중복되는 부분을 제거
c. 파생 클래스(derived class)
🌟 기초 클래스의 모든 특성을 물려받아 새롭게 작성된 클래스
📘 문법
class 파생클래스이름 : 접근제어지시자 기초클래스이름[, 접근제어지시자 기초클래스이름, ...]
{
// 파생 클래스 멤버 리스트
}
-------------------------------------------------------------------------------------------
접근 제어 지시자 : 기초 클래스의 멤버를 사용할 수 있는 파생 클래스의 접근 제어 권한을 설정
접근 제어 지시자를 생략하면, 파생 클래스의 접근 제어 권한은 private로 기본 설정
쉼표(,)를 사용하여 상속받을 기초 클래스를 여러 개 명시
➡ 파생 클래스가 상속받는 기초 클래스가 하나이면 단일 상속(single inheritance)
➡ 여러 개의 기초 클래스를 상속받으면 다중 상속(multiple inheritance)
d. 파생 클래스의 특징
- 반드시 자신만의 생성자를 작성
- 기초 클래스의 접근할 수 있는 모든 멤버 변수들이 저장
- 기초 클래스의 접근할 수 있는 모든 멤버 함수를 사용
- 필요한 만큼 멤버를 추가
🧡 파생클래스의 기초클래스 생성자 사용
⏳ 예제 : Person 클래스 (기초 클래스)
class Person { private: string name_; int age_; public: Person(const string& name, int age); // 기초 클래스 생성자의 선언 void ShowPersonInfo(); }; ... Person::Person(const string& name, int age) // 기초 클래스 생성자의 정의 { name_ = name; age_ = age; }
⏳ 예제 : Student 클래스 (파생 클래스)
class Student : public Person { private: int student_id_; public: Student(int sid, const string& name, int age); // 파생 클래스 생성자의 선언 }; ... Student::Student(int sid, const string& name, int age) : Person(name, age) // 파생 클래스 생성자의 선언 { student_id_ = sid; }
위처럼 파생 클래스의 생성자는 기초 클래스의 생성자를 사용
이유는 파생 클래스의 생성자가 기초 클래스의 private 멤버에 접근할 수 없기 때문
👉 따라서 기초 클래스의 생성자를 사용해야만 기초 클래스의 private 멤버를 초기화 할 수 있음
이때 기초 클래스의 생성자를 명시하지 않으면, 프로그램은 기초 클래스의 디폴트 생성자를 사용
e. 파생 클래스의 객체 생성 순서
🧡 파생 클래스의 객체가 생성되는 순서
- 파생 클래스의 객체를 생성하면, 프로그램은 제일 먼저 기초 클래스의 생성자를 호출
- 기초 클래스 생성자는 상속받은 멤버 변수의 초기화를 진행
- 파생 클래스의 생성자가 호출
- 파생 클래스의 수명이 다하면, 먼저 파생 클래스의 소멸자가 호출되고, 그 후에 기초 클래스의 소멸자가 호출
2. 멤버 함수 오버라이딩
a. 오버라이딩(overriding)
🌟 이미 정의된 함수를 무시하고, 같은 이름의 함수를 새롭게 정의하는 것
b. 멤버 함수 오버라이딩
파생 클래스는 상속받을때 접근 제어 권한에 맞는 모든 멤버를 상속받는데,
이 멤버 함수는 그대로 사용해도 되고, 재정의 해도 됨
오버라이딩은 함수의 동작만 재정의하는것이라 원형은 기존 멤버 함수의 원형과 같아야 함
🧡 2가지 방법으로 오버라이딩
- 파생 클래스에서 직접 오버라이딩
- 가상 함수를 이용해 오버라이딩
c. 파생 클래스에서의 오버라이딩
⏳ 예제: 파생 클래스에서 상속받은 기초 클래스의 멤버 함수를 직접 재정의
void Person::ShowPersonInfo()
{
cout << name_ << "의 나이는 " << age_ << "살입니다." << endl;
}
...
void Student::ShowPersonInfo()
{
cout << "이 학생의 학번은 " << student_id_ << "입니다." << endl;
}
✨ 실행결과
순신의 나이는 35살입니다.
이 학생의 학번은 123456789입니다.
범위 지정 연산자(::)를 사용하면 파생 클래스에서 기초 클래스의 원래 멤버 함수를 호출할 수 있음
⏳ 예제 : 기초 클래스의 멤버 함수 호출하기
Student hong(123456789, "길동", 29);
hong.ShowPersonInfo();
hong.Person::ShowPersonInfo();
✨ 실행결과
이 학생의 학번은 123456789입니다.
길동의 나이는 29살입니다.
d. 파생 클래스에서 오버라이딩의 문제점
일반적으로 잘 동작하지만 포인터 변수를 사용할 때, 예상치 못한 결과를 반환
⏳ 예제
#include <iostream>
using namespace std;
class Person
{
private:
string name_;
int age_;
public:
Person(const string& name, int age); // 기초 클래스 생성자의 선언
void ShowPersonInfo();
};
class Student : public Person
{
private:
int student_id_;
public:
Student(int sid, const string& name, int age); // 파생 클래스 생성자의 선언
void ShowPersonInfo(); // 파생 클래스에서 상속받은 멤버 함수의 재정의
};
int main(void)
{
Person* ptr_person;
Person lee("순신", 35);
Student hong(123456789, "길동", 29);
ptr_person = &lee;
ptr_person->ShowPersonInfo();
ptr_person = &hong;
ptr_person->ShowPersonInfo();
return 0;
}
Person::Person(const string& name, int age) // 기초 클래스 생성자의 정의
{
name_ = name;
age_ = age;
}
void Person::ShowPersonInfo()
{
cout << name_ << "의 나이는 " << age_ << "살입니다." << endl;
}
Student::Student(int sid, const string& name, int age) : Person(name, age) // 파생 클래스 생성자의 정의
{
student_id_ = sid;
}
void Student::ShowPersonInfo()
{
cout << "이 학생의 학번은 " << student_id_ << "입니다." << endl;
}
✨ 실행결과
순신의 나이는 35살입니다.
길동의 나이는 29살입니다.
위 예제는 Person 객체를 가리킬 수 있는 포인터 변수 ptr_person에 Person 객체와 Student 객체의 주소값을 대입해
ShowPerson() 함수를 호출함
두 번 모두 Person 객체의 함수가 호출되는데, 이유는
➡ C++ 컴파일러는 포인터 변수가 실제로 가리키는 객체의 타입을 기준으로 함수를 호출하는 것이 아니라,
해당 포인터의 타입을 기준으로 함수를 호출
👉 따라서 Person 객체를 가리킬 수 있는 포인터 변수로는 Person 객체의 멤버 함수만을 호출
이러한 문제점을 해결하기 위해 virtual 키워드를 사용한 가상함수를 제공
⏳ 예제 : 가상함수를 이용
#include <iostream>
using namespace std;
class Person
{
private:
string name_;
int age_;
public:
Person(const string& name, int age); // 기초 클래스 생성자의 선언
virtual void ShowPersonInfo();
};
class Student : public Person
{
private:
int student_id_;
public:
Student(int sid, const string& name, int age); // 파생 클래스 생성자의 선언
virtual void ShowPersonInfo(); // 파생 클래스에서 상속받은 멤버 함수의 재정의
};
int main(void)
{
Person* ptr_person;
Person lee("순신", 35);
Student hong(123456789, "길동", 29);
ptr_person = &lee;
ptr_person->ShowPersonInfo();
ptr_person = &hong;
ptr_person->ShowPersonInfo();
return 0;
}
Person::Person(const string& name, int age) // 기초 클래스 생성자의 정의
{
name_ = name;
age_ = age;
}
void Person::ShowPersonInfo()
{
cout << name_ << "의 나이는 " << age_ << "살입니다." << endl;
}
Student::Student(int sid, const string& name, int age) : Person(name, age) // 파생 클래스 생성자의 정의
{
student_id_ = sid;
}
void Student::ShowPersonInfo()
{
cout << "이 학생의 학번은 " << student_id_ << "입니다." << endl;
}
✨ 실행결과
순신의 나이는 35살입니다.
이 학생의 학번은 123456789입니다.
3. 다중 상속
a. 다중 상속(multiple inheritance)
🌟 두 개 이상의 클래스로부터 멤버를 상속받아 파생 클래스를 생성하는 것을 의미
쉼표(,)를 사용하여 상속받을 여러 개의 기초 클래스를 명시하는 것으로 간단히 다중 상속을 구현
📘 문법
class 파생클래스이름 : 접근제어지시자 기초클래스이름, 접근제어지시자 기초클래스이름[, 접근제어지시자 기초클래스이름, ...]
{
// 파생 클래스 멤버 리스트
}
b. 다중 상속의 문제점
여러 개의 기초 클래스가 가진 멤버를 모두 상속받을 수 있다는 점에서 매우 강력한 상속 방법이지만
단일 상속에는 없는 여러 가지 새로운 문제를 발생시킴
- 상속받은 여러 기초 클래스에 같은 이름의 멤버가 존재할 가능성 있음
- 하나의 클래스를 간접적으로 두 번 이상 상속받을 가능성 있음
- 가상 클래스가 아닌 기초 클래스를 다중 상속하면, 기초 클래스 타입의 포인터로 파생 클래스를 가리킬 수 ❌
다중 상속은 프로그래밍을 복잡하게 만들 수 있으며, 그에 비해 실용성 👇
💥 따라서 사용을 자제!!
출처 : http://www.tcpschool.com/cpp/cpp_inheritance_derivedClass
'프로그래밍 언어 > C++' 카테고리의 다른 글
[게임 프로그래머 입문 올인원] 동적할당과 캐스팅 : 동적할당 기초 (35강) (0) | 2023.09.14 |
---|---|
C++ OPP 3대요소 : 다형성 (0) | 2023.09.14 |
C++ OPP 3대요소 : 캡슐화 (0) | 2023.09.14 |
C++ 연산자 오버로딩 (0) | 2023.09.13 |
[자료형(data type)] C++ 파생형 클래스 2 : 생성자와 소멸자 (+39강, 41강) (0) | 2023.09.13 |