C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 강좌/문서
C++Builder Programming Tutorial&Docments
[122] 스트링 배열을 편리하게 다루기.
김태선 [cppbuilder] 26562 읽음    2007-03-29 00:27
보통 C/C++에서 문자열 배열은 다음과 같이 다룹니다.

char *strings[] =
{
    "첫째 줄",
    "두번째 줄",
    "세번째 줄"
};
아주 원초적인 선언입니다.
해당 문자열을 엑세스 할때는
{
    printf("%s", strings[0]);  // 첫째 줄 프린트
}
식으로 간단히 처리할 수 있습니다.
문자열 배열은 이런식으로 사용하면 됩니다.

그리고 문자배열이 몇개인지 계산하는 방법은
전체 문자열 배열의 크기에서 하나당 엘레멘트(요소)의 크기를 나누면 됩니다.
int  size = sizeof(strings) / sizeof(strings[0]);
전체 크기는 4바이트 포인트 변수가 3줄이니 총 12바이트입니다.
하나의 엘레먼트는 포인트 변수이므로 4바이트를 차지하게 됩니다.
그러므로 배열의 크기는 12 / 4 = 3 이 됩니다.
size 에는 3이 들어가게 됩니다.

배열의 크기를 구하는 이러한 기법은 실무에 아주 자주 쓰이니 꼭 기억해 둡시다.

그러면, 문자열 배열에 관해 더 알아야 할 것은 별로 없어 보입니다.

하지만,
문자열을 저렇게 숫자로 배열 인덱싱을 하면,
몇개 안되는 문자열이면 문제가 없으나 그 갯수가 많거나
배열의 인덱싱을 직접적인 숫자가 아닌 의미가 있는 상수로 하고 싶을때가 있습니다.
이러한 경우는 실무에서 매우 많이 접하게 되는데,
보통은 이렇게 합니다.
enum 으로 먼저 상수형을 정하고 그 순서에 맞게
문자열 배열을 열거하는 하는 것입니다.

enum Tstrings
{
   STR_FIRST,
   STR_SECOND,
   STR_THIRD,
};

char *strings[] =
{
    "첫째 줄",
    "두번째 줄",
    "세번째 줄"
};

이 예와 같이 하는 것이 대부분의 경우입니다.
Tstrings 형의 0부터 시작하는 순서 값을 가지는 문자열을 가르키는 상수형을 정의하고
실제 상수를 엑세스 할때 이를 사용하는 방법입니다.
{
    printf("%s", strings[STR_SECOND]);  // 두번째줄 프린트
}

이렇게 하면 대부분의 실무에 별다른 문제가 없습니다.

그런데, 만일 문자열이 많고 문자열의 순서가 프로그램 도중에 자꾸 바뀐다면 어떻게 할까요?
가령 통신 프로토콜 구현을 할때 프로토콜의 토큰은 구현 과정에서 자주 바뀌기도 합니다. 없어지기도 하고 새로 생기기도 하는데, 새로 생길때 마다 맨 뒤에 추가할려니 기분이 영 않 좋습니다. 왜냐하면 프로그래머는 연관있는 것 끼리 묶어서 두고 싶어 하기 때문입니다.

그럴때는 어떻게 할까요?
이럴때는 C++ 언어의 강력한 클래스와 오퍼레이터 재정의를 통해 해결할 수 있습니다.
스트링 클래스를 만들어 사용하는 것입니다.

우선 아래 소스를 봅시다.
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------

struct TTString
{
int   Head;
char *str;
int  Tag;

public:
operator String() {  return str;  }
operator char *() {  return str;  }
};

enum TTMSG
{
  ttA,
  ttB,
  ttC,
  ttD
};
TTString  strs[] =
{
  ttA, " 하하", 0,
  ttB, " 호호", 0,
  ttD, " 하악", 0,
  ttC, " 허허", 0,
};
class CTString
{
public:
  TTString *p;
  int   Size;
public:
  CTString(TTString *str, int size)
  {
   p = strs;
   Size = size;
  }
  TTString& operator[] (int head)
  {
   for(int c = 0; c < Size; c++)
   {
    if (p[c].Head == head)
     return p[c];
   }
   throw "배열에 없습니다.";
  }
  /*
  char *operator[] (int head)
  {
   for(int c = 0; c < Size; c++)
   {
    if (p[c].Head == head)
     return p[c].str;
   }
   return NULL;
  }
  */
};
CTString gTString(strs, sizeof(strs) / sizeof(strs[0]));

void __fastcall TForm1::FormCreate(TObject *Sender)
{
int  len = sizeof(strs) / sizeof(strs[0]);
for(int c = 0; c < len; c++)
{
  char *p = strs[c];
  Memo1->Lines->Add(p);
  Memo1->Lines->Add(strs[c]);
}
Memo1->Lines->Add("");
Memo1->Lines->Add(gTString[ttB]);
}
//---------------------------------------------------------------------------


char *strings[] =
{
"첫째 줄",
"두번째 줄",
"세번째 줄"
};

void __fastcall TForm1::Button1Click(TObject *Sender)
{
int  size = sizeof(strings) / sizeof(strings[0]);
Memo1->Lines->Add(size);
}
//---------------------------------------------------------------------------
결과:

Memo1
하하
하하
호호
호호
하악
하악
허허
허허



호호
3

//---------------------------------------------------------------------------
이렇게 찍힙니다.


그러면 위의 소스를 분석해보죠.

우선 스트링을 다룰 구조체를 하나 선언합니다.
스트링 배열이 주 목적이기는 하지만 스트링외에 몇가지 데이타가 더 들어간다고 가정합니다.

struct TTString
{
int   Head;
char *str;
int  Tag; // 이건 부가적인 데이타... 이런식으로 계속적인 추가 가능.

public:
operator String() {  return str;  }
operator char *() {  return str;  }
};


TTString 형의 구조를 정의하고, AnsiString 이 요구되거나 char * 형이 요구되는 곳은
멤버 변수를 참조하지 않고 바로 객체 명으로 대입이 가능하게 operator 2개를 재정의 했습니다.

다음은 문자열의 순서를 정한 상수값 정의입니다.

enum TTMSG
{
  ttA,
  ttB,
  ttC,
  ttD
};
이렇게 0부터 시작하는 값이 아닌,

enum TTMSG
{
  ttA = 100,
  ttB,
  ttC = 200,
  ttD
};
식으로 정의해도 무방합니다.

다음은 정의한 스트링 구조체에 데이타가 들어가도록 합니다.

TTString  strs[] =
{
  ttA, " 하하", 0,
  ttB, " 호호", 0,
  ttD, " 하악", 0,
  ttC, " 허허", 0,
};
ttA, ttB, ttD, ttC 로 순서도 제 마음대로 입니다.
이제 이를
strs[ttC] 식으로 엑세스 하고 싶은 것입니다.
그런데 아직은 안되죠.
strs[200] 식으로 번역되어 이는 메모리 엑세스 바이얼레이션 에러를 일으키게 됩니다.
그래서 순서 값이 마음대로 되어 있는 상수값을 가지고 내부의 바른 위치 데이타를 리턴해 줄 수 있는 클래스가 하나 필요하게 됩니다.

CTString 이라는 클래스를 하나 정합니다. 내부에
TTString 형의 포인트와 그 구조체의 배열 크기를 저장해 놓게 합니다.

class CTString
{
public:
  TTString *p;
  int   Size;
public:
  CTString(TTString *str, int size)
  {
   p = strs;
   Size = size;
  }
  TTString& operator[] (int head)
  {
   for(int c = 0; c < Size; c++)
   {
    if (p[c].Head == head)
     return p[c];
   }
   throw "배열에 없습니다.";
  }
};

그리고 객체명뒤에 []를 붙여서 해당 구조체를 바로 엑세스 하도록 [] 연산자를 재정의 합니다.
이렇게 하면
CTString[ttC]; 식으로 해서 제어하면
ttC 가 있는 TTString 객체가 제어되게 되는 것이죠.

그러면 실제 사용을 위해  CTString 를 선언합니다.
CTString gTString(strs, sizeof(strs) / sizeof(strs[0]));
이렇게 하면 되죠.

그러면 strs 를 통해 통상적인 방식으로 객체를 핸들링 할 수 있습니다.
Memo1->Lines->Add(strs[index]);
하지만 우리가 원하는 아무렇게나 순서가 정의된 enum TTMSG 상수 값으로
TTString 클래스를 제어하는 일이 가능하게 되었습니다.
Memo1->Lines->Add(gTString[ttC]);
잘 동작합니다.

실제로 이 코드는 의미적으로는
Memo1->Lines->Add(strs[ttC]);
라고도 생각할 수 있습니다.

TTStrings 가 String 형에 대해 재정의가 되어 있으므로 이와 같이
Memo1->Lines->Add(gTString[ttC]);
최종적으로는 그냥 문자열 다루듯이 다룰 수 있게 되는 것입니다.

여기까지는 문자열을 읽어내는데 문제가 없습니다.
중간에 문자열 바꾸고 싶을때는 어떻게 할까요?
gTString[ttD] = "새로운 문자열로 대치";
이렇게 되면 좋겠지만
이렇게 하려면 TTString의 생성자로 char *형을 인자로 하는 함수를 만들어야 하는데
이렇게 만들면 위처럼 데이타를 열거해서 초기화 리스트를 작성하는게 안됩니다.
오히려 더 불편하게 될수도 있죠.

그러므로 대입시는
gTString[ttD].str = "새로운 문자열로 대치";
이런식으로 해야 합니다.

그럼.

+ -

관련 글 리스트
122 스트링 배열을 편리하게 다루기. 김태선 26562 2007/03/29
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.