icon안동민 개발노트

상속의 개념과 구현


상속의 개념

 상속(Inheritance)은 객체 지향 프로그래밍의 핵심 개념 중 하나입니다.

 상속은 기존 클래스의 특성을 새로운 클래스가 물려받아 재사용하고 확장할 수 있게 해주는 메커니즘입니다.

 상속의 주요 목적

  1. 코드 재사용
  2. 계층 구조 표현
  3. 다형성 구현의 기반

 상속 관계의 용어

  • 기본 클래스 (Base class) 또는 부모 클래스 (Parent class) : 특성을 물려주는 클래스
  • 파생 클래스 (Derived class) 또는 자식 클래스 (Child class) : 특성을 물려받는 클래스

C++에서의 상속 구현

 C++에서 상속은 다음과 같은 문법으로 구현됩니다.

class DerivedClass : [access-specifier] BaseClass {
    // 파생 클래스의 멤버
};

 여기서 access-specifierpublic, protected, 또는 private일 수 있으며, 생략 시 기본값은 private입니다.

상속의 종류

 1. public 상속

  • 가장 일반적인 형태
  • 기본 클래스의 public 멤버 → 파생 클래스에서 public
  • 기본 클래스의 protected 멤버 → 파생 클래스에서 protected

 2. protected 상속

  • 기본 클래스의 public 멤버 → 파생 클래스에서 protected
  • 기본 클래스의 protected 멤버 → 파생 클래스에서 protected

 3. private 상속

  • 기본 클래스의 public 멤버 → 파생 클래스에서 private
  • 기본 클래스의 protected 멤버 → 파생 클래스에서 private
class Base {
public:
    void publicMethod() {}
protected:
    void protectedMethod() {}
private:
    void privateMethod() {}
};
 
class PublicDerived : public Base {
    // publicMethod() is public
    // protectedMethod() is protected
    // privateMethod() is not accessible
};
 
class ProtectedDerived : protected Base {
    // publicMethod() is protected
    // protectedMethod() is protected
    // privateMethod() is not accessible
};
 
class PrivateDerived : private Base {
    // publicMethod() is private
    // protectedMethod() is private
    // privateMethod() is not accessible
};

생성자와 소멸자

 상속 관계에서 객체 생성 및 소멸 시 호출 순서는 다음과 같습니다.

 1. 객체 생성 시

  • 기본 클래스의 생성자
  • 파생 클래스의 생성자

 2. 객체 소멸 시

  • 파생 클래스의 소멸자
  • 기본 클래스의 소멸자
class Base {
public:
    Base() { std::cout << "Base constructor" << std::endl; }
    ~Base() { std::cout << "Base destructor" << std::endl; }
};
 
class Derived : public Base {
public:
    Derived() { std::cout << "Derived constructor" << std::endl; }
    ~Derived() { std::cout << "Derived destructor" << std::endl; }
};
 
int main() {
    Derived d;
    return 0;
}
실행 결과
Base constructor
Derived constructor
Derived destructor
Base destructor

멤버 초기화

 파생 클래스의 생성자에서 기본 클래스의 생성자를 명시적으로 호출할 수 있습니다.

class Animal {
protected:
    std::string name;
public:
    Animal(const std::string& n) : name(n) {}
};
 
class Dog : public Animal {
private:
    std::string breed;
public:
    Dog(const std::string& n, const std::string& b) 
        : Animal(n), breed(b) {}
};

상속과 접근 제어

  • private 멤버: 파생 클래스에서 직접 접근 불가
  • protected 멤버 : 파생 클래스에서 접근 가능, 외부에서는 접근 불가
  • public 멤버 : 어디서든 접근 가능
class Base {
private:
    int privateMember;
protected:
    int protectedMember;
public:
    int publicMember;
};
 
class Derived : public Base {
public:
    void accessMembers() {
        // privateMember = 1;  // Error: private member not accessible
        protectedMember = 2;   // OK: protected member accessible
        publicMember = 3;      // OK: public member accessible
    }
};

실습 : 도형 클래스 계층 구현

 다음 요구사항을 만족하는 도형 클래스 계층을 구현해보세요.

  • Shape를 기본 클래스로 하고, Circle, Rectangle, Triangle을 파생 클래스로 구현
  • 각 클래스는 적절한 생성자를 가져야 함
  • 모든 도형은 넓이를 계산하는 getArea() 메서드를 가져야 함
  • 모든 도형은 둘레를 계산하는 getPerimeter() 메서드를 가져야 함
  • 모든 도형은 자신의 정보를 출력하는 print() 메서드를 가져야 함
#include <iostream>
#include <cmath>
#include <vector>
 
class Shape {
protected:
    std::string name;
public:
    Shape(const std::string& n) : name(n) {}
    virtual double getArea() const = 0;
    virtual double getPerimeter() const = 0;
    virtual void print() const {
        std::cout << "Shape: " << name << std::endl;
    }
    virtual ~Shape() {}  // 가상 소멸자
};
 
class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : Shape("Circle"), radius(r) {}
    double getArea() const override {
        return M_PI * radius * radius;
    }
    double getPerimeter() const override {
        return 2 * M_PI * radius;
    }
    void print() const override {
        Shape::print();
        std::cout << "Radius: " << radius 
                  << ", Area: " << getArea() 
                  << ", Perimeter: " << getPerimeter() << std::endl;
    }
};
 
class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : Shape("Rectangle"), width(w), height(h) {}
    double getArea() const override {
        return width * height;
    }
    double getPerimeter() const override {
        return 2 * (width + height);
    }
    void print() const override {
        Shape::print();
        std::cout << "Width: " << width << ", Height: " << height 
                  << ", Area: " << getArea() 
                  << ", Perimeter: " << getPerimeter() << std::endl;
    }
};
 
class Triangle : public Shape {
private:
    double a, b, c;  // 세 변의 길이
public:
    Triangle(double side1, double side2, double side3) 
        : Shape("Triangle"), a(side1), b(side2), c(side3) {}
    double getArea() const override {
        // 헤론의 공식 사용
        double s = (a + b + c) / 2;
        return sqrt(s * (s - a) * (s - b) * (s - c));
    }
    double getPerimeter() const override {
        return a + b + c;
    }
    void print() const override {
        Shape::print();
        std::cout << "Sides: " << a << ", " << b << ", " << c 
                  << ", Area: " << getArea() 
                  << ", Perimeter: " << getPerimeter() << std::endl;
    }
};
 
int main() {
    std::vector<Shape*> shapes;
    shapes.push_back(new Circle(5));
    shapes.push_back(new Rectangle(4, 6));
    shapes.push_back(new Triangle(3, 4, 5));
    
    for (const auto& shape : shapes) {
        shape->print();
    }
    
    // 메모리 해제
    for (auto shape : shapes) {
        delete shape;
    }
    
    return 0;
}

연습 문제

  1. Vehicle 클래스를 기본 클래스로 하고, CarMotorcycle 클래스를 파생 클래스로 구현하세요. 각 클래스는 적절한 속성(예 : 속도, 연료량)과 메서드(예 : 가속, 감속)를 가져야 합니다.
  2. Employee 클래스를 기본 클래스로 하고, ManagerEngineer 클래스를 파생 클래스로 구현하세요. 각 클래스는 적절한 속성(예 : 이름, 급여)과 메서드(예 : 급여 계산)를 가져야 합니다.
  3. BankAccount 클래스를 기본 클래스로 하고, SavingsAccountCheckingAccount 클래스를 파생 클래스로 구현하세요. 각 클래스는 적절한 속성(예 : 계좌번호, 잔액)과 메서드(예 : 입금, 출금, 이자 계산)를 가져야 합니다.

 참고자료