프로그래밍 언어/C++

C++ OPP 3대요소 : 상속성

순정법사 2023.09.14

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. 파생 클래스의 객체 생성 순서

파생 클래스의 생성자가 호출되면 수행되는 동작을 설명

 

🧡 파생 클래스의 객체가 생성되는 순서

 

  1. 파생 클래스의 객체를 생성하면, 프로그램은 제일 먼저 기초 클래스의 생성자를 호출
  2. 기초 클래스 생성자는 상속받은 멤버 변수의 초기화를 진행
  3. 파생 클래스의 생성자가 호출
  4. 파생 클래스의 수명이 다하면, 먼저 파생 클래스의 소멸자가 호출되고, 그 후에 기초 클래스의 소멸자가 호출

 

 

2. 멤버 함수 오버라이딩

a. 오버라이딩(overriding)

🌟 이미 정의된 함수를 무시하고, 같은 이름의 함수를 새롭게 정의하는 것

 

b. 멤버 함수 오버라이딩

파생 클래스는 상속받을때 접근 제어 권한에 맞는 모든 멤버를 상속받는데,

이 멤버 함수는 그대로 사용해도 되고, 재정의 해도 됨

오버라이딩은 함수의 동작만 재정의하는것이라 원형은 기존 멤버 함수의 원형과 같아야 함

 

🧡 2가지 방법으로 오버라이딩

 

  1. 파생 클래스에서 직접 오버라이딩
  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. 다중 상속의 문제점

여러 개의 기초 클래스가 가진 멤버를 모두 상속받을 수 있다는 점에서 매우 강력한 상속 방법이지만

단일 상속에는 없는 여러 가지 새로운 문제를 발생시킴

 

  1. 상속받은 여러 기초 클래스에 같은 이름의 멤버가 존재할 가능성 있음
  2. 하나의 클래스를 간접적으로 두 번 이상 상속받을 가능성 있음
  3. 가상 클래스가 아닌 기초 클래스를 다중 상속하면, 기초 클래스 타입의 포인터로 파생 클래스를 가리킬 수 ❌

 

다중 상속은 프로그래밍을 복잡하게 만들 수 있으며, 그에 비해 실용성 👇

💥 따라서 사용을 자제!!

 

 

 


출처 : http://www.tcpschool.com/cpp/cpp_inheritance_derivedClass

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com