C++ 문법 상속성에 대해 알아보기
본문 바로가기

C#

C++ 문법 상속성에 대해 알아보기

728x90
반응형

오늘은 C++문법 중 상속성에 대해 공부했습니다.

책은 C++하이킹 책을 보고 공부했습니다.

 

 C++상속성의 의미

이미 정의된 훌룡한 클래스를 상속받아 새로운 클래스를 만든다면 많은 기능을 받을 수 있다.

C++에서도 부모 클래스와 자식클래스가 있고 이 사이에 상속이 일어난다.

즉, 기존에 사용하던 알고리즘을 부모 클래스에 정의해 놓고 새로운 기능을 추가해 파생클래스를 만든다.

 

코드를 재활용하기 위해 나온 개념이다.

 

기반클래스

-공통적으로 기술된 멤버함수와 멤버함수들로 구성된 클래스

기반클래스를 지정할 땐 접근 지정자에 유의해야한다.

상속성이 적용되는 클래스에서는 기반클래스의 멤버변수를  파생 클래스가 접근 할 수 있도록 할 것인지에 따라 protected 접근지정자까지 고려해줘야한다.

데이터 은닉도 고수하면서 상속도 가능하도록 할려고 사용할 수 있는 접근 지정자가 protect지정자이다.

 

파생클래스

- 상속받는 클래스

파생클래스 기본형식

class 파생 클래스 : 접근 지정자 기반 클래스 {

멤버변수 :

멤버함수 ;

};

 

기반클래스와 파생클래스 설계하기

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
int c;
public :
void init(int new_A, int new_B);
void prn();
};
void Calc::init(int new_A, int new_B) {
a = new_A;
b = new_B;
c = 0;
}
void Calc::prn() {
cout << a << "\t" << b << "\t" << c << endl;
}
class Add :public Calc {
public:
void sum();
};
void Add::sum() {
c = a + b;
}
class Mul :public Calc {
public:
void Gob();
};
void Mul::Gob() {
c = a * b;
}
void main() {
Add x;
x.init(3, 5);
Mul y;
y.init(2, 7);
x.sum();
x.prn();
y.Gob();
y.prn();
}

 

상속관계에서의 생성자

생성자는 기반클래스의 생성자가 먼저호출되고 파생클래스의 생성자가 나중에 호출된다.

소멸자는 파생클래스의 소멸자가 먼저 호출되고 기반클래스의 소멸자가 나중에 호출된다.

 

상속관계에서의 생성자와 소멸자 알아보기

#include<iostream>
using namespace std;

class base {
public :
base();
~base();
};
base::base() {
cout << "기반클래스의 생성자" << endl;
}
base::~base() {
cout << "기반클래스의 소멸자" << endl;
}
class derived : public base {
public :
derived();
~derived();
};
derived::derived() {
cout << "파생클래스의 생성자" << endl;
}
derived::~derived() {
cout << "파생클래스의 소멸자" << endl;
}
void main() {
derived obj;
}

Calc 클래스에 생성자 정의하기

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
int c;
public:
void prn();
Calc(int new_a, int new_b);
};
void Calc::prn() {
cout << a << "\t" << b << "\t" << c << endl;
}
Calc::Calc(int new_a, int new_b) {
a = new_a;
b = new_b;
c = 0;
}
void main() {
Calc x(3, 5);
x.prn();
}

 

생성자를 정의한 Calc 클래스의 파생 클래스 설계하기

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
int c;
public:
void prn();
Calc(int new_a, int new_b);
};
void Calc::prn() {
cout << a << "\t" << b << "\t" << c << endl;
}
Calc::Calc(int new_a, int new_b) {
a = new_a;
b = new_b;
c = 0;
}
class Add :public Calc {
public:
void sum();
};
void Add::sum() {
c = a + b;
}
void main() {
Add y(3, 5);
y.prn();
}

이러면 에러가 발생한다.

Add클래스에 매개변수가 2개인 생성자를 따로 정의해야하기 때문이다.

 

Add클래스에 생성자 정의하기

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
int c;
public:
void prn();
Calc(int new_a, int new_b);
};
void Calc::prn() {
cout << a << "\t" << b << "\t" << c << endl;
}
Calc::Calc(int new_a, int new_b) {
a = new_a;
b = new_b;
c = 0;
}
class Add :public Calc {
public:
void sum();
Add(int new_a, int new_b);
};
void Add::sum() {
c = a + b;
}
void main() {
Add y(3, 5);
y.prn();
}

이 코드도 에러가발생한다.

왜냐하면Add클래스의 생성자를 호출하는 과정에서 기반(Calc)클래스의 매개변수를 갖지 않는 기본 생성자를 호출하지만 기본 생성자가 없다는 에러 메시지가 출력된다.

 

위에 에러를 3가지방법으로 해결가능

1.기반(Calc)클래스에 매개변수가 없는 기본 생성자를 정의

2.기반클래스에 정의된 매개변수 2개를 갖는 생성자를 제거한다.

3, 파생클래스에서 기반 클래스의 매개변수가 2개인 생성자를 명시적으로 호출한다.

 

상속관계에서 생성자 문제 해결하기

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
int c;
public:
void prn();
Calc(int new_a, int new_b);
Calc();
};
void Calc::prn() {
cout << a << "\t" << b << "\t" << c << endl;
}
Calc::Calc(int new_a, int new_b) {
a = new_a;
b = new_b;
c = 0;
}
Calc::Calc() {
a = 0;
b = 0;
c = 0;
}
class Add :public Calc {
public :
void sum();
Add(int new_a, int new_b);
Add();
};
void Add::sum() {
c = a + b;
}
Add::Add(int new_a,int new_b):Calc(new_a,new_b)
{}
Add::Add() : Calc(){
}
void main() {
Calc x(3, 5);
x.prn();
Add y(3, 5);
y.prn();
Add z;
z.prn();
}

 

함수의 오버라이딩

기반클래스에 정의되어 있는 함수와 동일한 형태로 파생 클래스에서 다시 정의하는 것을 함수의 오버라이딩이라 한다.

유의할 점

1.기반 클래스에 정의되어 있는 함수의 원형과 동일한 형태로 정의한다.

2. 파생 클래스에서 오버라이딩할 멤버함수의 반환값과 매개변수의 자료형과 매개변수의 개수가 기반 클래스의 멤버함수와 동일해야한다.

 

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
public:
void prn();
Calc(int new_a, int new_b);
Calc();
};
void Calc::prn() {
cout << a << "\t" << b<< endl;
}
Calc::Calc(int new_a, int new_b) {
a = new_a;
b = new_b;
}
Calc::Calc() {
a = 0;
b = 0;
}
class Add :public Calc {
protected:
int c;
public:
void sum();
Add(int new_a, int new_b);
Add();
void prn();//오버라이딩
};
void Add::sum() {
c = a + b;
}
Add::Add(int new_a,int new_b):Calc(new_a,new_b)
{
a = new_a;
b = new_b;
c = 0;
}
void Add::prn() { //오버라이딩
cout<<a<<"+"<<b<<"="<<c<<endl;
}
Add::Add():Calc() {
}
class Mul :public Calc {
protected:
int c;
public:
Mul();
Mul(int new_a, int new_b);
void God();
void prn(); //오버라이딩
};
Mul::Mul() {

}
Mul::Mul(int new_a, int new_b) :Calc(new_a, new_b) {
a = new_a;
b = new_b;
c = 0;
}
void Mul::God() {
c = a * b;
}
void Mul::prn() {
cout << a << "*" << b << "=" << c << endl;
}
void main() {
Calc x(3, 5);
x.prn();
Add y(3, 5);
y.sum();
y.prn();
Mul z(3,5);
z.God();
z.prn();
}

 

형변환

C++에서 서도 다른 자료형에 대해서 대입 연산을 할 경우 형변환이 일어나는데, 형변환에는 1. 암시적 형변환 2. 명시적인 형변환이 있다.

 

상속 관계에 있는 클래스 사이의 형변환은 업캐스팅 / 다운캐스팅 2가지로 구분된다.

 

업캐스팅

기반클래스의 포인터 변수가 파생 클래스의 인스턴스를 가리킬 수 있는데 이를 업 캐스팅이라하며 자동으로 형변환이 이루어진다.

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
public:
void Calcprn();
Calc(int new_a, int new_b);
Calc();
};
void Calc::Calcprn() {
cout << "---Calc::Calcprn---" << endl;
cout << a << "\t" << b << endl;
}
Calc::Calc(int new_a, int new_b) {
a = new_a;
b = new_b;
}
Calc::Calc() {
a = 0;
b = 0;
}
class Add :public Calc {
protected:
int c;
public:
void sum();
Add(int new_a, int new_b);
Add();
void Addprn();//오버라이딩
};
void Add::sum() {
c = a + b;
}
Add::Add(int new_a,int new_b):Calc(new_a,new_b)
{
a = new_a;
b = new_b;
c = 0;
}
void Add::Addprn() { //오버라이딩
cout << "---Add::Addprn---"<<endl;
cout << a << "+" << b << "=" << endl;
}
void main() {
Add Addobj(3, 5);
Add* Addptr;
Addptr = &Addobj;
Addptr->sum();
Addptr->Addprn();
Calc* Calcptr;
Calcptr = &Addobj; //업캐스팅
Calcptr->Calcprn();
}

업캐스팅은 부모로부터 상속받은 부부만 참조하고 나머지는 사용하지 않겠다고 포기를 선언하는 것과 같다.

파생 클래스의 인스턴스를 기반 클래스 포인터로 참조되게 되면 참조 가능한 영역을 축소하겠다는 의미이다.

업캐스팅을 정리하자면

1. 파생객체의 포인터가 기반 객체의 포인터로 형변환되는것이다.

2. 업캐스팅을 하면 참조가능한 영역이 축소된다.

3. 업캐스팅은 컴파일러에 의해서 자동형변환이된다.

 

다운캐스팅

파생클래스로 선언된 포인터 변수에 기반 클래스로 선언된 객체의 주소를 저장하는 것인데 다운캐스팅에 대해서는 컴파일러가 자동으로 형변환하지 않기 때문에 컴파일 에러가 발생한다.

그래서 명시적으로 캐스트 연산자를 기술해서 강제로 형변환을 하는 경우에는 다운캐스팅을 허용해 준다.

 

 

다운캐스팅하기

#include<iostream>
using namespace std;

class Calc {
protected:
int a;
int b;
public:
void Calcprn();
Calc(int new_a, int new_b);
Calc();
};
void Calc::Calcprn() {
cout << "---Calc::Calcprn---" << endl;
cout << a << "\t" << b << endl;
}
Calc::Calc(int new_a, int new_b) {
a = new_a;
b = new_b;
}
Calc::Calc() {
a = 0;
b = 0;
}
class Add :public Calc {
protected:
int c;
public:
void sum();
Add(int new_a, int new_b);
Add();
void Addprn();//오버라이딩
};
void Add::sum() {
c = a + b;
}
Add::Add(int new_a,int new_b):Calc(new_a,new_b)
{
a = new_a;
b = new_b;
c = 0;
}
void Add::Addprn() { //오버라이딩
cout << "---Add::Addprn---"<<endl;
cout << a << "+" << b << "=" <<c<< endl;
}
void main() {
Calc* Calcptr;
Calcptr = new Add(3, 5); //업캐스팅
Calcptr->Calcprn();

Add* Addptr;
Addptr = (Add*)Calcptr; //다운캐스팅
Addptr->Calcprn();
Addptr->sum();
Addptr->Addprn();

}

다운캐스팅의 잘못된 예 알아보기

위에 소스와 동일하고 메인소스만 다름

void main(){

Calc obj(3,5);

Add*Addptr;

Addptr-&obj;

Addptr->Calcprn();

Addptr->sum();

}

 

다운캐스팅 정리

1.다운캐스팅은 파생 클래스로 형변환 하는 것이다.

2. 다운 캐스팅은 참조가능한 영역이 확대되는 것을 의미한다.

3. 다운캐스팅에대해서는 컴파일러가 자동으로 형변환하지 않는다.

4. 다운캐스팅은 프로그래머가 명시적으로 형변환해줘야만 컴파일상의 에러를 방지할 수 있다.

5. 다운 캐스팅은 강제 형변환한 후에도 실행시 예외사항이 발생할 수 있으므로 인스턴스의 클래스형과 참조하는 포인터 변수의 상속관계를 생각해서 명시적 형변환을 해야한다.

(한번 업 캐스팅이 된 포인터 값을 다운 캐스팅하는 경우에만 안전한다)

 

기반 클래스형 포인터 변수로 오버라이딩된 멤버함수 호출하기

#include<iostream>
using namespace std;

class calc {
protected:
int a;
int b;
public:
calc();
calc(int new_a,int new_b);
void prn();
};
calc::calc() {
a = 0;
b = 0;
}
calc::calc(int new_a, int new_b) {
a = new_a;
b = new_b;
}
void calc::prn() {
cout << "---calc::prn---" << endl;
cout << a << "\t" << b << endl;
}
class Add :public calc {
protected:
int c;
public:
Add();
Add(int new_a, int new_b);
void sum();
void prn();
};
Add::Add() :calc() {

}
Add::Add(int new_a, int new_b) : calc(new_a, new_b) {
a = new_a;
b = new_b;
c = 0;
}
void Add::sum() {
c = a + b;
}
void Add::prn() {
cout << "---Add::prn---" << endl;
cout << a << "+" << b << "=" << c << endl;
}
void main() {
calc* calcptr;
calcptr = new Add(3,5);
calcptr->prn();
}

 

반응형