학습 개요
- 상속은 일반화와 특수화를 통해 계층적으로 클래스를 선언함으로써 추상화를 하는 유용한 기능임
- 기초 클래스의 일반적 속성 및 메소드는 파생 클래스들에 상속 됨으로써 파생 클래스들에 공통적으로 필요한 속성 및 메소드를 중복적으로 프로그래밍 하지 않아도 됨
- 기초 클래스와 파생 클래스로 구성되는 클래스 계층 구조를 구현하는 클래스를 선언하고 파생 클래스의 객체를 통해 기초 클래스에 포함된 멤버를 활용하는 방법에 대하여 학습함
- 심화 학습 주제로
final
을 이용하여 파생 클래스를 더 이상 선언하지 못하게 막는 방법과 동일 이름이 선언된 영역에 따라 어떻게 사용할 수 있는지 학습함
학습 목표
- 계층 관계로 표현되는 대상들을 기초 클래스와 파생 클래스로 표현할 수 있음
- 기초 클래스의 멤버를 파생 클래스에서 어떻게 사용할 수 있는지 가시성과 연관 지어 설명할 수 있음
- 기초 클래스와 파생 클래스의 생성자 및 소멸자가 어떻게 실행되는지 설명할 수 있음
- 파생 클래스의 멤버로서 기초 클래스로부터 상속된 멤버의 가시성을 지정할 수 있음
주요 용어
- 상속(inheritance)
- 상위 클래스가 갖는 일반적 속성 및 행위를 하위 클래스가 이어 받게 하는 기능
- 기초 클래스(base class)
- 클래스 계층 구조에서 일반적 속성 및 메소드를 포함하는 상위 클래스
- 파생 클래스(derived class)
- 클래스 계층 구조에서 상위 클래스의 일반적 개념을 상속 받으면서, 해당 클래스에 특수한 속성 및 메소드가 추가된 하위 클래스
- 클래스 멤버의 가시성 (visibility)
- 클래스의 멤버가 외부에 공개되는 범위
- 멤버 함수 재정의(overriding)
- 기초 클래스에 정의된 멤버 함수와 동일한 멤버 함수를 파생 클래스에서 그것에 맞게 다시 정의하는 것
- 이름 은폐(name hiding)
- 어떠한 영역에 선언된 이름을 그 영역에 내포 된 영역에서 다시 선언하면 내포 된 영역에서는 바깥 영역의 이름이 은폐 되는 것
강의록
기초 클래스와 파생 클래스
클래스의 상속
공통적인 멤버를 포함하는 유사한 유형의 클래스
일반화와 특수화를 통한 클래스 계층 구조 설계
파생 클래스의 선언
파생 클래스 선언 형식
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
클래스 구조
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
,public
class
를 선언할 때는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
로 선언된 클래스는 파생 클래스를 더 이상 정의할 수 없음