[C++] 스터디 CPPALOM 4주차: 열혈 C++ 프로그래밍 Part 3
(파워 포인트 파일(.pptx)을 Markdown으로 변환하여 업로드하였음)
# <br>상속 모든 멤버를 물려받는다. UnivStudent는 WhoAreYou()에서 자신이 상속받은 WhatYorName(), HowOldAreYou()를 사용할 수 있다! ```C++ class Person { private: int age; // 나이 char name[50]; // 이름 public: Person(int myage, char * myname) : age(myage) { strcpy(name, myname); } void WhatYourName() const { cout<<"My name is "<<name<<endl; } void HowOldAreYou() const { cout<<"I'm "<<age<<" years old"<<endl; } }; ``` ```C++ class UnivStudent : public Person { private: char major[50]; // 전공과목 public: UnivStudent(char * myname, int myage, char * mymajor) : Person(myage, myname) { strcpy(major, mymajor); } void WhoAreYou() const { WhatYourName(); HowOldAreYou(); cout<<"My major is "<<major<<endl<<endl; } }; ``` # <br>생성자 * 자식 클래스의 생성자는 자신이 상속한 부모 클래스의 멤버를 초기화할 의무를 지닌다. * 그래서 자식의 생성자는 부모의 생성자를 호출하는 형태로 부모 클래스의 멤버를 초기화하는 것이 좋다. * private 멤버의 상속 * 자식 클래스는 부모 클래스의 private 멤버에 접근할 수 없다. * private의 범위는 클래스이기 때문이다. ```C++ UnivStudent(char * myname, int myage, char * mymajor) : Person(myage, myname) { strcpy(major, mymajor); } ``` # <br>유도 클래스의 객체 생성 과정 유도 클래스의 객체생성 과정에서 기초 클래스의 생성자는 100% 호출된다. 생성자 호출을 명시하지 않을 시, 기초 클래스의 void 생성자가 호출된다. ```C++ class SoBase { private: int baseNum; public: SoBase() : baseNum(20) { cout<<"SoBase()"<<endl; } SoBase(int n) : baseNum(n) { cout<<"SoBase(int n)"<<endl; } void ShowBaseData() { cout<<baseNum<<endl; } }; ``` ```C++ class SoDerived : public SoBase { private: int derivNum; public: SoDerived() : derivNum(30) { cout<<"SoDerived()"<<endl; } SoDerived(int n) : derivNum(n) { cout<<"SoDerived(int n)"<<endl; } SoDerived(int n1, int n2) : SoBase(n1), derivNum(n2) { cout<<"SoDerived(int n1, int n2)"<<endl; } void ShowDerivData() { ShowBaseData(); cout<<derivNum<<endl; } }; ``` ```C++ int main(void) { cout<<"case1..... "<<endl; SoDerived dr1; dr1.ShowDerivData(); cout<<"-------------------"<<endl; cout<<"case2..... "<<endl; SoDerived dr2(12); dr2.ShowDerivData(); cout<<"-------------------"<<endl; cout<<"case3..... "<<endl; SoDerived dr3(23, 24); dr3.ShowDerivData(); return 0; }; ``` ```C++ case1..... SoBase() SoDerived() 20 30 ----------------- case2..... SoBase() SoDerived(int n) 20 12 ----------------- case3..... SoBase(int n) SoDerived(int n1, int n2) 23 24 ``` # <br>유도 클래스의 객체 소멸 과정 스택에 생성된 객체의 소멸순서는 생성순서와 반대이다. ```C++ class SoBase { private: int baseNum; public: SoBase(int n) : baseNum(n) { cout<<"SoBase() : "<<baseNum<<endl; } ~SoBase() { cout<<"~SoBase() : "<<baseNum<<endl; } }; ``` ```C++ class SoDerived : public SoBase { private: int derivNum; public: SoDerived(int n) : SoBase(n), derivNum(n) { cout<<"SoDerived() : "<<derivNum<<endl; } ~SoDerived() { cout<<"~SoDerived() : "<<derivNum<<endl; } }; ``` ```C++ int main(void) { SoDerived drv1(15); SoDerived drv2(27); return 0; }; ``` ```C++ SoBase() : 15 SoDerived() : 15 SoBase() : 27 SoDerived() : 27 ~SoDerived() : 27 ~SoBase() : 27 ~SoDerived() : 15 ~SoBase() : 15 ``` # <br>동적 할당의 경우 생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제한다. ```C++ class Person { private: char * name; public: Person(char * myname) { name=new char[strlen(myname)+1]; strcpy(name, myname); } ~Person() { delete []name; } void WhatYourName() const { cout<<"My name is "<<name<<endl; } }; ``` ```C++ class UnivStudent : public Person { private: char * major; public: UnivStudent(char * myname, char * mymajor) :Person(myname) { major=new char[strlen(mymajor)+1]; strcpy(major, mymajor); } ~UnivStudent() { delete []major; } void WhoAreYou() const { WhatYourName(); cout<<"My major is "<<major<<endl<<endl; } }; ``` ```C++ int main(void) { UnivStudent st1("Kim", "Mathmatics"); st1.WhoAreYou(); UnivStudent st2("Hong", "Physics"); st2.WhoAreYou(); return 0; }; ``` ```C++ My name is Kim My major is Mathematics My name is Hong My major is Physics ``` # <br>protected * private < protected < public * public이 허용하는 접근의 범위가 가장 넓고, privat이 허용하는 접근의 범위가 가장 좁다. * protected로 선언된 멤버변수는 이를 상속하는 유도 클래스에서 접근이 가능하다. ```C++ class Base { private: int num1; protected: int num2; public: int num3; Base() : num1(1), num2(2), num3(3) { } }; class Derived : protected Base { }; int main(void) { Derived drv; cout<<drv.num3<<endl; // compile error return 0; } ``` # <br>세가지 형태의 상속 * protected 상속 * protected보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다. * private 상속 * private보다 접근의 범위가 넓은 멤버는 private로 변경시켜서 상속하겠다. * public 상속 * public보다 접근의 범위가 넓은 멤버는 public으로 변경시켜서 상속하겠다. ```C++ class Base { private: int num1; protected: int num2; public: int num3; Base() : num1(1), num2(2), num3(3) { } }; class Derived : protected Base { }; int main(void) { Derived drv; cout<<drv.num3<<endl; // compile error return 0; } ``` # <br>상속의 조건 * IS-A 관계 * 무선 전화기는 전화기입니다. * 노트북 컴퓨터는 컴퓨터입니다. * 기초 클래스와 유도 클래스 간에 IS-A 관계가 성립하지 않는다면, 적절하지 않은 상속일 수 있다. * 더 나아가: * LSP, Liskov Substitution Principle을 고려해야 한다. # <br>복합 관계 * Composition 관계, HAS-A 관계의 경우. * 경찰은 총을 가지고 있다. ```C++ class Gun { private: int bullet; // 장전된 총알의 수 public: Gun(int bnum) : bullet(bnum) { } void Shut() { cout<<"BBANG!"<<endl; bullet--; } }; ``` ```C++ class Police { private: int handcuffs; // 소유한 수갑의 수 Gun * pistol; // 소유하고 있는 권총 public: Police(int bnum, int bcuff) : handcuffs(bcuff) { if(bnum>0) pistol=new Gun(bnum); else pistol=NULL; } void PutHandcuff() { cout<<"SNAP!"<<endl; handcuffs--; } void Shut() { if(pistol==NULL) cout<<"Hut BBANG!"<<endl; else pistol->Shut(); } ~Police() { if(pistol!=NULL) delete pistol; } }; ``` ```C++ int main(void) { Police pman1(5, 3); pman1.Shut(); pman1.PutHandcuff(); Police pman2(0, 3); // 권총소유하지 않은 경찰 pman2.Shut(); pman2.PutHandcuff(); return 0; } ``` ```C++ BBANG! SNAO! Hut BBANG! SNAP! ``` # <br>다형성 부모 클래스 객체에 대한 포인터는 자식 클래스 객체 역시 가리킬 수 있다. ```C++ class Person { public: void Sleep() { cout<<"Sleep"<<endl; } }; ``` ```C++ class Student : public Person { public: void Study() { cout<<"Study"<<endl; } }; class PartTimeStudent : public Student { public: void Work() { cout<<"Work"<<endl; } }; ``` ```C++ int main(void) { Person * ptr1=new Student(); Person * ptr2=new PartTimeStudent(); Student * ptr3=new PartTimeStudent(); ptr1->Sleep(); ptr2->Sleep(); ptr3->Study(); delete ptr1; delete ptr2; delete ptr3; return 0; } ``` ```C++ Sleep Sleep Study ``` # <br>다형성을 이용한 문제 해결 * EmployeeHandler는 Employee들의 정보를 관리하며 그것들을 기반으로 의미 있는 정보를 보여준다. * 문제: * 단 하나의 Employee 클래스에만 의존하고 있었으나, * 요구사항의 변경으로 인해 Employee뿐만 아니라 PermanentWorker, SalesWorker, TemporaryWorker들 또한 다루어야 한다. * EmployeeHandler의 변경을 피하면서 이를 해결할 수 있을까? ```C++ class EmployeeHandler { private: Employee* empList[50]; int empNum; public: EmployeeHandler() : empNum(0) { } void AddEmployee(Employee* emp) { empList[empNum++]=emp; } void ShowAllSalaryInfo() const { for(int i=0; i<empNum; i++) empList[i]->ShowSalaryInfo(); } void ShowTotalSalary() const { int sum=0; for(int i=0; i<empNum; i++) sum+=empList[i]->GetPay(); cout<<"salary sum: "<<sum<<endl; } ~EmployeeHandler() { for(int i=0; i<empNum; i++) delete empList[i]; } }; ``` * 해결책: * 기존의 Employee 클래스를 상속하는 PermanentWorker, SalesWorker, TemporaryWorker들을 작성한다. * 또 다른 문제: * Employee는 ShowSalaryInfo() 함수가 존재하지 않는다. 결국 컴파일 에러가 난다. ```C++ class Employee { private: char name[100]; public: Employee(char * name) { strcpy(this->name, name); } void ShowYourName() const { cout<<"name: "<<name<<endl; } }; ``` ```C++ class PermanentWorker : public Employee { private: int salary; public: PermanentWorker(char * name, int money) : Employee(name), salary(money) { } int GetPay() const { return salary; } void ShowSalaryInfo() const { ShowYourName(); cout<<"salary: "<<GetPay()<<endl<<endl; } }; ``` ```C++ class EmployeeHandler { void ShowAllSalaryInfo() const { for(int i=0; i<empNum; i++) empList[i]->ShowSalaryInfo(); } }; ``` * 해결책: * Employee 클래스에 빈 ShowSalryInfo() 함수를 만들고, 이를 오버라이딩하도록 한다. * 또 다른 문제: * 코드는 여전히 Employee 포인터를 이용해서 함수를 호출하므로, 컴파일러는 Employee::ShowSalryInfo()를 호출한다. ```C++ class Employee { private: char name[100]; public: Employee(char * name) { strcpy(this->name, name); } void ShowYourName() const { cout<<"name: "<<name<<endl; } void ShowSalaryInfo() const { } }; ``` ```C++ class PermanentWorker : public Employee { private: int salary; public: PermanentWorker(char * name, int money) : Employee(name), salary(money) { } int GetPay() const { return salary; } void ShowSalaryInfo() const { ShowYourName(); cout<<"salary: "<<GetPay()<<endl<<endl; } }; ``` ```C++ class EmployeeHandler { private: Employee* empList[50]; public: void ShowAllSalaryInfo() const { for(int i=0; i<empNum; i++) empList[i]->ShowSalaryInfo(); } }; ``` # <br>가상 함수 ```C++ class First { public: void MyFunc() { cout<<"FirstFunc"<<endl;} }; class Second: public First { public: void MyFunc() { cout<<"SecondFunc"<<endl; } }; class Third: public Second { public: void MyFunc() { cout<<"ThirdFunc"<<endl; } }; int main(void) { Third * tptr=new Third(); Second * sptr=tptr; First * fptr=sptr; fptr->MyFunc(); sptr->MyFunc(); tptr->MyFunc(); delete tptr; return 0; } ``` ```C++ class First { public: virtual void MyFunc() { cout<<"FirstFunc"<<endl;} }; class Second: public First { public: virtual void MyFunc() { cout<<"SecondFunc"<<endl; } }; class Third: public Second { public: virtual void MyFunc() { cout<<"ThirdFunc"<<endl; } }; int main(void) { Third * tptr=new Third(); Second * sptr=tptr; First * fptr=sptr; fptr->MyFunc(); sptr->MyFunc(); tptr->MyFunc(); delete tptr; return 0; } ``` * virtual 키워드를 사용하지 않은 경우: * 포인터의 타입에 따라서 함수가 호출된다. * virtual 키워드를 사용한 경우: * 포인터의 타입에 상관 없이 실제 가리키는 객체에 따라서 함수가 호출된다. ```C++ FirstFunc SecondFunc ThirdFunc ``` ```C++ ThirdFunc ThirdFunc ThirdFunc ``` # <br>순수 가상함수와 추상 클래스 * virtual 함수 뒤에 = 0을 붙이면 순수 가상함수가 된다. * 구현이 없다는 의미이므로, 해당 클래스의 객체를 생성할 수 없다. * 이를 추상 클래스라고 말한다. ```C++ class Employee { private: char name[100]; public: Employee(char * name) { strcpy(this->name, name); } void ShowYourName() const { cout<<"name: "<<name<<endl; } virtual int GetPay() const = 0; virtual void ShowSalaryInfo() const = 0; }; ``` # <br>가상 소멸자 * 소멸자를 virtual로 선언하면 가상 소멸자가 되며, 이는 자동으로 유도 클래스의 소멸자부터 우선 호출하도록 만든다. * 만약 선언하지 않는다면, 아래 코드의 경우에서 Second 클래스의 소멸자는 호출되지 않는다. * First 포인터로 선언되어 있으므로, 컴파일러는 First 클래스의 객체라고 인지하기 때문이다. ```C++ class First { private: char * strOne; public: First(char * str) { strOne=new char[strlen(str)+1]; } virtual ~First() { cout<<"~First()"<<endl; delete []strOne; } }; class Second: public First { private: char * strTwo; public: Second(char * str1, char * str2) : First(str1) { strTwo=new char[strlen(str2)+1]; } virtual ~Second() { cout<<"~Second()"<<endl; delete []strTwo; } }; ``` ```C++ int main(void) { First * ptr=new Second("simple", "complex"); delete ptr; return 0; } ``` # <br>참조자의 참조 가능성 참조자 역시 포인터와 같이 동적으로 객체를 참조할 수 있다. ```C++ int main(void) { Third obj; obj.FirstFunc(); obj.SecondFunc(); obj.ThirdFunc(); obj.SimpleFunc(); Second & sref=obj; sref.FirstFunc(); sref.SecondFunc(); sref.SimpleFunc(); First & fref=obj; fref.FirstFunc(); fref.SimpleFunc(); return 0; } ``` ```C++ class First { public: void FirstFunc() { cout<<"FirstFunc()"<<endl; } virtual void SimpleFunc() { cout<<"First's SimpleFunc()"<<endl; } }; class Second: public First { public: void SecondFunc() { cout<<"SecondFunc()"<<endl; } virtual void SimpleFunc() { cout<<"Second's SimpleFunc()"<<endl; } }; class Third: public Second { public: void ThirdFunc() { cout<<"ThirdFunc()"<<endl;} virtual void SimpleFunc() { cout<<"Third's SimpleFunc()"<<endl; } }; ``` ```C++ FirstFunc() SecondFunc() ThirdFunc() Third’s SimpleFunc() FirstFunc() SecondFunc() Third’s SimpleFunc() FirstFunc() Third’s SimpleFunc() ``` # <br>가상 함수가 참조되는 방식 * 각 클래스는 자신만의 v-table을 지니고 있다. * v-table의 각 key(함수 signature)는 호출되어야 할 함수를 가리키고 있다. * 참조를 이용해서 특정 함수 signature를 접근할 때, v-table의 값을 이용해 동적 디스패칭 된다. * 다음 코드의 경우: * pA가 가리키는 객체의 v-table ptr를 확인. * v-table ptr는 B 클래스의 v-table을 가리킴. * v-table의 virtualFunc()를 확인. * virtualFunc() 호출. ```C++ A* pA = new B(); pA->virtualFunc(); ``` ![](img%5Cweek4_subin0.png) # <br>다중 상속 * 둘 이상의 클래스를 동시에 상속하는 것이다. * 다중 상속에는 모호성이 있다: * 두 기초 클래스에 동일한 이름의 멤버가 존재하는 경우. * 해결책: 직접 범위를 명시한다. ```C++ #include <iostream> using namespace std; class BaseOne { public: void SimpleFuncOne() { cout<<"BaseOne"<<endl; } }; class BaseTwo { public: void SimpleFuncTwo() { cout<<"BaseTwo"<<endl; } }; class MultiDrived : public BaseOne, protected BaseTwo { public: void ComplexFunc() { SimpleFuncOne(); SimpleFuncTwo(); } }; int main(void) { MultiDrived mdr; mdr.ComplexFunc(); return 0; } ``` ```C++ class MultiDrived : public BaseOne, protected BaseTwo { public: void ComplexFunc() { BaseOne::SimpleFunc(); BaseTwo::SimpleFunc(); } }; ``` # <br>가상 상속 ```C++ class Base { public: Base() { cout<<"Base Constructor"<<endl; } void SimpleFunc() { cout<<"BaseOne"<<endl; } }; class MiddleDrivedOne : virtual public Base { public: MiddleDrivedOne() : Base() { cout<<"MiddleDrivedOne Constructor"<<endl; } void MiddleFuncOne() { SimpleFunc(); cout<<"MiddleDrivedOne"<<endl; } }; class MiddleDrivedTwo : virtual public Base { public: MiddleDrivedTwo() : Base() { cout<<"MiddleDrivedTwo Constructor"<<endl; } void MiddleFuncTwo() { SimpleFunc(); cout<<"MiddleDrivedTwo"<<endl; } }; ``` * 이런 구조의 경우, LastDerived는 Base를 두 번 상속 받게 된다. * virtual 키워드를 이용해서 단 한 번만 상속받게 할 수 있다. ```C++ class LastDerived : public MiddleDrivedOne, public MiddleDrivedTwo { public: LastDerived() : MiddleDrivedOne(), MiddleDrivedTwo() { cout<<"LastDerived Constructor"<<endl; } void ComplexFunc() { MiddleFuncOne(); MiddleFuncTwo(); SimpleFunc(); } }; int main(void) { cout<<"객체 생성 전 ..... "<<endl; LastDerived ldr; cout<<"객체 생성 후 ..... "<<endl; ldr.ComplexFunc(); return 0; } ``` ```C++ int main(void) { cout<<"객체 생성 전 ..... "<<endl; LastDerived ldr; cout<<"객체 생성 후 ..... "<<endl; ldr.ComplexFunc(); return 0; } ``` ```C++ 객체 생성 전 ..... Base Constructor MiddleDerivedOne Constructor MiddleDerivedTwo Constructor LastDerived Constructor 객체 생성 후 ..... BaseOne MiddleDerivedOne BaseOne MiddleDerivedTwo BaseOne ```
댓글
댓글 쓰기