[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();
```

# <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
```
댓글
댓글 쓰기