728x90
728x90

유니코드 타입인 TCHAR형 문자열은 "" 쌍따옴표를 사용하여 문자열을 대입하거나 복사할 경우 오류가 생긴다. TEXT(""), L"" 로 문자열을 TCHAR형에 맞게 변형시켜야 한다.

 

//오류
lstrcpy(str, "WinAPI 양배추한닢");
 
//해결1
lstrcpy(str, TEXT("WinAPI 양배추한닢"));
//해결2
lstrcpy(str, L"WinAPI 양배추한닢");

 

 

[WinAPI] "const char *" 형식의 인수가 "LPCWSTR" 형식의 매개 변수와 호환되지 않습니다

유니코드 타입인 TCHAR형 문자열은 "" 쌍따옴표를 사용하여 문자열을 대입하거나 복사할 경우 오류가 생긴다 따라서 TEXT(""), L"" 로 문자열을 TCHAR형에 맞게 변형시켜줘야 한다 //오류 lstrcpy(str, "WinA

choryeonworkshop.tistory.com

 

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

SetDlgItemText(), SetDlgItemTextA(), SetDlgItemTextW(), GetDlgItemText(), .... 등은 에디트 컨트롤에 텍스트를 출력, 읽기 등이 가능한 함수


 

SetDlgItemText

SetDlgItemText() : 에디트 컨트롤에 텍스트 출력. 16bit

void SetDlgItemText(int nID, LPCTSTR lpszString);
// 매개 변수 nID의 아이디를 가진 컨트롤에 lpszString의 문자열을 입력해주는 함수이다.

 

SetDlgItemTextA() : 32bit
SetDlgItemTextW() : wide

 

* LPCTSTR : 

LP : long pointer의 약자.
C : constant (상수)
T : 운영체제가 multi-byte환경이면, char형으로,
unicode환경이면, w_char, wide char형으로 type casting
STR : string 형의 자료

 

 

GetDlgItemText 

GetDlgItemText() : 에디트 컨트롤에서 텍스트를 가져오는 함수. 16bit
GetDlgItemTextA() : 32bit
GetDlgItemTextW() : wide

 

 

결론

자동으로 유니코드 문자열을 사용하나, 멀티바이트 문자열을 사용하느냐에 따라서 전처리기가 변환해주기 때문에 혹시 모를 미래를 생각해서 SetDlgItemText, GetDlgItemText를 써 주는 게 좋다.

 

 

현재는 유니코드 모드여서 SetDlgItemText를 사용하면 SetDlgItemTextW로 자동 변환되고 있다.
멀티바이트 모드를 사용중이라면 SetDlgItemTextA로 자동 변환될 것이다.

* 참조 포스팅 : 1

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
파일 처리 관련 라이브러리

1. CFile
CObject로부터 계승
바이너리 파일을 해석하고 다루기 위한 클래스.
파일을 읽을 때 사용.

2. CStdioFile
CFile로부터 계승
텍스트 형식의 파일을 해석하고 다루기 위한 클래스.
주로 라인 단위로 읽고 쓴다.

3. CMemFile
CFile로부터 계승
메모리 파일을 해석하고 다루기 위한 클래스.

* 메모리 파일이란 파일을 메모리에 생성하고 디스크 파일인 것처럼 다룬다는 것.
메모리에서 작업하여 디스크 상의 작업보다 작업 속도가 빠르다.
임시로 파일을 만들어야 할 때 쓰인다.

 

더보기

CFile 클래스를 사용하면 바이너리 또는 텍스트모드로 파일을 열어서 사용할수가 있습니다. 하지만
CFile 클래스는 파일을 입출력하는 함수가 텍스트기반이 아닌 바이너리 기반의 함수형태만
제공하기 때문에 텍스트 파일을 입출려할때 다소 불편함이 있습니다. 예를들어, 텍스트 파일을
열고 한줄씩 텍스트 데이터를 읽는다고 한다면 한자씩 읽어서 아스키코드를 비교해야하는
번거로운 작업을 프로그래머가 직접 구성해야 합니다.

이런불편함을 줄여주고자 예전에 런타임함수에서 사용하던 fgets, fputs 같은 함수를 제공하는
CStdioFile 클래스를 추가적으로 제공하는 것입니다. 

 

● 참고한 곳 : 1, 2, 3

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

C++ POSIX function names 오류 메세지이다.

The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name: new-name.

 

To turn off deprecation warnings for these functions, define the preprocessor macro _CRT_NONSTDC_NO_WARNINGS. You can define this macro at the command line by including the option /D_CRT_NONSTDC_NO_WARNINGS.

* 예제를 풀어보면서 strupr 함수 사용 시 오류 발생

 

임시로 적용

#define _CRT_NONSTDC_NO_WARNINGS

 

비슷한 걸로 strcpy 오류를 잠재우는

#define _CRT_SECURE_NO_WARNINGS

 

참고한 곳

https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4996?f1url=%3FappId%3DDev16IDEF1%26l%3DKO-KR%26k%3Dk(C4996)%26rd%3Dtrue&view=msvc-160

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

1. 전장 내용확인

int main(void)
{
	Simple * sim1 = new ....;
	Simple * sim2 = new ....;
}

두 포인터 변수는 new 연산자에 의해 생성된 객체가 가리킨다.

Q. 두 포인터 변수가 가리키는 객체의 자료형은 무엇일까 생각해보자.

sim1, sim2가 가리키는 객체는 Simple 클래스이거나 Simple 클래스를 상속하는 클래스의 객체일 것이다.

 

 

2. 전장 내용확인

class Base
{
public:
	void BaseFunc() { cout<<"Base"<<endl; }
};

class Derived : public Base
{
public:
	void DerivedFunc() { cout<<"Derived"<<endl; }
};
int main(void)
{
Base * bptr = new Derived(); //컴파일 ㅇㅋ
bptr->DerivedFunc(); //에러
....
}

 

컴파일 에러 이유 : bptr이 Base형 포인터이기 때문이다.

가리키는 대상은 Derived 객체지만 컴파일되지 않는다. C++ 컴파일러는 포인터연산의 가능성 여부를 판단할 때,
포인터의 자료형을 기준으로 판단하지 실제 가리키는 객체의 자료형을 기준으로 판단하지 않는다.

 

 

int main(void)
{
Base * bptr = new Derived(); //컴파일 ㅇㅋ
Derived * dptr = bptr; //에러 - 포인터 bptr의 포인터 형만 갖고 대입가능성을 판단한다.
....
}

오류메세지 : base*에서 derived*로의 잘못된 변환

 

 

역시 포인터 형만 갖고 대입가능성을 판단하여 역시 컴파일 에러가 난다. 
Base * bptr = new Derived();가 컴파일이 되는 이유 : Derived 클래스는 Base 클래스의 유도클래스니까 Base 클래스의 포인터 변수로 Derived 객체의 참조가 가능하니 컴파일에 문제가 없다. 

 

int main(void)
{
Derived * dptr = new Derived(); //컴파일 ㅇㅋ
Base * bptr = dptr; //컴파일 ㅇㅋ
....
}

 

 

다른 예

#include <iostream>
using namespace std;

class First
{
public:
    void FirstFunc() { cout << "firstFunc" << endl; }
};

class Second: public First
{
public:
    void SecondFunc() { cout << "SecondFunc" << endl; }
};

class Third: public Second
{
public:
    void ThirdFunc() { cout << "ThirdFunc" << endl; }
};


int main()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;
}

Third형 포인터 변수 tptr이 가리키는 객체는 Second형 포인터 변수 sptr도 가리킬 수 있으므로 컴파일 오류가 없다.
그러나 객체를 참조하는 포인터의 형에 따라서 호출할 수 있는 함수의 종류에는 제한이 따른다.

int main()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;

    tptr->FirstFunc(); // (o)
    tptr->SecondFunc(); // (o)
    tptr->ThirdFunc(); // (o)

    sptr->FirstFunc(); // (o)
    sptr->SecondFunc(); // (o)
    //sptr->ThirdFunc(); // (x) class Second에 ThirdFunc 멤버가 없습니다

    fptr->FirstFunc(); // (o)
    //fptr->SecondFunc(); // (x)
    //fptr->ThirdFunc(); // (x) class First에 ThirdFunc 멤버가 없습니다

}

 

 결론 : 포인터 형에 해당하는 클래스에 정의된 멤버에만 접근이 가능하다. 

 

 

함수의 오버라이딩과 포인터 형
#include <iostream>
using namespace std;

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()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;

    fptr->MyFunc();
    sptr->MyFunc();
    tptr->MyFunc();
    
    delete tptr;
    return 0;
}

 

third형 포인터 변수 tptr 이 참조하는 객체에는 총 3개의 MyFunc()이 있고, 오버라이딩 관계라서 
가장 마지막에 오버라이딩한 Third 클래스의 MyFunc()이 호출된다.

 

 

가상함수

포인터 변수의 자료형에 따라 호출되는 함수의 종류가 달라지지 않게 하기 위한 함수
virtual 키워드의 선언을 통해 이뤄진다.

class First
{
public:
    /*void MyFunc() { cout << "firstFunc" << endl; }*/
    virtual void MyFunc(){ cout << "firstFunc" << endl; }
};

 

가상함수가 선언되면, 가상함수를 오버라이딩 하는 함수도 가상함수가 된다.
가상함수가 되면, 함수 호출 시 포인터의 자료형을 기반으로 호출대상을 결정하지 않고, 
포인터 변수가 실제로 가리키는 객체를 참조하여 호출을 대상을 결정한다. ***

#include <iostream>
using namespace std;

class First
{
public:
    /*void MyFunc() { cout << "firstFunc" << endl; }*/
    virtual void MyFunc(){ cout << "firstFunc" << endl; }
};

class Second: public First
{
public:
    virtual void MyFunc() { cout << "SecondFunc" << endl; }
    //void MyFunc() { cout << "SecondFunc" << endl; } //오버라이딩
};

class Third: public Second
{
public:
    virtual void MyFunc() { cout << "ThirdFunc" << endl; }
    //void MyFunc() { cout << "ThirdFunc" << endl; }
};


int main()
{
    Third* tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;

    fptr->MyFunc();
    sptr->MyFunc();
    tptr->MyFunc();
    
    delete tptr;
    return 0;
}

 

 

이전 단원의 문제 해결

// EmployeeManager4.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring> 
using namespace std;

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
    {
        return 0;
    }

    virtual void ShowSalaryInfo() const
    {

    }
};

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;
    }
};

// 임시직. 실제 일을 한 시간으로 급여 계산
class TemporaryWorker : public Employee
{
private:
    int workTime; // 이달에 일한 총 시간
    int payPerHour; // 시간 당 급여
public:
    TemporaryWorker(char* name, int pay)
        : Employee(name), workTime(0), payPerHour(pay)
    {  }
    void AddWorkTime(int time) // 일한 시간 추가
    {
        workTime += time;
    }
    int GetPay() const // 이달의 급여
    {
        return workTime * payPerHour;
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout << "salary: " << GetPay() << endl << endl;
    }
};

// 영업직 (정규직 일종). Employee가 아닌 PermanentWorker 상속
// 기본급여과 관련된 부분을 멤버로 포함, 상여금 부분만 멤버로 추가
class SalesWorker : public PermanentWorker
{
private:
    int salesResult; //월 판매실적
    double bonusRatio; //상여금 비율
public:
    SalesWorker(char* name, int money, double ratio)
        : PermanentWorker(name, money), salesResult(0), bonusRatio(ratio)
    {   }
    void AddSalesResult(int value)
    {
        salesResult += value;
    }
    int GetPay() const
    {
        return PermanentWorker::GetPay() //PermanentWorker의 GetPay() 호출
            + (int)(salesResult * bonusRatio);
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout << "salary: " << GetPay() << endl << endl; //SalesWorker의 GetPay() 호출 
    }
};


class EmployeeHandler
{
private:
    Employee* empList[50]; //Employee 객체의 주소값 저장
    // Employee 클래스를 상속하는 클래스의 객체도 이 배열에 함께 저장 가능
    int empNum;
public:
    EmployeeHandler() : empNum(0)
    {
    }
    void AddEmployee(Employee* emp)  //Employee 객체의 주소값을 전달 
     // Employee 클래스를 상속하는 클래스 PermenentWorker 객체의 주소값도 전달 가능
    {
        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];
    }
};

int main(void)
{
    // 직원관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
    EmployeeHandler handler;

    // 정규직 등록
    handler.AddEmployee(new PermanentWorker((char*)"KIM", 1000));
    handler.AddEmployee(new PermanentWorker((char*)"LEE", 1500));

    // 임시직 등록
    TemporaryWorker* alba = new TemporaryWorker((char*)"Jung", 700);
    alba->AddWorkTime(5); //5시간 일한결과 등록
    handler.AddEmployee(alba);

    // 영업직 등록
    SalesWorker* seller = new SalesWorker((char*)"Hong", 1000, 0.1);
    seller->AddSalesResult(7000); //영업실적 7000
    handler.AddEmployee(seller);

    // 이달 지불할 급여 정보
    handler.ShowAllSalaryInfo();

    // 이달 지불할 급여 총합
    handler.ShowTotalSalary();
    return 0;
}

 

 

순수 가상함수와 추상 클래스

Employee클래스는 객체 생성을 목적으로 정의된 것이 아니다
다음과 같은 실수가 있어도 문법적으로는 문제가 없어 컴파일러에서 잡지 못한다.

Employee * emp = new Employee("Lee Dong Sook");


가상함수 말고 순수 가상함수로 바꿔서 객체 생성을 문법적으로 막아준다.
순수 가상함수 : 함수의 몸체가 정의되지 않은 함수

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
    {
        return 0;
    }*/

    //순수가상함수 
    virtual int GetPay() const = 0;

    //가상함수
    /*virtual void ShowSalaryInfo() const
    {

    }*/

    //순수가상함수 
    virtual void ShowSalaryInfo() const = 0;
};

 

= 0은 명시적으로 몸체를 정의하지 않았음을 컴파일러에 알리는 것이다. 
이제 Employee 객체를 생성하려고 하면 컴파일에러가 난다.

하나 이상의 멤버함수를 순수 가상함수로 선언한 클래스추상클래스라고 한다.
완전하지 않아 객체생성이 불가능한 클래스라는 뜻이다.

 

 

다형성

방금처럼 문장은 같은데 결과는 다름을 의미한다.

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

비쥬얼스튜디오에 생성된 프로젝트에서 마우스 우클릭하여 속성>구성 속성>고급에 들어간다.


 

 

문자 집합 설정을 보면 유니코드 문자 집합 사용, 멀티바이트 문자 집합 사용 모드를 볼 수 있다.
윈도우 프로젝트 기본 설정은 유니코드 환경으로 되어있다.

 

 

 

 

문자집합은 2진법으로도 문자를 처리하기 위해 탄생한 것이고,
유니코드는 세계 모든 문자를 컴퓨터에서 일관되게 표현, 처리하기 위한 국제 표준 문자집합이다.

과거 하위버전의 비주얼스튜디오에서 작성했거나 멀티바이트 문자집합으로 작성한 프로젝트를 유니코드 문자 집합 환경으로 열면 오류가 발생한다.

 

 

문자집합 3종류 (출처)

  • SBCS(Single Byte Character Set) : 문자 하나를 표현 하는데 있어 1바이트를 사용 (예: 아스키코드, ..)
  • MBCS(Multi Byte Character Set) : 문자 하나를 표현 하는데 있어 다양한 바이트 수를 사용  
  • WBCS(Wide Byte Character Set) : 문자 하나를 표현 하는데 있어 2바이트를 사용 (예: 유니코드, ..)

 

비주얼스튜디오 문자집합은 비주얼스튜디오 프로젝트 생성 시 정의되는 기본 매크로의 차이가 있다.
프로젝트에서 마우스 우클릭하여 속성>C/C++>고급에 들어간다.

 

 

 

전처리기 preprocesser

프로그램을 컴파일할 때 컴파일 직전에 실행되는 별도의 프로그램. 전처리기가 실행되면 각 코드 파일에서 지시자(directives, #으로 시작해서 줄 바꿈으로 끝나는 코드)

전처리기는 컴파일러가 실행되기 직전에 단순히 텍스트를 조작하는 치환 역할을 하기도 하고, 디버깅에도 도움을 주며 헤더 파일의 중복 포함도 방지해주는 기능을 가진다. (#include <...>, #define ..., )

 

 

각각 유니코드/멀티바이트 문자집합 설정 시

 

전처리 과정에서 매크로 값의 차이로 인해 헤더에 선택되는 자료형 및 함수가 달라진다.

 

 

비주얼스튜디오 유니코드 문자집합과 멀티바이트 문자집합의 차이점 

멀티바이트 : 한 문자에 할당하는 공간이 일정하지 않다 (영어 : 1바이트, 다국어 2바이트)
유니코드 : 항상 2바이트 할당. 외국어 윈도우에서도 한글이 깨지지 않고, 다국어버전을 만들기 쉽다

char str = "문자열"; //멀티바이트
wchar_t  = L"문자열"; //유니코드

https://lunikism.com/entry/C-%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%8B%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EC%A3%BC%EC%9A%94-%ED%95%A8%EC%88%98

 

기존 멀티바이트 환경에서는 문자열을 입력할 때 큰따옴표(" ") 안에 정의했지만, 유니코드 환경에서는 큰따옴표 앞에 대문자 L을 넣어줘야한다. TCHAR에서 제공하는 통합형 매크로는 TEXT나 _T를 큰따옴표 앞에 넣어주면 된다.

char chTest[50] = "ABCDEFG"; //멀티바이트
wchar_t wchTest[50] = L"ABCDEFG"; //유니코드
TCHAR tchTest[50] = TEXT("ABCDEFG");    //또는 _T("ABCDEFG"); //TCHAR 통합형 매크로


호환성을 위해 환경에 맞게 유니코드, 멀티바이트 변환되는 TCHAR 타입을 쓰는 게 좋다.
그러면 유니코드, 멀티바이트 모두에서 사용 가능하다.

#include <tchar.h>
...
TCHAR str = TEXT("문자열"); //컴파일 환경에 따라 바뀐다
// 혹은
_T("문자열");

 

3가지 버전이 어떤 값을 뱉어낼 지 생각하며 써야 한다.
그리고 웬만하면 TCHAR 타입을 쓰는 게 좋다.

https://lunikism.com/entry/C-%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%8B%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EC%A3%BC%EC%9A%94-%ED%95%A8%EC%88%98
https://lunikism.com/entry/C-%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%8B%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EC%A3%BC%EC%9A%94-%ED%95%A8%EC%88%98

 

 

비주얼스튜디오 유니코드 문자집합과 멀티바이트 문자집합 결론

옵션은 프로젝트 상황에 따라서, 코드는 TCHAR로.

728x90
728x90

'C, C++' 카테고리의 다른 글

[MFC] CStdioFile과 CFile의 차이점  (0) 2021.10.22
[C++] POSIX function names 오류  (0) 2021.10.19
함수의 인수 전달 방법 3가지  (0) 2021.09.27
콜론연산자(:), 더블콜론연산자(::)  (1) 2021.09.25
C 구조체  (0) 2021.09.12
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
7장 복습

1. 상속의 문법적 특성
2. 컨트롤클래스

8장은 상속의 장점, 상속이 필요한 상황적인 부분 등을 다룬다.

 


 

 

8-1 상속의 조건

public 상속은 is-a 관계가 성립되게 하자 (상속은 95%가 public 상속)

학생은 사람이다 (0) 

 

is-a 관계

 

올라갈수록 일반화, 아래로 내려갈수록 구체화된다. (특수화된다) 
즉 범위는 좁아지고 특성(멤버함수, 멤버변수)과 기능은 많아진다.   →  C++ 상속적 특성

 

잘못된 상속의 예

사람은 학생이다 (X)

 

 

 

 HAS-A (소유) 관계에 의한 상속

- 경찰은 몽둥이를 소유한다

 

 

예제

// hasa1.cpp
#include <iostream>
using namespace std;

//몽둥이
class Cudgel {
public:
    void Swing()
    {
        cout << "몽둥이를 휘둘러라!" << endl;
    }
};

//몽둥이를 가진 경찰
class Police: public Cudgel 
{
public:
    void UseWeapon() {
        Swing();
    } 

}; 

int main()
{
    Police pol;
    pol.UseWeapon();
    return 0;
}

 

 

 

HAS-A  관계에 의한 상속 그리고 대안 (대안방법이 낫다)

- 포함 관계를 통해 소유 관계를 표현
- 객체 멤버에 의한 포함 관계의 형성
- 객체 포인터 멤버에 의한 포함 관계의 형성

 

 

 

예제 :: 객체 멤버에 의한 포함 관계
/*   hasa2.cpp */
class Cudgel  //몽둥이
{ 
public:
	void Swing(){ cout<<"Swing a cudgel!"<<endl; }
};
class Police //몽둥이를 소유하는 경찰
{
	Cudgel cud; //멤버변수로 객체가 옴    
    // 구조체 때 배웠던 것처럼
    // 클래스 멤버로 클래스 객체가 올 수 있다
public:
	void UseWeapon(){ cud.Swing(); }
};

int main()
{
	Police pol;
	pol.UseWeapon();
	return 0;
}

 

 

Police 클래스 객체의 메모리공간 할당되는 순간에
멤버변수 Cudgel 객체의 메모리공간 할당되는 순간에
Cudgel 객체의 void 생성자 호출
Police 객체의 생성자 호출

클래스 객체가 멤버로 포함되기 위해서는 반드시 인자값을 받지 않는 void 생성자를 지녀야 한다.
// Cudgel cud(10);  // (x) 구조체처럼 C++ 클래스 객체 멤버는 생성과 동시에 초기화 불가 (자바나 C#은 가능)

클래스의 멤버는 Cudgel클래스에 void생성자가 반드시 있어야 한다.

 

 

 

예제 :: 객체 포인터 멤버에 의한 포함 관계
// hasa3.cpp

#include <iostream>
using namespace std;

//몽둥이
class Cudgel {
public:
    void Swing()
    {
        cout << "몽둥이를 휘둘러라!" << endl;
    }
};

//몽둥이를 가진 경찰
class Police  
{
    Cudgel* cud; //객체의 포인터라 변경 가능하다.
public:
    Police()
    {
        cud = new Cudgel; //객체 동적 생성, 힙에 존재.. void 생성자 호출
    }
    ~Police()
    {
        delete cud;
    }
    void UseWeapon()
    {
        cud->Swing(); //스택에 존재
    }
};

int main()
{
    Police pol;
    pol.UseWeapon();
    return 0;
}

pol 이름으로 메모리공간 할당
멤버로 cud 포인터 메모리공간 4바이트 할당
Police 생성자 호출
cud 객체 동적 생성 (Cudgel 객체를 동적할당)
명시적인 게 없으므로 void 생성자(디폴트 생성자) 호출하면서 Cudgel 객체를 생성
Police 객체는 Cudgel객체를 가리킨다

 

예제 2, 3 결과는 같으나 3이 낫다.

=> 논리적인 포함관계는 상속보다는 대안 방법 (포함, 3번 예제의 사용) 이 낫다.
느슨하면서 확실한 관계가 좋고, 커플링(결합도)이 낮아지는 게 좋다

 

 

 

8-2 상속된 객체와 포인터 관계

객체포인터, 객체포인터 권한에 대해 알아본다.

 

객체 포인터

- 객체의 주소 값을 저장할 수 있는 포인터 (포인터인데 객체를 가리킬 수 있는 포인터)
- AAA(Person)클래스의 포인터는 AAA객체 뿐만 아니라,
AAA클래스를 상속하는 Derived 클래스(Student) 객체의 주소 값도 저장 가능

 

 

Student는 Person이다. Person 클래스 포인터 p로 Student 객체를 가리킬 수 있다.
Student 객체로는 Person을 가리킬 수 없다.

 

 

예제
// inherit8_2.cpp 

#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;
}

 

 

 

 객체 포인터의 권한

- 포인터를 통해서 접근할 수 있는 객체 멤버의 영역
- Base 클래스의 객체 포인터는 가리키는 대상에 상관없이 Base 클래스 내에 선언된 멤버에만 접근

B클래스 포인터로 접근하면 B클래스 내로 접근이 가능

 

 

 

#include <iostream>
using namespace std;

class A
{
public:
	void a(){
	    cout<<"a()"<<endl;
	}
};

class B : public A
{
public:
	void b(){
	    cout<<"b()"<<endl;
	}
};

class C : public B
{
public:
	void c(){
	    cout<<"c()"<<endl;
    } 
};

int main()
{
	C* c = new C(); //void 생성자 호출
	c->a();
	c->b();
	c->c();
	
	return 0;
}

 

 

#include <iostream>
using namespace std;

class A
{
public:
	void a(){
	    cout<<"a()"<<endl;
	}
};

class B : public A
{
public:
	void b(){
	    cout<<"b()"<<endl;
	}
};

class C : public B
{
public:
	void c(){
	    cout<<"c()"<<endl;
    } 
};

int main()
{
	C* c = new C(); //void 생성자 호출
    B* b = c;  // b클래스의 포인터, c클래스의 포인터로 포인터의 타입이 달라도 대입 가능하다
    // C클래스의 포인터는 C클래스의 포인터이자 B, A클래스의 포인터도 되는 것이기 떄문이다
    // 그래서 타입이 좌 우가 일치한다.
    //A* a = c; //그래서 이것도 가능하다.
    A* a = b; // 이것도 가능하다.
    
    //cout<<c<<endl;
    //cout<<b<<endl;
    //cout<<a<<endl;
    // 출력을 해보면 같은 위치를 가리키고 있다
    
    b->a();  // b라는 포인터가 가리키는 객체 안에는 a, b, c 함수가 있는데
    b->b(); // 호출할 수 있는 함수는 a, b로 제한되어진다
    // b클래스 내에 선언되어있는, 비클래스가 상속하고 있는 클래스의 멤버에만 접근이 가능하다.
    //b->c(); // 컴파일타임에 에러가 발생
    
    a->a();
    //a->b();
    //a->c();
    
	
	return 0;
}

 

 

 

예제
// CPointer2.cpp 
 
#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* p3 = new PartTimeStd;
    p3->Sleep();
    //p3->Study(); //error
    //p3->Work();  //error

    // 컴파일과정에서는 판단이 안되고 런타임에 p3이 PartTimeStd객체라는게 판단이 되기 때문에 허용이 안된다

    return 0;
}

 

 

 

Employee 문제 해결 첫 번째 단계

무슨 문제였는지 참고 : 7장

Department 객체는 Permanent 객체를 저장, 관리한다 (컨트롤클래스라서)
저장하기 위한 객체 포인터배열이 존재하여 포함관계를 갖는다.

 

프로그램의 확장성을 위해  Department 클래스는 그대로 둔다.

 

 

 

class Employee
{
protected:
    char name[20];  //이름
public:
    Employee(char* _name);
    const char* GetName(); //이름참조
};
Employee::Employee(char* _name) //생성자
{
    strcpy(name, _name);
}
const char* Employee::GetName()
{
    return name;
}

 

salary가 빠진 이유는 고용직이 매달 가져가는 급여(봉급) 과 임시직 일당은 달리 계산해야 되어서

 

 

 

#include <iostream>
#include <cstring>
using namespace std;

/**** Employee *****/
class Employee
{
protected:
    char name[20];  //이름
public:
    Employee(char* _name);
    const char* GetName(); //이름참조
};

Employee::Employee(char* _name) //생성자
{
    strcpy(name, _name);
}
const char* Employee::GetName()
{
    return name;
}

/**** Permanent *****/
class Permanent : public Employee
{
private:
    int salary; //기본급여
public:
    Permanent(char* _name, int sal);
    int GetPay();
};

Permanent::Permanent(char* _name, int sal) 
    :Employee(_name)  { //생성자
    //Permanent 생성자는 생성자를 통해서 필요한 이름정보와 급여정보를 받아와야 한다
    //상속받고 있는 employee 생성자를 호출하는데
    //멤버이니셜라이저로 이름정보는 employee 클래스에 넘겨주고
    //Permanent 기본급여를 관리해야해서 salary에 대입
    
    salary=sal; //기본급여
    //생성자 내에서 Employee 생성자를 상속하는데
}
int Permanent::GetPay()
{
    return salary;
}

/**** Temporary ****/
class Temporary : public Employee
{
private:
    int time; //일한시간 //Employee의 멤버로 올리지 않았음..올려도 되고..상황에 따라 판단.
    int pay; //시간당급여
public:
     Temporary(char* _name, int _time, int _pay);
     int GetPay();
};

Temporary::Temporary(char* _name, int _time, int _pay)
: Employee(_name)
{
    // 생성자
    // 멤버이니셜라이저와 함께 초기화
    time=_time;
    pay=_pay;
}

int Temporary::GetPay()
{
    return time*pay; //시간당 시급
}
 


/**** Department ****/
// 콘트롤 클래스

class Department
{
private:
    //Permanent * empList[10];
    Employee* empList[10];
    int index;
public:
    Department(): index(0) { };
    void AddEmployee(Employee* emp); //Employee 객체를 상속하는 객체는 전달 가능할 것
    void ShowList(); //급여 리스트 출력
};

void Department::AddEmployee(Employee* emp){
    empList[index++]=emp;
}

void Department::ShowList(){
    for(int i=0; i<index; i++)
    {
        cout<<"name: "<<empList[i]->GetName()<<endl;
        
        //cout<<"salary: "<<empList[i]->GetPay()<<endl;  
        // empList[i]는 employee클래스의 포인터이고
        // 가리키는 대상은 Permanent가 될 수 있지만
        // 포인터 객체의 권한 특성으로
        // 컴파일 타임에 에러가 발생한다...
        
        cout<<endl;
    }
}


/**** main function ****/
int main()
{
    // 직원을 관리하는 control 클래스
    Department department;
    
    // 직원 등록
    // Permanent객체 생성이 되고 그 자리로 
    // Permanent객체가 저장된 위치의 포인터가 리턴되어 AddEmployee 인자로 전달이 된다
    department.AddEmployee(new Permanent("KIM", 1000));
    department.AddEmployee(new Permanent("LEE", 1500));
    department.AddEmployee(new Temporary("HAN", 10, 200));
    department.AddEmployee(new Temporary("JANG", 12, 300));
    
    // 이번 달에 지불해야할 급여
    department.ShowList();
    return 0;
}

 

 

 

 

이 부분의 주석을 풀면

 

 

 

8-3 상속된 객체와 참조 관계
객체 레퍼런스

-객체를 참조할 수 있는 레퍼런스
-클래스 포인터의 특성과 일치 (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(void)
{
	PartTimeStd p;
	Student& ref1=p; //Student 참조를 가지고 PartTimeStd 객체를 참조하고 있다
	Person& ref2=p;
	
	p.Sleep();
	ref1.Sleep();
	ref2.Sleep();
	
	return 0;
}

 

참조를 쓰게 되면 Call by reference 인지 value인지 의미가 불분명하기 때문에 쓰기 꺼려하기도 한다.
따라서 둘 중 하나를 고르자면 포인터에 더 익숙해지는 게 낫다.

 

 

 

객체 포인터의 권한 

- 포인터를 통해서 접근할 수 있는 객체 멤버의 영역
- AAA객체와, AAA를 상속하고 있는 클래스의 객체도 가리킬 수 있다 (IS-A 관계) (Person 클래스의 p포인터로..)
- 그러나 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(void)
{
	PartTimeStd p;
	p.Sleep();
	p.Study();
	p.Work();

	Person& ref=p;
	ref.Sleep();
//	ref.Study(); // Error의 원인
//	ref.Work();  // Error의 원인
// 호출 불가능하다. Person 클래스의 참조이기 때문에.

	return 0;
}

 

 

 

8-4 Static Binding & Dynamic Binding

*오버로딩과 비교해서 복습

오버라이딩(Overriding)

- Base 클래스에 선언된 멤버와 같은 형태의 멤버를 Derived 클래스에서 선언
- Base 클래스의 멤버를 가리는 효과
- 보는 시야(Pointer)에 따라서 달라지는 효과
- 오버라이딩 자체가 재정의는 아니다. 재정의를 포함할 뿐.

 

 

 

fct 함수는 B클래스의 fct에 의해 오버라이딩 되었다 = 가려졌다 / 은닉되었다

보는 시야를 달리하면 볼 수도 있다는 이야기.

#include <iostream>
using namespace std;

class AAA
{
public:
	void fct(){
		cout<<"AAA"<<endl;
	}
};

class BBB : public AAA
{
public: 
	void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.
		cout<<"BBB"<<endl;
	}
};

int main(void)
{
	BBB b;
	b.fct();  // BBB가 출력!!!!

	return 0;
}

 

두 번째 예제
#include <iostream>
using namespace std;

class AAA
{
public:
	void fct(){
		cout<<"AAA"<<endl;
	}
};

class BBB : public AAA
{
public: 
	void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.
		cout<<"BBB"<<endl;
	}
};

 
int main(void)
{
	
    //	BBB b;
    //	b.fct();  // BBB가 출력 
    
	BBB* b=new BBB;
	b->fct(); // BBB가 출력 !!!

	AAA* a=b;
	a->fct(); // AAA가 출력 !!!

	delete b;
	return 0;
}

 

 

멤버 함수를 가상으로 선언하기

- 오버라이딩 되는 경우의 특징은? : 과장해서 말하자면 가상...즉 없는 것으로 간주한다.
- 가상이니 대신 BBB의 fct()가 호출된다
- virtual의 특성도 상속된다

 

오버라이딩이 재정의가 아니고 virtual 키워드가 없으면 은닉, virtual 키워드를 넣어주면 재정의 특성을 갖게 된다

재정의 특성에 대한 단점도 있다. 9장에서 다룬다.

#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(void)
{
	
    //	BBB b;
    //	b.fct();  // BBB가 출력 
    
	BBB* b=new BBB;
	b->fct(); // BBB가 출력 !!!

	AAA* a=b;
	a->fct(); // BBB가 출력 !!!

	delete b;
	return 0;
}

 

virtual의 특성도 상속된다.

#include <iostream>
using namespace std;

class AAA
{
public:
	virtual void fct(){
		cout<<"AAA"<<endl;
	} //이 클래스만 있으면 의미가 없다
	// 상속될 때만 virtual의 의미가 있음.
	
	// A* p = new A
	// p-> fct();
	// A a;
	// a.fct()
};

class BBB : public AAA
{
public: 
	void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.
	// virtual void fct()
	// 자동으로 virtual 키워드가 들어가나 
	// 코드가독성을 위해 명시적으로 써주는게 좋다
		cout<<"BBB"<<endl;
	}
};

class CCC : public BBB
{
public: 
    //상속하면서 오버라이딩
	void fct(){ 	//  virtual 
		cout<<"CCC"<<endl;
		// 얘는 또 의미가 없다
		// 얘를 상속받는 다른 클래스가 없어서
	}
};


 
int main(void)
{
	
   	BBB* b=new CCC;
	b->fct(); //CCC출력

	AAA* a=b;
	a->fct(); //CCC출력

	delete b;
	return 0;
}

 

 

Static Binding vs. Dynamic Binding

스태틱 바인딩 : 호출될 함수가 정해진 것 

A a;
a.f();

 

다이나믹 바인딩: 포인터가 아니고 포인터가 가리키는 객체에 따라 호출되는 함수가 동적으로 달라진다.

A* a = new ???
A클래스가 f함수는 virtual B클래스에 f함수가 있고 상속, C클래스(f함수 있음) 에 의해 상속되어진다



 

 

오버라이딩 된 함수의 호출

오버라이딩 된 함수의 호출이 필요한 이유 (연습문제 8-3 관련)

 

#include <iostream>
using namespace std;

class AAA
{
public:
	virtual void fct(){
		cout<<"AAA"<<endl;
	}  
};

class BBB : public AAA
{
public: 
	void fct(){  
	    AAA::fct(); //방법 1. : 오버라이딩 되어있어도 AAA 호출 가능
	    // fct(); //재귀로 무한루프..사용ㄴㄴ
		cout<<"BBB"<<endl;
	}
}; 

 
int main(void)
{
	AAA* a= new BBB;
    cout<< "첫 번째 시도" <<endl;
	a->fct();
	
	cout<< "2 번째 시도" <<endl;
	a->AAA::fct(); // 방법 2. 
	 
	return 0;
}

 

 

8-5 Employee Problem 완전한 해결
활용해야 할 문법적 요소

- 함수 오버라이딩
- virtual 함수의 오버라이딩
- 상속되어지는 virtual 특성
- 오버라이딩 된 함수의 호출 방법

(이전 단계에서 GetPay()함수가 호출이 안되는 문제가 있었다)

 

 

Employee클래스의 포인터배열 이기 때문에 상속하는 모든 클래스의 객체는 참조가 가능하다
그러나 그대로 Employee클래스에 GetPay()를 선언하면 Employee클래스의 GetPay가 호출되니 virtual를 추가해줬다.

 

상속이 가져다주는 이점과 함께 생각해서 정리해본다.

#include <iostream>
#include <cstring>
using namespace std;

/**** Employee *****/
class Employee
{
protected:
    char name[20];  //이름
public:
    Employee(char* _name);
    const char* GetName(); //이름참조
    virtual int GetPay() //add
    {
        return 0;
    }
};

Employee::Employee(char* _name) //생성자
{
    strcpy(name, _name);
}
const char* Employee::GetName()
{
    return name;
}

/**** Permanent *****/
class Permanent : public Employee
{
private:
    int salary; //기본급여
public:
    Permanent(char* _name, int sal);
    int GetPay();
};

Permanent::Permanent(char* _name, int sal) 
    :Employee(_name)  { //생성자
    //Permanent 생성자는 생성자를 통해서 필요한 이름정보와 급여정보를 받아와야 한다
    //상속받고 있는 employee 생성자를 호출하는데
    //멤버이니셜라이저로 이름정보는 employee 클래스에 넘겨주고
    //Permanent 기본급여를 관리해야해서 salary에 대입
    
    salary=sal; //기본급여
    //생성자 내에서 Employee 생성자를 상속하는데
}
int Permanent::GetPay()
{
    return salary;
}

/**** Temporary ****/
class Temporary : public Employee
{
private:
    int time; //일한시간 //Employee의 멤버로 올리지 않았음..올려도 되고..상황에 따라 판단.
    int pay; //시간당급여
public:
     Temporary(char* _name, int _time, int _pay);
     int GetPay();
};

Temporary::Temporary(char* _name, int _time, int _pay)
: Employee(_name)
{
    // 생성자
    // 멤버이니셜라이저와 함께 초기화
    time=_time;
    pay=_pay;
}

int Temporary::GetPay()
{
    return time*pay; //시간당 시급
}
 


/**** Department ****/
// 콘트롤 클래스

class Department
{
private:
    //Permanent * empList[10];
    Employee* empList[10];
    int index;
public:
    Department(): index(0) { };
    void AddEmployee(Employee* emp); //Employee 객체를 상속하는 객체는 전달 가능할 것
    void ShowList(); //급여 리스트 출력
};

void Department::AddEmployee(Employee* emp){
    empList[index++]=emp;
}

void Department::ShowList(){
    for(int i=0; i<index; i++)
    {
        cout<<"name: "<<empList[i]->GetName()<<endl;
        cout<<"salary: "<<empList[i]->GetPay()<<endl;   
        
        cout<<endl;
    }
}


/**** main function ****/
int main()
{
    // 직원을 관리하는 control 클래스
    Department department;
    
    // 직원 등록
    // Permanent객체 생성이 되고 그 자리로 
    // Permanent객체가 저장된 위치의 포인터가 리턴되어 AddEmployee 인자로 전달이 된다
    department.AddEmployee(new Permanent("KIM", 1000));
    department.AddEmployee(new Permanent("LEE", 1500));
    department.AddEmployee(new Temporary("HAN", 10, 200));
    department.AddEmployee(new Temporary("JANG", 12, 300));
    
    // 이번 달에 지불해야할 급여
    department.ShowList();
    return 0;
}

 

 

순수 가상함수와 추상 클래스

위의 예제에서 Employee에서 GetPay() 함수는 호출되지 않았다.
호출되기 위해서 존재가 아니라 대신 호출되기 위해서 교량의 역할로 존재한다.

 virtual int GetPay();


호출은 되지만 실행되지 않으니 몸체를 생략해버리면 컴파일이 안 된다.

 

선언만 존재하는 순수 가상함수 
 virtual int GetPay() = 0; //선언만 하고 정의를 일부로 안한 것이라고 컴파일러에 알려주는 법

 

- 추상클래스 : 하나 이상 순수가상함수를 가지는 클래스
- 추상클래스는 객체화될 수 없다.
완전한 클래스가 아니다.  (Employee클래스는 불완전 클래스)
그래서 객체화 될 수 없다. 포인터 선언은 가능하다.

 

 

8-6 virtual 소멸자의 필요성
상속하고 있는 클래스 객체 소멸 문제점
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class AAA
{
    char* str1;
public:
    AAA(const char* _str1)
    {
        str1 = new char[strlen(_str1) + 1];
        strcpy(str1, _str1);
    }
    ~AAA() //virtual ~AAA()
    {
        cout << "~AAA() call!" << endl;
        delete []str1;
    }
    virtual void ShowString() {
        cout << str1 << ' ';
    }
};

class BBB : public AAA
{
    char* str2;
public:
    BBB(const char* _str1, const char* _str2) :AAA(_str1)
    {
        str2 = new char[strlen(_str2) + 1]; //동적할당
        strcpy(str2, _str2);
    }
    ~BBB() //소멸자가 호출될 떄 A클래스 소멸자도 호출된다
    {
        cout << "~BBB() call!" << endl;
        delete []str2;        // 메모리 해제
    }
    virtual void ShowString() {
        AAA::ShowString();
        cout << str2 << endl;
    }
};

int main()
{
    //AAA* a = new BBB("Good", "evening");
     
    BBB* b = new BBB("Good", "morning");        //b클래스의 객체를 생성하고 b클래스 포인터로 참조
    // 생성과정에서 두 생성자가 호출된다
    // b클래스 생성자에 의해, a클래스 생성자에 의해서 메모리공간이 동적할당된다 

    //a->ShowString();
    b->ShowString();

    cout << "----객체 소멸 직전---" << endl;
    
    //delete a;
    delete b;

    return 0;
}

 

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class AAA
{
    char* str1;
public:
    AAA(const char* _str1)
    {
        str1 = new char[strlen(_str1) + 1];
        strcpy(str1, _str1);
    }
    ~AAA() //virtual ~AAA()
    {
        cout << "~AAA() call!" << endl;
        delete []str1;
    }
    virtual void ShowString() {
        cout << str1 << ' ';
    }
};

class BBB : public AAA
{
    char* str2;
public:
    BBB(const char* _str1, const char* _str2) :AAA(_str1)
    {
        str2 = new char[strlen(_str2) + 1]; //동적할당
        strcpy(str2, _str2);
    }
    ~BBB() //소멸자가 호출될 떄 A클래스 소멸자도 호출된다
    {
        cout << "~BBB() call!" << endl;
        delete []str2;        // 메모리 해제
    }
    virtual void ShowString() {
        AAA::ShowString();
        cout << str2 << endl;
    }
};

int main()
{
    //AAA* a = new BBB("Good", "evening");
    
    // 1) 
    //BBB* b = new BBB("Good", "morning");        //b클래스의 객체를 생성하고 b클래스 포인터로 참조
    // 생성과정에서 두 생성자가 호출된다
    // b클래스 생성자에 의해, a클래스 생성자에 의해서 메모리공간이 동적할당된다

    // 2) 포인터의 타입만 바꿔봤다 
    // A클래스의 소멸자만 호출된다
    // 소멸의 주체가 되는 포인터가 A클래스였기 때문이다
    AAA* b = new BBB("Good", "morning");


    //a->ShowString();
    b->ShowString();

    cout << "----객체 소멸 직전---" << endl;
    
    //delete a;
    delete b;

    return 0;
}

 

 

 

 

 

상속하고 있는 클래스 객체 소멸 문제점 해결
virtual ~AAA(){			
cout<<"~AAA() call!"<<endl;
delete []str1;
}

 

앞의 오버라이딩은 대신 호출됐었고, 가상 소멸자는...소멸자 호출하러 갔더니 가상 소멸자라서 A클래스를 상속하고 있는 B클래스의 소멸자가 대신 호출된다. B클래스의 소멸자는 a객체를 상속하고 있어서 다시 A객체의 소멸자도 호출된다.

 

 

OOP 프로젝트 5단계

(7장내용) 전역함수를 하나의 control클래스로 묶는다.

OOP 프로젝트 6단계 (상속, 오버라이딩, virtual.. 다형성 적용)

계좌클래스를 상속하는 신용계좌 클래스 추가
- 입금 시 바로 1%의 이자가 추가로 더해짐

계좌클래스를 상속하는 기부계좌 클래스 추가
- 입금 시 바로 1%에 해당되는 금액이 사회에 기부

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

IS-A 관계에 대해 계속 설명한다.

 

객체 포인터 변수

객체의 주소 값을 저장하는 포인터 변수
클래스를 기반으로도 포인터 변수를 선언할 수 있다.

Person* ptr; //포인터변수 선언
ptr = new Person(); //포인터 변수의 객체 참조

Person형 포인터는 Person 객체, Person을 상속하는 유도 클래스의 객체도 가리킬 수 있다.

class Student: public Person
{ .... } ;
Person* ptr = new Student();

Student 클래스를 상속하는 유도 클래스 PartTimeStudent가 다음의 형태로 정의되었다고 가정하면
Person형 포인터변수는 PartTimeStudent 객체도 가리킬 수 있다.

class PartTimeStudent: public Student
{ ..... };
Person* ptr = new PartTimeStudent();

즉, C++에서 AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접/간접적으로 상속하는 모든 객체를 가리킬 수 있다. (객체의 주소값을 저장할 수 있다.)

 

// ObjectPointer.cpp
#include <iostream>
using namespace std;

class Person
{
public:
    void Sleep() {
        cout<<"Sleep"<<endl;
    }
};

class Student: public Person
{
public:
    void Study() {
        cout<<"Study"<<endl;
    }
};

class PartTimeStudent: public Student
{
public:
    void Work() {
        cout<<"Work"<<endl;
    }
};

int main()
{
    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;
}

 

 

Student *s가 Person 클래스의 객체를 가리키는 건 문제가 된다. 역방향은 성립하지 않기 때문이다 (IS-A가 아님)

 

유도클래스의 객체까지 가리킬 수 있다

C++에서 AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접/간접적으로 상속하는 모든 객체를 가리킬 수 있다. (객체의 주소값을 저장할 수 있다.) Student, PartTimeStudent 객체는 Person객체의 일종이기 때문이다. 

 

전 단원의 급여관리 예제 확장 + 함수오버라이딩

정규직, 영업직, 임시직 모두 고용인이며 영업직은 정규직의 일종이다.
EmployeeHandler 클래스가 저장,관리 하는 대상이 Employee 객체가 되면 이후 Employee 클래스를 직간접적으로 상속하는 클래스가 추가되어도 EmployeeHandler 클래스는 변화가 없다.

// EmployeeManager2.cpp

#include <iostream>
#include <cstring>
using namespace std;

class Employee // 고용인
{
private:
    char name[100]; //고용인의 이름
public:
    Employee(char* name)
    {
        strcpy(this->name, name);
    }
    void ShowYourName() const
    {
        cout<<"name : "<<name<<endl;
    }
};

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;
    }
};

class EmployeeHandler
{
private:
    Employee* empList[50];  //Employee 객체의 주소값 저장
    // Employee 클래스를 상속하는 클래스의 객체도 이 배열에 함께 저장 가능
    int empNum;
public:
    EmployeeHandler()
        : empNum(0)
    {    }
    void AddEmployee(Employee* emp)  //Employee 객체의 주소값을 전달 
    // Employee 클래스를 상속하는 클래스 PermenentWorker 객체의 주소값도 전달 가능
    {
        empList[empNum++]=emp;
    }
    void ShowAllSalaryInfo() const{
        
    }
    void ShowTotalSalary() const{
        int sum=0;
        cout<<"salary sum: "<<sum<<endl;
    }
    ~EmployeeHandler()
    {
        for(int i=0; i<empNum; i++)
            delete empList[i];
    }
};

int main(void)
{
    // 직원관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
    EmployeeHandler handler;
    // 직원 등록
    handler.AddEmployee(new PermanentWorker("KIM", 1000));
    handler.AddEmployee(new PermanentWorker("LEE", 1500));
    handler.AddEmployee(new PermanentWorker("JUN", 2000));
    // 이달 지불할 급여 정보
    handler.ShowAllSalaryInfo();
    // 이달 지불할 급여 총합
    handler.ShowTotalSalary();
    return 0;
}

 

EmployeeHandler 객체가 여전히 PermanentWorker 객체를 저장/관리하고 있다.

그래서

영업직 급여 = 월 기본급여 + 인센티브
임시직 급여 = 시간당급여 * 일한시간

형태로 정의를 추가한다.

 

임시직 클래스

// 임시직. 실제 일을 한 시간으로 급여 계산
class TemporaryWorker: public Employee
{
private:
    int workTime; // 이달에 일한 총 시간
    int payPerHour; // 시간 당 급여
public:
    TemporaryWorker(char* name, int pay)
        : Employee(name), workTime(0), payPerHour(pay)
    {  }
    void AddWorkTime(int time) // 일한 시간 추가
    { 
        workTime+=time;
    }
    int GetPay() const // 이달의 급여
    {
        return workTime*payPerHour;
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary: "<<GetPay()<<endl<<endl;
    }
};

 

영업직 클래스

// 영업직 (정규직 일종). Employee가 아닌 PermanentWorker 상속
// 기본급여과 관련된 부분을 멤버로 포함, 상여금 부분만 멤버로 추가
class SalesWorker: public PermanentWorker
{
private:
    int salesResult; //월 판매실적
    double bonusRatio; //상여금 비율
public:
    SalesWorker(char* name, int money, double ratio)
        : PermanentWorker(name, money), salesResult(0), bonusRatio(ratio)
    {   }
    void AddSalesResult(int value)
    {
        salesResult+=value;
    }
    int GetPay() const
    {
        return PermanentWorker::GetPay() //PermanentWorker의 GetPay() 호출
                +(int)(salesResult*bonusRatio);
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary: "<<GetPay()<<endl<<endl; //SalesWorker의 GetPay() 호출
    }
};

 

정규직과 영업직에는 동일 함수 GetPay()와 ShowSalaryInfo()가 있다. (기본클래스 유도클래스에 같은 함수가 있다)

* 함수 오버라이딩 : 함수오버로딩과 혼동 x
기초 클래스와 동일한 이름의 함수를 유도클래스에서 정의되어도 매개변수의 자료형 및 개수가 다르면 함수오버로딩이 되어 전달되는 인자에 따라 호출함수가 결정된다. 함수오버로딩은 상속관계에서도 구성 가능.

오버라이딩 된 기초클래스 함수는 오버라이딩 한 유도클래스 함수에 가려진다.
SalesWorker 클래스 내에서 GetPay를 호출하면 SalesWorker 클래스에 정의된 GetPay함수가 호출된다.

단 SalesWorker 클래스 내에서도 PermanentWorker::GetPay() 이렇게 호출하면 PermanentWorker 클래스 내부의 GetPay가 호출된다.

아래처럼도 호출 가능하다.
seller 객체의 PermanentWorker 클래스에 정의된 ShowSallryInfo()를 호출..이런 뜻

int main(void)
{
	SalesWorker seller("H", 1000, 0.1);
	cout<<seller.PermanentWorker::GetPay()<<endl;
	seller.PermanentWorker::ShowSalaryInfo();
    .... 
}

 

완성본

// EmployeeManager3.cpp

#include <iostream>
#include <cstring>
using namespace std;

class Employee  // 고용인
{
private:
    char name[100];  // 고용인의 이름
public:
    Employee(char* name)
    {
        strcpy(this->name, name);
    }
    void ShowYourName() const
    {
        cout<<"name : "<<name<<endl;
    }
};

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;
    }
};

class EmployeeHandler
{
private:
    Employee* empList[50]; //Employee 객체의 주소값 저장
    // Employee 클래스를 상속하는 클래스의 객체도 이 배열에 함께 저장 가능
    int empNum;
public:
    EmployeeHandler()
        : empNum(0)
    {    }
    void AddEmployee(Employee* emp)  //Employee 객체의 주소값을 전달 
     // Employee 클래스를 상속하는 클래스 PermenentWorker 객체의 주소값도 전달 가능
    {
        empList[empNum++]=emp;
    }
    void ShowAllSalaryInfo() const{
        
    }
    void ShowTotalSalary() const{
        int sum=0;
        cout<<"salary sum: "<<sum<<endl;
    }
    ~EmployeeHandler()
    {
        for(int i=0; i<empNum; i++)
            delete empList[i];
    }
};

// 임시직. 실제 일을 한 시간으로 급여 계산
class TemporaryWorker: public Employee
{
private:
    int workTime; // 이달에 일한 총 시간
    int payPerHour; // 시간 당 급여
public:
    TemporaryWorker(char* name, int pay)
        : Employee(name), workTime(0), payPerHour(pay)
    {  }
    void AddWorkTime(int time) // 일한 시간 추가
    { 
        workTime+=time;
    }
    int GetPay() const // 이달의 급여
    {
        return workTime*payPerHour;
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary: "<<GetPay()<<endl<<endl;
    }
};

// 영업직 (정규직 일종). Employee가 아닌 PermanentWorker 상속
// 기본급여과 관련된 부분을 멤버로 포함, 상여금 부분만 멤버로 추가
class SalesWorker: public PermanentWorker
{
private:
    int salesResult; //월 판매실적
    double bonusRatio; //상여금 비율
public:
    SalesWorker(char* name, int money, double ratio)
        : PermanentWorker(name, money), salesResult(0), bonusRatio(ratio)
    {   }
    void AddSalesResult(int value)
    {
        salesResult+=value;
    }
    int GetPay() const
    {
        return PermanentWorker::GetPay() //PermanentWorker의 GetPay() 호출
                +(int)(salesResult*bonusRatio);
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary: "<<GetPay()<<endl<<endl; //SalesWorker의 GetPay() 호출
        // PermanentWorker 클래스의 ShowSalaryInfo()랑 같은 내용의 함수인데도
        // 오버라이딩 한 이유는 상속이 되었어도 어느 GetPay를 부를 지 컨트롤 할 수 없어서
    }
};

int main(void)
{
    // 직원관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
    EmployeeHandler handler;
    
    // 정규직 등록
    handler.AddEmployee(new PermanentWorker("KIM", 1000));
    handler.AddEmployee(new PermanentWorker("LEE", 1500));
    
    // 임시직 등록
    TemporaryWorker* alba=new TemporaryWorker("Jung", 700);
    alba->AddWorkTime(5); //5시간 일한결과 등록
    handler.AddEmployee(alba);
    
    // 영업직 등록
    SalesWorker* seller=new SalesWorker("Hong", 1000, 0.1)   ;
    seller->AddSalesResult(7000); //영업실적 7000
    handler.AddEmployee(seller);
    
    // 이달 지불할 급여 정보
    handler.ShowAllSalaryInfo();
    
    // 이달 지불할 급여 총합
    handler.ShowTotalSalary();
    return 0;
}

 

메인 함수 내 handler.AddEmployee(new PermanentWorker("KIM", 1000)); 와 같은 코드 4줄에서 발생.
에러 내용 : 인수 1을(를) const char[4]에서 char*  (으)로 변환할 수 없습니다.
이유 : "aa" 문자열은 상수인데 변수에 값을 넣으려 해서.
C에서는 문자열 리터럴이 char의 배열이지만, C++에서는 const char의 배열이다.

에러 수정 : 
1) 포인터가 아닌 배열로 선언
2) (char*) 형식으로 형변환
3) 자료형을 const char* 형태로 바꾸기
4) const_cast<char*> 사용하기

참고 : http://egloos.zum.com/kim0522ms/v/6438724

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
7-1 상속으로 들어가기에 앞서서

상속이 필요한 상황을 잘 알고 적용해야 한다.

 

상속을 정복하기 위한 접근 방식

1. 문제제기 - 문법과 범위를 잘 이해해야!
2. 기본 개념 소개 - 8장까지 이어짐
3. 문제해결

 

*  Freelec 급여 담당자의 요구사항 
- 급여 관리를 위해 직원 정보 저장 (고용직 : 연봉제)
- 매달 지불되어야 할 급여 정보 확인

 

#include <iostream>
using namespace std;

//Entity : 실체, 객체라는 의미로 실무적으론는 앤터티라고 부른다. 
//즉 업무에 필요하고 유용한 정보를 저장하고 관리하기 위한 집합적인 것으로 설명할 수 있다. 
//엔터티는 사람, 장소, 물건, 사건, 개념등의 명사에 해당한다. 
//엔터티는 저장이 되기 위한 어떤 것(thing)

class Permanent //entity 엔터티 클래스 (데이터적 성격)
{
private:
    char name[20];
    int salary;
public:
    Permanent(char* _name, int sal);
    const char* GetName();
    int GetPay();
};

Permanent::Permanent(char* _name, int sal){
    strcpy(name, _name);
    salary=sal;
}

const char* Permanent::GetName()
{
    return name;
}

int Permanent::GetPay()
{
    return salary;
}



// 컨트롤클래스 (사용자에게 제공되는 기능적 층면), 매니저클래스라고도 한다
// 플로우차트의 컨트롤, 흐름을 제어하는 것
// has-a 관계로 상속이 아니다.
class Department 
{
private:
    Permanent* empList[10];
    int index;
public:
    Department(): index(0){  }; 
    // 생성자 초기화 리스트로 Department() { index = 0; }과 같다
    // 초기화리스트는 필드에 const로 선언된 변수를 초기화하는 경우,
	// 필드에 선언된 레퍼런스 변수를 초기화하는 경우 사용되어진다.
    // 즉 필드에 선언과 동시에 초기화가 이루어져야 하는 경우.
    // https://velog.io/@sjongyuuu/C-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%B4%88%EA%B8%B0%ED%99%94%EB%A6%AC%EC%8A%A4%ED%8A%B8
    
    void AddEmployee(Permanent* emp); //직원정보 저장
    void ShowList(); //급여리스트 출력
};

void Department::AddEmployee(Permanent* emp)
{
    empList[index++]=emp;
}

void Department::ShowList() //급여리스트 출력
{
    for(int i=0; i<index; i++) 
    {
        cout<<"name: "<<empList[i]->GetName()<<endl;
        cout<<"salary: "<<empList[i]->GetPay()<<endl;
        cout<<endl;
    }
}

int main()
{
  Department department;
  department.AddEmployee(new Permanent("김종호", 100));
  department.AddEmployee(new Permanent("홍길동", 200));
  department.ShowList();
  return 0;
}

 

클래스를 배웠음에도 프로그램을 짜는데 어려움을 느낀다면
컨트롤클래스에 대한 개념이 약해서 그렇다.

 

 

요구사항의 변경

- 급여 형태 다양화 (판매직은 연봉제 + 인센티브, 임시직은 시간당 급여 등)

요구사항의 변경을 위해 할 것

- Department 클래스의 변경 (프로그램의 전체적인 변경)
- 상속을 사용해서 앞으로 직원이나 급여의 형태가 다양해져도 Department 클래스는 변동이 없도록 수정한다.

 

 

7-2 상속의 기본 개념

Student가 Person클래스를 상속할 경우 Student는 Person 클래스의 멤버변수와 멤버함수를 물려받게 된다.
상속은 UML (클래스들의 관계)로 표시한다. UML은 그림그리기 위한 표준화 된 도구이다.

 

상속 uml

 

 

상속하는 클래스의 객체가 생성되는 과정

객체 할당 순서는 메모리에서 base 클래스를 먼저 할당한 후 derived 클래스를 할당한다.
객체를 닫을 땐 derived 클래스를 소멸하고 base클래스를 소멸한다.

 

http://www.parkjonghyuk.net/lecture/2018-2nd-lecture/programming2/ch/chap06.pdf

 

 

1. 메모리 공간 할당
----생성자 호출-----
2. Base 클래스의 생성자 실행 (호출이 아니다)
3. Derived 클래스의 생성자 실행

호출은 Derived 클래스가 먼저 호출 (main...BBB b;)

 

class AAA //Base class
{
	int a;
public:
	AAA(){
		cout<<"AAA()call"<<endl;
		a=10;
	}
	AAA(int i){
		cout<<"AAA(int i)call"<endl;
	}
};
class BBB:public AAA //Derived class
{
	int b;
public:
	BBB(){
		cout<<"BBB()call"<<endl;
		b=20;
	}
	BBB(int i){
		cout<<"BBB(int i)call"<endl;
	}
};

 

BBB클래스 내에는 멤버로서 int a가 없지만 AAA클래스를 상속함으로서
BBB클래스의 객체 안에는 AAA클래스의 멤버가 존재하며
AAA클래스의 생성자도 객체생성과정에서 호출되어지고 있다.

 

실행 순서

 



AAA클래스의 생성자를 호출하게 하는 이유

AAA클래스의 생성자를 통해 AAA클래스의 멤버를 초기화 하는게
BBB클래스 내에서 AAA클래스의 멤버를 초기화하는 것보다 이상적이라서.

 

예제 :: 나이나 이름은 객체생성과 동시에 원하는 값으로 초기화시키지 못하는 문제

객체생성 -> Person 생성 -> Student 생성 -> Student 소멸 -> Person 소멸 순서 (스택의 FILO 구조)

* FILO 구조 : First In, Last Out 구조. LIFO라고도 함.

스택은 처음 들어간 것이 가장 마지막에 나오게 되어 있는 구조이다.
자동메모리는 스택 기반으로 동작하고, 대부분 네트워크 프로토콜도 스택을 기반으로 구성되어 있다.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class Person
{ 
    int age;
    char name[20];
public:
    int GetAge() const {
        return age;
    }
    const char* GetName() const {
        return name;
    }
    Person(int _age=1, const char* _name="noname") 
    //디폴트 매개변수 설정으로 void 매개변수 호출도 사용 가능
    {
        age = _age;
        strcpy(name, _name);
    }
};


// Person클래스를 상속하지만 public 선언된 것만 상속된다.
class Student : public Person
{
    char major[20]; //전공
public:
    Student(const char* _major) {
        strcpy(major, _major);
    }
    const char* GetMajor() const
    {
        return major;
    }
    void ShowData() const
    {
        cout << "이름:" << GetName() <<endl;
        cout << "나이:" << GetAge() <<endl;
        cout << "전공:" << GetMajor() << endl;
    }
};

int main()
{
    Student Kim("coumputer");
    Kim.ShowData();

    return 0;
}

 

 

 

7-3 객체의 생성 및 소멸 과정

위의 전공 예제에서 Person 멤버변수들을 public로 바꿔도 되지만 정보은닉을 무너뜨리고
private으로 유지하면 상속이 되더라도 private 멤버는 선언이 된 클래스 내에서만 가능해서 안된다

 

멤버 이니셜라이저

그렇다면 6장에서 나온 멤버이니셜라이져를 사용하면 된다. const멤버변수를 초기화시킬 방법으로 나왔던 개념이다.

멤버이니셜라이져를 사용하면,
베이스 클래스의 생성자를 명시적으로 호출하여 베이스 클래스의 멤버 변수를 사용자 임의 값으로 초기화 할 수 있다.
// Student(int a, char* _name, char* _major): Person(a, _name) { ... 

 

Base 클래스의 생성자 명시적 호출
BBB(int j):AAA(j) 
{
cout<<"BBB(int j) Call"<<endl;
}

 

멤버이니셜라이져는 const 선언된 멤버변수를 사용자 임의의 값으로 초기화 할 때도 사용된다.

 

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class Person
{ 
    int age;
    char name[20];
public:
    int GetAge() const {
        return age;
    }
    const char* GetName() const {
        return name;
    }
    Person(int _age=1, const char* _name="noname")
    {
        age = _age;
        strcpy(name, _name);
    }
    ~Person() //소멸자
    {
        cout << "~Person()" << endl;
    }
};

class Student : public Person
{
    int num;
    char major[20]; //전공
public:       
    Student(int a, const char* _name, const char* _major) : Person(a, _name) {
    //Student(int a, char* _name, const char* _major) : num(a) { //const 멤버변수를 초기화    
        //멤버 이니셜라이져 - 클래스 이름일 경우 a라는 이름의 인자값을 받을 수 있는 생성자를 호출      
        //age=a;
        //strcpy(name, _name);
        strcpy(major, _major);
    }
    const char* GetMajor() const
    {
        return major;
    }
    void ShowData() const
    {
        cout << "이름:" << GetName() <<endl;
        cout << "나이:" << GetAge() <<endl;
        cout << "전공:" << GetMajor() << endl;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
};

int main()
{
    Student Kim(20, "kim", "computer");
    Kim.ShowData();

    return 0;
}

 

 

소멸자의 호출 순서

1. Derived 객체 소멸자 호출
2. Base 객체  소멸자 호출
3. 메모리 반환

스택의 FILO 구조로 소멸된다. 함수 호출 및 소멸과 동일하다. (출처)

 

protected 멤버

상속 관계에 놓여있을 경우 하위클래스에서 접근 허용
그 외에는 private 멤버와 동일한 특성을 보인다.

 

class AAA
{
private: int a;
protected: int b;
};

class BBB: public AAA
{
public:
	void SetData()
	{
		a=10; //private member. error
		b=20; //protected member
	}
};

int main(void)
{
	AAA aaa; //private이라서 컴파일에러
	aaa.a=10;

	aaa.b=20; //protected라서 컴파일에러
	return 0;
}

 

 

예제

protected 멤버변수를 사용하면 자식 클래스에서도 사용 가능하지만,
protected보다 멤버이니셜라이저를 사용하는 게 좋은 구조이다.

Person 클래스의 name을 char Person_name으로 변경한다면 Student 클래스 내의 name까지 바꿔줘야 하는,
한 클래스의 변경이 다른 클래스까지의 변경을 유도하기 때문이다.

 

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class Person
{ 
protected:
    int age;
    char name[20];
public:
    int GetAge() const {
        return age;
    }
    const char* GetName() const {
        return name;
    }
    Person(int _age=1, const char* _name="noname")
    {
        age = _age;
        strcpy(name, _name);
    }
    ~Person() //소멸자
    {
        cout << "~Person()" << endl;
    }
};



class Student : public Person
{
    char major[20]; //전공
public:       
    Student(int a, const char* _name, const char* _major) { : Person(a, _name) {
    // 직접 멤버 변수를 변경하면 Person 멤버변수가 변경되면 Student 클래스에서도 변경이 불가피하다.
    
    //Student(int a, char* _name, const char* _major) : num(a) { //const 멤버변수를 초기화    
        // 멤버 이니셜라이져 - 클래스 이름일 경우 a라는 이름의 인자값을 받을 수 있는 생성자를 호출
        // 생성자이니셜라이저를 이용해서 초기화하면
        // Person클래스의 멤버 변수를 변경하더라도 해당 클래스 내에서만 멤버변수를 변경하면 된다.
        // 객체 내의 변수를 외부에서 직접 변경하지 않도록 설계하면 유지보수에 유리하고 오류도 적을 것.
        
        age=a;
        strcpy(name, _name);
        strcpy(major, _major);
    }
    const char* GetMajor() const
    {
        return major;
    }
    void ShowData() const
    {
        cout << "이름:" << GetName() <<endl;
        cout << "나이:" << GetAge() <<endl;
        cout << "전공:" << GetMajor() << endl;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
};

int main()
{
    Student Kim(20, "kim", "computer");
    Kim.ShowData();

    return 0;
}

 

 

7-5 세 가지 형태의 상속

- public 상속이 대부분이다.

 

접근 권한 변경

- Base 클래스의 멤버는 상속되는 과정에서 접근 권한 변경

 

 

protected 상속일 때, 자기(protected)보다 넓은 접근 권한이 있다면 좁힌다. (public멤버는 protected로...)
자기(protected)보다 좁은 접근 권한이 있다면 ... private 멤버의 경우 접근 불가가 된다. 표를 참고하면 된다.

 

 

 

 

 

7-6 상속을 하는 이유
문제의 도입

- 운송 수단에 관련된 클래스 정의
- 자동차, 열차, 선박, 비행기 등등... 중복되는 부분이 생긴다.

중복되는 것들은 상위 객체에 넣어 상위 객체를 가져와서 코드를 간소화 할 수 있다.

 

 

 

 

문제의 해결

- 클래스를 상속하여 구성하면 코드가 간결해진다.
- 클래스를 확장하기 좋다.

 

 

// 탈 것
class Vehicle {
  int pass;
  int bag;
public:
  Vehicle(int person = 0, int weight = 0);
  void Ride(int person);
  void Load(int weight);
};
 
// 비행기
class Airplane: public Vehicle {
  int crew;   //승무원 인원
public:
  Airplane(int p = 0, int b = 0, int c = 0):Vehicle(p,w);
  void TakeCrew(int crew);
};
 
 // 기차
class Train:public Vehicle {
  int length;     //열차 칸 수
public:
  Train(int p = 0, int b = 0, int l = 0):Vehicle(p,w);
  void SetLength(int len);
};

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
윤성우 열혈 C++ 07.상속의 이해 연습문제

 

문제 07-1. [상속과 생성자의 호출]

1. 다음 클래스에 적절한 생성자를 삽입하고, 확인을 위한 main 함수를 정의해보자.

#include <iostream>
using namespace std;

class Car 
{ //기본 연료 자동차
private:
    int gasolineGauge;
public: 
    int GetGasGauge()
    {
        return gasolineGauge;
    }
};
 
class HybridCar: public Car
{ //하이브리드 자동차
private:
    int electricGauge;
public:  
    int GetElecGuage()
    {
        return electricGauge;
    }
};

class HybridWaterCar: public HybridCar
{ //하이브리드 수중차
private:
    int waterGuage;
public:  
    void ShowCurrentGuage()
    {
        cout<<"잔여 가솔린: "<<GetGasGauge()<<endl;
        cout<<"잔여 전기량: "<<GetElecGuage()<<endl;
        cout<<"잔여 워터량: "<<waterGuage<<endl;
    }
};

 

푼 것

#include <iostream>
using namespace std;

class Car 
{ //기본 연료 자동차
private:
    int gasolineGauge;
public:
    Car(int mygasolineGauge) : gasolineGauge(mygasolineGauge)
    {    }
    int GetGasGauge()
    {
        return gasolineGauge;
    }
};
 
class HybridCar: public Car
{ //하이브리드 자동차
private:
    int electricGauge;
public: 
    HybridCar(int mygasolineGauge, int myelectricGauge) 
        : Car(mygasolineGauge), electricGauge(myelectricGauge)
    {    }
    int GetElecGuage()
    {
        return electricGauge;
    }
};

class HybridWaterCar: public HybridCar
{ //하이브리드 수중차
private:
    int waterGuage;
public: 
    HybridWaterCar(int mygasolineGauge, int myelectricGauge, int mywaterGuage) 
        : HybridCar(mygasolineGauge, myelectricGauge), waterGuage(mywaterGuage)
    {    }
    void ShowCurrentGuage()
    {
        cout<<"잔여 가솔린: "<<GetGasGauge()<<endl;
        cout<<"잔여 전기량: "<<GetElecGuage()<<endl;
        cout<<"잔여 워터량: "<<waterGuage<<endl;
    }
};

int main()
{
    HybridWaterCar car1(1,2,3);
    car1.ShowCurrentGuage();
    
    return 0;
}

 

 

2. 다음 두 클래스에 적절한 생성자와 소멸자를 정의해보자. 그리고 확인을 위한 main 함수를 정의해보자.

#include <iostream>
using namespace std; 

class MyFriendInfo
{
private:
    char* name;
    int age;
public:
    void ShowMyFreindInfo()
    {
        cout<<"이름: "<<name<<endl;
        cout<<"나이: "<<age<<endl;
    }
};

class MyFriendDetailInfo: public MyFriendInfo
{
private:
    char* addr;
    char* phone;
public:
    void ShowMyFreindDetailInfo()
    {
        ShowMyFreindInfo();
        cout<<"주소: "<<addr<<endl;
        cout<<"전화: "<<phone<<endl<<endl;
    }
};

 

푼 것

* delete []배열!

#include <iostream>
#include <cstring>
using namespace std; 

class MyFriendInfo
{
private:
    char* name;
    int age;
public:
    MyFriendInfo(char *myname, int myage) 
        : age(myage)
    {
        name=new char[strlen(myname)+1];
        strcpy(name, myname);
    }
    void ShowMyFreindInfo()
    {
        cout<<"이름: "<<name<<endl;
        cout<<"나이: "<<age<<endl;
    }
    ~MyFriendInfo()
    {
        cout<<"MyFriendInfo 소멸자 호출"<<endl;
		delete []name;
    }
};

class MyFriendDetailInfo: public MyFriendInfo
{
private:
    char* addr;
    char* phone;
public:
    MyFriendDetailInfo(char *myname, int myage, char *myaddr, char *myphone) 
        : MyFriendInfo(myname, myage)
    {
        addr=new char[strlen(myaddr)+1];
        phone=new char[strlen(myphone)+1];
        strcpy(addr, myaddr);
        strcpy(phone, myphone);
    }
    void ShowMyFreindDetailInfo()
    {
        ShowMyFreindInfo();
        cout<<"주소: "<<addr<<endl;
        cout<<"전화: "<<phone<<endl<<endl;
    }
     ~MyFriendDetailInfo()
    {
        cout<<"MyFriendDetailInfo 소멸자 호출"<<endl;
		delete []addr;
		delete []phone;
    }
};

int main()
{
    MyFriendDetailInfo friend1("코딩맛집", 20, "ㅇㅇ시", "010");
    friend1.ShowMyFreindDetailInfo();

    return 0;
}

 

 

 

 

 

문제 07-2. [IS-A 관계의 상속]

1. 정사각형을 의미하는 Square 클래스와 직사각형을 의미하는 Rectangle 클래스를 정의하고자 한다. 정사각형은 직사각형의 일종이므로 다음의 형태로 클래스의 상속관계를 구성하고자 한다.

class Rectangle
{
	....
};
class Square: public Rectangle
{
	....
};

 

이에 다음 main 함수와 함께 실행이 가능하도록 위의 클래스를 완성해보자. 참고로 상속을 한다고해서 유도 클래스에 무엇을 많이 담아야 한다는 생각은 버리자.

int main(void)
{
	Rectangle rec(4,3);
	rec.ShowAreaInfo();
    
	Square sqr(7);
	sqr.ShowAreaInfo();
	return 0;
}

실행 예

면적: 12
면적: 49

 

 

푼 것

#include <iostream>
#include <cstring>
using namespace std;

// 사각형
class Rectangle
{
private:
    int x;
    int y;
    int rectangleArea;
public:
    Rectangle(int width, int length)
        : x(width), y(length)
    {
        rectangleArea = x*y;
    }
	void ShowAreaInfo()
	{
	    cout<<"면적 : "<<rectangleArea<<endl;
	}
};

// 정사각형
class Square: public Rectangle
{ 
private:
    int l;
public:
    Square(int line)
        : Rectangle(line, line)
    {    }
};

int main(void)
{
	Rectangle rec(4,3);
	rec.ShowAreaInfo();
    
	Square sqr(7);
	sqr.ShowAreaInfo();
	
	return 0;
}

 

 

 

2. 책을 의미하는 Book 클래스와 전자 책을 의미하는 Ebook 클래스를 정의하고자 한다. 전자 책도 책의 일종이므로 다음의 형태로 클래스 상속관계를 구성하고자 한다. (클래스에 선언되어야 할 멤버변수만 제시)

class Book
{
private:
	char* title;  // 책의 제목
	char* isbn;  // 국제표준도서번호
	int price;   // 책의 정가 
	....
};

class EBook: public Book
{
private:
	char* DRMKey; // 보안관련 키
	....
};

main 함수와 함께 실행이 가능하도록 위의 클래스를 완성해보자

int main(void)
{
	Book book("좋은 C++", "555-12345-890-0", 20000);
	book.ShowBookInfo();
	cout<<endl;
    
	EBook ebook("좋은 C++ ebook", "555-12345-890-1", 10000, "fdx9w0i8kw");
	ebook.ShowEBookInfo();
	return 0;
}

실행 예

제목: 좋은 C++
ISBN: 555-12345-890-0
가격: 20000

제목: 좋은 C++ ebook
ISBN: 555-12345-890-1
가격: 10000
인증키: fdx9w0i8kw

 

 

푼 것

#include <iostream>
#include <cstring>
using namespace std;

class Book
{
private:
	char* title;  // 책의 제목
	char* isbn;  // 국제표준도서번호
	int price;   // 책의 정가 
	
public:
    Book(const char* btitle, const char* bisbn, int bprice)
        : price(bprice)
    {
        title=new char[strlen(btitle)+1];
        isbn=new char[strlen(bisbn)+1]; 
        strcpy(title, btitle);     
        strcpy(isbn, bisbn);     
    }
    void ShowBookInfo()
    {
        cout<<"제목: "<<title<<endl;
        cout<<"ISBN: "<<isbn<<endl;
        cout<<"가격: "<<price<<endl;
    }
    ~Book(){
        delete []title;
        delete []isbn;
    }
};

class EBook: public Book
{
private:
	char* DRMKey; // 보안관련 키
public:
    EBook(const char* btitle, const char* bisbn, int bprice, const char* bDRMKey)
        : Book(btitle, bisbn, bprice)
    {
        DRMKey=new char[strlen(bDRMKey)+1]; 
        strcpy(DRMKey, bDRMKey);     
    }
    void ShowEBookInfo()
    {
        ShowBookInfo();
        cout<<"인증키: "<<DRMKey<<endl;
    }
    
    ~EBook(){
        delete []DRMKey;
    }
};

int main(void)
{
	Book book("좋은 C++", "555-12345-890-0", 20000);
	book.ShowBookInfo();
	cout<<endl;
    
	EBook ebook("좋은 C++ ebook", "555-12345-890-1", 10000, "fdx9w0i8kw");
	ebook.ShowEBookInfo();
	return 0;
}

 

 

답안지 비교

이렇게 쓰면 편리하겠다..

Book(const char* _title, const char* _isbn, int _value)
        : price(_value)
    {
        this->title=new char[strlen(_title)+1];
        this->isbn=new char[strlen(_isbn)+1];
        strcpy(this->title, _title);     
        strcpy(this->isbn, _isbn);     
    }
    
    ...

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

01 상속(inheritance)에 들어가기에 앞서

 

상속 : 클래스 재활용과 더불어 어떤 점들이 더 있다.-는 아래에서 설명

컨트롤 클래스(핸들러 클래스) : 기능의 처리를 실제로 담당하는 클래스

 

#include <iostream>
#include <cstring>
using namespace std;

// 직원 데이터
class PermanentWorker
{
private:
    char name[100];
    int salary; 
public:
    PermanentWorker(char* name, int money)
    : salary(money)
    {
        strcpy(this->name, name);
    }
    int GetPay() const
    {
        return salary;    
    }
    void ShowSalaryInfo() const
    {
        cout<<"name: "<<name<<endl;
        cout<<"salary: "<<GetPay()<<endl<<endl;
    }
};

// 컨트롤 클래스 (실 기능 처리)
class EmployeeHandler
{
private:
// 정직원 객체들을 저장한 배열을 멤버로 지닌다
    PermanentWorker* empList[50];
    int empNum;
public:
    // 생성
    EmployeeHandler(): empNum(0)
    { }
    // 새로운 직원 등록
    void AddEmployee(PermanentWorker* 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];
    }
};

int main()
{
    // 컨트롤 클래스 객체생성
    EmployeeHandler handler;
    
    // 직원 등록
    handler.AddEmployee(new PermanentWorker("KIM", 1000));
    handler.AddEmployee(new PermanentWorker("LEE", 1500));
    handler.AddEmployee(new PermanentWorker("JUN", 2000));
    
    // 이달 지불할 급여 정보
    handler.ShowAllSalaryInfo();
    
    // 이달 지불할 총급여
    handler.ShowTotalSalary();

    return 0;
}

 

소프트웨어 설계에서 변경에 대응하는 프로그램의 유연성, 기능의 추가에 따른 확장성이 중요
새로 프로그램을 만드는 형태로 하면 안된다. 위 예제는 그런 점이 부족하다. 상속을 적용해면 해결할 수 있다.

 

 

02 상속의 문법적인 이해

예 : 자신의 고유한 특성 + 부모에게 여러 특징들을 물려 받는 것.

A클래스가 B클래스를 상속하게 되면 A클래스는 B클래스가 지닌 모든 멤버들을 물려받는다
A 객체에는 A클래스에 선언된 멤버와 B클래스에 선언된 멤버 모두 존재한다.

 

1. 메모리공간 할당
2. Base 클래스 생성자 실행
3. Derived 클래스의 생성자 실행

 

예제

#include <iostream>
#include <cstring>
using namespace std;

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;
    }
};

class UnivStudent: public Person // Person 클래스의 상속
{
private:
    char major[50]; // 전공 
public:
    UnivStudent(char* myname, int myage, char* mymajor)
        : Person(myage, myname) // 상속받은 클래스의 생성자
        {
            // UnivStudent 클래스의 생성자는 Person 클래스의 멤버까지 초기화해야 한다.
            strcpy(major, mymajor);
        }
        
        void WhoAreYou() const{
            WhatYourName(); // 상속받은 클래스의 멤버함수라서 호출이 가능하다
            HowOldAreYou();
            cout<<"My major is "<<major<<endl<<endl;
        }
};


int main()
{
    UnivStudent ustd1("Lee", 22, "Computer eng.");
    ustd1.WhoAreYou();
    
    UnivStudent ustd2("Yoon", 21, "Elec eng.");
    ustd2.WhoAreYou();

    return 0;
}

 

UnivStudent 클래스의 생성자는 Person 클래스의 멤버까지 초기화해야 한다. 그래서 UnivStudent 클래스의 생성자는 Person 클래스의 생성자를 호출하는 형태로 Person클래스의 멤버를 초기화하는게 좋다.
상속받는 클래스는 이니셜라이저를 이용해서 상속하는 클래스의 생성자 호출을 명시할 수 있다.

* 멤버는 클래스가 정의될 때 멤버의 초기화를 목적으로 정의된 생성자를 통해서 초기화하는게 가장 안정적이다.

상속받은 멤버변수의 접근 제한의 기준은 클래스이다.
클래스 외부에서는 private 멤버에 접근 불가하고, 상속받아도 멤버변수에 직접접근이 불가하다.
클래스 내 정의된 public 함수를 통해 간접 접근해야 한다.

 

Person ↔ UnivStudent

상위클래스  ↔ 하위 클래스
기초 클래스(base) ↔ 유도(derived) 클래스 *derive : 유래, 파생
슈퍼 클래스 ↔ 서브 클래스
부모 클래스 ↔ 자식 클래스

 

 

유도 클래스의 객체 생성과정
#include <iostream>
#include <cstring>
using namespace std;

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;
    }
};

class SoDerived: public SoBase
{
private:
    int deriveNum;
public:
    SoDerived(): deriveNum(30)
    {
        cout<<"SoDerived()"<<endl;
    }
    SoDerived(int n): deriveNum(n)
    {
        cout<<"SoDerived(int n)"<<endl;
    }
    SoDerived(int n1, int n2): SoBase(n1), deriveNum(n2)
    {
        cout<<"SoDerived(int n1, int n2)"<<endl;
    }
    void ShowDeriveData()
    {
        ShowBaseData();
        cout<<deriveNum<<endl;
    }
};

int main()
{
    cout<<"case1........"<<endl;
    SoDerived dr1;
    dr1.ShowDeriveData();
    cout<<"-------------"<<endl;
    cout<<"case2........"<<endl;
    SoDerived dr2(12);
    dr2.ShowDeriveData();
    cout<<"-------------"<<endl;
    cout<<"case3........"<<endl;
    SoDerived dr3(23, 24);
    dr3.ShowDeriveData();
    return 0;
}

 

 

유도 클래스의 객체생성 과정에서 기초 클래스의 생성자는 100% 호출된다.
유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면 기초 클래스의 void 생성자가 호출된다.

유도 클래스의 객체생성 과정에서 생성자가 두 번 호출된다.
1) 기초클래스의 생성자     2)유도 클래스의 생성자

 

SoDerived dr3(23, 24); ---- 메모리 공간 할당
SoDerived(int n1, int n2): SoBase(n1), deriveNum(n2) { ... } ---- 객체생성문에 의해 23, 24가 전달되면서 유도클래스 생성자 호출
SoBase(n1) ---- 기초클래스 생성자 호출 : 상속 관계에 의해 기초 클래스의 생성자 호출을 위해 이니셜라이저 확인, 매개변수 n1으로 전달된 값을 인자로 받을 수 있는 SoBase클래스의 생성자 호출
SoBase(int n): baseNum(n) {  }
---- 기초클래스 멤버변수 초기화
---- 유도 클래스의 생성자 실행 완료
---- 유도클래스의 멤버변수 초기화 완료

SoDerived dr1; ---- 메모리 공간 할당
---- 유도 클래스 생성자 호출
SoDerived(): deriveNum(30) { ... } ---- 생성자 관련 초기화 내용이 없으면 기초 클래스의 void 생성자를 대신 호출
deriveNum = 30; ---- 기초클래스의 멤버변수 초기화 완료
---- 유도클래스 생성자의 나머지 실행
---- 초기화된 SoDerived 객체 생성

 

유도 클래스의 객체 생성과정에서도 적용되는 원칙 : 클래스의 멤버는 해당 클래스의 생성자를 통해서 초기화해야 한다.

 

 

유도 클래스 객체의 소멸과정

유도 클래스의 객체 생성 과정에서는 생성자가 2번 호출,
유도 클래스의 객체 소멸 과정에서는 소멸자가 2번 호출된다.

#include <iostream>
using namespace std;

class SoBase
{
private:
    int baseNum;
public:
    SoBase(int n): baseNum(n)
    {
        cout<<"SoBase(): "<<baseNum<<endl;
    }
    ~SoBase()
    {
        cout<<"~SoBase(): "<<baseNum<<endl;
    }
};

class SoDerived: public SoBase
{
private:
    int derivNum;
public:
    SoDerived(int n): SoBase(n), derivNum(n)
    {
        cout<<"SoDerived(): "<<derivNum<<endl;
    }
    ~SoDerived()
    {
        cout<<"~SoDerived(): "<<derivNum<<endl;
    }
};

int main()
{
    SoDerived drv1(15);
    SoDerived drv2(27);
    return 0;
};

 

유도 클래스의 객체가 소멸될 때
유도 클래스의 소멸자가 실행된 뒤 기초 클래스의 소멸자가 실행된다.
스택에 생성된 객체의 소멸순서는 생성순서와 반대.

생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제한다.

#include <iostream>
#include <cstring>
using namespace std;

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;
    }
};

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;
    }
};

int main()
{
    UnivStudent st1("Kim", "Math");
    st1.WhoAreYou();
    UnivStudent st2("Hong", "Physics");
    st2.WhoAreYou();
    
    return 0;
}

 

 

반응형

 

 

03 protected 선언과 세 가지 형태의 상속

 

멤버변수의 범위

접근제어 지시자의 접근 범위는 private < protected < public 과 같다.
private, protected 는 외부에서 접근 불가, 클래스 내부에서는 접근 가능하나 상속이 되면 조금 달라진다. 

protected, public 선언된 멤버변수는 상속하는 유도클래스에서 접근할 수 있고 private 멤버는 컴파일 에러가 발생한다.
protected는 유도클래스에게만 제한적으로 접근을 허용한다. (private과 유일한 차이점)

 

세 가지 형태의 상속
class Derived: public Base { ... }
class Derived: protected Base { ... }
class Derived: private Base { ... }

 

protected 상속

protected보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속하겠다는 뜻

#include <iostream>
using namespace std;

class Base
{
private:
    int num1;
protected:
    int num2;
public:
    int num3;
    Base(): num1(1), num2(2), num3(3)
    { }
};
 
class Derived: protected Base{ };

int main()
{
    Derived drv;
    cout<<drv.num3<<endl; //error
}

public변수도 외부 접근 불가능 멤버가 되었다.

 

private 상속

private보다 접근의 범위가 넓은 멤버는 protected로 변경시켜서 상속

#include <iostream>
using namespace std;

class Base
{
private:
    int num1;
protected:
    int num2;
public:
    int num3;
    Base(): num1(1), num2(2), num3(3)
    { }
};
 
class Derived: private Base{ };

int main()
{
    Derived drv;
    cout<<drv.num3<<endl; //error
}

위 코드는 아래의 형태가 되고, num2, num3는 Derived 클래스 내에서만 접근이 가능한 멤버가 된다. 다른 클래스가 Derived클래스를 다시 상속하면 Derived 클래스 모든 멤버가 private이거나 접근불가여서  public으로 받아도 모두 접근불가가 되어 의미 없는 상속이 된다.

class Base : 
{
접근불가:
    int num1;
private:
    int num2;
private:
    int num3; 
};

private 상속이 이뤄진 클래스를 재상속할 경우, 멤버함수를 포함하여 모든 멤버가 접근불가가 되어서 의미없는 상속

 

public 상속

private을 제외한 나머지는 그냥 그대로 상속한다.

상속의 대부분은 public 상속이며 다중상속과 같은 특별한 때가 아니면 나머지는 잘 안 사용한다.

 

 

 

04 상속을 위한 조건

상속으로 클래스의 관계를 구성하려면 조건이 필요하다. 상속을 위한 최소한의 조건을 정리해본다.

 

상속을 위한 기본 조건인 IS-A 관계의 성립

유도클래스 = 기초클래스가 지니는 모든 것 + 유도클래스만의 추가적 특징


기초 클래스 - 유도 클래스
------------------------------
전화기 - 무선전화기 
컴퓨터 - 노트북컴퓨터

통화, 계산 + 이동성 
------------------------------
무선전화기 is a 전화기
놋북 is a 컴퓨터
(~이다)

상속관계가 성립하려면 기초클래스와 유도클래스 간에 IS-A 관계가 성립해야 한다.
성립하지 않는다면 적절한 상속관계가 아닐 확률이 높다.

 

#include <iostream>
#include <cstring>
using namespace std;

class Computer //소유자 정보 저장, 계산 함수
{
private:
    char owner[50];    
public:
    Computer(char* name){
        strcpy(owner, name);
    }
    void Calculate()
    {
        cout<<"요청 내용을 계산합니다."<<endl;
    }
};

class NotebookComp: public Computer // 배터리 관련 변수, 함수 추가 
{
private:
    int Battery;
public:
    NotebookComp(char* name, int initChag)
        :Computer(name), Battery(initChag)
    { }
    void Charging() { Battery+=5; }
    void UseBattery() { Battery-=1; }
    void MovingCal()
    {
        if(GetBatteryInfo()<1){
            cout<<"충전이 필요합니다"<<endl;
            return;
        }
        cout<<"이동하면서 ";
        Calculate();
        UseBattery();
    }
    int GetBatteryInfo() { return Battery; }
};

class TabletNoteBook: public NotebookComp // 펜을 등록, 등록이 된 펜을 사용해야 필기가 가능한 상황 표현
{
private:
    char regstPenModel[50];
public:
    TabletNoteBook(char* name, int initChag, char* pen)
    : NotebookComp(name, initChag)
    {
        strcpy(regstPenModel, pen);
    }
    void Write(char* penInfo)
    {
        if(GetBatteryInfo()<1)    
        {
            cout<<"충전이 필요합니다"<<endl;
            return;
        }
        if(strcmp(regstPenModel, penInfo)!=0)
        {
            cout<<"등록된 펜이 아닙니다";
            return;
        }
        cout<<"필기 내용을 처리합니다"<<endl;
        UseBattery();
    }
};

int main(){
    NotebookComp nc("이수종", 5);
    TabletNoteBook tn("정수영", 5, "ISE-241-242");
    nc.MovingCal();
    tn.Write("ISE-241-242");
    return 0;
}

 

TabletNotebook 클래스 객체 생성과정에서
TabletNotebook 클래스가 상속하는 NotebookComp 클래스의 생성자와 NotebookComp 클래스가 상속하는 Computer 클래스의 생성자가 모두 호출된다 

'타블렛컴퓨터는 컴퓨터이다'의 관계도 성립된다.

 

 

HAS-A 관계(소유의 관계)도 상속의 조건은 되지만 복합 관계로 이를 대신하는 게 일반적이다.
유도클래스는 기초클래스가 지닌 모든걸 소유하기에 소유의 관계도 상속으로 표현할 수 있다.

#include <iostream>
#include <cstring>
using namespace std;

class Gun
{
private:
    int bullet; //장전된 총알 갯수
public:
    Gun(int bnum):bullet(bnum)
    {  }
    void Shot()
    {
        cout<<"BBANG!"<<endl;
        bullet--;
    }
};

class Police: public Gun
{
private:
    int handcuffs; //소유한 수갑 갯수
public:
    Police(int bnum, int bcuff)
        : Gun(bnum), handcuffs(bcuff)
    {  }
    void PutHandCuff()
    {
        cout<<"SNAP!"<<endl;
        handcuffs--;
    }
};

int main(void)
{
    Police pman(5, 3); //총알5, 수갑 3
    pman.Shot();
    pman.PutHandCuff();
    return 0;
}

 경찰 has a 총

 

 

상속이 아니고서도 소유관계를 표현할 수 있다. 위의 코드보다 확장성이 훨씬 좋다.

#include <iostream>
#include <cstring>
using namespace std;

class Gun
{
private:
    int bullet; //장전된 총알 갯수
public:
    Gun(int bnum):bullet(bnum)
    {  }
    void Shot()
    {
        cout<<"BBANG!"<<endl;
        bullet--;
    }
};

class Police
{
private:
    int handcuffs; //소유한 수갑 갯수
    Gun* pistol; // 소유한 권총
public:
    Police(int bnum, int bcuff)
        : handcuffs(bcuff)
    {
        if(bnum>0)
            pistol=new Gun(bnum);
            // 생성자에서 Gun객체를 생성해서 참조
        else
            pistol=NULL;
    }
    void PutHandCuff()
    {
        cout<<"SNAP!"<<endl;
        handcuffs--;
    }
    void Shot()
    {
        // Gun 객체를 멤버변수를 통해 참조하는 구조여서 별도의 함수를 정의해야 한다.
        if(pistol==NULL)
            cout<<"Hut BBANG!"<<endl;
        else
            pistol->Shot();
    }
    ~Police()
    {
        if(pistol==NULL)
            delete pistol;
    }
};

int main(void)
{
    Police pman1(5, 3); 
    pman1.Shot();
    pman1.PutHandCuff();
    
    // 총 없는 경찰 객체
    Police pman2(0, 3); 
    pman2.Shot();
    pman2.PutHandCuff();
    
    return 0;
}

 

 

상속으로 묶인 두 클래스는 강환 연관성을 띈다. HAS-A 관계의 표현에도 사용될 수 있으나 프로그램의 변경에 많은 제약을 가져다 줄 수 있다.

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
모듈 : 함수나 변수 또는 클래스를 모아 놓은 파일. 기능을 모아놓은 파일

 

외장함수 사용 

다른 모듈의 외장함수를 사용하려면 import 키워드로 모듈을 불러온다.

import math

num=math.factorial(5)
print(num) #외장함수 사용
# 120

 

특정 함수만 import

모듈 이름을 생략하고 내장함수나 사용자 지정함수처럼 사용할 수 있는 차이점이 있다

from math import factorial

 

 

math 모듈
import math
print(math.pi)
print(math.e) #자연상수
print(math.pow(3,3)) #제곱
print(math.sqrt(4)) #제곱근
print(math.log(4,2))

 

 

random 모듈 : 난수와 무작위
import random
print(random.random()) #0과 1사이의 난수
from  random import*

a=random()
print(a)

 

from  random import*

a=randint(1,100) #1~100 랜덤 정수
print(a) #35
from  random import*

a=uniform(0.1, 9.99) #특정범위의 실수 랜덤
print(a) #5.56257574924524
from  random import*

a=randrange(1,101,2) #1~100사이의 임의의 정수
print(a)
from  random import*

a=randrange(10) #0~해당숫자까지의 갯수, 0~9까지
print(a)

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v