▣ 기초 클래스와 파생 클래스
● 클래스의 상속 (inheritance)
▷ 공통적인 멤버를 포함하는 유사한 유형의 클래스
▷ 일반화와 특수화를 통한 클래스 계층구조 설계
공통적인 멤버로 구성된 클래스를 기초 클래스, 기초 클래스로부터 상속받는 클래스를 파생 클래스라고 한다.
코드의 중복을 방지할 수 있다.
● 파생 클래스 선언
▷ 파생 클래스 선언 형식
class DClassName : visibilitySpec BClassName {
visibilitySpec_1:
데이터 멤버 또는 멤버함수 리스트;
visibilitySpec_2:
데이터 멤버 또는 멤버함수 리스트;
...
};
// DClassName: 파생 클래스 이름
// BClassName: 기초 클래스 이름
// visibilitySpec: 가시성 지시어
● Person 클래스와 Student 클래스
▷ Person 클래스
- 사람을 나타내는 클래스로 사람 객체는 '이름'을 가지고, 이름을 지정하거나 이름을 알릴 수 있다.
Person |
-name: string |
+setName(n:string) : void +getName( ) : string +print( ) : void |
Person.h
#ifndef PERSON_H_INCLUDED
#define PERSON_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
class Person{
string name;
public:
void setName(const string& n) {name = n;}
string getName() const {return name;}
void print() const {cout << name;}
};
#endif
▷ Student 클래스
- 학생을 나타내는 클래스로 학생 객체는 사람의 기능을 '상속'받으면서 학교 이름을 지정하거나 저장된 학교 이름을 알리는 기능이 있다.
Student |
-school : string |
+setSchool (s : string) : void +getSchool ( ) : string +print ( ) : void |
파생클래스에서 기초클래스의 멤버를 재정의하는 것을 overriding이라고 한다. 여기는 print ( ) 함수를 Student 클래스에서 재정의하였다.
Student.h
#ifndef STUDENT_H_INCLUDED
#define STUDENT_H_INCLUDED
#include <iostream>
#include <string>
#include "Person.h"
class Student : public Person {
string school;
public:
void setSchool(string s) {school = s;}
string getSchool() const {return school;}
void print() const { //멤버함수 재정의! overriding
Person::print();
cout << " goes to " << school;
}
};
#endif
name이 Person.h에서 private하게 선언되었기 때문에 Student 클래스에서 사용할 수 없기 때문에 기초클래스의 멤버를 Person::print( )와 같이 사용해서 name을 출력받는다.
▷Person & Student 클래스 사용 (main.cpp)
#include <iostream>
#include "Person.h"
#include "Student.h"
using namespace std;
int main()
{
Person KIM; //기초 클래스의 객체 선언
KIM.setName("Kim"); //기초 클래스의 함수 호출
Student RM; //파생 클래스의 객체 선언
RM.setName("RapMonster");//기초 클래스의 함수 호출
RM.setSchool("Ilsan"); //파생 클래스의 함수 호출
KIM.print(); //기초 클래스의 함수 호출
cout << endl;
RM.print(); //파생 클래스의 함수 호출
cout << endl;
RM.Person::print(); //기초 클래스의 함수 호출
cout << endl;
return 0;
}
▷출력결과
● 클래스 계층
클래스 계층 구조는 "트리 구조"와 "그래프 구조"로 나뉜다.
▷ 트리 구조
가장 일반적인 형태지만, 부모 클래스가 둘 이상인 경우에는 사용하기 어렵다.
▷ 그래프 구조
부모 클래스가 둘 이상인 경우에 사용하기 좋은 구조다.
▣ 파생 클래스의 생성자 및 소멸자
● 생성자 선언 형식
DClassName(fParameterList) : BClassName(bArgsList)
{
... // 파생 클래스 생성자에서 추가되는 항목
}
// DClassName: 파생 클래스 생성자 - 파생 클래스 이름 사용
// BClassName: 기초 클래스 생성자 - 기초 클래스 이름 사용
// fParameterList: 파생 클래스 생성자 형식 매개변수 목록
// bArgsList: 기초 클래스 생성자에 전달할 인수 목록
● 생성자 및 소멸자의 실행 순서
▷ 생성자: 기초 클래스 생성자 --> 파생 클래스 생성자
- 파생 클래스는 기초 클래스의 내용을 바탕으로 하고 있기 때문에 위 순서로 생성자가 동작한다.
▷ 소멸자: 파생 클래스 생성자 --> 기초 클래스 생성
- 기초 클래스의 속성이 제거되기 전에 이를 활용할 가능성이 있는 파생 클래스 객체를 제거해야 한다.
● 예시 - Person 클래스와 Student 클래스
Person |
- name : string |
+ Person ( n : string ) + ~Person ( ) + getName ( ) : string + print ( ) : void |
Student |
- school : string |
+ Student ( s: string ) + ~Student ( ) + getSchool ( ) : string + print ( ) : void |
▷ Person.h
- name 객체를 string으로 만들었기 때문에 소멸자 선언안해도 알아서 소멸되기 때문에 굳이 소멸자를 선언할 필요는 없다. (배열도 아니고..)
#ifndef PERSON_H_INCLUDED
#define PERSON_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
class Person{
string name;
public:
Person(const string& n){
cout << "Person's constructor" << endl;
name = n;
}
~Person(){
cout << "Person's destructor" << endl;
}
string getName() const {return name;}
void print() const {cout << name;}
};
#endif
▷ Student.h
#ifndef STUDENT_H_INCLUDED
#define STUDENT_H_INCLUDED
#include <iostream>
#include <string>
#include "Person.h"
class Student : public Person {
string school;
public:
Student(const string& n, const string& s) : Person(n) {
cout << "Student's constructor" << endl;
school = s;
}
~Student(){
cout << "Student's destructor" << endl;
}
string getSchool() const {return school;}
void print() const { //멤버함수 재정의! overriding
Person::print();
cout << " goes to " << school;
}
};
#endif
- Student(const string& n, const string& s) : Person(n)--> name에 n을 넣어야 하는데 Person클래스의 private한 value인 name에 접근할 수 없으니 Person의 생성자를 호출해서 Student 내의 Person의 name에 값이 전달된다.
▷ main.cpp
#include <iostream>
#include "Person.h"
#include "Student.h"
using namespace std;
int main()
{
Student RM("Namjoon", "Ilsan");
cout << RM.getName() << " goes to "
<< RM.getSchool() << endl;
return 0;
}
▷ 출력결과
출력 결과를 보면,
- 기초 생성자
- 파생 생성자
- 출력
- 파생 소멸자
- 기초 소멸자
순서로 동작하는 것을 확인할 수 있다.
▣ 접근 제어
● 가시성
▷ 가시성 지시어
가시성 지시어 | 공개 범위 |
private (default) |
- 소속 클래스의 멤버함수 - 친구 클래스의 멤버함수 및 친구함수 |
protected | - 소속 클래스의 멤버함수 - 친구 클래스의 멤버함수 및 친구 함수 - 파생 클래스의 멤버함수 - 파생 클래스의 친구 클래스의 멤버함수 및 친구함수 |
public | - 전 범위 |
- 클래스의 경우에는 default가 private이고 구조체(struct)에서는 public이 default다.
● 가시성의 상속
▷ 기초클래스로부터 상속받은 멤버의 가시성
class DClassName: visibilitySpec BClassName {
....
};
- visibilitySpec : 기초 클래스로부터 상속된 멤버가 파생 클래스의 멤버로서 가지게 되는 가시성을 제어한다.
- private, protected, 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. 파생클래스에서 기초 클래스를 public으로 상속받을 때
- 기초 클래스 (Mom.h)
class Mom {
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;}
};
- 파생 클래스 (Son.h)
class Son : public Mom {
public:
int sum() const
{return a + b + c;} // a가 Mom 클래스에서 private하기 때문에 오류가 발생한다.
void printbc() const
{cout << b << ' ' << c;}
};
--> a가 Mom 클래스에서 private하기 때문에 오류가 발생한다.
--> 아래와 같이 기초 클래스의 public한 멤버함수를 이용해서 데이터를 이용해야 한다.
class Son : public Mom {
public:
int sum() const
{return geta() + b + c;}
void printbc() const
{cout << b << ' ' << c;}
};
- 실행 코드 (main.cpp)
int main() {
Son id;
id.a = 1; // a: private
id.b = 2; // b: protected
id.c = 3; // c: public
return 0;
}
위의 출력결과와 같이 오류가 발생한다. public으로 접근 가능한 id.c = 3; 명령만 수행 가능하고 나머지는 접근 권한이 없어서 오류가 발생한다.
▷ 예시 1. 파생클래스에서 기초 클래스를 protected로 상속받을 때
기초 클래스는 위의 Mom.h로 동일하다고 하고 파생클래스 Son.h를 아래와 같이 변경하였을 때 어떤 변화가 있는지 살펴보겠다.
- 파생 클래스 (Son.h)
class Son : protected Mom {
public:
int sum() const
{return geta() + b + c;} // a: private, b&c: protected
void printbc() const
{cout << b << ' ' << c;}
};
- 실행 코드 (main.cpp)
위와 동일하게 main.cpp를 실행하면 a는 private, b와c:는 protected가 되므로 모든 문장에서 오류가 발생하는 것을 볼 수 있다.
▣ 참고 사항
● final 클래스란?
▷ 더 이상 상속 클래스를 정의할 수 없도록 만드는 방법이다.
- final로 선언하면 더 이상 파생 클래스를 정의할 수 없다.
class A {...};
class B : public A {...};
class C final : public B {...};
class D : public C {...}; // 에러!!
- final은 키워드가 아니라 식별자(identifier)다.
- 그래서 final 클래스 지정과 같이 특별히 정해진 위치에 사용하지 않는 경우 외에는 final 단어를 변수의 이름으로도 사용할 수는 있다. 권장하지는 않는다.
● 이름 은폐, name hiding
▷ 어떠한 영역에서 그 영역을 내포하는 영역에 선언된 이름을 다시 선언하면 바깥 영역의 이름이 은폐된다.
▷ 예시 1
#include <iostream>
using namespace std;
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);
f(20.0);
::f(32.1);
}
- main() 함수 내에서 함수 f(int x)를 다시 선언했기 때문에 f(20.0);에서 함수 f는 main() 함수 내에서 선언됐던 f(int x)에 의해 묵시적 형변환이 일어나서 20.0을 20으로 정수형으로 변환한 후 f(int x)함수에 의해 출력된다. main()함수 바깥에서 선언했던 f(double x)를 사용하고 싶으면 전역함수를 의미하는 ::를 함수 f 앞에 붙여서 ::f(32.1);과 같이 사용해야 f(double x) 함수를 호출할 수 있다.
▷ 예시 2
#include <iostream>
using namespace std;
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);
f("abc"); // 에러
::f("abc");
}
- f("abc")는 f(int x)를 호출할 수도 없는 형태이기 때문에 오류가 발생한다. 만약 ::f("abc");로 함수를 호출하면 아래와 같이 정상 출력되는 것을 확인할 수 있다.
▷ 클래스 계층구조에서 이름 은폐
- 파생 클래스에서 기초 클래스에 선언된 이름을 다시 선언하면, 기초 클래스에 선언됐던 이름이 은폐된다.
#include <iostream>
using namespace std;
class A{
public:
void f(double x){cout << "B::f() --> " << x << endl;}
};
class B : public A{
public:
void f(int x){cout << "A::f() --> " << x << endl;}
};
int main(){
B sonB;
sonB.f(20.0);
sonB.f(10);
sonB.A::f(32.1);
}
- 출력결과
'Programming > C++' 카테고리의 다른 글
C++ 언어 기초 (14) - 템플릿 template (Feat. 버블정렬) (0) | 2020.09.05 |
---|---|
C++ 언어 기초 (13) - 추상클래스, 다중상속 (0) | 2020.09.04 |
C++ 언어 기초 (10) - 연산자 다중정의 II (0) | 2020.09.03 |
C++ 언어 기초 (9) - 연산자 다중정의 (0) | 2020.08.31 |
[C++] 위임 생성자, 초기화 리스트 생성자 (0) | 2020.08.29 |