설명 #

"함수포인터"는 C언어에서 자주 쓰이는 기본 용법중 하나입니다. 정의는 다음과 같습니다.
선언된 함수가 저장된 메모리 번지값을 담고 있는 포인터 변수
하지만 이것을 왜 보통의 포인터와는 달리 특별취급하냐면 다음과 같은 이유 때문입니다.
  1. 일반 포인터 변수와는 조금 다르게 보이는 문법으로 선언됩니다. 예를 들어 함수 포인터 변수는 다음과 같이 선언합니다. (functionPtr이 포인터 변수명이 됩니다)
    void (*functionPtr)(const char *s_)
    
  2. 함수포인터는 실행부분을 가리키는 포인터 변수인탓에 () 연산자를 사용할 수 있습니다. 즉, 위의 functionPtr은 아래와 같이 실행가능합니다.
    functionPtr("바보나라~")
    

주로 함수포인터는 콜백(callback)을 구현하는데 사용됩니다. 어떤 라이브러리를 제작할 때 개발자가 어떤 "경우"에 대한 반응을 함수로 작성하고 이를 다시 라이브러리에서 호출하도록 작성하고자 할 때, 개발자가 작성한 함수의 포인터를 넘겨받아 실행하도록 하면 아주 유용할 때가 많습니다. (대표적인 예가 win32 api에서 윈도우 프로시져를 WNDCLASS의 lpfnWndProc맴버에 등록하는 부분을 들 수 있습니다) 즉, 함수를 마치 변수와 같이 취급하는 것이 가능해집니다.

예제 #

아래 예제는 함수포인터를 활용한 간단한 예제입니다.
#include <stdio.h>

void testout1(const char *s) {
  printf("시험출력1:%s\n", s);
}

void testout2(const char *s) {
  printf("시험출력2:%s\n", s);
}

// 함수포인터 변수
void (*funcPtr)(const char *s);

int main() {
  // 함수포인터를 testout1, testout2로 각각 대입해보고 실행해본다.
  funcPtr = testout1;
  funcPtr("테스트");
  funcPtr = testout2;
  funcPtr("테스트");
}

실행결과는 아래와 같습니다.
시험출력1:테스트 시험출력2:테스트
main()안에서는 funcPtr만 실행했지만 마치 testout1, testout2를 각각 실행한 것과 같은 효과가 있음을 알 수 있습니다.

typedef 거는 법 #

함수포인터를 어떤 포인터 변수로서 활용하고 싶을 때 typedef를 활용하면 매우 편리합니다. 실제로 win32 API에서는 윈도우 프로시저 함수포인터형으로 WNDPROC라는 타입이 있습니다. (이부분을 보면 어느정도 사용법에 대한 느낌이 올겁니다) winuser.h 헤더화일에서 검색해보면 다음과 같이 선언되어있습니다.
typedef LRESULT(CALLBACK *WNDPROC)(HWND,UINT,WPARAM,LPARAM);
실제로 사용될 때에는 다음과 같이 사용됩니다.
LRESULT CALLBACK winproc1(HWND,UINT,WPARAM,LPARAM) { }; ... WNDPROC winprocPtr = winproc1; ...
꽤 유용한 문법이니 알아두시면 좋습니다.

주의사항 #

함수포인터는 알아두면 상당히 유용한 기법이지만 사용시에는 다음과 같은 부분을 주의해야합니다.
  1. 대입되지 않은 (NULL값상태의) 함수포인터를 실행하지 않도록 주의해야합니다. 초기화를 항상 NULL로 해두고 실행전에 검사하는 것도 좋은 방법입니다.
  2. "같은 함수 포인터 타입"이라는 것을 증명하려면, 반환값, 매개변수의 타입 및 개수가 모두 일치해야합니다. 보통 하나라도 다른 함수포인터값을 대입하면 컴파일시 오류가 나게 됩니다.


출처 : http://www.redwiki.net/wiki/wiki.php/%C7%D4%BC%F6%C6%F7%C0%CE%C5%CD

공동 혹은 협력 개발 작업을 하다가 보면 서로의 정보를 공유하기 위해
매우 다양한 방법을 사용하게 됩니다.

그러한 방법 중에 하나로 서로가 개발한 부분의 사용법이나
자세한 작동 내용을 설명하기 위해 클래스나 함수들의 용법을 설명하는
문서를 자주 사용하게 됩니다.

그런데 이러한 문서를 작성하고 있다보면
개발보다 결코 쉽지 않고 만만치 않은 시간이 소비됩니다.
이미 개발하는 당사자도 많은 주석을 통해 코드 곳곳에 정보를 남겼을 것인데
이를 다시 보기 좋게 정리하고 문서에 투자하는 시간은 어쩌면 낭비되는 시간일지도 모릅니다.

이러한 고민이 아주 오래전부터 있었던 모양입니다.
벌써 많은 문서화 툴이 제작되고 사용되고 있는 중이지요.

이 글에서는 그중에 비교적 빨리 적응할 수 있고
오픈 소스 정신에 입각하여 누구나 사용할 수 있는 Doxygen을 소개하고자 합니다.

역시나 많은 분들이 사용하는 Windows XP 이상을 기반으로 설명할 것이며
DoxyWizard를 사용하는 것을 주로 설명합니다.

1. 설치
    관련 주소 : http://www.stack.nl/~dimitri/doxygen/ - 개발자의 홈페이지
 우선 다운로드 주소에서 윈도우즈용 최신 버전을 다운로드해서 설치합니다.
 그리고 그래프를 표현하기 위한 프로그램인 Graphviz도 다운로드해서 설치합니다.

2. 테스트용 소스
 다음과 같이 테스트용 소스를 만들었습니다.
 Doxygen은 다양한 형식의 주석을 지원합니다만 아래의 테스트 코드는 제가 흔히 사용하는 스타일로 적어 보았습니다. Doxygen의 문법은 KLDPWiki를 참고하시면 좋습니다.

소스 보기

디렉토리 구조

E:\Work\Doxygen_example

├ src

│ ├ main.cpp

│ └ class.cpp

└ inc

└ class.h


--- main.cpp의 코드 ---

#include "class.h"


//! 메인 함수

int main(void)

{

    // 메세지 출력

    printf(_T("Hello! Doxygen\n"));


    // 질문 메세지 박스 출력

    cDoxyClass test;

    bool ans = test.showQuestionBox(_T("Doxygen 마음에 드십니까?"), _T("질문"));


    // 반응 출력

    if (ans)

        printf(_T("아이고 감사합니다.\n"));

    else

        printf(_T("이런 죄송합니다.\n"));


    // 정상 종료

    return 0;

}


--- class.h의 코드 ---

//! @brief Doxygen 예제용 클래스

//!

//! Doxygen 주석 문법을 설명하기 위한 간단한 예제용 클래스입니다.\n

//! 뭐 특별히 하는 일은 없습니다.

lass cDoxyClass

{

protected:

    int m_nNum;    //!< 메세지 박스 출력 횟수

public:

    //! 생성자

    cDoxyClass();

    //! 소멸자

    virtual ~cDoxyClass();

public:

    //! @brief 질문 메세지 박스 출력

    //!

    //! 최상위에 Yes/No로 질문하는 메세지 박스를 출력합니다.

    //! @return Yes를 선택하면 true, 그 외에는 false

    bool showQuestionBox(

         const TCHAR* msg            //!< 질문 내용

        , const TCHAR* title = NULL    //!< 메세지 박스 제목

        );


    //! 메세지 박스 출력 횟수 얻기

    //! @return 메세지 박스 출력 횟수

    int getNum(void);

};


//! @brief Doxygen 예제용 하위 클래스 1

class cDoxySubClass1 : public cDoxyClass

{

public:

    int getNum(void) { return 1; }

};


//! @brief Doxygen 예제용 하위 클래스 2

class cDoxySubClass2 : public cDoxyClass

{

public:

    int getNum(void) { return 2; }

};


--- class.cpp의 코드 ---

#include "class.h"


///////////////////////////////////////////////////////////////////////////////

// Doxygen 예제용 클래스


//! 생성자

cDoxyClass()

{

    m_nNum = 0;

}

//! 소멸자

~cDoxyClass()

{

}


//! @brief 질문 메세지 박스 출력

bool showQuestionBox(

     const TCHAR* msg            //!< 질문 내용

    , const TCHAR* title = NULL    //!< 메세지 박스 제목

    )

{

    m_nNum++;

    if ( IDYES == MessageBox(NULL, msg, title, MB_YESNO | MB_ICONQUESTION | MB_TOPMOST) )

        return true;

    return false;

}


//! 메세지 박스 출력 횟수 얻기

//! @return 메세지 박스 출력 횟수

int getNum(void)

{

    return m_nNum;

}

------

※ 주의사항 : 한글을 혼용하기 위해서 UTF-8로 소스를 작성하시는 것이 좋습니다. 그리고 UTF-8로 비주얼 스튜디오에서 작업하실 때 파일 첫부분에 빈공백을 넣어주어야 정상적으로 작동하는 것을 확인했습니다.

VS2005의 UTF-8 저장하는 법

Visual Studio .NET 2005에서는 파일 메뉴의 저장 고급 옵션에서 파일의 형식을 선택할 수 있습니다. 예전엔 ANSI 파일 말고는 읽지도 못했는데 엄청난(남들 다 하지만 MS는 안하는?) 발전이라고 할 수 있겠습니다. 여기서 서명있는 UTF-8 형식을 선택하면 이후부터 해당 파일은 UTF-8로 저장됩니다.
 그러나 UTF-8은 분명 자동인식 할 수 있는 형식임에도 불구하고 다른 편집기에서 저장한 서명없는 UTF-8 문서는 읽지 못하는 고지식한 면을 보이기도 합니다. 이럴 때는 별 수 없이 ANSI로 다시 저장하고 VS2005에서 읽은 후 서명있는 UTF-8로 바꿔줘야 합니다.
 그리고 이 UTF-8 서명 때문인지 소스 파일의 첫줄에 빈줄을 넣어주어야 Doxygen이 문제없이 인식합니다.


3. 문서 생성 - Step1
 자 위의 소스가 작동하는지 안하는지는 머리 아프게 고민하지 말고 바로 문서를 만들어 보도록 하겠습니다. 우선 DoxyWizard를 실행합니다.

  Wizard 버튼을 눌러서 몇가지 설정을 따라합니다.

Wizard 설정

프로젝트 이름과 버전 혹은 코드명을 적고,
소스코드의 최상위 디렉토리를 선택합니다. (나중에 개별로 추가도 가능합니다.)
그리고 Scan recursively를 체크해서 하위 디렉토리까지 찾도록 했습니다.
마지막으로 문서가 생성될 위치를 선택합니다.

우선 C++ 출력에 최적화 시켰습니다.

출력은 웹문서로만 하게 선택했습니다.(PHP가 된다면 검색도 달아줍니다!)
PDF로도 출력이 가능합니다.

GraphViz로 각종 다이어그램도 추가했습니다.

OK 버튼을 누르고 나면 대다수 설정을 마쳤습니다. 추가로 몇가지 설정을 더 살펴보고 문서를 생성하도록 하겠습니다.

 Expert 버튼을 눌러서 고급 설정으로 들어갑니다.

Expert 설정

Project 탭입니다. 문서 프로젝트의 전반적인 옵션을 설정합니다.
우선 손상된 한글을 고쳐주고 OUTPUT_LANGUAGE를 Korean으로 바꿔줍니다.

그리고 아래쪽으로 내려가면 파일이름을 표시할 때 앞쪽의 긴 디렉토리명을 떼어낼 수 있도록 STRIP_FROM_PATH라는 옵션에 상위 디렉토리를 추가해줍니다.

SHORT_NAMES라는 옵션을 체크하지 않으면 GraphViz에서 오류를 발생시킬 수도 있습니다.

Build 탭으로 이동하면 좀 더 세부적인 옵션들이 있습니다.
EXTRACT_ALL과 EXTRACT_ANON_NSPACES를 체크하지 않으면 네임스페이스에 속하지 않은 함수나 구조체들이 포함되지 않을 수 있습니다.

Input 탭에서는 소스 파일이 있는 디렉토리를 별도로 추가할 수 있습니다.

Source Browser 탭은 문서에 소스를 어떻게 포함시킬지 결정합니다.
SOURCE_BROWSER 옵션에 체크를 하면 문서에 컬러링과 링크를 제공하는 소스 전체를 포함시켜 주기 때문에 문서에서 바로 소스를 확인할 수 있습니다.
그리고 VERBATIM_HEADERS를 사용하면 cpp와 같은 소스를 포함하지 않고 헤더 파일의 소스만 포함시켜서 라이브러리 등을 전달하고자 할 때 꽤나 유용합니다.

HTML 탭에서 생성되는 웹문서의 옵션을 선택할 수 있습니다.
GENERATE_TREEVIEW 옵션을 체크하면 좌측에 프레임으로 이루어진 트리뷰를 추가해 줍니다.
Dot 탭에서는 포함될 각종 그래프 옵션을 설정할 수 있습니다.
특히 CLASS_DIAGRAMS 옵션은 꽤나 유용하므로 체크해서 사용하시면 좋습니다.

물론 설치된 GraphViz의 위치를 설정해주는 것도 잊으시면 안됩니다.

OK 버튼을 눌러서 설정을 적용합니다.
소개된 것 외에도 많은 옵션이 있으므로 Doxygen 문서를 확인하시면서 이것저것 시도해서 확인해 보시는 것도 좋습니다.

4. 문서 생성 - Step2
 우선 설정 파일을 저장해야 합니다. doc 디렉토리를 만들고 Save 버튼을 눌러서 그곳에 저장하겠습니다.

그러면 상태가 '저장됨'으로 바뀝니다.

저장하지 않으면 문서를 생성할 수 없을 뿐더러 아무 경고 없이 이제까지 한 설정이 날아갈 수도 있습니다. 물론 한번 저장해둔 설정은 Load 버튼으로 언제든지 다시 불러서 계속 생성할 수 있습니다.

5. 문서 생성 - Step3
 Doxygen이 작업할 디렉토리를 선택합니다. 로그 파일이나 생성 중의 중간 파일 등이 사용할 위치입니다. 그냥 doc 디렉토리를 선택하겠습니다.


6. 문서 생성 - Step4
 Start 버튼을 눌러서 최종적인 문서를 생성합니다. 만약 오류가 있었다면 로그창에 표시됩니다. 그러면 해당 오류 부분을 바로잡아 주고 다시 생성하면 됩니다. 소스 코드에는 어떠한 위해도 가하지 않으므로 Doxygen의 오류 코드를 심각하게 걱정할 필요는 없습니다. 제 경험상으로는 GraphViz와의 오류가 많았습니다만 한번 작동하는 옵션 조합을 발견하고 나면 문제없이 잘 작동했습니다. SHORT_NAMES라는 옵션을 한번 확인해 보시기 바랍니다.


7. 문서 확인
 바야흐로 생성된 문서의 결과를 확인할 때가 되었습니다. 작업 디렉토리로 선택했던 doc 디렉토리를 보면 html이라는 디렉토리가 생성되었을 것입니다. 이 디렉토리 안의 index.html을 열어서 확인해 보겠습니다.

클래스 계통도까지 삽입되어서 매우 깔금한 문서가 만들어 졌습니다. (그런데 프로젝트 제목의 한글 부분은 손상되었군요. 프로젝트 제목은 한글로 입력하면 안되겠습니다.)

그리고 VERBATIM_HEADERS 옵션을 사용했기 때문에 헤더 파일의 전체 코드가 포함되었습니다.


DoxyWizard의 사용법을 중심으로 Doxygen 사용법을 알아보았습니다.
많은 분들이 Doxygen을 사용하여 문서화의 작업 시간을 단축하셨으면 하는 바램입니다.

 

출처 : http://blog.tinywolf.com/2

요즘은 ATL에서 CString을 지원하면서 std::string을 사용할 일이 줄어들긴 했지만, 여전히 유용한 클래스임에는 틀림없다.

가끔 MFC등 윈도용으로 개발된 클래스를 포팅해야 할 때 제일 문제가 되는 것 중에 하나가 CString이기도 한데 -_-
여튼. std::string에서 아쉬운 부분중에 하나인 ReplaceAll 함수를 간단히 구현해 보자.

   

#include <string>

 

typedef std::basic_string<TCHAR> _tstring;

 

_tstring replace_all( const _tstring& source, const _tstring& pattern, const _tstring& replace )

{

_tstring result = source;

_tstring::size_type pos = 0;

_tstring::size_type offset = 0;

_tstring::size_type pattern_len = pattern.size();

_tstring::size_type replace_len = replace.size();

 

while ( ( pos = result.find( pattern, offset ) ) != _tstring::npos )

{

result.replace( result.begin() + pos, result.begin() + pos + pattern_len, replace );

offset = pos + replace_len;

}

return result;

}

 

출처 : http://blog.daum.net/studiocoma/6800925

'C/C++언어 > STL' 카테고리의 다른 글

STL 컨테이너(map, multimap)  (0) 2010.07.04
STL 숙제.. 렌터카 2번째..  (0) 2008.05.02
STL 강좌입니다.  (0) 2007.09.06
"C++ STL 실전 프로그래밍 예제 소스"중 일부  (0) 2007.03.20

 map과 multimap은 '연관 컨테이너'입니다. 모든 연관 컨테이너(set, multiset, map, multimap)는 '노드 기반 컨테이너'입니다. 또 모든 연관 컨테이너는 균형 2진 트리로 구현됩니다. 균형 2진 트리는 보통 레드-블랙 트리를 사용합니다. map은 key와 value의 쌍으로 구성된 데이터 셋의 집합입니다. 여기서 key는 유니크합니다. multiset도 map과 같이 key와 value의 쌍으로 구성된 데이터 셋의 집합입니다. 단, multiset은 map과 달리 중복된 key가 존재할 수 있습니다.

 

map의 주요 개념을 그림으로 표현하면

map_개념.png 

set과 map은 데이터의 key가 유니크합니다.

 

multiset의 주요 개념을 그림으로 표현하면

multimap_개념.png 

multiset과 multimap은 같은 key값의 데이터를 컨테이너에 저장할 수 있습니다.

 

map, multimap의 주요 특징과 함수

 map도 set처럼 데이터를 추가하는 push_? 류의 함수를 가지고 있지 않습니다. 삽입(insert)한다는 의미에서 insert() 함수를 가지고 있습니다.

map의 특징은 key와 value의 쌍으로 데이터를 저장합니다. 그리고 key값을 이용하여 빠르게 value값을 찾을 수 있습니다. 연관 컨테이너에서 유일하게 [] 연산자 중복을 제공합니다.

 

기본적인 map 예제

#include <iostream>
#include <map>
using namespace std;
void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    cout << m[5] << endl;
    cout << m[3] << endl;
    cout << m[7] << endl;
    cout << m[2] << endl;
    cout << m[4] << endl;
}
  1. 100
    50
    80
    100
    100

map은 key와 value의 쌍으로 구성된 데이터들의 집합입니다. []연산자의 인덱스는 key이며 인덱스가 가리키는 메모리 값이 value입니다.

주의할 점은 m[5] = 100 연산에서 5라는 key가 없으면 노드 '추가'가되며 5라는 key가 있으면 '갱신'이 됩니다. 아래에서 공부합니다.

그림으로 설명하면..

map_간단_코드.png

map의 트리 그림입니다.

map_데이터_추가.png 

map의 key값 5로 value를 접근하는 그림입니다.

map_데이터_접근.png 

 

 map은 key와 value의 쌍을 pair 객체로 저장합니다. STL의 쌍을 이루는 모든 요소는 pair 객체를 사용합니다.

위 예제는 insert()함수를 사용하여 아래 예제로 바꿀 수 있습니다.

#include <iostream>
#include <map>
using namespace std;
void main( )
{
    map<int , int > m;

    m.insert( pair<int,int>( 5, 100) );
    m.insert( pair<int,int>( 3, 50) );
    m.insert( pair<int,int>( 7, 80) );
    m.insert( pair<int,int>( 2, 100) );
    pair<int,int> pr( 4, 100);
    m.insert( pr );

    cout << m[5] << endl;
    cout << m[3] << endl;
    cout << m[7] << endl;
    cout << m[2] << endl;
    cout << m[4] << endl;
}

  1. 100
    50
    80
    100
    100

 map은 key, value를 pair 객체로 각각 first와 second에 저장합니다.

그림으로 표현하면

map_pair_객체.png 

 

 

반복자를 사용한 모든 key, value 출력 예제

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

void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    map<int, int>::iterator iter;
    for( iter = m.begin(); iter != m.end() ; iter++)
        cout << "m["<<(*iter).first <<"]: " << (*iter).second << endl;

    cout << "==============" << endl;
    for( iter = m.begin(); iter != m.end() ; iter++)
        cout << "m["<<iter->first <<"]: " << iter->second << endl;
}
  1. m[2]: 100
    m[3]: 50
    m[4]: 100
    m[5]: 100
    m[7]: 80
    ==============
    m[2]: 100
    m[3]: 50
    m[4]: 100
    m[5]: 100
    m[7]: 80

 map도 양방향 반복자를 제공합니다.

그림으로 간단하게..

 map_반복자.png

 

 

연관 컨테이너는 모두 동일한 함수군을 가지며 동작 방식도 같습니다.

map의 검색 함수들입니다.

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

void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    map<int, int >::iterator iter;

    iter = m.find( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    iter = m.lower_bound( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    iter = m.upper_bound( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    pair< map<int, int>::iterator, map<int, int>::iterator> iter_pair;
    iter_pair = m.equal_range(5);

    if( (iter_pair.first) == m.end() && (iter_pair.second == m.end()) )
        cout << "없음!" << endl;
    else
    {
        cout << "m["<< iter_pair.first->first <<"]: " << iter_pair.first->second <<" ~ ";
        cout << "m["<< iter_pair.second->first <<"]: " << iter_pair.second->second <<endl;
    }
}
  1. m[5]: 100
    m[5]: 100
    m[7]: 80
    m[5]: 100 ~ m[7]: 80

 동작은 set에서 설명했으므로 패스~!

그림으로 설명하면...

map_검색_함수.png 

 

 

 

마지막으로 multimap 예제입니다.

map과 다른 점은 [] 연산자가 없으며 key 값이 중복될 수 있습니다.

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

void main( )
{
    multimap<int , int > m;

    m.insert( pair<int,int>( 5, 100) );
    m.insert( pair<int,int>( 3, 50) );
    m.insert( pair<int,int>( 7, 80) );
    m.insert( pair<int,int>( 2, 100) );
    m.insert( pair<int,int>( 4, 100) );
    m.insert( pair<int,int>( 7, 200) );
    m.insert( pair<int,int>( 7, 300) );

    pair<multimap<int,int>::iterator, multimap<int, int>::iterator > iter_pair;
    iter_pair = m.equal_range( 7 );
    multimap<int,int>::iterator iter;
    for( iter = iter_pair.first ; iter != iter_pair.second ; iter++)
        cout << "m["<< iter->first <<"]: " << iter->second <<' ';
    cout << endl;

}
  1. m[7]: 80 m[7]: 200 m[7]: 300

 설명은 multiset과 같습니다.

아래는 위 예제의 그림입니다.

multimap_equal_range(1).png  

 

 

 

여기까지 STL의 컨테이너를 마무리합니다.

C++ 표준에 추가된 해쉬 컨테이너는 다음에 공부합니다. 

출처 : http://coolprogramming.springnote.com/pages/4826369

 

최근엔 hesh_map 등이 더 인기가 있는거 같은..

쓰기에는 map 과 같은 인터페이스에 using namespase std 가 아닌 stdext 를 쓴다

자세한 레퍼렌스는 http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k(HASH_MAP);k(DevLang-%22C%2B%2B%22);k(TargetOS-WINDOWS)&rd=true 를 참고하자

지금까지는 하나의 클래스에 대해 이야기를 했습니다. 지금부터는 하나 이상의 클래스를 이야기할까 합니다. 즉, 클래스 간의 관계에 대한 이야기입니다.

 

클래스의 관계는 여러 가지가 있지만 재사용 측면에서 크게 두 가지 관계가 존재합니다.( 우리는 복잡한 것을 생각하지 않으므로 간단하게 접근합니다.)

  • 첫째, '-이다'의 관계( is_a 관계)

    • 상속(inheritance)
  • 둘째, '-가지다'의 관계( has_a 관계)

    • 포함(composition)

 

첫째, is_a 관계는 Person 클래스와 Student 클래스의 관계입니다.

  • Student(학생)은 Person(사람) 이다. 관계이기 때문에..
  • 그림으로는 아래와 같이 표현할 수 있습니다.

상속Person.png 

이때 Person 클래스를 부모(Parent) 클래스라 하고 Student, Professor 클래스를 자식(child) 클래스라 합니다.

 

 

둘째, has_a 관계는 Car 클래스와 Radio 클래스의 관계입니다.(이것은 다음에 진행합니다. --;)

  • Car(자동차)는 Radio(라디오)를 가진다. 관계이기 때문에..
  • 그림으로는 아래와 같이 표현할 수 있습니다.

포함car.png 

 

 1, 상속

상속은 부모 클래스의 모든 내용(속성과 행동)을 자식 클래스가 물려 받는 것입니다.

위 그림에서 Person의 속성(멤버 변수)와 행동(멤버 함수)을 Student가 물려 받습니다. 그리고 자신만의 상태와 행동을 추가하여 내용을 확장합니다.

그래서 상속을 이용하면 코드를 재상용하여 효율적으로 클래스를 생성할 수 있습니다.

 

Student 클래스를 만들기 위한 간단한 예제코드를 볼까요?

첫째, 상속을 사용하지 않는 Student 클래스 정의

class Student
{
    char name[20];
    int age;
    int grade;
public:
    void Eat( ) {   }
    void Study( ) { }
};

void main( )
{
    Student std1;
}

 학생은 사람이므로 사람으로서 필요한 모든 내용(속성과 행동)을 Student 클래스가 정의하고 있습니다. 이렇게 되면 Professor 클래스를 정의할 때도 사람에 필요한 모든 내용을 정의해야 합니다. 그러면 코드 중복이 발생하여 여러 가지 불이익을 받게 됩니다.

 

Student 객체의 그림입니다.

상속없는_student_객체.png 

 

 

둘째, 상속을 사용한 Student 클래스 정의

class Person
{
    char name[20];
    int age;
public:
    void Eat( ) {   }
};
class Student : public Person
{
    int grade;
public:
    void Study( ) { }
};

void main( )
{

    Person person1;

 

    Student student1;

}

여기서 Person 클래스가 이미 존재한다고 가정한다면 Student 클래스를 정의하는 것은 위쪽 예제보다 훨씬 간단합니다. 속성 grade와 행동 Sudent()만 정의할 뿐이니까요. 그리고 부가적으로 따라오는 이득도 만만치 않습니다. 문법적으로 상속은 " : public Person"와 같이 합니다.(빨간색 부분)

 

Person 객체와 Student 객체의 그림입니다.

당연한 이야기지만 Student 객체는 위쪽 상속을 사용하지 않는 객체의 속성, 행동과 같은 객체가 만들어 집니다. 상속을 사용해서 Student 클래스를 정의한 것뿐이니까요.

상속없는_student_객체(2).png 

 

 

2, 함수 재정의(function overriding)

 함수 재정의란 부모 클래스에 정의된 멤버 함수를 자식 클래스에서 다시 정의하는 것을 말합니다.

 

예로 아래와 같은 Person 클래스가 있다고 가정합니다.

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
void main( )
{
    Person person1("김영수", 20);

    person1.Print( );
}
  1. name : 김영수, age : 20

 person1의 Print() 멤버 함수는 이름과 나이를 출력합니다.

 

이때 학생을 정의하기 위한 Student 클래스가 필요합니다.

이미 Person 클래스가 존재하므로 Student 클래스는 Person을 상속받아 만들기로 결정합니다.

아래와 같이..

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g)     {
    }
    void Study( ) { }
};
void main( )
{
    Student student1("김학생", 20, 1);

    student1.Print( );
}
  1. name : 김학생, age : 20

 그리고 학생 객체 student1을 만들어 학생의 정보를 출력(Print() 호출)하지만 Print() 함수는 grade의 정보를 출력하지 못합니다.

Print()함수는 Person의 메소드로 grade의 어떠한 내용도 알지 못하기 때문입니다.

한마디로 Person 클래스보다 Student 클래스가 더 구체화된 클래스이므로 Print() 함수의 기능도 Student에 맞게 더 구체화 시켜야 합니다.

그래서 부모 클래스(Person)의 함수(Print())를 자식 클래스(Student)에서 재정의하는 것입니다.

아래 예제와 같이...

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g)     {
    }
    void Study( ) { }
    void Print( ) // 함수 재정의
    {
        Person::Print(); // 이름과 나이를 출력하고
        cout << "grade : " << grade << endl; // 학년도 출력합니다.
    }
};
void main( )
{
    Student student1("김학생", 20, 1);

    student1.Print( );
}
  1. name : 김학생, age : 20
    grade : 1

 Student 클래스에서 Print()함수를 재정의하여 이름과 나이도 출력하고(Person::Print()) 학년도 출력합니다. 재정의 함수에서 부모의 함수를 호출하려면 접근 연산자(::)를 사용해야 합니다. ( Person::Print() <=  이렇게... )

아니면 재귀함수 호출이 되겠죠?

 
출처 : http://coolprogramming.springnote.com/pages/3422007

+ Recent posts