Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 26 | 27 | 28 | 29 | 30 |
Tags
- Javascript
- Web
- mysql
- 딥러닝
- hackthissite
- BOF 원정대
- Scala
- 경제
- hackerschool
- Linux
- c++
- BOF
- backend
- 리눅스
- 러닝 스칼라
- 웹해킹
- php
- 러닝스칼라
- Shellcode
- hacking
- deep learning
- 파이썬
- 챗GPT
- 백엔드
- ChatGPT
- c
- Python
- webhacking
- flask
- 인공지능
Archives
- Today
- Total
jam 블로그
[C++] 008. 상속과 다형성 본문
728x90
I. 상속의 조건(미완성)
상속은 만병통치약이 아니다.
- 잘못된 상속의 경우 클래스의 관계를 복잡하게 만드는 단점이 있다.
IS-A 관계에 의한 상속
- is a 관계라는 것은 ~은 ~이다. 라는 뜻으로 class Student : public Person 이면 Student class는 public Person 이다. 라는 것이다.
Has-A 관계에 의한 상속! 그리고 대안
- 상속은 소유를 표현하기 위해서도 사용된다.
II. 상속된 객체와 포인터의 관계
- 객체 포인터 : 객체의 주소 값을 저장할 수 있는 포인터
- #include <iostream>
- using namespace std;
- class Person
{
public:
void Sleep()
{
cout<<"Sleep"<<endl;
}
}; - class Student : public Person
{
public:
void Study()
{
cout<<"Study"<<endl;
}
}; - class PartTimeStd : public Student
{
public:
void Work()
{
cout<<"Work"<<endl;
}
}; - int main()
{
Person* p1 = new Person;
Person* p2 = new Student;
Person* p3 = new PartTimeStd; - p1->Sleep();
p2->Sleep();
p3->Sleep();
return 0;
}
메인에서 p1은 Person을 p2는 Student를 p3는 PartTimestd를 가리킨다. 그렇기 때문에 sleep을 사용할 수 있다.
Person* p1 = new PartTimestd; 처럼 바꾸어도 컴파일이 된다.
- 이유는 PartTimestd 클래스의 객체는 PartTimestd 객체이자, Student객체이면서 동시에 Person객체 이기도 하다.
Employee problem 해결의 첫번째 단계
- 이부분은 책으로 봐주세요
객체 포인터의 권한
- 클래스의 객체 포인터는 가리키는 대상이 어떠한 객체이건, 클래스 타입 내에 선언된 멤버와 클래스가 상속한 클래스의 멤버에만 접근이 가능하다.
III. 상속된 객체와 참조의 관계
객체 레퍼런스 : 객체를 참조할 수 있는 레퍼런스
- AA 클래스의 포인터는 *AAA 객체의 주소 값 뿐만 아니라, AAA 클래스를 상속받는 클래스 객체의 주소 값도 저장이 가능하다.
- AAA 클래스의 레퍼런스는 AAA 객체 뿐만아니라, AAA 클래스를 상속받는 클래스의 객체도 참조 가능하다.
- #include <iostream>
- using namespace std;
- class Person
{
public:
void Sleep()
{
cout<<"Sleep()"<<endl;
}
}; - class Student : public Person
{
public:
void Study()
{
cout<<"study()"<<endl;
}
}; - class PartTimeStd : public Student
{
public:
void Work()
{
cout<<"Work()"<<endl;
}
}; - int main()
{
PartTimeStd p;
Student& ref1 = p;
Person& ref2 = p; - p.Sleep();
ref1.Sleep();
ref2.Sleep();
return 0;
}
- 객체를 참조하는 레퍼런스의 특징을 보여주는 소스이다.
객체 레퍼런스의 권한
- AAA 클래스의 객체 포인터는 가리키는 대상이 어떠한 객체이건, AAA 클래스 타입 내에 선언된 멤버와 AAA클래스가 상속한 클래스의 멤버에만 접근이 가능하다.
- AAA 클래스의 레퍼런스는 참조하는 대상이 어떠한 객체이건, AAA 클래스 타입 내에 선언된 멤버와 AAA ㅡㅋㄹ래스가 상속한 클래스의 멤버에만 접근이 가능하다.
- #include <iostream>
- using namespace std;
- class Person
{
public:
void Sleep()
{
cout<<"Sleep()"<<endl;
}
}; - class Student : public Person
{
public:
void Study()
{
cout<<"study()"<<endl;
}
}; - class PartTimeStd : public Student
{
public:
void Work()
{
cout<<"Work()"<<endl;
}
}; - int main()
{
PartTimeStd p;
p.Sleep();
p.Study();
p.Work(); - Person& ref = p;
ref.Sleep();
ref.Study();
ref.Work();
return 0;
}
위의 소스에서 오류가 나는데 메인에서 ref.Study()와 ref.Work()가 오류가 난다.
- 이유는 ref에 p를 선언 하였지만 Person클래스 이기 때문에 Person클래스 안에 있는 Sleep만 불러 올수 있다.
IV. Static Binding & Dynamic Binding
- 오버라이딩의 이해
- #include <iostream>
- using namespace std;
- class AAA
{
public:
void fct()
{
cout<<"AAA"<<endl;
}
}; - class BBB : public AAA
{
public:
void fct()
{
cout<<"BBB"<<endl;
}
}; - int main()
{
BBB b;
b.fct();
return 0;
}
class BBB나 class AAA 에서 fct 함수를 같이 썼는데 메인에서 BBB에 있는 fct를 호출하게 된다.
- 이유는 AAA에 정의했던 fct는 BBB에서 정의했던 fct 함수에 가져진 것이다.
- 오버라이딩된 함수를 호출 하는 방법
- #include <iostream>
- using namespace std;
- class AAA
{
public:
void fct()
{
cout<<"AAA"<<endl;
}
}; - class BBB : public AAA
{
public:
void fct()
{
cout<<"BBB"<<endl;
}
}; - int main()
{
BBB *b = new BBB;
b->fct(); - AAA* a = b;
a->fct(); - delete b;
return 0;
}
오버라이딩 된 함수를 호출 하는 방법은 포인터를 써서 하는 것이다.
AAA* a = b; 처럼 포인터 b가 지니고 있는 값을 a에 대입 시켜 주므로써 fct를 호출 하면 AAA에 있는 것이 호출 된다.
- 여기는 설명이 좀 이상하니 나중에 보충하겠습니다.
멤버 함수를 가상(virtual)으로 선언하기
- 오버라이딩되는 함수를 가상으로 선언할수 있다. 즉, dynamic binding을 한다는 의미이다.
- #include <iostream>
- using namespace std;
- class AAA
{
public:
virtual void fct()
{
cout<<"AAA"<<endl;
}
}; - class BBB : public AAA
{
public:
void fct()
{
cout<<"BBB"<<endl;
}
}; - int main()
{
BBB *b = new BBB;
b->fct(); - AAA* a = b;
a->fct(); - delete b;
return 0;
}
- 결과값을 보면 AAA 클래스 안에 fct가 없는 것처럼 나온다. 가상의 함수로 설정해 놨기 때문에 BBB AAA 가 나와야 할것이 BBB BBB가 나온 것이다.
- 가상 함수의 특성은 상속 된다.
- #include <iostream>
- using namespace std;
- class AAA
{
public:
virtual void fct()
{
cout<<"AAA"<<endl;
}
}; - class BBB : public AAA
{
public:
void fct()
{
cout<<"BBB"<<endl;
}
};
class CCC : public BBB
{
public:
void fct()
{
cout<<"CCC"<<endl;
}
}; - int main()
{
BBB *b = new CCC;
b->fct(); - AAA* a = b;
a->fct(); - delete b;
return 0;
}
- 위의 결과를 찍어보면 CCC CCC가 나오는데 AAA에 있는 virtual 로 선언하여 상속 받은 BBB나 CCC가 fct 함수가 virtual이 선언 되어 있는 것과 같으나 최종적으로 오버라이딩한 함수를 제외한 나머지 함수는 가려지게 되어서 CCC에있는 fct 함수가 호출된다.
- 무엇이 static binding이고, 무엇이 dynamic binding인가?
- #include <iostream>
- using namespace std;
- class AAA
{
public:
virtual void fct()
{
cout<<"AAA"<<endl;
}
}; - class BBB : public AAA
{
public:
void fct()
{
cout<<"BBB"<<endl;
}
};
int main()
{
BBB b;
b.fct(); - AAA* a = new BBB;
a->fct();
return 0;
}
main을 보면 b.fct() 가 static binding이고 AAA* a = new BBB가 dynamic binding 이다.
- 왜냐하면 b.fct는 컴파일하는 동안에 호출될 함수가 결정되기 때문이다. 즉 호출할 함수가 이미 고정되어 있다는 것이다
- AAA* a = new BBB는 컴파일 동안이 아닌 실행하는 동안에 호출될 함수가 결정된다. 즉 포인터가 가리키는 객체가 무엇이냐에 따라서 그 문장이 호출되는 함수가 유동적이기 때문이다.
오버라이딩된 함수 호출하기
- 범위 지정 연산자를 통해서 오버라이딩된 함수를 호출 가능하다.
- #include <iostream>
- using namespace std;
- class AAA
{
public:
virtual void fct()
{
cout<<"AAA"<<endl;
}
}; - class BBB : public AAA
{
public:
void fct()
{
AAA::fct();
cout<<"BBB"<<endl;
}
};
int main()
{
AAA* a = new BBB;
cout<<"1"<<endl;
a->fct(); - cout<<"2"<<endl;
a->AAA::fct();
return 0;
}
- 첫번째 방법은 BBB를 호출시 AAA쪽에 fct를 호출하라는 뜻이다.
- 두번째 방법은 a를 AAA:fct()로 아예 경로를 지정해 주는 방법이다.
V. Employee Problem 완전 해결
- 여기 부분은 책을 봐주세요
순수 가상 함수와 추상 클래스
virtual int GetPay() = 0 이런식으로 선언 한것이 순수 가상 함수이다.
- 컴파일러에게 이 함수가 호출될 일이 없거든 선언만 하고 정의를 안한 것이다. 라고 얘기 해주는 정도의 내용이다
- 이러한 멤버함수가 하나 이상이 있으면 추상 클래스라고 한다.
VI. virtual 소멸자의 필요성
- #include <iostream>
- using namespace std;
- class AAA
{
char* str1;
public:
AAA(char* _str1)
{
str1 = new char[strlen(_str1)+1];
strcpy(str1,_str1);
}
~AAA()
{
cout<<"~AAA() call"<<endl;
delete []str1;
}
virtual void ShowString()
{
cout<<str1<<' ';
}
}; - class BBB : public AAA
{
char* str2;
public:
BBB(char* _str1,char* _str2):AAA(_str1)
{
str2 = new char[strlen(_str2)+1];
strcpy(str2,_str2);
}
~BBB()
{
cout<<"~BBB() call"<<endl;
delete []str2;
}
virtual void ShowString()
{
AAA::ShowString();
cout<<str2<<endl;
}
}; - int main()
{
AAA* a = new BBB("Good","123");
BBB* b = new BBB("Good","345"); - a->ShowString();
b->ShowString(); - cout<<"-----객체 소멸 직전-----"<<endl;
delete a;
delete b;
return 0;
}
- 위의 소스에서 main 함수를 보면 delete a를 해줄때 포인터 a를 통한 BBB 객체의 소멸을 시도 하지만 AAA 타입이기 때문에 BBB객체를 AAA 객체로 인식한다. 그래서 AAA의 소멸자만 호출이 된다.
virtual 소멸자
- 위에 해결책은 AAA 소멸자에 virtual을 해주면 된다.
- virtual ~AAA()
{
cout<<"~AAA() call"<<endl;
delete []str1;
}
- 위의 방식은 먼저 AAA클래스의 소멸자를 호출하려 할때 virtual 관계여서 같은 역할을 하는 BBB 소멸자를 호출한다. 그런데 BBB 클래스는 AAA클래스를 상속 받고 있기 때문에 BBB 클래스의 소멸자를 한 후에 AAA 클래스의 소멸자를 호출한다.
'개발 및 관련 자료 > C' 카테고리의 다른 글
[C++] 010. 연산자 오버로딩 (0) | 2013.05.12 |
---|---|
[C++] 009. virtual의 원리와 다중 상속 (0) | 2013.05.12 |
[C++] 007. 상속(Inheritance)의 이해 (0) | 2013.05.12 |
[C++] 006. static 멤버와 const 멤버 (0) | 2013.05.12 |
[C++] 005. 복사 생성자 (0) | 2013.05.12 |
Comments