{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[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
```

댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기