본문 바로가기
대학강의정리/24.1 고급 c++

9주차 ch15 상속과 다형성 앞부분

by 피스타0204 2024. 5. 7.

 

상속

이번 포스트는 앞으로 나올 모든 챕터 중 가장 중요한 상속과 다형성에 대해 다룰 것입니다. 

1.상속이란

c++ 에서 상속은 부모 클래스에게서 자식 클래스가 "데이터 필드"와 "멤버함수"를 물려받는 것을 말합니다. 여기서 부모 클래스는 기본 클래스, 자식 클래스는 파생 클래스라고도 부릅니다. 파생 클래스는 부모의 클래스를 물려받으면서 일부를 새롭게 재정의하거나 아니면 아예 새로운 클래스를 추가할 수도 있습니다. 

또 상속은 is a관계, 예를 들어, 사람은 멸치다가 아니라 직원은 사람이다. 와 같은 관계를 기술하는 데도 사용됩니다.

2. GeometricObject와 Circle, Rectangle 클래스의 ADT

 

GeometricObject.h를 사용하지 않는다면 include하지 않아도 됩니다. 부모 클래스를 include하지 않아도 자식 클래스를 사용할 수 있습니다.

#Circle circle(5);가 정의된 생성자 중 어떤 생성자를 사용하는지

//test.cpp
#include "GeometricObject.h"
#include "Circle.h"
#include "Rectangle.h"
#include <iostream> 
using namespace std;  
int main() {	    
  	GeometricObject shape;
  	cout << shape.toString() << endl
    		<< " color: " << shape.getColor() << endl << endl; 

	Circle circle1;
  	Circle circle2(5);
  	circle2.setColor("black"); 
	Circle circle3(4, "red");
  	cout << circle1.toString()<< endl 
    		<< " color: " << circle1.getColor() << endl
    		<< " radius: " << circle1.getRadius() << endl << endl;
    cout << circle2.toString()<< endl 
    		<< " color: " << circle2.getColor() << endl
    		<< " radius: " << circle2.getRadius() << endl << endl;
    cout << circle3.toString()<< endl 
    		<< " color: " << circle3.getColor() << endl
    		<< " radius: " << circle3.getRadius() << endl << endl;

  	Rectangle rectangle1;
	Rectangle rectangle2(4, 5);
	Rectangle rectangle3(2, 3, "orange");
  	cout << rectangle1.toString()<< endl
		<< " color: " << rectangle1.getColor() << endl
    		<< " width: " << rectangle1.getWidth() << endl
    		<< " height: " << rectangle1.getHeight()<< endl << endl ;
    cout << rectangle2.toString()<< endl
		<< " color: " << rectangle2.getColor() << endl
    		<< " width: " << rectangle2.getWidth() << endl
    		<< " height: " << rectangle2.getHeight()<< endl << endl ;
    cout << rectangle3.toString()<< endl
		<< " color: " << rectangle3.getColor() << endl
    		<< " width: " << rectangle3.getWidth() << endl
    		<< " height: " << rectangle3.getHeight()<< endl << endl ;
  	return 0; }
//GeometricObject.h
#ifndef GEOMETRICOBJECT_H
#define GEOMETRICOBJECT_H
#include <string>
using namespace std;

class GeometricObject
{
     public:
  	GeometricObject();
  	GeometricObject(const string& color);
  	string getColor() const;
  	void setColor(const string& color);
  	string toString() const; 

    private:
  	string color;
  }; 
#endif
//GeometricObject.cpp
#include "GeometricObject.h"

GeometricObject::GeometricObject( ) { //기본 생성자
  	color = "white";
}

GeometricObject::GeometricObject(const string& color) { //인자있는 생성자
  	this->color = color;
}

string GeometricObject::getColor() const {
  	return color;
}

void GeometricObject::setColor(const string& color) {
  	this->color = color;
}


string GeometricObject::toString() const {
  	return "Geometric Object";
}

상속을 하고 싶을 때는 class 자식클래스: public 부모클래스{}를 하면됩니다. 예를 들어 class Circle: public GeometricObject{} 와 같이 사용될 수 있습니다. 특수한 경우에만 private으로 상속받습니다.

또 자식클래스 안에 부모의 데이터 필드도 초기화하는 생성자를 만드는 것이 좋습니다.  Circle(double radius, const string& color);

//Circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include "GeometricObject.h"

class Circle: public GeometricObject
{
    public:
  	Circle();
  	Circle(double radius);
  	Circle(double radius, const string& color);
  	double getRadius() const;
  	void setRadius(double);
  	string toString() const;

    private:
  	double radius;
}; 
#endif

부모 클래스가 private으로 설정한 데이터 필드에는 자식 클래스도 접근할 수 없습니다. 접근하고 싶다면 접근자(get함수)나 set함수를 이용해야 합니다. setColor(color);

 

자식 클래스는 부모클래스가 정의한 멤버함수를 재정의 할 수 있습니다. 다음과 같이 이름이 같은 멤버 변수도 다르게 사용할 수 있습니다. string Circle::toString() const{return "Circle object";}

//Circle.cpp
#include "Circle.h"

Circle::Circle() {  //기본 생성자
  	radius = 1;
}
Circle::Circle(double radius) {  //인자있는 생성자1
	this->radius = radius;
} 
Circle::Circle(double radius, const string& color) {  //인자있는 생성자2
  	this->radius = radius;
  	setColor(color);
}

double Circle::getRadius() const {
  	return radius;
}

void Circle::setRadius(double radius){
  	this->radius = (radius >= 0) ? radius : 0;
}

string Circle::toString() const{
  	return "Circle object";
}
//Rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include "GeometricObject.h"

class Rectangle: public GeometricObject
{
    public:
  	Rectangle();
  	Rectangle(double width, double height);
  	Rectangle(double width, double height, const string& color);
  	double getWidth() const;
  	void setWidth(double);
  	double getHeight() const;
	void setHeight(double);
  	string toString() const;

    private:
  	double width;
	double height;
}; 
#endif
//Rectangle.cpp
#include "Rectangle.h"

Rectangle::Rectangle(){  //기본 생성자
 	width = 1;
  	height = 1;
}
Rectangle::Rectangle(double width, double height) { // 인자있는 생성자1
  	 this->width = width;
  	 this->height = height;
}
Rectangle::Rectangle(double width, double height, const string& color){//인자있는 생성자2
  	this->width = width;
  	this->height = height;
  	setColor(color);	
}
double Rectangle::getWidth() const { return width; }
void Rectangle::setWidth(double width){
   	this->width = (width >= 0) ? width : 0;
}
double Rectangle::getHeight() const{ return height; }
void Rectangle::setHeight(double height){
  	this->height = (height >= 0) ? height : 0;
}
string Rectangle::toString() const { return "Rectangle object"; }

 

3. 보호(protected) 키워드

protected 키워드는 자기 자신과 자식 클래스만 접근할 수 있도록 하는 접근 지정자입니다. setColor말고 this->color;를 자식 클래스에서 사용하고 싶다면 GeometricObject의 데이터 필드의 접근지정자를 private에서 protected로 바꾸면 됩니다. 

//test.cpp
#include "GeometricObject.h"
#include "Circle.h"
#include "Rectangle.h"
#include <iostream>  //1) 다른 생성자호출로 Circle객체, Rectangle객체 2개씩 더 생성후 출력
using namespace std;  //2)부모클래스의 멤버변수를 protected로 지정
int main() {	    //3)상속된 멤버변수는 부모생성자 통해 초기화함
  	GeometricObject shape;
  	cout << shape.toString() << endl
    		<< " color: " << shape.getColor() << endl << endl; 

	Circle circle1;
  	Circle circle2(5);
  	circle2.setColor("black"); // 상속받은 부모함수 사용
	Circle circle3(4, "red");
  	cout << circle1.toString()<< endl //출력부분은 지면관계상 생략함
    		<< " color: " << circle1.getColor() << endl
    		<< " radius: " << circle1.getRadius() << endl << endl;

  	Rectangle rectangle1;
	Rectangle rectangle2(4, 5);
	Rectangle rectangle3(2, 3, "orange");
  	cout << rectangle1.toString()<< endl //출력부분은 지면관계상 생략함
		<< " color: " << rectangle1.getColor() << endl
    		<< " width: " << rectangle1.getWidth() << endl
    		<< " height: " << rectangle1.getHeight()<< endl << endl ;
  	return 0; }

 

보호 키워드 예제 ▼

class A {
   public:
  	int i;
   protected:
 	int j;
   private:
  	int k;
};
class B: public A {
  public:
   	 void display() const { //자식 클래스의 멤버함수에서 접근
    		cout << i << endl; // 접근가능
    		cout << j << endl; // 접근가능
    		cout << k << endl; // 접근불가  	
	}
};
int main() { //외부함수에서 접근
 	A a;
 	cout << a.i << endl; // 접근가능
  	cout << a.j << endl; // 접근불가
  	cout << a.k << endl; // 접근불가
  	return 0; }

 

4. 생성자와 소멸자

데이터 필드와 멤버 함수와 달리

부모의 생성자와 소멸자는 파생 클래스에 상속되지 않습니다.

기본 클래스의 생성자는 데이터 필드를 초기화하기 위해 파생 클래스의 생성자로부터 명시적으로나 암묵적으로나 호출만 가능합니다. 부모에게서 받은 데이터 필드는 부모의 생성자를 호출하여 자식생성자에서 초기화합니다. 상속된 멤버 변수의 메모리 할당은 기본 클래스의 생성자에 의해 이뤄지는 것이 원칙입니다.

 

하지만 자식 생성자에서 초기화 목록을 이용해 부모 생성자를 명시적으로 호출할 수도 있습니다. 만약 명시적으로 초기화 리스트를 작성하지 않으면 기본 클래스의 인수없는 생성자가 자동으로 호출됩니다. 

Circle::Circle(double radius, const string& color)
	: GeometricObject(color) 
{  
  	this->radius = radius;
}
Rectangle::Rectangle(double width, double height, const string& color)
	: GeometricObject(color)
{
	this->width = width;
  	this->height = height;
}

 

5. 생성자와 소멸자의 연쇄적 처리

클래스의 인스턴스를 생성할 때 상속 연결을 따로 모든 부모 클래스들의 생성자가 순서대로 호출됩니다. A>B>C순서로 부모>자식이라면 C에서 인스턴스를 생성하면 A->B->C순서대로 생성자가 호출됩니다. 즉 기본 클래스의 생성자가 파생 클래스의 생성자보다 먼저 호출됩니다.

반대로, 소멸자는 파생 클래스의 소멸자가 먼저 호출되어 생성자의 역순으로 호출됩니다.C->B->A 순으로 소멸자가 호출됩니다.  이 과정을 생성자 및 소멸자의 연쇄 처리라고 합니다. 

 

class Person {
   public:
  	Person()	{ cout << "Person 생성자" << endl ; }
  	~Person()	{ cout << "Person 소멸자" << endl; }
};
class Employee : public Person {
   public:
  	Employee() { cout << "Employee 생성자" << endl; }
  	~Employee() { cout << "Employee 소멸자" << endl; }
};
class Faculty: public Employee {
   public:
  	Faculty() { cout << "Faculty 생성자" << endl; }
  	~Faculty() { cout << "Faculty 소멸자" << endl; }
};

int main(){
  	Faculty * fp = new Faculty();
	delete fp;
	fp = NULL; 	
	return 0;
}

출력:
Person 생성자
Employee 생성자
Faculty 생성자
Faculty 소멸자
Employee 소멸자
Person 소멸자