<

프로그래밍을 하다 보면 코드의 일부 내용을 반복적으로 바꿔줘야할 때가 있다.


오래된 프로젝트를 리팩토링한다거나 프로젝트 문자셋을 멀티바이트에서 유니코드로 바꿀 때가 그 대표적인 예다.


물론 일일이 컴파일 오류나는 부분을 찾아서 바꿔주거나 전체 찾기해서 바꿔주는 원초적인 방법이 있다.


보다 효율적이고 능률적인 방법을 소개하겠다.


바로 정규표현식(aka. 정규식)을 사용한 찾기 및 바꾸기이다.


C++에서는 정규식이 조금 낯설수 있는데, 간략하게 설명하자면 정규표현식이란, 문자열에서 일정한 규칙(Pattern)을 갖는 문자열 집합(Group)을 찾아내는 것이다.


필자는 멀티바이트로 작성된 프로젝트를 유니코드로 바꾸는 과정에서 수많은 문자열 메시지 부분을 유니코드에 맞게 바꾸줘야하는 작업을 위 정규식을 이용해 작업했다.


찾기 바꾸기 대화상자(단축키:Ctrl+Shift+F)에서 찾기옵션에 정규식 사용에 체크를 해야한다.



MFC의 대표적인 문자열 타입인 Cstring의 경우 멀티바이트 인 경우 큰따옴표("")로 문자열을 표시했지만,


유니코드로 바꾸려면 _T 매크로를 넣어주거나 L를 앞에 넣어줘야한다.


물론 _T 매크로가 삽입되어 있다면, 매크로에서 자동으로 L로 변환이 된다.


(멀티바이트든, 유니코드는 _T 매크로를 넣어주는 버릇을 갖다.)


_T매크로는 _T(로 시작해서 괄호) 로 끝나기 때문에 그냥 찾아 바꾸기만 해서는 안된다.


다음은 내가 가장 많이 쓰는 정규식이다.



1. Cstring의 Format 멤버함수

예시:


찾을 대상

strTitle.Format("%s: ABCDEFG123456가나다라", this->getTitle());


바꿀 문자열

strTitle.Format(_T("%s: ABCDEFG123456가나다라"), this->getTitle());

Format 멤버함수의 문자열을 감쏴고 있는 큰따옴표 앞에 _T 매크로를 넣어줘야한다.


정규표현식:


찾을 문자열:

.Format\(\"(.*)\"


바꿀 문자열

.Format(_T("$1")

위에서 보다시피 정규식을 찾을때에는 문자열에 포함되는 괄호, 따옴표에 대해서는 백슬래시(\)로 구분을 해줘야한다.

그리고 바꿀 문자열에 대해서는 백슬래시가 필요없으며 그룹 문자열($1,$2,$3...) 등을 사용하면 된다.



2. Afxmessage 메시지 팝업

예시:

찾을 대상

AfxMessageBox("입력값 초과");


바꿀 문자열

AfxMessageBox(_T("입력값 초과"));


정규표현식:


찾을 문자열:

AfxMessageBox\(\"(.*)\"


바꿀 문자열

AfxMessageBox(_T("$1")


Worker 쓰레드나 UI쓰레드에서 UI 객체를 바로 접근하는것은 매우 위험하다.


컴파일러에 따라서 오류를 발생시키는 경우도 있지만 


컴파일러가 이를 못잡아준 경우에는 해당 쓰레드가 실행되면서 UI접근시 Assertion fail 오류가 발생하면서 죽게된다.


쓰레드에서 UI를 직접 접근하는 방법은 몇가지가 있으나 불편하고, 안정적이지 못하다.


가장 안전하면서 API 프로그래밍 특성인 메시지를 이용하는 방법을 추천한다.


사용자 메시지를 만들어서 PostMessage를 통해 넘겨서 UI를 접근할 수 있다.


물론 Param에 인자값 까지 넘겨서 사용할 수 있다.


::PostMessage(*phObjectHandle, WM_UPDATE_UI, 0, 0);



관련 내용 : http://forums.codeguru.com/showthread.php?312454-MFC-Thread-How-to-access-UI-elements-from-a-thread-in-MFC

C++에서 XML 문서를 파싱하거나 생성해야할 때가 있다.


물론 DOM형식이지만 텍스트 파일에서 보이므로 하나씩 토크나이징하면서 하나하나 파싱하는 방법도 있겠지만


필요이상의 작업이 들어가고 속도도 느리다.


따라서 Paser를 이용하여 쉽고 빠르게 문서 내용을 파싱해야한다.


C++에서는 기본적으로 제공하는 클래스가 없다. ㅠㅠ (C#이 왜 강려크한지 그 이유..)


오픈소스를 찾아보니 CMarkup이라는 클래스가 있다.


사이트에 들어가 다운받으면 아래와 같은 두 파일이 보인다.


Markup.cpp 

Markup.h

위 두 파일을 프로젝트에 추가시키고


 #include "Markup.h"



CMarkup xml;

bool bSuccess = xml.Load( "C:\\Temp\\hello.xml" ); 

이런식으로 CMarkup 클래스 생성해서 사용하면 된다.


홈페이지에 상세 API가 잘 되어 있으니 참고해서 활용하면 될듯하다.


STL에 포함되어 있는 다양한 컨테이너(Vector, Map, List etc...)들은 Iterator를 기본 제공한다.


Iterator를 쉽게 설명하자면 컨테이너의 각각의 Index를 탐색 or 순환 할 수 있는 일종의 포인터 개념이다.


STL의 컨테이너에 Iterator를 붙여서 사용하면 일반 배열 탐색할때 인덱스를 반복문으로 훓으면서 쓰던것처럼 할 수 있다.


근데 문제는 이 Iterator를 쓰기 좀 번거롭다.


for (std::map<std::string, bool>::iterator iter = m_mapItemFlag.begin(); iter != m_mapItemFlag.end(); iter++)
{
	(*iter).second = bCheckFlag;
}

위의 코드처럼 Map이든 Vector든 내부의 각 원소를 전부 탐색해서 원소를 초기화 하거나 조건식을 추가하여 조건에 맞는 원소들의 값을 이용하는 것으로 많이 사용한다.



기존의 배열을 반복문과 함께 이용하는 것과 매우 흡사하다.



위처럼 Iterator를 컨테이너의 처음부터 끝까지 탐색할 경우 아래와 같은 방법으로 하면 쉽게 해결된다.


for (auto item : m_mapItemFlag)
{
	item.second = bCheckFlag;
}

C#보다 C++에 익숙한 사람은 auto 키워드에 낯설수도 있다. 나도 그랬다.


C#의 var 키워드 처럼 뒤에 대입받는 자료형에 따라 자동으로 해당 변수의 자료형을 선택해주는 아주 편한 키워드이다. 


하지만 남용하면 가독성이 떨어지므로 위와 같이 Range-based. 범위기반 반복문에서나 사용하자.



요약하자면 STL 컨테이너든, 일반 배열이든, 전체를 순차적으로 탐색할 때에는 위와 같은 범위기반 반복문을 사용하면 훨씬 편하다는 거다.


복잡하게 Iterator 생성해서 형식 맞춰주고 할 필요가 없다. 






윈도우 API에서 제공하는 RECT는 구조체이다. 멤버로는 left, top, right, bottom 밖에 없다.

단순 사각형을 그리거나 위치 계산을 위한 구조체로 이용해 왔다.


RECT구조체와 비슷하게 MFC에서 제공하는 CRect 클래스가 있다.


RECT와 CRect 둘다 마찬가지로 사각형 영역의 위치를 처리하기 위한 타입이다.


CRect에 좀 더 다양하고 편리한 함수가 제공된다. 


예를 들어 위 두 사각형이 겹치는 영역을 구할때 사용하면 편리한 함수가 바로 IntersectRect이다.

CRect overlapRect = CRect(0, 0, 0, 0);
overlapRect.IntersectRect(rect1, rect2);
if (overlapRect.IsRectEmpty() || overlapRect.IsRectNull())
	//Do not overlap
 

중첩된 영역을 지정할 overlapRect를 선언후 IntersectRect 함수를 통해 rect1과 rect2를 피라메터로 넘겨주면


겹치는 영역이 overlapRect에 저장된다.


이후 IsRectEmpty() 나 .IsRectNull()로 겹치지 않았을 때 처리를 해주고 겹쳐진 영역을 이용하면 된다.




심심해서 로또 번호 생성 코드를 짜봤다.


일단 로또번호는 1부터 45까지의 수에서 중복없이 6개로 이뤄진다.


그래서 rand함수로 번호를 뽑은다음, 중복값인지 체크하는 루틴도 있어야 한다.


배열 이용하는 방법이 가장 쉽고 간편하지만, 여기서는 STL 리스트를 이용해 본다.


#define LOTTONUM 6

	std::list listNum;
	srand((unsigned)time(NULL));
	int nNum = 0;
	listNum.clear();
	for (int i = 0; i < LOTTONUM; i++)
	{
		nNum = rand() % 45 + 1;

		if (std::find(listNum.begin(), listNum.end(), nNum) == listNum.end())
			listNum.push_back(nNum);
	}


+ Recent posts