안녕하세요! 김백일입니다.
지난번(16번) 팁에서 대소문자 변환 방법에 대해 다루었습니다.
계속해서 이번에는 Scott Meyers의 'Effective STL'의 부록 A에 있는
'大小文字를 구분하지 않는 문자열 비교법'에 대해서 간단히 정리하겠습니다.
이 부분은 Scott Meyers의 글은 아니고요, Matt Austern의 글이 실려있는 겁니다.
=========================================================================
대소문자를 구분하지 않는(without case, ignore case 또는 case-insensitive)
문자열 비교방법을 구현하는 방법은,
물론 ANSI C에서 지원하는 stricmp() 또는 strcmpi()가 있지만,
(사실, 두 영어 문자열만 비교할 때는 이 함수가 가장 빠릅니다.)
ANSI C의 약점인 로케일 처리의 문제점이 있습니다.
우선 생각할 수 있는 방법은,
아예 대소문자 구분이 없는 문자열 클래스를 따로 만드는 것입니다.
ANSI C++의 string은
basic_string<char, char_traits<char>, allocator<char> >의
다른 이름일 뿐입니다.
그러므로 char_traits 를 다르게 정의한 문자열 클래스를 만들어서
구현하는 방법이 있습니다.
실제로 이렇게 구현한 예제는
Herb Sutter의 'Exceptional C++'의 Item 2와 3(pages 4-9)에 자세히 나와있습니다.
그러나 이러한 방법에 대해서 Matt Austern은 별로 가치가 없는 방법이라고
혹평하고 있습니다. 그 이유를 정리하면 다음과 같습니다.
1) 표준 라이브러리의 I/O 스트림 클래스를 사용하기가 불편하다.
2) 하나의 객체가 경우에 따라 달리 작동하는 방식이어야 한다. -> 경우에 따라 별도의 타입을 만드는 것은 오히려 불편함을 가중시킨다.
3) 표준 라이브러리의 방식에 자연스럽지 않다 -> char_trait은 상태가 없는 타입
4) std::search나 std::find_end 와 같은 일반 알고리듬을 사용할 수 없다.
그러므로, Austern은 표준 라이브러리의 설계 철학과 자연스럽게 맞는 방법을 제시했는데요,
이는 대소문자를 구분하지 않는 함수 객체를 사용하는 방법입니다.
예를 들어 다음과 같이 사용하는 방법이지요.
sort(string_container.begin(), string_container.end(), lt_str);
// lt는 less than의 의미입니다. 즉, operator< 의 의미(기능)을 하도록 해야 합니다.
여기서부터는 이 함수 객체를 만드는 방법에 대해 설명하겠습니다.
일단, 영어의 경우만 고려한다면 상당히 간단한 방법으로 구현할 수 있습니다.
[방법 1] stricmp()/strcmpi()를 사용한 방법(C 방식)
#include <cstring> // ANSI C 라이브러리의 string.h
struct lt_eng_str : public binary_function<string, string, bool>
{
bool operator() (const string& x, const string& y) const
{
return stricmp(x.c_str(), y.c_str()) == -1;
}
};
[방법 2] ::toupper()를 사용한 방법(C++ 방식)
struct lt_eng_str : public binary_function<string, string, bool>
{
struct lt_eng_char : public binary_function<char, char, bool>
{
bool operator() (char x, char y) const
{ // 아래 표현은 toupper((unsigned char)(x)) < toupper((unsigned char)(y)) 와 같지만
return toupper(static_cast<unsigned char>(x)) < // C++에서는 static_cast를 명시적으로
toupper(static_cast<unsigned char>(y)); // 쓰는 것이 권장 사항입니다.
}
};
bool operator() (const string& x, const string& y) const
{
return lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), lt_eng_char());
}
};
그러나 이전 글(16번)에서 썼듯이 영어가 아닌 다른 유럽어(독일어, 러시아어 등)에서는
제대로 작동하지 않는다는 단점이 있습니다.
다른 유럽어에서도 제대로 작동하도록 고쳐져야 합니다.
[방법 3] 모든 문자에 대해 ctype::toupper()로 대문자로 변환한 다음,
그 결과를 캐싱해두고 변환함.
struct lt_str : public binary_function<string, string, bool>
{
struct lt_char
{
const char *tab;
lt_char(const char *t) : tab(t) {}
bool operator() (char x, char y) const
{
return tab[x - CHAR_MIN] < tab[y - CHAR_MIN];
}
};
char tab[CHAR_MAX - CHAR_MIN + 1];
// 모든 문자를 저장하는 문자 배열입니다.
// 파스칼같으면
// tab : array[CHAR_MIN .. CHAR_MAX] of char;
// 로 쓰면 간단하지만,
// C/C++에서는 파스칼처럼 배열 첨자의 최소값을 지정할 수 없으므로,
// 이런식으로 할 수 밖에는 없겠죠. -_-a
lt_str(const locale& L = locale::classic())
{
const ctype<char>& ct = use_facet<ctype<char> >(L);
for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) // cast하기 귀찮아도 for문에서는 char보다 int가 빠릅니다.
tab[i - CHAR_MIN] = static_cast<char>(i); // (char)i 와 같은 표현
ct.toupper(tab, tab + CHAR_MAX - CHAR_MIN + 1);
}
bool operator() (const string& x, const string& y) const
{
return lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), lt_char(tab));
}
};
|