2012년 2월 23일 목요일

[C++] 문자열 함수 外


size_t strlen ( const char * str );

문자열의 실제 길이를 리턴하는 함수이다. 널문자를 제외한 길이라는 점에 주의하자!!! 참고로 size_t 란건 unsigned int를 재정의한 것이다. 






int strcmp ( const char * str1, const char * str2 );

두개의 문자열을 비교하여 동일할 경우 0이 리턴되고, str1이 클 경우 양수, str2가 클 경우 음수가 리턴된다. 여기서 크다는 것은 문자를 아스키 코드값으로 비교한 결과를 말하는 것이다.






errno_t strcpy_s(
   char *strDestination,
   size_t numberOfElements,
   const char *strSource 
);
문자열을 복사하는 strcpy의 새로운 표준형이다. strcpy의 경우 배열의 첨자범위를 넘어설 위험성이 많으므로 최근의 visual studio에서 사용하면 경고 메시지가 뜬다. 중요한건 두번째 인자가 Dest 버퍼의 크기라는 점만 알면 된다. errno_t는 에러체크를 위해 사용되는 리턴값 같은데 실제 사용할때는 신경안써도 된다.


errno_t strcat_s(
   char *strDestination,
   size_t numberOfElements,
   const char *strSource 
);
복사가 아니라 이어붙이기. 나머지 설명은 위와 동일.









아래는 실제로 문자열을 깊은 복사할때의 코드이다.
가장 주의해서 볼 점은 strlen() 함수는 NULL 문자를 제외한 길이를 리턴한다는 점이다.



char szTemp[256];
cin >> szTemp;


int nLen = strlen(szTemp);


char* m_szName = new char[nLen + 1];


strcpy_s(m_szName, nLen + 1, szTemp);








※ 문자열 함수는 아니지만 콘솔 문자열 입력 관련

cin 객체는 한 개의 문자 단위로 입력받아 처리하며, 공백문자나 개행문자(엔터, '\n')로 tokening 된다.
이때 두 개 이상의 문자를 입력하면 그만큼 예외처리가 여러번 일어나는 것을 볼 수 있다.
이것을 막으려면 입력버퍼를 초기화시키는 fflush(stdin); 명령을 사용하면 편리하다.

cin.getline()  공백문자 포함해서 콘솔에서 문장을 입력받아 저장하는데 사용하는 함수





※ 참조:
http://www.cplusplus.com/
http://msdn.microsoft.com

[C++] 연산자 오버로딩


OperatorOverLoad OperatorOverLoad::operator+( const OperatorOverLoad& rhw)
{
OperatorOverLoad temp;
temp.BindXY(m_nX + rhw.m_nX, m_nY + rhw.m_nY );
return    temp;
}



위는 m_nX와 m_nY 두개의 멤버 변수를 가진 클래스에서의 '+' 연산자 오버로딩의 예시이다.

그런데 사실 이건 중요하지 않고ㅡ,.ㅡ;;

복사생성자 관련 포스트에서도 언급한것 같지만 '=' 연산자 오버로딩이 중요하다.





OperatorOverLoad OperatorOverLoad::operator=( const OperatorOverLoad& rhw)
{
///< 자기 자신 확인 
if( this == &rhw )
{
return *this;
}

///< 확인 
SetName(rhw.m_pName);


return *this;
}




void OperatorOverLoad::SetName(const char* pName)
{
///< 포인터 검사 
if( pName != 0 )
{
int nLen = strlen(pName);
if( nLen != 0 )
{
delete [] m_pName;
m_pName = NULL;


m_pName = new char[nLen + 1];
strcpy_s(m_pName,nLen+1,pName);
}
}
}



위 내용을 요약하자면 char* 형의 멤버변수에 대하여 깊은 복사를 해주고 있다.

char* 형의 멤버변수가 존재할 경우 깊은 복사를 하도록 복사생성자와 '=' 연산자 오버로딩을 둘 다 생성해주어야 한다.

[C++][디자인패턴] static과 싱글톤 패턴

디자인 패턴이란

프로그래머들이 프로젝트를 하면서 자주 사용되는 몇가지 유용한 프로그램 스타일 들에 이름을 붙여서 분류한 것이다. 싱글톤, 팩토리, 옵저버, 커맨드, 상태 패턴 등이 있다. 각 패턴들이 어떤 상황에서 사용되는지를 아는 것이 중요하다.



싱글톤 패턴의 특징

-싱글톤은 매니저 역할을 하는 패턴이다.
-단 하나만 존재하는 전역 클래스이다.
-함수와 변수를 static으로 선언하여 사용한다.




<cStudentMgr.h>


#pragma once


class cStudentMgr
{
private:
static cStudentMgr* m_pInstance;
int m_nID;

public:
static cStudentMgr* GetInstance(void);
void Destory(void);


inline void SetID(int nID)
{
m_nID = nID;
}


inline int GetID(void) const
{
return m_nID;
}


private:
cStudentMgr(void);
~cStudentMgr(void);
};





위 코드를 보면 생성자와 소멸자가 private 영역에 들어가 있다.
이런 경우 클래스의 객체를 만들면 생성자와 소멸자가 private이므로 호출할 수 없다는 에러가 발생한다.
생성자와 소멸자 대신에 GetInstance(), Distroy()를 사용해 프로젝트를 통틀어 단 하나의 객체만을 생성한다.
두 함수의 내부 구현은 이렇다.




<cStudentMgr.cpp>



#include "cStudentMgr.h"


///< 정적 초기화 
cStudentMgr* cStudentMgr::m_pInstance = 0;


cStudentMgr::cStudentMgr(void)
:m_nID(0)
{
}


cStudentMgr::~cStudentMgr(void)
{
}


///< 싱글톤 인스턴스 포인터 얻기 
cStudentMgr* cStudentMgr::GetInstance(void)
{
///< 처음 이라면 
if( m_pInstance == 0 )
{
m_pInstance = new cStudentMgr;
}


return m_pInstance;
}


///< 생성된 static 인스턴스 삭제
void cStudentMgr::Destory(void)
{
if( m_pInstance != 0 )
{
delete m_pInstance;
m_pInstance = 0;
}
}






가장 위는 정적변수의 초기화이다.
생성자와 소멸자보다 위의 정적영역에서 초기화되고 있다.

(참고로 생성자에서 강조된 부분은 멤버 이니셜라이저라고 하는데, const 나 레퍼런스 변수처럼 선언과 동시에 초기화되어야 하는 변수들을 클래스에서 초기화해주는 방법이다. 여기서는 굳이 사용할 이유는 없지만 그냥 사용하고 있다-ㅅ-; 또한 상속시에 부모 생성자에 인자를 넘길때도 사용된다.)

그리고 아래 2개의 함수는 단 하나의 객체만을 생성 & 소멸시키는 기능을 하고 있다.
메인함수에서는 아래와 같이 사용된다.




#define ST_MGR cStudentMgr::GetInstance()   /// 사용하기 편리하도록


int main(void)
{
ST_MGR->SetID(1);


cout << ST_MGR->GetID() << endl;

ST_MGR->Destory();


return 0;
}




싱글톤패턴은 프로젝트에 단 하나뿐인 전역 클래스로서 주로 관리자 역할을 하는 클래스를 만들어 사용하는 방식이다.
static 이므로 객체 생성없이 정적 영역의 멤버변수와 함수를 어디서나 호출하여 사용할 수 있다.
특징은 전역변수와 마찬가지로 사용하기엔 편리하지만 남용할 경우 유지보수, 가독성, 재사용성 등에 문제가 발생한다.
따라서 프로젝트당 단 하나의 클래스만 전역으로 만드는 것이 좋으며, 프로젝트 팀장의 성향에 따라 싱글톤을 아예 못쓰게 하는 경우도 있다고 한다.




※ 참고: 정적(static) 함수란?
개념적으로는 정적 멤버변수만을 접근 가능한 함수이다.
클래스가 인스턴스되지 않아도 사용가능하다.
내부적으로는 다른 멤버함수와 달리 this 포인터를 넘기지 않는다.
때문에 c에서 callback으로 넘길때 유용하다(이건 아직 무슨 말인지 모르겠다).
this 포인터가 없으므로 상속해서 오버라이딩 불가능.
또한 c에서는 하나의 파일에서만 해당 함수를 사용하고 싶을때 static을 붙인다.

2012년 2월 20일 월요일

포풍같은 주말을 보내고..

뭐랄까.. 평생 다시 이런 일이 없을지도 모르는 주말이었는데

뭔가 완전히 불태우지 못한것 같아서 아쉽고 또 아쉬움만 남는다.

그래도 감동이었고...

뭔가 응어리같은게 풀린 느낌.. 죽어도 여한이 없다? 까지는 아니지만..ㅎㅎ

콘서트란건 한번은 좌석에서 전체를 보고 한번은 스탠딩을 뛰는것이 진리구나 라고 깨달음ㅋㅋ

아... 주말부터 오늘까지는 아무것도 손에 안잡혔다.

지금 해야할게 많은데...

그냥 에헤라디야 딩가딩가 하면서 살면 안될까요?





2012년 2월 15일 수요일

[C++][클래스] this 포인터

지난 포스트에서 const 인자 관련해서 this 라는 알 수 없는 녀석이 등장했었다.
이번엔 그것을 정리해보겠다.



일단 정의부터,

객체의 멤버함수는 자신을 소유한 객체의 주소를 가리키는 this 라는 포인터를 자동으로 가지게 된다.



예를 들어 아래와 같이 멤버함수를 실행했다고 하자.

int main()
{
    TestClass c1;

    c1.TestFunc();            // *

    return 0;
}


위의 별표로 표시한 라인이 실행될때 자동으로 this 포인터가 생성이 되고 그것이 함수 내부에 인자로 들어간다.

더 자세히 보자면 



TestClass* const this = &c1;             // 가리키는 객체를 바꾸면 안되므로 const
//const TestClass* const this = &c1;    // const 함수의 경우 멤버 변수도 바꾸면 안되므로 또 const



함수 호출시 자동으로 위 선언이 실행되고 
TestFunc() 함수 내부에 this가 인자로서 전달된다.
즉, TestFunc() 내부에서 아래와 같이 this를 사용할 수 있다.



TestFunc()
{
    cout << this->m_ID;
}






※ 보충설명:
컴퓨터 내부적으로는 각 객체에 대하여 멤버변수는 각각 메모리를 할당받지만 멤버함수는 그렇게 할 필요가 없다. 어차피 같은 내용이므로 메모리 낭비가 되기 때문이다. 그렇기 때문에 실제 메모리상에 멤버함수의 코드부분을 저장하는 곳은 클래스당 한 곳만 존재하면 되지만, 대신에 각 멤버함수를 객체별로 구분하기 위하여 this 포인터의 개념이 도입된 것이다. [참조: 뇌를 자극하는 C++, p.622]





[C++][클래스] 복사 생성자

복사 생성자의 형태는 아래와 같다.


클래스명(const  클래스명&  name)
{
         /// 내용
}



- 인자가 레퍼런스인 이유는?
인자를 직접 복사받아서 사용한다면 복사생성자 내에서 다른 복사생성자를 호출하고 그 복사 생성자가 다시 다른 복사생성자를 호출하는 무한 루프 상황이 발생할 수 있으며, 그때 실제 메모리 복사가 일어나므로 메모리 오버플로우 등의 에러가 발생할 수 있기 때문이다.
레퍼런스의 경우 메모리에 실체가 복사되는 것이 아니므로 안전하다.



- 복사 생성자를 정의하는 이유?
클래스에 char* 처럼 메모리 동적할당을 받는 멤버변수가 있을 경우, 반드시 복사 생성자를 직접 정의하여 '깊은 복사'를 해줘야한다.

이때 복사생성자와 쌍으로 대입연산자(=)를 오버로딩하여 마찬가지로 깊은 복사를 하도록 정의해 주어야 한다.

참고로 깊은 복사란 메모리 공간을 별도로 할당 받고 그 내용을 복사받는 형태, 얕은 복사는 포인터가 가리키는 주소값 자체를 복사해 오는 것이다.
디폴트 복사 생성자는 얕은 복사를 하는데, 이 경우 같은 주소를 여러개의 다른 객체가 가리키는 것이므로 각각의 객체가 소멸될때마다 delete 명령어로 같은 메모리를 여러번 삭제하는 위험한 사태가 발생할 수 있게 된다.



- 복사 생성자가 호출되는 3가지 경우

1. 객체를 선언하고 초기화 할때

cTestClass tc1;
cTestClass tc2 = tc1;         // 여기서 호출된다.


2. 함수 인자로 객체가 복사되어 들어갈때(임시객체 생성)

void   TestFunc(TestClass tc);


3. 함수에서 객체를 값으로 복사해서 리턴할때(임시객체 생성)

TestClass  TestFunc();











[C++][클래스] 객체 레퍼런스를 인자로 받을때 const 사용시 에러발생 문제


// 다른 객체에서 정보 복사해오기
//void cStudentInfo::CopyInfo(const cStudentInfo& c)
void cStudentInfo::CopyInfo(cStudentInfo& c)
{
char* newID = c.GetID();
char* newName = c.GetName();
int newKorPnt = c.GetKorPoint();
int newEngPnt = c.GetEngPoint();
int newMatPnt = c.GetMatPoint();


SetID(newID);
SetName(newName);
SetKorPoint(newKorPnt);
SetEngPoint(newEngPnt);
SetMatPoint(newMatPnt);
}



위의 코드는 클래스 멤버함수의 정의부이다.
자신과 동일한 클래스 타입 객체 레퍼런스를 인자로 받아 내용을 복사하는 함수인데
인자에 const를 붙이면 Getxxx() 함수를 사용할때 아래와 같은 에러가 난다.



1>c:\documents and settings\baikanp\my documents\visual studio 2008\projects\120214\report.cpp(146) : error C2662: 'cStudentInfo::GetID' : 'this' 포인터를 'const cStudentInfo'에서 'cStudentInfo &'(으)로 변환할 수 없습니다.



const를 제거하면 정상적으로 빌드가 되며,
참고로 Getxxx() 함수는 단순히 멤버변수를 리턴하는 함수이다.








남쌤에게 문의한 결과,
const로 선언하면 cStudentInfo& c 가 절대 변하지 않는다는 보장을 해준 것인데, 
c.Getxxx() 함수를 사용할 때 임시객체의 생성과 소멸 등이 발생하여 객체에 변경이 생기기 때문에 에러가 나는 것이라 한다.
지금으로서는 확실히 이해가 안되지만 Getxxx() 함수를 사용하는 것이 객체에 변경을 가져온다는 것으로 이해면 될것같다.
아마도 에러메시지로 보건데, 객체에 암묵적으로 포함된 this 라는 포인터가 변경되는 것이 직접적인 에러의 원인인듯 하다.




해결법은 두가지이다. 
가장 위의 코드처럼 const를 제거하거나, 
또는 아래와 같이 Getxxx() 함수를 거치지 않고 직접 멤버변수를 복사하는 것이다.



void cStudentInfo::CopyInfo(const cStudentInfo& c)
{
char* newID = c.m_szID;
char* newName = c.m_szName;
int newKorPnt = c.m_nKorPoint;
int newEngPnt = c.m_nEngPoint;
int newMatPnt = c.m_nMatPoint;






여기서 또 한가지 재밌는 것은 위 멤버 변수들은 private 로 선언되어 있다는 것이다.
즉,  어떤 객체의 메소드는 같은 타입 다른 객체를 인자로 받았을 때 그  private 멤버변수를 자유롭게 접근할 수 있다는 것이다.
(예를 들어 복사 생성자의 경우도 함수 내부에서 타 객체의 멤버를 직접 접근하고 있다.)








※ 내용보충 (2012. 02. 16):
const 객체를 사용해서 const가 아닌 멤버함수를 호출하는 것은 불가능하다. 컴퓨터는 const가 아닌 멤버함수는 멤버변수의 값을 변경한다고 생각하기 때문이다(뇌를 자극하는 C++ p.599). 위 문제의 경우 Get 함수들을 아래와 같이 const 함수로 선언해주면 const 객체를 통해서 사용해도 에러가 생기지 않는다. 참고로 const 함수란 멤버변수의 값을 변경하지 않는 함수를 말한다.




char* cStudentInfo::GetID(void) const