학습 개요
- 상속은 일반화와 특수화를 통해 계층적으로 클래스를 선언함으로써 추상화를 하는 유용한 기능임
- 기초 클래스의 일반적 속성 및 메소드는 파생 클래스들에 상속 됨으로써 파생 클래스들에 공통적으로 필요한 속성 및 메소드를 중복적으로 프로그래밍 하지 않아도 됨
- 기초 클래스와 파생 클래스로 구성되는 클래스 계층 구조를 구현하는 클래스를 선언하고 파생 클래스의 객체를 통해 기초 클래스에 포함된 멤버를 활용하는 방법에 대하여 학습함
- 심화 학습 주제로
final을 이용하여 파생 클래스를 더 이상 선언하지 못하게 막는 방법과 동일 이름이 선언된 영역에 따라 어떻게 사용할 수 있는지 학습함
학습 목표
- 계층 관계로 표현되는 대상들을 기초 클래스와 파생 클래스로 표현할 수 있음
- 기초 클래스의 멤버를 파생 클래스에서 어떻게 사용할 수 있는지 가시성과 연관 지어 설명할 수 있음
- 기초 클래스와 파생 클래스의 생성자 및 소멸자가 어떻게 실행되는지 설명할 수 있음
- 파생 클래스의 멤버로서 기초 클래스로부터 상속된 멤버의 가시성을 지정할 수 있음
주요 용어
- 상속(inheritance)
- 상위 클래스가 갖는 일반적 속성 및 행위를 하위 클래스가 이어 받게 하는 기능
- 기초 클래스(base class)
- 클래스 계층 구조에서 일반적 속성 및 메소드를 포함하는 상위 클래스
- 파생 클래스(derived class)
- 클래스 계층 구조에서 상위 클래스의 일반적 개념을 상속 받으면서, 해당 클래스에 특수한 속성 및 메소드가 추가된 하위 클래스
- 클래스 멤버의 가시성 (visibility)
- 클래스의 멤버가 외부에 공개되는 범위
- 멤버 함수 재정의(overriding)
- 기초 클래스에 정의된 멤버 함수와 동일한 멤버 함수를 파생 클래스에서 그것에 맞게 다시 정의하는 것
- 이름 은폐(name hiding)
- 어떠한 영역에 선언된 이름을 그 영역에 내포 된 영역에서 다시 선언하면 내포 된 영역에서는 바깥 영역의 이름이 은폐 되는 것
강의록
기초 클래스와 파생 클래스
클래스의 상속
공통적인 멤버를 포함하는 유사한 유형의 클래스
![image.png]()
일반화와 특수화를 통한 클래스 계층 구조 설계
![image.png]()
파생 클래스의 선언
파생 클래스 선언 형식
1 2 3 4 5 6
class DClassName : visibilitySpec BClassName { visibilitySpec_1: 데이터 멤버 또는 멤버 함수 리스트; visibilitySpec_2: 데이터 멤버 또는 멤버 함수 리스트; };
DClassName- 파생 클래스 이름
BClassName- 기초 클래스 이름
visibilitySpec- 가시성 지시어
Person 클래스와 Student 클래스
- Person 클래스
- 사람을 나타내는 클래스를 선언하고자 한다.
- 사람 객체는 이름을 가지고 있으며, 이름을 지정하거나 이름을 알릴 수 있다.
- ex) Dudley
- Student 클래스
- 학생을 나타내는 클래스를 선언하고자 한다.
- 학생 객체는 사람의 기능을 상속 받으면서, 학교 이름을 지정하거나 저장된 학교 이름을 알리는 기능이 추가된다.
- ex) Harry goes to Hogwarts
클래스 구조
![image.png]()
Person 클래스의 선언 - Person1.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef PERSON1_H_INCLUDED
#define PERSON1_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string name;
void setName(const string& n) { name = n; }
string getName() const { return name; }
void print() const { cout << name; }
};
#endif // PERSON1_H_INCLUDED
Student 클래스의 선언 - Student1.h
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "Person1.h"
// 파생 클래스 Student를 Person을 상속 받아 선언함
class Student : public Person {
public:
string school;
void setSchool(const string& s) { school = s; }
string getSchool() const { return school; }
void print() const {
Person::print();
cout << " goes to " << school;
}
};
Person 및 Student 객체의 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include "Person1.h"
#include "Student1.h"
using namespace std;
int main() {
Person dudley; // 기초 클래스의 객체 선언
dudley.setName("Dudley"); // 기초 클래스의 함수 호출
Student harry; // 파생 클래스의 객체 선언
harry.setName("Harry"); // 파생 클래스의 함수 호출
harry.setSchool("Hogwarts"); // 파생 클래스의 함수 호출
dudley.print(); // 기초 클래스의 함수 호출
cout << endl;
harry.print(); // 파생 클래스의 함수 호출
cout << endl;
dudley.print(); // 기초 클래스의 함수 호출
cout << endl;
harry.Person::print(); // 기초 클래스의 함수 호출
cout << endl;
return 0;
}
// Dudley
// Harry goes to Hogwarts
// Harry
클래스 계층

파생 클래스의 생성자 및 소멸자
파생 클래스의 생성자 및 소멸자
생성자 선언 형식
1 2 3 4
DClassName(fParameterList) : BClassName(bArgsList) { // 파생 클래스 생성자에서 추가되는 사항 }
DClassName- 파생 클래스 생성자
- 파생 클래스 이름을 사용
BClassName- 기초 클래스 생성자
- 기초 클래스 이름을 사용
fParameterList- 파생 클래스 생성자 형식 매개 변수 목록
bArgsList- 기초 클래스 생성자에 전달할 인수 목록
생성자 및 소멸자의 실행 순서
- 생성자
- 기초 클래스 생성자 → 파생 클래스 생성자
- 파생 클래스는 기초 클래스의 내용을 바탕으로 하고 있음
- 객체의 기초 클래스 해당 내용이 먼저 준비 된 후 파생 클래스에 선언된 내용을 초기화할 필요가 있음
- 소멸자
- 파생 클래스 소멸자 → 기초 클래스 소멸자
- 기초 클래스의 속성이 제거되기 전에 이를 활용할 가능성이 있는 파생 클래스 객체를 제거해야 함
- 생성자
Person 클래스와 Student 클래스 (생성자/소멸자 추가)

Person 클래스의 선언 - Person2.h
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
string name;
public:
Person(const string& n) {
cout << "Person의 생성자" << endl;
name = n;
}
~Person() {
cout << "Person의 소멸자" << endl;
}
string getName() const { return name; }
void print() const { cout << name; }
};
Student 클래스의 선언 - Student2.h
1
2
3
4
5
6
7
8
9
10
11
class Student : public Person {
string school;
public:
Student(const string& n, const string& s) : Person(n) {
cout << "Student의 생성자" << endl;
school = s;
}
~Student() {
cout << "Student의 소멸자" << endl;
}
};
생성자 및 소멸자 동작 - Student2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include "Person2.h"
#include "Student2.h"
using namespace std;
int main() {
Student harry("Harry", "Hogwarts");
cout << harry.getName() << " goes to " << harry.getSchool() << endl;
return 0;
}
// Person의 생성자
// Student의 생성자
// Harry goes to Hogwarts
// Student의 소멸자
// Person의 소멸자
엑세스 제어
가시성
가시성 지시어
가시성 지시어 공개 범위 private(디폴트)소속 클래스의 멤버 함수 친구 클래스의 멤버 함수 및 친구 함수 protected소속 클래스의 멤버 함수 친구 클래스의 멤버 함수 및 친구 함수 파생 클래스의 멤버 함수 파생 클래스의 친구 클래스의 멤버 함수 및 친구 함수 public전 범위
가시성의 상속
기초 클래스로부터 상속 받은 멤버의 가시성
1 2 3
class DClassName : visibilitySpec BClassName { };
visibilitySpec- 기초 클래스로부터 상속 된 멤버가 파생 클래스의 멤버로서 가지게 되는 가시성을 제어함
private,protected,publicclass를 선언할 때는private이 디폴트struct를 선언할 때는public이 디폴트
visibilitySpec에 지시된 것이 가시성의 상한이 되도록 제한됨
가시성의 상속
기초 클래스로부터 상속 받은 멤버의 가시성
가시성 상속 지시어 B의 public멤버는B의 protected멤버는class D1 : private B {}D1의 private멤버D1의 private멤버class D2 : protected B {}D2의 protected멤버D2의 protected멤버class D3 : public B {}D3의 public멤버D3의 protected멤버기초 클래스
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class BaseC { private: int a; protected: int b; public: int c; int geta() const { return a; } void set(int x, int y, int z) { a = x; b = y; c = z; } };
public파생 클래스1 2 3 4 5 6 7 8 9
class Drvd1 : public BaseC { public: int sum() const { return geta() + b + c; } void printbc() const { cout << "b << " << b << ", c " << c; } };
객체 사용
1 2 3 4 5 6 7 8 9 10
// 객체 사용 int main() { Drvd1 d1; // d1.a = 1; // 에러(private 멤버) // d1.b = 2; // 에러(protected 멤버) d1.c = 3; // OK d1.set(10, 20, 30); // 값 설정 d1.printbc(); // b와 c 출력 return 0; }
protected파생 클래스1 2 3 4 5 6 7 8 9
class Drvd1 : protected BaseC { public: int sum() const { return geta() + b + c; } void printbc() const { cout << "b << " << b << ", c " << c; } };
객체 사용
1 2 3 4 5 6 7 8 9 10
// 객체 사용 int main() { Drvd1 d1; // d1.a = 1; // 에러(private 멤버) // d1.b = 2; // 에러(protected 멤버) // d1.c = 3; // 에러(protected 멤버) // d1.set(10, 20, 30); // 값 설정 (protected) d1.printbc(); // b와 c 출력 (public) return 0; }
심화 학습
final클래스
- 파생 클래스 선언의 금지
final클래스로 선언된 클래스는 파생 클래스를 더 이상 정의할 수 없음1 2 3 4
class A { ...... }; class B : public A { ...... }; class C : final public B { ...... }; class D : public C { ...... }; // 에러
final은 키워드가 아닌 식별자(identifier)임final클래스 지정과 같이 특별히 정해진 위치에 사용되지 않은 경우 식별자(예: 변수 이름)의 용도로 사용할 수도 있음(바람직한 용법은 아님)
이름 은폐
- 이름 은폐(name hiding)
어떤 영역에 선언된 이름을 그 영역에 내포된 영역에서 다시 선언하면 내포된 영역에서는 바깥 영역의 이름이 은폐 됨
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
void f(int x) { cout << "f(int x) --> " << x << endl; } void f(double x) { cout << "f(double x) --> " << x << endl; } int main() { f(10); // int f(20.0); // double } // f(int x) --> 10 // f(double x) --> 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
void f(int x) { cout << "f(int x) --> " << x << endl; } void f(double x) { cout << "f(double x) --> " << x << endl; } int main() { void f(int x); f(10); // int f(20.0); // int ::f(30.0); // double } // f(int x) --> 10 // f(int x) --> 20 // f(double x) --> 30
1 2 3 4 5 6 7 8 9 10 11 12 13
void f(int x) { cout << "f(int x) --> " << x << endl; } void f(const char *x) { cout << "f(const char *x) --> " << x << endl; } int main() { void f(int x); f(10); // void f(int x); f("abc"); // 에러 ::f("abc"); // void f(const char* x) }
- 클래스 상속에서의 이름 은폐
기초 클래스에 선언된 이름을 파생 클래스에서 재정의하면 파생 클래스의 객체에서 기초 클래스의 이름이 은폐 됨
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class A { public: void f(int x) { cout << "A::f() --> " << x << endl; } }; class B : public A { public: void f(double x) { cout << "B::f() --> " << x << endl; } }; int main() { B objB; objB.f(10.0); objB.f(20); } // B::f() --> 10 // B::f() --> 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class A { public: void f(int x) { cout << "A::f() --> " << x << endl; } }; class B : public A { public: using A::f; // A 클래스의 f도 보이게 만듦 void f(double x) { cout << "B::f() --> " << x << endl; } }; int main() { B objB; objB.f(10.0); objB.f(20); } // B::f() --> 10 // A::f() --> 20
연습 문제
다음 중 클래스 계층 구조를 설계할 경우 나머지 클래스의 기초 클래스가 될 수 있는 것은?
a. 자동차
- 기초 클래스는 일반적인 개념, 파생 클래스는 기초 클래스의 성질을 공유하면서 개개의 특수한 성질을 갖는 하위 개념을 정의할 수 있어야 함
위 지문과 같이 클래스가 선언 되었을 때 ㈀에서 사용할 수 없는 문장은?
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class A { char addr[20]; protected: char name[10]; public: void printA() { cout << name << addr; } }; class B : public A { char dep[10]; public: void f() // { (ㄱ) } };
a.
cout << addr;- 파생 클래스의 멤버 함수에서는 기초 클래스의
protected멤버와public멤버를 직접 사용할 수 있으며,private멤버인addr은 직접 액세스 할 수 있음 - 사용할 수 있는 문장
cin >> name;cin >> dep;printA();
- 파생 클래스의 멤버 함수에서는 기초 클래스의
위 지문의 ㈀은 클래스 B의 생성자를 선언하는 문장이다. 두 개의 정수를 인수로 전달 받아 첫 번째 인수는 A의 a, 두 번째 인수는 B의 b에 초기화한다. ㈀에 넣을 내용은 무엇인가?
1 2 3 4 5 6 7 8
class A { int a; public: A(int x) : a(x) {} void display() const { cout << a; } };
1 2 3 4 5 6 7 8 9
class B : public A { int b; public: // (ㄱ) void display() const { A::display(); cout << " " << b; } };
a.
B(int x, int y) : A(x), b(y) {}- 생성자의 초기화 리스트로 기초 클래스의 생성자가 호출 되도록 함
소멸자에 대한 올바른 설명은?
a. 파생 클래스 객체가 제거될 때는 파생 클래스 소멸자가 실행된 후 기초 클래스의 소멸자가 실행된다.
- 파생 클래스 객체는 기초 클래스를 바탕으로 만들어지므로, 기초 클래스의 속성이 정리되기 전에 이를 활용할 가능성이 있는 파생 클래스 객체의 정리 작업이 이루어져야 함
위 지문과 같이 클래스 A와 B가 선언되었다. 가시성을 고려하였을 때 ㈀과 ㈁에서 사용할 수 있는 문장을 나열한 것은?
1 2 3 4 5 6 7 8
class A { int a1; protected: int a2; public: int a3; };
1 2 3 4 5 6 7 8
class B : protected A { int b1; protected: int b2; public: int b3; int f() // { (ㄱ) } };
1 2 3 4
int main() { B objB; // (ㄴ) }
a. ㈀
cin >> a2;㈁objB.f( );- B의 멤버 함수에서는 A의
protected멤버와public멤버, B의 모든 멤버를 직접 액세스할 수 있음 - 클래스 B는 가시성 상속 지시어
protected로 A로부터 상속을 받으므로 A의protected와public멤버는 클래스 B의protected멤버가 됨
- B의 멤버 함수에서는 A의
정리 하기
- 클래스의 계층 구조에서 기초 클래스는 일반적 속성 및 메소드를 파생 클래스는 특수한 속성 및 메소드를 포함함
- 기초 클래스의
protected멤버는 파생 클래스에서 액세스할 수 있음 - 파생 클래스의 객체가 생성 될 때 기초 클래스의 생성자가 먼저 실행 된 후 파생 클래스의 생성자 몸체 블록이 실행 됨
- 파생 클래스 객체가 소멸될 때는 파생 클래스 생성자 몸체 블록이 먼저 실행된 후 기초 클래스의 생성자가 실행 됨
- 기초 클래스로부터 상속 받은 멤버의 가시성은 가시성 상속 지시어에 의해 공개 범위의 상한이 결정 됨
final로 선언된 클래스는 파생 클래스를 더 이상 정의할 수 없음


