7. 상속성 inheritance(hierachy)

sunghoon·2025년 3월 17일
1

2.0 Glove Project

목록 보기
21/35
post-thumbnail

inheritance와 hierarchy 둘의 뉘앙스 차이

  • inheritance는 “관계 하나”,
    누가 누구에게서 뭘 직접 물려받는 순간에 집중
  • hierarchy는 “전체 구조”,
    어떤 존재가 위에 있고, 그 아래로 어떤 질서가 있는지에 집중

7.1 상속의 구조

상속의 개념: 자식이 부모로부터 재산을 물려받는 개념

inheritance in opp

  • 완성된 하나의 데이터형인 클래스를 다른 클래스에 상속함으로써 기능을 제공하는 것이 “객체지향에서의 상속성”
  • 기능을 상속할 뿐 아니라 확장할 수 있다.(override)
  • 급여시스템 클래스가 정규 직원과 계약직원 종류에 따라 상속없이 나눠진다면 무슨 문제가 있을까?
    • 코드 중복 발생
    • 공통 수정 시, 어려움
  • 마치 군대 사단 사항과 연대 사항..

공통 영역 작성

#include <iostream>

using namespace std;

class Employee {
public:
    Employee() {
        strName = NULL;
        strAddr = NULL;
    }
    ~Employee() {
         delete[] strName;
         delete[] strAddr;
    }
    Employee(const char* pName, const char* pAddr) {
        strName = new char[strlen(pName) + 1];
        strAddr = new char[strlen(pAddr) + 1];
        
        strcpy(strName, pName);
        strcpy(strAddr, pAddr);

        strName[strlen(pName) + 1] = '\0';
        strName[strlen(pAddr) + 1] = '\0';
    }
    void DisplayEmployee() {
        cout << "이름: " << strName << endl;
        cout << "주소: " << strAddr << endl;
    }
private:
    char* strName;
    char* strAddr;
};

int main () {
    Employee emp("Kim", "seoul");
    emp.DisplayEmployee();

    return 0;
}
  • strlen() 문자열 표준 함수
  • strcpy()

C++에서 클래스의 자식(즉, 파생 클래스)을 정의할 때 사용하는 :(콜론)은 "상속 지정자"(Inheritance Specifier)라고 부릅니다.

(in JS/JAVA 는 콜론이 아닌 extends)

상속 연결 코드

#include <iostream>

using namespace std;

class Employee {
 public:
  Employee() {
    strName = NULL;
    strAddr = NULL;
  }
  ~Employee() {
    delete[] strName;
    delete[] strAddr;
  }
  Employee(const char* pName, const char* pAddr) {
    strName = new char[strlen(pName) + 1];
    strAddr = new char[strlen(pAddr) + 1];

    strcpy(strName, pName);
    strcpy(strAddr, pAddr);

    strName[strlen(pName) + 1] = '\0';
    strName[strlen(pAddr) + 1] = '\0';
  }
  void DisplayEmployee() {
    cout << "이름: " << strName << ", 주소: " << strAddr << endl;
  }

 protected:
  char* strName;
  char* strAddr;
};

class Regular : Employee {
 public:
  Regular() {}
  Regular(char* pName, char* pAddr, double pSalary) {
    strName = new char[strlen(pName) + 1];
    strAddr = new char[strlen(pAddr) + 1];
    strcpy(strName, pName);
    strcpy(strAddr, pAddr);
    salary = pSalary;

    cout << "이름: " << strName << ", 주소: " << strAddr << endl;
  }
  ~Regular() {}
  double PayCheck() { 
	  return salary; 
    delete[] strName;
    delete[] strAddr;
  }

 private:
  double salary;
};

class Temporary : Employee {
 public:
  Temporary() {}
  Temporary(const char* pName, const char* pAddr, double pDailyPay, int pDays) {
    strName = new char[strlen(pName) + 1];
    strAddr = new char[strlen(pAddr) + 1];
    strcpy(strName, pName);
    strcpy(strAddr, pAddr);

    dailyPay = pDailyPay;
    days = pDays;

    cout << "이름: " << strName << ", 주소: " << strAddr << endl;
  }
  ~Temporary() {}
  double PayCheck() { 
	  return days * dailyPay; 
    delete[] strName;
    delete[] strAddr;
  }

 private:
  double dailyPay;
  int days;
};

int main() {
  Employee emp("Kim", "seoul");
  emp.DisplayEmployee();

  Regular rgl("park", "Deagu", 300);
  cout << "ㄴ 정규직원 급여: " << rgl.PayCheck() << endl;

  Temporary tmp("choi", "Bundang", 20, 30);
  cout << "ㄴ 계약직원 급여: " << tmp.PayCheck() << endl;

  return 0;
}
  • protected 접근 지정자란? C++에서 protected객체지향 프로그래밍의 상속(inheritance) 개념과 관련이 깊은 접근 지정자(access specifier)야. 일반적으로 클래스 멤버(변수와 함수)는 private, protected, public으로 접근 범위를 설정하는데, protectedprivate과 public의 중간 정도의 역할을 해.
    • protected의 특징:
      1. 클래스 내부에서는 접근 가능
        • protected 멤버는 private 멤버처럼 해당 클래스 내부에서는 자유롭게 접근할 수 있어.
      2. 자식(파생) 클래스에서도 접근 가능
        • private 멤버는 자식 클래스에서 접근할 수 없지만,protected 멤버는 자식 클래스에서 직접 접근할 수 있어.
      3. 외부에서는 접근 불가능
        • public과 달리, 클래스 외부에서는 직접 접근할 수 없어.
  • 자식 클래스 생성 시, 부모 클래스를 먼저 생성 후 자식을 생성한다.

상속의 장점

  • 계층의 명확성
    • 각 클래스간의 역할 관계를 명확하게 정의
  • 코드의 재사용성
  • 확장성

7.2 상속관계의 특성

  • 상속되지 않는 멤버
    • 생성자와 소멸자
    • 생성자와 소멸자는 자식 클래스로 상속되지 않는다.
    • 부모 클래스는 어떠한 자식 클래스에 상속을 하여도 부모 클래스 입장에서는 알 수 없다. 즉 초기화 루틴 공유가 불가능하다.
    • 상속한 멤버는 부모 입장에서 자식이 죽을 쑤든 밥을 먹든 상관할 수 없다. 유산을 물려주었다는 선에서 땡이다.
  • friend 멤버
    • 부모 클래스의 friend멤버는 자식 클래스로 상속되지 않는다.
    • 부모 클래스의 friend 멤버가 상속 된다면 자식 클래스 입장에서 원하지 않는 멤버들을 상속받게 된다.
    • 부모님의 친구가 내 친구가 될 수 없다.
  • 자식 클래스의 객체 생성 및 소멸
    • 자식 클래스로 객체를 생성하는 경우
      • 부모 생성자를 호출 후 자식 객체가 생성된다.
      • 소멸 시에는 객체의 클래스(마지막 자손) ⇒ 부모 순으로 진행
    • 중복 소멸에 대한 문제 생길 수 있다.(연산자 오버로딩)
      • 중복되는 동적메모리 할당은 부모에 통일할 필요가 있다.
      • 자손 생성자에 부모 생성자의 인자를 전달한다.
      • derived(ag1, ag2) : base(arg1, arg2)
#include <iostream>

using namespace std;

class Employee {
public:
    Employee() {
        strName = NULL;
        strAddr = NULL;
    }
    ~Employee() {
         delete[] strName;
         delete[] strAddr;
    }
    Employee(const char* pName, const char* pAddr) {
        strName = new char[strlen(pName) + 1];
        strAddr = new char[strlen(pAddr) + 1];
        
        strcpy(strName, pName);
        strcpy(strAddr, pAddr);

        strName[strlen(pName) + 1] = '\0';
        strName[strlen(pAddr) + 1] = '\0';
    }
    void DisplayEmployee() {
        cout << "이름: " << strName << ", 주소: " << strAddr << endl;
    }
protected:
    char* strName;
    char* strAddr;
};

class Regular : Employee
{
public:
    Regular() {
    }
    Regular(char* pName, char*  pAddr, double pSalary) : Employee(pName, pAddr)
    {
        salary = pSalary;

        cout << "이름: " << strName << ", 주소: " << strAddr << endl;
    }
    ~Regular() {

    }
    double PayCheck() {

        return salary;
    }
private:
    double salary;
};

class Temporary : Employee
{
public:
    Temporary() {

    }
    Temporary(const char* pName, const char* pAddr, double pDailyPay, int pDays) : Employee(pName, pAddr) {

        dailyPay = pDailyPay;
        days = pDays;

        cout << "이름: " << strName << ", 주소: " << strAddr << endl;
    }
    ~Temporary() {

    }
    double PayCheck() {
        return days * dailyPay;
    }
private:
    double dailyPay;
    int days;
};

int main () {
    Employee emp("Kim", "seoul");
    emp.DisplayEmployee();

    Regular rgl("park", "Deagu", 300);
    cout << "ㄴ 정규직원 급여: "<< rgl.PayCheck() << endl;

    Temporary tmp ("choi", "Bundang", 20, 30);
    cout << "ㄴ 계약직원 급여: " << tmp.PayCheck() << endl;

    return 0;
}

같은 위치에서 생성과 소멸이 되는 것이 코드에 있어서 중요

멤버함수의 오버라이딩(overrinding)

  • 오버라이딩의 의미 오버라이딩이란 사전적 의미로 “위에 덮어쓰다.” 또는 “~에 우선한다.”는 의미를 가지고 있다. 부모 클래스에서 정의한 멤버함수가 있다면 이를 자식 클래스에서 그대로 상속받되, 내용을 변경하여 새로운 기능을 만드는 것.
  • 멤버함수 오버라이딩 조건
    1. 이름이 같아야 한다.
      1. 오버로딩, 오버라이딩 모두 함수 이름이 같기에 헷갈릴 수 있다.
    2. 매개변수의 타입 및 개수가 같아야 한다.
      1. 오버로딩은 달라야했으나, 오버라이딩은 같아야함
    3. 반환 타입이 같아야 한다.

오버라이딩 조건으로 알 수 있듯이 형태는 동일하고 조건만 달라지는 상속의 형태이다.

code - SalesMan 추가하기

#include <iostream>

using namespace std;

class Employee {
public:
    Employee() {
        strName = NULL;
        strAddr = NULL;
    }
    ~Employee() {
         delete[] strName;
         delete[] strAddr;
    }
    Employee(const char* pName, const char* pAddr) {
        strName = new char[strlen(pName) + 1];
        strAddr = new char[strlen(pAddr) + 1];
        
        strcpy(strName, pName);
        strcpy(strAddr, pAddr);

        strName[strlen(pName) + 1] = '\0';
        strName[strlen(pAddr) + 1] = '\0';
    }
    void DisplayEmployee() {
        cout << "이름: " << strName << ", 주소: " << strAddr << endl;
    }
protected:
    char* strName;
    char* strAddr;
};

class Regular : Employee
{
public:
    Regular() {
    }
    Regular(char* pName, char*  pAddr, double pSalary) : Employee(pName, pAddr)
    {
        salary = pSalary;

        cout << "이름: " << strName << ", 주소: " << strAddr << endl;
    }
    ~Regular() {

    }
    double PayCheck() {
        return salary;
    }
private:
    double salary;
};

class SalesMan : public Regular
{
public:
    SalesMan(){}
    ~SalesMan(){}
    SalesMan(char* pName, char*  pAddr, double pSalary, double allowance)
        :Regular(pName, pAddr, pSalary)
    {
        this->allowance = allowance;
    }
    double Paycheck() {
        return Regular::PayCheck() + allowance;
    }
private:
    double allowance;

};

class Temporary : Employee
{
public:
    Temporary() {

    }
    Temporary(const char* pName, const char* pAddr, double pDailyPay, int pDays) : Employee(pName, pAddr) {

        dailyPay = pDailyPay;
        days = pDays;

        cout << "이름: " << strName << ", 주소: " << strAddr << endl;
    }
    ~Temporary() {

    }
    double PayCheck() {
        return days * dailyPay;
    }
private:
    double dailyPay;
    int days;
};

int main () {
    Employee emp("Kim", "seoul");
    emp.DisplayEmployee();

    Regular rgl("park", "Deagu", 300);
    cout << "ㄴ 정규직원 급여: "<< rgl.PayCheck() << endl;

    Temporary tmp ("choi", "Bundang", 20, 30);
    cout << "ㄴ 계약직원 급여: " << tmp.PayCheck() << endl;

    SalesMan slm("Kim", "Busan", 300, 100);
    cout << "ㄴ 영업사원 급여: " << slm.PayCheck() << endl;

    return 0;
}

부모클래스와 자식클래스 사이의 변환

  • 부모 클래스의 자식 클래스 객체 포인터 접근
    • 부모 클래스 객체 포인터는 자식 클래스의 객체 접근이 가능
    • 부모 클래스 객체 포인터를 통해 자식 클래스 객체 포인터를 제어

우선 코드의 구조를 파악해보자

Regular rgl("park", "Deagu", 300);
cout << "ㄴ 정규직원 급여: "<< rgl.PayCheck() << endl;

Temporary tmp ("choi", "Bundang", 20, 30);
cout << "ㄴ 계약직원 급여: " << tmp.PayCheck() << endl;

SalesMan slm("Kim", "Busan", 300, 100);
cout << "ㄴ 영업사원 급여: " << slm.PayCheck() << endl;

자식 객체를 부모 객체에 주소값을 전달한다. 아래의 결과는 어떻게 될까

Employee* emp = (Employee*)&rgl;

cout << emp->PayCehck() << endl;

결과

> 0

자식의 객체를 부모객체에게 넘겨도 적용되지 않고 타입을 기준으로 타입의 멤버를 호출합니다.

객체를 넘겼을 때, 받은 객체의 멤버를 호출하고 싶다면..

가상함수 개념을 통해 해결할 수 있습니다.

📚 reference

  
profile
프라다 신은 빈지노와 쿠페를 타는 꿈을 꿨다.

0개의 댓글