안녕하세요. 한 6개월 정도 이 핑계 저 핑계 대면서 글도 안 쓰고 게으름 피우다가 요즘에 심기일전하고 이런 저런 내용의 글을 쓰고 있습니다. 이번 내용은 모 개발자 커뮤니티에 올라온 질문에 대한 답변을 쓰면서 심심해서(!!) 정리해 본 내용입니다.
문자(character)는 문자열에 비해 직접적으로 많이 사용되는 타입은 아닙니다. 하지만 가장 기초적인 데이터 타입 중에 하나이므로 소홀히 할 수 없습니다.
Is the Hangul character?
문자 타입(character type)? 문자열이 아닌 문자 타입이라... 그런 타입도 있었나? 라고 생각하는 독자라면 지금 곧바로 마우스에서 손을 떼고 숟가락을 놔야 한다. 문자는 정수와 더불어 가장 기초 중의 기초 데이터 타입이다. 필자가 항상 강조하는 원리와 기초에 딱 맞아 떨어지는 타입 중의 하나라고 할 수 있겠다.
문자 타입은 System.Char 구조체(structure)로서 표현되며 C#에서는 char 라는 기본 타입으로도 표현된다. 일단 구조체라고 했으니 기초가 탄탄한 독자라면 문자 타입이 값 형식(value type)이라는 것을 인지 해야 한다. 즉, 힙에 할당되지 않으며 기본적으로 참조가 아닌 값의 복사에 의해 매개변수 전달이 된다든가 등등...
이런 기본 중의 기본을 이야기하고자 키보드를 잡은 것은 아니다. 매우 기본적이지만 많은 개발자가 간과할 수 있는 것은 닷넷에서 문자 타입은 무조건 16비트 크기를 갖는다는 점이다. 닷넷이 항상 유니코드(UNICODE)를 사용하기 때문이다. 이 때문에 알파벳 'A'도 한글 '가' 도 모두 16비트의 크기를 가짐에 항상 주의하자.
"나랏 말쌈이 양키에 달아, 문자와 서르 사맛디 아니할세..."
프로그래밍을 하다 보면, 주어진 문자가 한글인가 아닌가 판단해야 할 때가 종종 발생하곤 한다. 대개 문자열이 한글로 시작하는지 아닌지 판단해야 하는 경우인데... 닷넷 이전의 C++ 같은 경우, 근본이 ASCII 코드인지라 영문이냐 한글이냐에 따라서 하나의 문자가 8비트(1바이트) 혹은 16비트(2바이트)를 차지할 수 있었고, 문자셋을 어떤 것을 쓰는가에 따라서 주어진 문자가 한글이냐 영문이냐 판단하기가 대략 쥐랄 같았다.
닷넷은 자바와 더불어 문자는 항상 유니코드 문자셋을 사용한다. 따라서 항상 하나의 문자는 16비트의 길이를 갖는다. 그래서 문자열의 첫 번째 문자를 추출할 때 1바이트를 취해야 하는지 2바이트를 취해야 하는지 고민할 필요가 없다.
쓸데 없는 소리는 이젠 그만하고 본론으로 들어가 보자. 문자열이 한글로 시작하는지 영문으로 시작하는지 판단해야 한다고 하면 독자는 어떤 방법을 쓰겠는가?
필자도 문자 타입을 쉽게 생각했다. 유니코드가 라틴계의 영어 문자로서 ASCII 코드를 기반으로 하고, 0 ~ 0xFF 까지가 라틴 문자(영문자 및 기타 문자들)이므로 한글이냐 아니냐를 판단하는데 다음과 같은 간단한 코드 하나면 끝이라고 생각했다.
// 주어진 문자가 한글인가 아닌가를 반환한다.
private bool IsHangul(char c)
{
if (c < '\xff')
return false;
else
return true;
}
리스트1. 잘못된 한글 문자 판단 코드
리스트1에서 생소하게 보일 수 있는 코드에 대해 먼저 언급하자면, 문자 상수는 작은 따옴표(single quote)로 묶어 표현할 수 있고 백 슬래시(\) 문자와 더불어 유니코드 값을 명시할 수 있다는 점(쫌 어려운 말로 escape 문자)이다. 어찌 되었건 리스트1 은 주어진 문자의 코드 값이 0xff 보다 작으면 한글이 아니고 0xff 보다 크면 한글이라 판단하고 있다.
하지만 이 코드는 매우 잘못된 코드이다. euc-kr 이나 ks_c_5601 의 문자셋이라면 리스트1의 코드가 일부 유효하다고 우길 수 있을 지도 모른다. 하지만 유니코드는 euc-kr 이나 ks_c_5601 문자셋과 전혀 다르다. euc-kr 이나 ks_c_5601 문자셋과 그 코드는 영문과 한글 및 기타 한자를 표시하기 위한 문자셋이다. 그래서 영문이 아니면 한글이다라고 우길 수 있는 여지가 조금이나마 있다. 하지만 유니코드 문자셋은 한글, 영문 뿐만 아니라, 라틴계, 러시아, 인도, 왜놈들 글자, 중동의 지렁이 글자들까지 모두 포함하는 매우 포괄적인 문자셋임을 기억할 필요가 있다. 닷넷이 유니코드를 사용한다는 점을 고려해 보면 영문이 아니면 한글이라는 논리의 리스트1 코드는 전혀 설득력이 없게 된다.
"이런 전차로 어린 개발자가 조낸 삽질을 해대니..."
그렇다면 한글인가 아닌가를 판단은 어떻게 해야 하는가? 닷넷에 이런 기능을 제공하는 클래스는 없을까? 필자 또 조낸 MSDN을 뒤졌다. 솔직히 말하면 Reflector로 Char 구조체의 몇몇 메쏘드를 까본 결과 유용한 클래스를 발견했다. 이 클래스가 System.Globalization 네임스페이스의 CharUnicodeInfo 클래스이다. CharUnicodeInfo 클래스는 닷넷 프레임워크 2.0에 추가된 클래스로서 sealed 클래스이자 인스턴스를 생성할 수 없는 클래스이다. 왜 이 클래스가 정적 클래스(static class)가 아닌지 잠시 의아해 했지만 그냥 넘어가기로 하자.(정적 클래스가 뭔지 모른다면 일단 쑛 잡고 30초 정도 반성한 후 C# 2.0 언어의 새로운 기능에서 정적 클래스 찾아 보기 바란다). CharUnicodeInfo 클래스는 달랑 몇 개의 static 메쏘드만을 갖고 있으며, 이 메쏘드들은 주어진 문자가 어떤 종류인가를 반환해 주거나(GetUnicodeCategory 메쏘드) 이 문자가 숫자로 표현할 수 있다면 숫자 값이 얼마인가를 반환해 준다(GetDigitValue, GetNumericValue 등).
잠시 눈이 반짝인 필자, CharUnicodeInfo 클래스의 GetUnicodeCategory 메쏘드는 주어진 문자가 한글인가를 반환해 주지 않을까 기대를 했었다. 결론부터 말하면 아쉽게도 GetUnicodeCategory 메쏘드가 반환하는 UnicodeCategory 열거자(enumeration)는 문자가 숫자인지 괄호인지 대문자인지 소문자인지 등을 알려주지만 그 외의 것은 모두 OtherLetter 라고 반환해 버린다. 즉, 한글이든 한자든, 왜놈 문자든 모두 OtherLetter 문자라는 카테고리에 속해 있다.
이런 줴길슨... 쓰바 만드는 김에 한글인지 한자인지 이런 거까지 구분해 주면 좀 좋아? 그런데 이게 유니코드 표준 3.1을 기반으로 했다니 별 할말도 없다. 이젠 닷넷 프레임워크의 클래스들에 의존할 수 없다.
"내 이를 어엿비 여겨, 코드 쪼가리를 맹그노니..."
그래서 유니코드를 정의하고 있는 http://www.unicode.org 사이트에 달려갔다. 어차피 유니코드란 것이 0x0 부터 0xFFFF 까지를 여러 문자에 대응시켜 놓은 문자셋이므로 한글 문자들이 해당하는 영역이 있을 것이다. 그렇다면 주어진 문자 코드가 한글 영역에 해당한다면 그 문자는 한글이고 그렇지 않다면 한글이 아닐 터...
세상은 만만한 게 하나도 없다. 쓰봉... 유니코드 상에서 한글 코드는 연속적인 영역을 차지하지 않았었고, 한글 초성, 중성, 종성에 대한 판단도 필요했다. 게다가 한글 고어 문자(아래 아 등등)에 대한 판단도 해야 하고, 괄호로 싸인 문자(㈀)나 원 문자(㉠) 역시 한글로 판단해야 할 수도 있었다. 하지만 괄호 문자, 원 문자는 프로그램에 따라 한글로 판단하지 말아야 할 수 있으므로 코드에서 제외했다. 상세한 판단 기준은 http://www.unicode.org 에서 한글 문자에 관련된 유니코드 영역을 살펴보아야 한다. 일단 코드를 보자.
public enum HangulCategory
{
한글아님,
한글완성,
한글자모,
한글초성,
한글중성,
한글종성,
고어자모
}
// UNICODE의 문자 카테고리에 의해 주어진 문자가
// 한글의 어떤 카테고리에 속하는지 반환한다.
private HangulCategory GetHangulCategory(char c)
{
if (c >= '\xAC00' && c <= '\xD7AF') {
return HangulCategory.한글완성;
}
if (c >= '\x3130' && c <= '\x318F') {
return HangulCategory.한글자모;
}
return HangulCategory.한글아님;
}
// 주어진 문자열의 특정 위치의 문자가 한글 카테고리의
// 어디에 속하는지 반환한다.
private HangulCategory GetHangulCategory(string s, int index)
{
return GetHangulCategory(s[index]);
}
// 주어진 문자가 한글인가 아닌가를 반환한다.
// (원 문자, 괄호 문자는 한글이 아닌 것으로 판단한다)
private bool IsHangul(char c)
{
return GetHangulCategory(c) != HangulCategory.한글아님;
}
리스트2. 주어진 문자가 한글인가를 반환하는 코드 조각
리스트2에서 GetHangulCategory 메쏘드는 주어진 문자가 유니코드의 한글 영역에 속하는지 판단한다. 유니코드 0xAC00 부터 0xD7AF 에 해당하는 문자가 완성된 한글을 나타내며, 0x3130 부터 0x318F 에 해당하는 문자는 한글 자모 글자(받침 포함) 영역에 해당한다. 이 외에도 유니코드는 한글 초성, 중성, 종성, 고어 등을 나타낼 수 있는 코드 영역이 있다. 필요하다면 영역을 찾아보고 GetHangulCategory 메쏘드에 살포시 if 문 하나를 낑겨 넣자.
참고로 리스트2의 코드는 빡세게 테스트한 코드가 아니다. 고로 위 코드를 인용하여 사용하고자 하는 독자가 있다면 스스로 테스트를 빡세게 해 보아야 할 것이며, 코드에 문제가 있는 경우 득달같이 이곳에 달려와 필자에게 꼰질러 주기 바란다.
혹은, 위 코드와 달리 닷넷 프레임워크에 이미 한글 문자 판단을 위한 메쏘드를 발견하였거나, 위 코드 보다 더 좋은 방법이 있어도 필자에게 꼰질러 주기 바란다. (혼자만 먹고 살려고 하지 말고 다 같이 나눠 먹자 쫌... -_-;)
"개발자마다 수비 니겨 날로 쑤메 뼌한킈 하고져 할 따라미니라"
위 코드를 이용하여 간단한 테스트 프로그램을 짜 봤다.

화면1. 예제 프로그램
이 프로그램은 입력된 문자가 CharUnicodeInfo 클래스의 GetUnicodeCategory 메쏘드가 어떤 값을 반환하는가 먼저 보여주고, 리스트2의 GetHangulCategory 메쏘드를 사용하여 한글인 경우 그 종류까지를 보여주는 프로그램이다. 화면1에서 대문자, 소문자, 숫자, 기호, 괄호, 한글 등을 포함하는 문자에 대해 각각의 종류를 보여주고 있다.
위 프로그램의 예제 코드는 물론 없다. 닭이 아닌 이상 이 정도의 정보면 MSDN 디비 보고 짱구 조금만 돌리면 위와 같은 프로그램은 어렵지 않게 작성할 수 있다. 한 두 줄이라도 본인이 직접 코드를 작성해 봐야 그 지식이 나의 것이 된다는 지론에 빠져있는 필자를 설득하려 들지 말지어다.
아놔... 뭐하셈? 얼른 비주얼 스튜디오 띄우고 코드 짜 봐야지 !!!