전문가를 위한 C++/ 스트링 현지화와 정규표현식

(전체가 아니라 C#과 차이가 있는 부분을 중심으로 요약 정리)

현지화

C나 C++ 프로그래밍을 배울 때 각각의 문자를 아스키(ASCII) 코드로 표현하는 바이트로 취급했다. 아스크 코드는 7비트로 구성됐으며 주로 8비트 char 타입으로 표현한다. 

정말 좋은 프로그램은 전 세계적으로 사용되지만, 당장 전 세계 사용자를 대상으로 삼지 않더라도 나중에 현지와를 지원하거나 다양한 로케일을 인식할 수 있도록 디자인하는 것이 좋다.

스트링 리터럴 현지화하기

현지화에서 가장 중요한 원칙 중 하나는 소스 코드에 특정 언어로 된 스트링을 절대로 넣으면 안 된다는 것이다. 단 개발 과정에서 임시로 사용하는 디버그 스트링은 예외다.

MS는 윈도우 애플리케이션을 개발하는 환경에서는 현지화에 관련된 스트링을 STRINGTABLE이란 리소스로 따로 빼둔다. 다른 플랫폼도 이러한 장치가 마련돼 있다. 그래서 애플리케이션을 다른 언어에 맞게 변환할 때 다른 코드를 건드릴 필요 없이 이 리소스에 담긴 내용만 번역하면 된다. 그리고 이러한 번역 작업을 도와주는 도구도 많이 나와 있다.

현지화 할 수 있도록 소스 코드를 구성하려면 스트링 리터럴을 조합하는 문장으로 만들면 안된다. 설령 각 스트링을 현지화할 수 있더라도 말이다. 예컨대 다음과 같다.

cout << "Read " << n << " bytes" << endl;

문장을 이렇게 구성하면 네덜란드어처럼 어순이 전혀 다른 언어로 현지화하기 힘들다. 이 스트링을 제대로 현지화할 수 있게 구성하려면 다음과 같이 작성한다.

cout << Format(IDS_TRANSFERRED, n) << endl;

여기서 IDS_TRANSFERRED는 스트링 리소스 테이블에 담긴 항목 중 하나다. 

와이드 문자

모든 언어가 한 문자를 1바이트에 담을 수 있는 것은 아니다. C++은 wchar_t라는 와이드 문자(확장 문자) 타입을 기본으로 제공한다. 한국어나 아랍어처럼 아스키 문자를 사용하지 않는 언어는 C++에서 wchar_t 타입으로 표현하면 된다.

하지만 C++ 표준은 wchar_t의 크기를 명확히 정의하지 않고 있다. 어떤 컴파일러는 16비트를 사용하는 반면 다른 컴파일러는 32비트로 처리하기도 하낟. 그래서 크로스 플랫폼을 지원하도록 코드를 작성하려면 wchar_t의 크기가 일정하다고 가정하면 위험하다.

영어권이 아닌 사용자를 대상으로 하는 프로그램을 작성한다면 처음부터 wchar_t 타입으로 작성하는 것이 좋다. 스트링이나 문자 리터럴을 wchar_t 타입으로 지정하려면 리터럴 앞에 L을 붙이면 된다. 그러면 와이드 문자 인코딩을 적용한다. 예컨대 wchar_t 타입의 값을 m이란 문자로 초기화하려면 다음과 같이 작성한다.

wchar_t myWideCharacter = L'm';

흔히 사용하는 타입이나 클래스마다 와이드 문자 버전이 존재한다. string 클래스의 와이드 문자 버전은 wstring이다. 스트림에 대해서도 w라는 접두어를 이용한 명명 규칙이 적용된다. 예컨대 와이드 문자 버전의 파일 출력 스트림으로 wofstream이 있고 입력 스트립은 wifstream이다. 또한 현지화를 지원하도록 스트링이나 스트림을 w가 붙은 버전으로 대체해서 코드를 작성하는 작업도 나름 재미가 쏠쏠하다.

cout, cin, cerr, clog도 각각 wcout, wcin, wcerr, wclog라는 와이드 문자 버전이 있다. 사용법은 일반 버전과 같다.

서구권이 아닌 문자 집합

와이드 문자를 도입한 것만으로도 현지화에 큰 도움이 된다. 한 문자에 필요한 공간의 크기를 원하는 대로 정할 수 있기 때문이다. 문자가 차지하는 공간을 결정했다면 아스키 코드처럼 각 문자를 코드 포인트(code point)라 부르는 숫자 형태로 표현할 수 있다.

이렇게 직접 정의한 문자 집합이 아스키 코드와 다른 점은 8비트로 제한되지 않는다는 것뿐이다. 이때 프로그래머의 모국어를 포함한 다른 언어를 포괄해야 하기 때문에 문자와 코드 포인트 사이의 대응 관계는 얼마든지 달라질 수 있다.

유니버설 문자 집합(Universal Character Set, UCS)은 ISO 10646이라는 국제 표준이고, 유니코드도 국제 표준 문자 집합이다. 둘 다 십만 개 이상의 문자를 담고 있으며, 각 문자마다 고유 이름과 코드 포인트가 정해져 있다. 두 표준에서 서로 겹치는 문자와 코드 포인트도 있고, 두 표준 모두 인코딩(encoding) 방식을 따로 정해두고 있다. 

예컨대 유니코드는 한 문자를 1-4개의 8비트로 인코딩하는 UTF-8 방식, 한 문자를 1-2개의 16비트값으로 인코딩하는 UTF-16 방식, 유니코드 문자를 정확히 32비트로 인코딩하는 UTF-32 방식이 있다.

애플리케이션마다 사용하는 인코딩 방식이 얼마든지 다를 수 있다. 아쉽게도 C++ 표준은 와이드 문자의 크기를 명확히 정해두지 않았다. 윈도우 환경에서는 16비트인 반면 32비트로 표현하는 플랫폼도 있다. 따라서 와이드 문자로 인코딩하는 프로그램에서 크로스 플랫폼을 지원하게 하려면 이러한 점을 반드시 인지해야 한다. 참고로 C++에서 제공하는 char16_t와 char32_t를 이용하면 이런 문제에 대처하는데 도움이 된다. 현재 지원하는 문자 타입을 정리하면 다음과 같다.

  • char: 8비트 값을 담는다. 주로 아스키 문자나 한 문자를 1-4개의 char로 인코딩하는 UTF-8 방식의 유니코드 문자를 저장하는데 사용된다.
  • char16_t: 최소 16비트 값을 담는다. 이 타입은 문자 하나를 1-2개의 char16_t로 인코딩하는 UTF-16 방식의 유니코드 문자를 저장하는 기본 타입으로 사용된다.
  • char32_t: 최소 32비트 값을 담는다. 이 타입은 UTF-32로 인코딩된 유니코드 문자를 하나의 char32_t로 저장하는데 사용된다.
  • wchar_t: 와이드 문자를 표현하는 타입으로 구체적인 크기와 인코딩 방식은 컴파일러마다 다르다.

wchar_t에 비해 char16_t나 char32_t가 좋은 점은 문자가 차지하는 크기를 컴파일러에 관계 없이 항상 최소 16비트 또는 32비트로 보장할 수 있다는 것이다. 반면 wchar_t에는 이러한 최소 기준이 없다.

C++ 표준은 다음과 같은 매크로도 정의하고 있다.

  • __STDC_UTF_32__: 컴파일러에서 지원한다면 char32_t는 UTF-32 인코딩을 적용하고, 그렇지 않으면 char32_t에 적용되는 인코딩 방식은 컴파일러에 따라 달라진다.
  • __STDC_UTF_16__: 컴파일러에서 지원한다면 char16_t는 UTF-16 인코딩을 적용하고, 그렇지 않으면 char16_t에 적용되는 인코딩 방식은 컴파일러에 따라 달라진다.

스트링 리터럴 앞에 특정한 접두어를 붙여서 타입을 지정할 수 있다. C++에서 제공하는 스트링 접두어는 다음과 같다.

  • u8: UTF-8 인코딩을 적용한 char 스트링
  • u: char16_t 스트링 리터럴을 표현하며, 컴파일러에 __STDC_UTF_16__이 정의돼 있으면 UTF-16을 적용한다.
  • U: char32_t 스트링 리터럴을 표현하며, 컴파일러에 __STDC_UTF_32__이 정의돼 있으면 UTF-32을 적용한다.
  • L: wchar_t 스트링 리터럴을 표현하며, 인코딩 방식은 컴파일러마다 다르다.

이러한 스트링 리터럴은 모두 일반 스트링 리터럴 접두어인 R과 조합할 수 있다. 예컨대 다음과 같다.

const char* s1 = u8R"Raw UTF-8 encoded string literal)";
const wchar_t* s2 = LR"Raw wide string literal)";
const char16_t* s3 = uR"Raw char16_t string literal)";
const char32_t* s4 = UR"Raw char32_t string literal)";

만일 스트링 리터럴을 UTF-8 유니코드 인코딩 방식(u8)으로 표현하거나 현재 컴파일러에 __STDC_UTF_16__이나 __STDC_UTF_32__가 정의돼 있다면 \uABCD 표기법을 사용해서 코드 포인트를 표현하는 방식으로 원하는 유니코드 문자를 스트링 리터럴에 추가할 수 있다.

예컨대 \u03C0은 파이(π)를 표현하고 \u00B2는 제곱(2)을 표현한다. 그래서 πr2 이란 수식을 다음과 같이 표현할 수 있다.

const char* formula = u8"\u03C0 r\u00B2";

마찬가지로 문자 리터럴 앞에도 이러한 접두어를 붙여서 타입을 구체적으로 지정하 룻 있다. C++은 문자 리터럴에 대해 u, U, L 뿐만 아니라 C++ 17부터 u8 접두어도 지원한다. 예컨대 다음과 같다.

u'a', U'a', L'a', u8'a'

C++은 std::string 클래스 외에 wstring, u16string, u32string도 제공한다. 각각 다음과 같이 정의돼 있다.

  • using string = basic_string<char>;
  • using wstring = basic_string<wchar_t>;
  • using u16string = basic_string<char16_t>;
  • using u32string = basic_string<char32_t>;

멀티바이트 문자(multibyte character)란 여러 바이트로 구성된 문자로서 인코딩 방식은 컴파일러마다 다를 수 있다. 마치 유니코드를 UTF-8을 사용하여 1-4개의 8비트로 표현하거나, UTF-16을 사용하여 1-2개의 16비트 값으로 표현하는 방식과 비슷하다. char16_t/char32_t와 멀티바이트 문자를 서로 변환하는 mbrtoc16, c16rtomb, mbrtoc32, c32rtomb 등의 변환 함수도 제공된다.

하지만 아쉽게도 char16_t와 char32_t에 대한 지원은 여기까지다. 다음 절에 소개하는 변환 클래스가 몇 가지 더 있지만 cout이나 cin에 대해 char16_t나 char32_t를 지원하는 버전은 없다. 그래서 char16_t나 char32_t 타입으로 된 스트링을 콘솔에 출력하거나 사용자로부터 입력 받기 상당히 까다롭다.

char16_t나 char32_t에 대한 고급 기능을 원한다면 서드파티 라이브러리를 찾아보는 수밖에 없다. 참고로 유니코드와 세계화(globalization)를 지원하는 대표적인 라이브러리로 ICU(International Components for Unicode)가 있다.

변환

C++ 표준에서는 다양한 방식으로 인코딩된 문자를 쉽게 변환하도록 codevt라는 클래스 템플릿을 제공한다. <locale> 헤더 파일을 보면 다음과 같은 네 가지 인코딩 변한 클래스가 정의돼 있다.

클래스 설명
codecvt<char, char, mbstate_t> 항등 변환. 즉 같은 것끼리 변환해서 실질적으로 변환이 일어나지 않는다.
codecvt<char16_t, char, mbstate_t> UTF-16과 UTF-8을 변환한다.
codecvt<char32_t, char, mbstate_t> UTF-32와 UTF-8을 변환한다.
codecvt<wchar_t, char, mbstate_t> (구현마다 달리 정의한) 와이드 문자와 내로우 문자 인코딩을 변환한다.

C++ 17 이전 버전에서는 <codecvt> 헤더 파일에 codecvt_utf8, codecvt_utf16, codecvt_utf8_utf16이라는 세 가지 코드 변환 패싯(facet)이 정의돼 있었다. 이러한 패싯은 두 가지 변환 인터페이스(wstring_convert와 wbuffer_convert)와 함께 사용했다.

그런데 C++ 17부터 이러한 세 가지 변환 패싯을 비롯하여 <codecvt>라는 헤더 파일 전체와 두 가지 변환 인터페이스가 폐기 됐다. 따라서 이 책에서는 자세히 설명하지 않는다. C++ 표준 위원회는 이러한 것들이 에러 처리에 불리하다는 이유로 폐기하도록 결정했다. 잘못된 유니코드 스트링으로 인해 보안 위험이 발생할 수 있으며, 실제로 시스템 보안 침투를 위한 공격 벡터로 활용된 사례도 있었기 때문이다.

또한 API 마저도 복잡하고 이해하기 힘들게 구성돼 있다. 따라서 C++ 표준 위원회에서 보다 적합하고 안전하고 사용하기 쉬운 대안을 마련할 때까지는 ICU와 같은 서드파티 라이브러리를 사용하길 추천한다.

로케일과 패싯

문자 집합은 나라마다 데이터를 표현하는 방식이 다른 여러 가지 요소 중 한 예에 불과하다. 영국과 미국처럼 사용하는 문자가 비슷한 나라마저도 날짜와 화폐를 표현하는 방식이 다르다.

이러한 특정한 데이터를 문화적 배경에 따라 그룹으로 묶는 방식을 C++에서는 로케일이라 부른다. 로케일은 날짜 포맷, 시간 포맷, 숫자 포맷 등으로 구성되는데, 이러한 요소를 패싯이라 부른다. 

로케일의 예로 U.S. English가 있고, 패싯의 예로 날짜 포맷이 있다. C++는 그 밖에 다양한 패싯을 기본으로 제공할 뿐만 아니라 이를 커스터마이즈하거나 새로운 패싯을 추가하는 기능도 제공한다.

로케일을 쉽게 다루게 해주는 서드파티 라이브러리도 많이 나와 이싿. 대표적인 예가 boost.locale이다. 이 라이브러리는 ICU를 기반으로 구축한 것으로 대조(collation)와 변환(conversion)을 지원하고, 스트링 전체를 한 번에 대문자로 변환하는 기능도 제공한다.

로케일 사용법

I/O 스트림을 사용할 때는 데이터의 포맷을 특정한 로케일에 맞춘다. 로케일은 스트림에 붙일 수 있는 객체로서 <locale> 헤더 파일에 정의돼 있다. 로케일 이름은 구현마다 다르다. POSIX 표준에서는 언어와 지역을 두 글자로 표현하고 그 뒤에 옵션으로 인코딩 방식을 붙여서 표현한다. 

예컨대 영어권 중에서 미국에 대한 로케일은 en_US로 표현하고, 영국에 대해서는 en_GB로 표기한다. 한국어는 ko_KR에 유닉스 확장 완성형 인코딩은 euc_KR로 UTF-8은 utf8을 옵션으로 붙여서 표기한다.

윈도우 환경에서는 두 가지 포맷으로 로케일 이름을 표현한다. 바람직한 첫 번째 방식은 POSIX 포맷과 비슷한데, 언더스코어 대신 대시를 사용한다는 점이 다르다. 두 번째 포맷은 예전 방식으로 다음과 같이 표현한다.

lang[_country_region[.code_page]]

대괄호 사이에 나온 부분은 모두 옵션이다. 몇 가지 예를 제시하면 다음 표와 같다.

  POSIX 윈도우 예전 윈도우
미국식 영어 en_US en-US English_United States
영국식 영어 en_GB en-GB English_Grate Britain

대부분의 OS는 사용자가 로케일을 지정하는 메커니즘을 제공한다. C++에서는 std::locale의 객체 생성자에 공백 스트링을 인수로 전달하면 사용자 환경에 저으이된 locale로 생성한다.

이 객체를 생성한 뒤부터는 locale 정보를 조회할 수 있고, 그 결과에 따라 코드를 작성할 수 있다. 다음 코드는 스트림의 imbue() 메서드를 이용하여 사용자 환경에 지정된 로케일을 사용하도록 설정하는 예를 보여준다. 이 코드를 실행하면 wcout에 보낸 데이터가 모두 시스템에 설정된 포맷으로 표현된다.

wcout.imbue(locale(""));
wcout << 32767 << endl;

이렇게 하면 현재 시스템에 설정된 로케일이 미국식 영어일 때 32,767로 출력되고 네덜란드 벨기에로 지정돼 있다면 32.767로 출력된다.

디폴트 로케일은 사용자 로케일이 아니라 클래식/뉴트럴(classic/neutral) 로케일이다. 클래식 로케일은 ANSI C 관례를 따르며 C로 표기한다. 이러한 클래식 로케일 C는 미국식 영어에 대한 로케일과 거의 같지만 약간 차이가 있다. 예컨대 숫자에 구두점을 붙이지 않는다.

wcout.imbue(locale("C"));
wcout << 32767 << endl;

이 코드를 실행하면 구두점이 없는 숫자가 출력된다.

다음 코드는 미국식 영어에 대한 로케일을 명시적으로 지정했다. 그래서 32767이란 숫자가 시스템 로케일과 별개로 미국식 표기법을 적용하여 쉼표를 붙여서 출력된다.

wcout.imbue(locale("en-US"));
wcout << 32767 << endl;

locale 객체로부터 로케일 정보를 조회할 수 있다. 예컨대 다음 코드는 사용자 시스템에 설정된 로케일에 대한 locale 객체를 생성한다. name() 메서드를 이용하면 이 로케일을 표현하는 C++ string을 구할 수 있다. 이렇게 구한 string 객체에 대해 find() 메서드를 호출하면 인수로 지정한 서브스트링을 검색할 수 있다. 지정한 서브스트링을 찾지 못하면 string::npos를 리턴한다. 이 코드는 윈도우 이름과 POSIX 이름을 모두 검사한다. 현재 로케일이 미국 영어인지 여부에 따라 둘 중 한 메시지가 출력된다.

locale loc("");

if (loc.name().find("en_US") == string::npos && loc.name().find("en-US") == string::npos)
{
wcout << L"Welcome non-U.S. English speaker!" << endl;
}
else
{
wcout << L"Welcome U.S. English speaker!" << endl;
}

Note) 나중에 프로그램에서 다시 읽을 데이터를 파일에 쓸 때는 클래식 로케일 “C”를 지정하는 것이 좋다. 그렇지 않으면 파싱하기 힘들어진다. 반면 사용자 인터페이스에 데이터를 출력할 때는 사용자 로케일 ” “를 지정하는 것이 좋다.

문자 분류

<locale> 헤더 파일을 보면 std::isspace(), isblank(), iscntrl(), isupper(), islower(), isalpha(), isdigit(), ispunct(), isxdigit(), isalnum(), isprint(), isgraph() 등과 같은 문자 분류 함수가 정의돼 있다. 이들 함수는 두 개의 매개변수를 받는다. 사용자 환경에 대한 로케일을 적용해서 isupper()을 호출하는 예는 다음과 같다.

bool result = isupper('A', locale(""));

문자 변환

<locale> 헤더에는 std::toupper()와 std::tolower()라는 문자 변환 함수도 정의돼 있다. 이들 함수는 두 개의 매개변수(변환할 문자와 변환에 적용할 로케일)을 받는다.

패싯 사용법

특정한 로케일에서 패싯을 구하려면 std::use_facet() 함수를 호출하면 된다. 이때 use_facet()의 인수로 locale을 지정한다. 예컨대 다음 문장은 POSIX 로케일 이름을 사용하여 영국식 영어 로케일을 지정했을 때 화폐 금액에 대한 표준 구두법을 조회한다.

use_facet<moneypunct<wchar_t>>(locale("en_GB"));

이 문장에서 가장 안쪽에 있는 템플릿 타입은 사용할 문자의 타입을 지정한다. 주로 wchar_t나 char를 사용한다. 템플릿 클래스가 중첩돼서 좀 복잡하지만 영국식 화폐 금액 구두법에 대한 모든 정보를 담은 객체를 구할 수 있다. 표준 패싯에서 제공하는 데이터는 모두 <locale> 헤더와 관련 파일에 정의돼 있다.

다음 표는 C++ 표준에서 정한 패싯의 범주를 보여준다. 각 패싯에 대한 자세한 사항은 표준 라이브러리 레퍼런스를 참고하기 바란다.

패싯 설명
ctype 문자 분류 패싯
codecvt 패싯 변환
collate 스트링을 사전 순으로 비교한다.
time_get 날짜와 시간을 파싱한다.
time_put 날짜와 시간을 포매팅한다.
num_get 숫자값을 파싱한다.
num_put 숫자값을 포매팅한다.
numpunct 숫자값에 대한 포매팅 매개변수를 정의한다.
money_get 화폐금액을 파싱한다.
money_put  화폐금액을 포매팅한다.
moneypunct 화폐 금액에 대한 포매팅 매개변수를 정의한다. 

(코드 예시 생략)

정규표현식

정규 표현식은 표준 라이브러리에서 제공하는 강력한 기능 중 하나로 <regex> 헤더 파일에 정의돼 있다. 이는 스트링 처리에 특화된 작은 언어라고 볼 수 있는데, 처음 보면 상당히 복잡해 보이지만 익숙해지면 스트링을 한결 간편하게 다룰 수 있다. 정규 표현식은 다음과 같이 다양한 스트링 연산에 활용된다.

  • 검증: 입력 스트링이 형식을 제대로 갖췄는지 확인한다.
  • 판단: 주어진 입력이 표현하는 대상을 확인한다.
  • 파싱: 입력 스트링에서 정보를 추출한다.
  • 변환: 주어진 서브스트링을 검색해서 다른 스트링으로 교체한다.
  • 반복: 주어진 서브스트링이 나온 부분을 모두 찾는다.
  • 토큰화: 정해진 구분자를 기준으로 스트링을 나눈다.

주어진 스트링에 대해 위의 연산을 직접 구현해도 되지만 정규 표현식을 활용하는 방식을 추천한다. 스트링을 처리하는 코드를 정확하고 안전하게 작성하기 쉽지 않을 수 있기 때문이다.

정규 표현식에 대해 본격적으로 설명하기 전에 몇 가지 용어를 알아둘 필요가 있다.

  • 패턴(pattern): 정규표현식은 스트링을 표현하는 패턴이다.
  • 매치(match): 주어진 정규표현식과 주어진 범위 [first, last) 안에 있는 모든 문자 사이에 일치하는 부분이 있는지 검사한다.
  • 탐색(search): 주어진 범위 [first, last) 안에 주어진 정규 표현식에 매치되는 서브스트링이 있는지 검사한다.
  • 치환(replace, 교체): 주어진 범위에서 서브스트링을 찾아서 치환 패턴(substitution pattern)에 일치하는 서브스트링으로 교체한다.

정규표현식에 대한 문법이 다양한데, C++은 다음과 같이 여러 문법을 제공한다.

  • ECMAScript: ECMAScript 표준 기반의 문법이다. ECMAScript는 ECMA-262 규격으로 표준화된 스크립트 언어다. JavaScript, ActionScript, JScript 등은 모두 ECMAScript 표준을 기반으로 만든 언어다.
  • basic: 기본 POSIX 문법
  • extended: 확장된 POSIX 문법
  • awk: POSIX awk 유틸리티에서 사용하는 문법
  • grep: POSIX grep 유틸리티에서 사용하는 문법
  • egrep: POSIX grep 유틸리티에서 -E 매개변수를 지정했을 때 사용하는 문법

위에서 소개한 문법 중에서 잘 알고 있는 것이 있다면 정규표현식 라이브러리의 syntax_option_type 옵션에 원하는 문법을 지정해서 정규표현식을 작성하면 된다. C++에서는 ECMAScript를 정규표현식의 기본 문법으로 사용한다.

ECMAScript 문법

정규표현식 패턴은 매치하려는 문자들을 표현한 것이다. 정규표현식에서 다음과 같은 특수 문자를 제외한 나머지 문자는 표현식에 명시된 그대로 매치한다.

^ $ \ . * + ? ( ) [ ] { } |

특수 문자에 대해 매치할 때는 그 문자 앞에 역슬래시(\)를 붙여 이스케이프(escape, 특수 문자로 해석되지 않고 매치할 문자로 해석하도록 탈출) 시킨다.

앵커

특수 문자 중에서 ^와 $를 앵커(anchor)라 부른다. ^는 줄 끝을 표현하는 문자의 바로 다음 지점을 가리키고, $는 줄 끝을 표현하는 바로 그 지점을 표현한다. 기본적으로 ^와 $는 스트링의 시작과 끝을 표현하는데, 이 동작은 끌 수도 있다.

와일드카드

와일드카드(wildcard) 문자인 .는 줄바꿈(newline, 개행) 문자를 제외한 모든 문자를 매치한다. 

선택 매치

수직선 |은 ‘또는’ 관계를 표현한다.

그룹

소괄호 ( )는 부분 표현식(subexpression) 또는 캡쳐 그룹(capture group)을 표현한다. 캡쳐 그룹은 다음 목적으로 사용된다.

  • 캡쳐 그룹은 원본 스트링에서 부분 시퀀스를 찾을 때 사용되며 캡쳐 그룹에 해당하는 부분 표현식을 리턴한다.
  • 매치하는 동안 역참조(back reference)를 위해 캡쳐 그룹을 활용하기도 한다.
  • 치환 작업에서 특정한 항목을 찾을 때도 캡쳐 그룹을 활용한다.

반복

다음 네 가지 반복 문자(repeat) 중 하나를 활용하면 정규표현식의 일부분을 반복 적용할 수 있다.

  • *는 그 앞에 나온 패턴을 0번 이상 매치한다.
  • +는 그 앞에 나온 패턴을 1번 이상 매치한다.
  • ?는 그 앞에 나온 패턴을 0번 또는 1번만 매치한다.
  • { … } 는 반복 횟수가 제한됐다는 것을 표현한다.
    • a{n}는 a를 정확히 n번 반복한 것을 매치한다. a{n,}는 a를 n번 이상 반복한 것을 매치한다. a{n, m}은 a를 n번 이상, m번 이하로 반복한 것을 매치한다.

앞서 소개한 반복 문자는 주어진 문장에서 정규표현식에 매치된 항목 중에서도 가장 긴 것을 찾는 그리디(greedy) 방식이 적용된다. 이와 반대로 논-그리디(non-greedy) 방식을 적용하려면 반복 문자 뒤에 ?를 붙이면 된다. 예컨대 *?, +?, ??, { … }? 등과 같다. 논-그리디 방식으로 작동할 때는 반복 횟수를 최소화하는 방식으로 매치한다.

연산 우선순위

정규표현식의 연산도 우선순위가 정해져 있다. 규칙은 다음과 같다.

  • 원소(element): a와 같은 기본 구성 요소를 말한다.
  • 한정자(quantifier): +, *, ?, { … } 등은 왼쪽에 나온 원소에 우선 적용된다. (예: b+)
  • 연결(concatenation): 한정자보다 우선순위가 낮다. (예: ab+c)
  • 선택(alternation): 우선순위가 가장 낮다. (예: |)

소괄호를 어떻게 묶느냐에 따라 우선순위를 바꾸 수 있다.

문자 집합 매치

알파벳을 매치하고 싶을 때 (a|b|c|…|z)와 같이 문자를 일일이 명시하면 번거롭기도 하고 캡쳐 그룹이 생성돼버린다. 이럴 때는 일정한 구간 또는 집합의 문자를 지정하는 특수 문법을 사용하는 것이 좋다. 또한 매치 대상에 부정(not) 연산을 적용시킬 수도 있다. 

문자 집합(character set)은 [c1, c2, … cn] 처럼 대괄호로 묶어서 지정한다. 그러면 c1, c2, … cn 중에서 일치하는 문자를 찾는다. 예컨대 [abc]라고 적으면 a, b 또는 c와 매치하는 것을 찾는다. 여기서 맨 앞에 ^를 적으면 부정 연산이 적용돼 ^ 뒤에 나온 문자를 제외한 나머지를 찾는다. 예컨대 다음과 같다.

  • ab[cde]: abc, abd, abe
  • ab[^cde]: abf, abp 등은 매치하지만 abc, abd, abe는 매치하지 않는다.

^, [, ]와 같은 특수 문자 자체를 매치하고 싶으면 이스케이프시켜야 한다. 예컨대 [\[\^\]]라고 적으면 [, ^, ]를 매치할 수 있다.

[abcd…XYZ]처럼 문자를 일일이 나열하는 방식으로 문자 집합을 지정해도 되지만 이렇게 작성하면 번거로울 뿐 아니라 실수가 나올 여지도 많다. 이럴 때는 다음의 두 방법 중 하나를 사용한다.

첫 번째 방법은 대괄호를 이용한 범위 지정 표기법을 사용하여 [a-zA-Z]와 같이 작성한다. 이 표현식을 a부터 z, A-Z 사이에 나오는 문자를 모두 매치한다. 하이픈(-)을 매치하려면 반드시 이스케이프 시켜야 한다.

두 번째 방법은 문자 클래스(character class)를 지정하는 것이다. 이렇게 하면 특정한 종류의 문자만 지정할 수 있다. 문법은 [:name:]과 같다. 사용할 수 있는 문자 클래스의 종류는 로케일마다 다르지만, 모든 로케일에서 공통적으로 인식할 수 있는 것을 정리하면 다음 표와 같다. 각 문자 클래스의 구체적인 의미도 로케일마다 다르다. 이 표는 표준 C 로케일을 기준으로 작성했다.

문자 클래스 설명
digit 숫자
d digit와 같다.
xdigit 숫자(digit)와 16진수를 표현하는데 사용되는 문자(‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’)
alpha 알파벳 문자. 표준 C 로케일에서는 소문자와 대문자를 모두 포함한다.
alnum alpha클래스와 digit 클래스를 합친 것
w alnum과 같다.
lower 소문자(로케일에서 지원하는 경우)
upper 대문자(로케일에서 지원하는 경우)
blank 일종의 공백 문자로서 문장에서 단어끼리 구분하는데 사용된다. 표준 C 로케일에서는 ‘ ‘와 ‘\t’이 여기에 해당한다.
space 공백 문자. 표준 C 로케일에서는 ‘ ‘, ‘\t’, ‘\n’, ‘\r’, ‘\v’, ‘\f’ 등이 여기에 해당한다.
s space와 같다.
print 출력할 수 있는 문자로서 출력 대상(예: 모니터 화면)에서 일정한 공간을 차지한다. 이 문자는 제어 문자(cntrl)와 반대다. 예컨대 소문자, 대문자, 숫자, 구두점 문자, 공백 문자 등이 있다.
cntrl 제어 문자. 출력할 수 있는 문자(print)와 반대로서 화면에 같은 출력 대상에서 공간을 차지하지 않는다. 표준 C 로케일에서 여기에 해당하는 문자로는 ‘\f’ (다음 페이지, 폼 피드, form feed), ‘\n'(줄 바꿈, new line), ‘\r'(맨 앞으로 이동. 캐리지 리턴, carriage return) 등이 있다.
graph 그래픽 표현 문자. 출력할 수 있는 문자(print)에서 공백(‘ ‘)을 제외한 문자
punct 구두점 문자. 표준 C 로케일에서는 graph에 속한 문자에서 alnum을 제외한 문자가 여기에 해당한다. 예컨대 ‘!’, ‘#’, ‘@’, ‘}’ 등이 있다.

문자 클래스는 문자 집합 안에서 사용한다. 예컨대 [[:alpha:]]*는 영어권 로케일에서 [a-zA-z]*와 같다.

숫자를 비롯한 몇몇 매치 작업은 자주 수행하기 때문에 축약 표현을 제공한다. 예컨대 [:digit:]을 간단히 [:d:]로 표기할 수 있다. 이는 [0-9]와 같다. 일부 문자 클래스는 이스케이프 표기법(\)으로 더 줄여서 표현한다. 예컨대 [:digit:]을 \d로 표기해도 된다.

단어 경계

단어 경계(word boundary)는 다음 조건을 만족한다.

  • 첫 문자가 단어용 문자(문자, 숫자, 언더스코어 중 하나)로 시작하면 스트링의 시작이다. 표준 C 로케일에서는 [A-Za-z0-9_]에 해당한다. 디폴트 설정은 스트링의 싲가을 매치하는데, 원하면 끌 수 있다. (regex_constants::match_not_bow)
  • 마지막 문자가 단어용 문자로 끝나면 스트링의 끝이다. 디폴트 설정은 스트링의 끝을 매치하는데, 원하면 끌 수 있다. (regex_constants::match_not_ eow)
  • 단어의 첫 문자는 단어용 문자지만, 그 앞에 나온 문자는 단어용 문자가 아니다.
  • 단어의 끝 문자는 단어용 문자가 아니지만, 그 앞에 나온 문자는 단어용 문자다.

이러한 단어 경계를 매치하려면 \b를 지정한다. 반대로 단어 경계를 제외한 나머지를 매치하려면 \B를 지정한다.

역참조

역참조(back reference)를 이용하면 정규표현식 안에 있는 캡쳐 그룹을 참조할 수 있다. \n은 n번째 캡쳐 그룹을 가리킨다. 이때 n > 0 이다. 예컨대 (\d+)-.*-\1은 다음과 같은 포맷의 스트림을 매치한다.

  • (\d+) 캡쳐 그룹에 캡쳐된 한 개 이상의 숫자
  • 그 뒤에 대시가 나온다.
  • 그 뒤에 0개 이상의 문자(.*)가 나온다.
  • 그 뒤에 대시(-)가 나온다.
  • 마지막으로 첫 번째 캡쳐 그룹(\1)에 의해 캡쳐된 것과 똑같은 숫자가 나온다.

이 표현식은 123-abc-123, 1234-a-1234 등은 매치하지만 123-abc-1234, 123-abc-321 등은 매치하지 않는다.

미리보기

정규표현식은 ?= 패턴으로 표기하는 양의 미리보기(positive lookahead)와 ?! 패턴으로 표기하는 음의 미리보기(negative lookahead)를 지원한다. 양의 미리보기라면 지정한 패턴이 반드시 나와야 하고, 음의 미리보기라면 그 패턴이 나오지 않아야 한다. 이때 미리보기 패턴에 매치된 문자는 정규 표현식 매치 결과에 포함되지 않는다.

예컨대 a(?!b) 패턴은 음의 미리보기를 지정하여 a 문자 뒤에 b가 나오지 않는 것을 매치한다. a(?=b) 패턴은 양의 미리보기를 지정하여 a 문자 뒤에 b가 나오지만 b는 매치 결과에 포함하지 않는다.

좀 더 복잡한 예를 보자. 다음 정규표현식은 입력 스트링 중에서 소문자와 대문자가 최소 하나 있고, 구두점도 하나 있고, 길이도 최소 여덟 글자 이상인 것을 매치한다. 예컨대 패스워드가 일정한 기준에 만족하는지 검사할 때는 다음과 같은 패턴을 정의한다.

(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{8, }

정규표현식과 로 스트링 리터럴

정규표현식에는 특수 문자가 많이 나온다. 이런 문자는 C++ 스트링 리터럴에 쓸 때 주로 이스케이프해야 했던 것들이다. 예컨대 정규표현식에 \d를 쓰면 숫자를 매치하는데, \는 C++에서 특수 문자라서 정규표현식을 스트링 리터럴에 담을 때는 \\d와 같이 이스케이프해야 한다. 그렇지 않으면 C++ 컴파일러가 \d란 문자로 해석해 버린다.

여기에 백슬래시(\) 문자를 매치하는 구문을 추가하려면 정규표현식이 더욱 복잡해진다. \는 정규표현식 문법 안에서도 특수 문자이기 때문에 \\와 같이 이스케이프시켜야한다. 게다가 \ 문자는 C++ 스트링 리터럴에서도 특수만자다. 따라서 백슬래시를 매치하는 정규표현식을 C++ 스트링 리터럴에 담을 때는 \\\\로 표기해야 한다.

로(raw) 스트링 리터럴을 사용하면 복잡한 정규표현식을 C++ 코드에서 보다 읽기 쉽게 표현할 수 있다.

예컨대 다음과 같이 작성된 정규표현식이 있다면

"( |\\n|\\r|\\\\)"

로 스트링 리터럴로 다음과 같이 훨씬 보기 좋게 바꿀 수 있다.

R"(( |\n|\r|\\))"

regex 라이브러리

정규표현식 라이브러리에 관련된 내용은 모두 <regex> 헤더 파일의 std 네임스페이스 아래에 정의돼 있다. 정규표현식 라이브러리에서 기본으로 제공해둔 템플릿 타입은 다음과 같다.

  • basic_regex: 특정한 정규표현식을 표현하는 객체
  • match_results: 정규표현식에 매치된 서브스트링으로서 캡처 그룹을 모두 포함한다. sub_match의 묶음인 셈이다.
  • sub_match: 반복자와 입력 시퀀스에 대한 쌍을 담은 객체다. 여기 나온 반복자는 매치된 캡쳐 그룹을 표현한다. 쌍에는 매치된 캡쳐 그룹에서 첫 번째 문자를 가리키는 반복자와 매치된 캡쳐 그룹에서 마지막 바로 다음 번째 지점을 가리키는 반복자가 담겨 있다. 이 객체는 매치된 캡쳐 그룹을 스트링으로 리턴하는 str() 메서드도 제공한다.

이 라이브러리는 regex_match(), regex_search(), regex_replace()라는 세 가지 핵심 알고리즘을 제공한다. 각각 원본 스트링을 string, 문자 배열 또는 시작/끝 반복자 쌍으로 입력받는 버전이 존재한다. 여기서 반복자 타입은 다음 중 하나다.

  • const char*
  • const wchar_t*
  • string::const_iterator
  • wstring::const_iterator

참고로 양방향 반복자로 작동하는 반복자라면 어떤 것도 사용할 수 있다. 

regex 라이브러리는 다음과 같이 정규표현식 반복자에 대해 두 가지 타입을 정의하고 있다. 원본 스트링에서 주어진 패턴에 매치되는 문자를 모두 찾고 싶다면 이 타입을 잘 알아둘 필요가 있다.

  • regex_iterator: 원본 스트링에서 패턴에 매치되는 모든 항목에 대해 반복한다.
  • regex_token_iterator: 원본 스트링에서 패턴에 매치되는 모든 캡쳐 그룹에 대해 반복한다.

C++ 표준은 이 라이브러리를 보다 쉽게 사용할 수 있도록 다음과 같이 타입 앨리어스를 정의하고 있다.

using regex = basic_regex<char>;
using wregex = basic_regex<wchar_t>;

using csub_match = sub_match<const char*>;
using wsub_match = sub_match<const wchar_t*>;
using ssub_match = sub_match<string::const_iterator>;
using wssub_match = sub_match<wstring::const_iterator>;

using cmatch = match_result<const char*>;
using wmatch = match_result<const wchar_t*>;
using smatch = match_result<string::const_iterator>;
using wsmatch = match_result<wstring::const_iterator>;

using cregex_iterator = regex_iterator<const char*>;
using wcregex_iterator = regex_iterator<const wchar_t*>;
using sregex_iterator = regex_iterator<string::const_iterator>;
using wsregex_iterator = regex_iterator<wstring::const_iterator>;

using cregex_token_iterator = regex_token_iterator<const char*>;
using wcregex_token_iterator = regex_token_iterator<const wchar_t*>;
using sregex_token_iterator = regex_token_iterator<string::const_iterator>;
using wsregex_token_iterator = regex_token_iterator<wstring::const_iterator>;

regex_match()

rege_match() 알고리즘은 주어진 원본 스트링을 주어진 정규표현식 패턴으로 비교한다. 주어진 원본 스트링 전체가 이 패턴에 매치되면 true를 그렇지 않으면 false를 리턴한다.

사용법은 아주 간단하다. regex_match() 알고리즘은 여섯 가지 버전이 제공되는데, 각 버전마다 인수의 타입은 다르지만 모두 다음과 같은 형식을 따른다.

template<...>
bool regex_match(InputSequence[, MatchResults], RegEx[, Flags]);

InputSequence는 다음 중 하나로 표현한다.

  • 원본 스트링에 대한 시작과 끝 반복자
  • std::string
  • C 스타일 스트링

옵션으로 MatchResults란 매개변수도 지정할 수 있다. 이 값은 match_results에 대한 레퍼런스로서 매치 결과를 여기서 받을 수 있다. regex_match()가 false를 리턴하면 match_results::empty()나 match_results::size()만 호출할 수 있다. 다른 메서드의 호출 결과는 정확히 알 수 없다. regex_match()가 true를 리턴하면 매치되는 결과가 있다는 뜻이므로 match_results 객체를 이용하여 정확히 매치된 결과를 살펴볼 수 있다.

RegEx 매개변수는 매치의 기준이 될 정규표현식이다. 옵션으로 Flags 매개변수도 지정할 수 있다. 이 매개변수는 매치 알고리즘에 대한 옵션을 지정하는데, 대부분 디폴트값을 사용한다. 

regex_match() 예제

(예제 생략)

regex_search()

regex_match() 알고리즘은 원본 스트링 전체가 정규표현식에 매치될 때만 true를 리턴하고 나머지 경우는 false를 리턴한다. 그래서 일부분만 매치되는 경우는 찾을 수 없다. 만약 서브스트링도 찾고 싶다면 regex_search() 알고리즘을 사용해야 한다. 이 알고리즘은 원본 스트링 중에서 주어진 패턴에 일치하는 부분을 탐색한다. regex_search() 알고리즘은 총 여섯 가지 버전이 있는데, 모두 다음과 같은 형식을 따른다.

template<...>
bool regex_search(InputSequence[, MatchResults], RegEx[, Flags]);

각 버전은 입력 스트링에서 매치된 결과가 한 부분이라도 있으면 true를 리턴하고 그렇지 않으면 false를 리턴한다. 매개변수는 regex_match()의 매개변수와 비슷하다.

regex_search() 알고리즘의 두 버전은 탐색할 입력 스트링에 대한 시작과 끝 반복자를 매개변수로 받는다. 이 버전을 이용하여 루프를 돌며 regex_search()를 호출할 때마다 시작과 끝 반복자를 조작하는 방식으로 주어진 원본 스트링에서 패턴에 매치되는 모든 항목을 찾도록 코드를 작성하는 경우가 있는데 절대로 이렇게 하면 안된다. 이렇게 작성하면 정규표현식에 앵커(^, $)나 단어 경계 등을 사용할 때 문제가 발생할 수 있다. 또한 공백도 매치되는 바람에 무한 루프에 빠질 수 있다. 이렇게 원본 스트링에 매치되는 모든 항목을 찾고 싶다면 뒤에서 소개할 regex_iterator나 regex_token_iterator를 사용한다.

Caution) 주어진 원본 스트링에 매치되는 결과를 모두 찾기 위해 루프 안에서 regex_search()를 호출하는 방식으로 구현하면 절대 안 된다. 대신 regex_iterator나 regex_token_iterator를 사용하기 바란다.

regex_search() 예제

(예제 생략)

regex_iterator

regex_iterator 예제

다음 예제는 원본 스트링을 입력 받아서 그 안에 나온 단어를 모두 찾은 다음 각각을 따옴표로 묶어서 화면에 출력한다. 이를 위해 한 개 이상의 단어를 검색하는 [\w]+란 정규 표현식을 정의했다.

이 예제는 원본 스트링을 std::string 타입으로 받기 때문에 sregex_iterator를 반복자로 사용했다. 표준 반복자 루프 패턴으로 작성했지만, 끝 반복자를 기존 표준 라이브러리 컨테이너와 약간 다르게 사용했다. 일반적으로 특정한 컨테이너에 맞는 끝 반복자를 지정하지만 regex_iterator에는 끝 반복자가 하나뿐이다. 이러한 끝 반복자는 regex_iterator 타입을 선언할 때 디폴트 생성자로 구할 수 있다.

for 문은 먼저 iter라는 시작 반복자를 생성한다. 이 반복자는 원본 스트링에 대한 시작과 끝 반복자와 패턴을 표현하는 정규표현식을 인수로 받는다. 매치되는 결과가 나올 때마다 루프의 본문이 호출되는데, 예제에서는 한 단어 단위로 호출된다.

매치된 모든 결과에 대해 반복하는 구문은 sregex_iterator로 구현했다. sregex_iterator를 역참조하면 smatch 객체를 구할 수 있다. 이 smatch 객체의 첫 번째 원소인 [0]을 접근하면 매치된 부분 스트링을 구할 수 있다.

regex reg("[\\w]+");

while (true)
{
cout << "Enter a string to split (q=quit): ";

string str;
if (!getline(cin, str) || str == "q")
{
break;
}

const sregex_iterator end;
for (sregex_iterator iter(cbegin(str), cend(str), reg); iter != end; ++iter)
{
cout << "\"" << (*iter)[0] << "\"" << endl;
}
}

이 코드를 실행한 결과는 다음과 같다.

Enter a string to split (q=quit): This, is    a test.
"This"
"is"
"a"
"test"

예제에서 볼 수 있듯이 아주 간단한 정규표현식만으로도 상당히 강력한 스트링 조작 기능을 구현할 수 있다.

참고로 regex_iterator와 regex_token_iterator는 내부적으로 정규표현식에 대한 포인터를 갖고 있다. 둘 다 우측값 정규표현식을 인수로 받는 생성자를 명시적으로 delete 하기 때문에 임시 regex 객체를 생성할 수 없다. 예컨대 다음과 같이 작성하면 컴파일 에러가 발생한다.

for (sregex_iterator iter(cbegin(str), cend(str), regex("[\\w]+")); iter != end; ++iter) { ... }

regex_token_iterator

앞 절에서 패턴에 매치된 모든 결과에 대해 반복하는 regex_iterator를 살펴봤다. 루프를 한 번 돌 때마다 이 반복자로부터 match_results 객체를 받는데, 이를 통해 캡쳐 그룹으로 매치된 부분 표현식을 추출할 수 있다.

regex_token_iterator를 이용하면 매치된 결과 중에서 캡쳐 그룹 전체 또는 선택한 캡쳐 그룹에 대해서만 루프를 돌 수 있다. 이 반복자에는 네 가지 버전의 생성자가 있는데 모두 다음 형식을 따른다.

regex_token_iterator(BidirectionalIterator a, BidirectionalIterator b, const regex_type& re [, SubMatches [, Flags]]);

네 버전 모두 입력 시퀀스를 시작과 끝 반복자로 지정한다. 옵션으로 SubMatches 매개변수를 지정할 수 있다. 이 값은 반복의 기준이 되는 캡쳐 그룹을 지정한다. SubMatches는 다음 네 가지 방식 중 하나로 지정한다.

  • 반복하려는 캡쳐 그룹의 인덱스를 나타내는 정숫값 하나로 지정한다.
  • 반복하려는 캡쳐 그룹의 인덱스를 나타내는 정숫값을 vector에 담아 지정한다.
  • 캡쳐 그룹 인덱스에 대한 initializer_list로 지정한다.
  • 캡쳐 그룹 인덱스에 대한 C 스타일 배열로 지정한다.

SubMatches 매개변수를 지정하지 않거나 그 값을 0으로 지정하면 인덱스가 0인 캡쳐 그룹을 모두 반복하는 반복자를 받게 된다. 다시 말해 정규표현식 전체에 매치되는 서브스트링에 대해 반복하게 된다. 옵션으로 매치 알고리즘을 선택하는 Flags 매개변수도 지정할 수 있지만 대부분 디폴트값을 그대로 사용한다. 

regex_token_iterator 예제

(예시 생략)

regex_token_iterator는 소위 필드 분할(field splitting) (또는 토큰화) 작업에도 활용된다. 이는 C에서 물려받은 strtok() 함수를 사용하는 것보다 훨씬 안전하고 유연하다. regex_token_iterator 생성자에서 반복할 캡쳐 그룹 인덱스를 -1로 지정하면 토큰화 모드로 작동한다. 토큰화 모드로 실행될 때는 입력 시퀀스에서 주어진 정규표현식에 매치되지 않은 모든 서브스트링에 대해 반복한다. 

다음 코드는 ,와 ;로 사용하는 스트링을 토큰화하는 예를 보여준다. 여기서 구분자 앞이나 뒤에 공백이 한 칸 이상 나올 수 있다.

regex reg(R"(\s*[,;]\s*)");

while (true)
{
cout << "Enter a string to split (q=quit): ";

string str;
if (!getline(cin, str) || str == "q")
{
break;
}

const sregex_token_iterator end;
for (sregex_token_iterator iter(cbegin(str), cend(str), reg, -1); iter != end; ++iter)
{
cout << "\"" << *iter << "\"" << endl;
}
}

regex_replace()

regex_replace() 알고리즘은 정규표현식과 매치된 서브스트링을 대체할 포맷 스트링(formatting string)을 인수로 받는다. 포맷 스트링에서 다음 표에 나온 이스케이프 시퀀스를 이용하면 매치된 서브스트링의 일부분을 참조할 수 있다.

이스케이프 시퀀스 교체할 내용
$n n 번째 캡쳐 그룹에 매치되는 스트링. 예컨대 $1은 첫 번째 캡쳐 그룹을, $2는 두 번째 캡쳐 그룹을 의미한다. 이때 n은 반드시 0보다 큰 수여야 한다.
$& 정규표현식 전체에 매치되는 스트링
$` 정규표현식에 매치된 서브스트리으이 왼쪽에 나온 입력 시퀀스의 일부분
$’ 정류표현식에 매치된 서브스트링의 오른쪽에 나온 입력 시퀀스의 일부분
$$ 달러 기호($)

regex_replace() 알고리즘은 여섯 가지 버전이 있다. 각각 인수의 타입만 다르다. 그중 네 가지 버전은 다음 형식을 따른다.

string regex_replace(InputSequence, RegEx, FormatString[, Flags]);

이 네 가지 버전은 교체 작업을 반영한 스트링을 리턴한다. InputSequence와 FormatString은 std::string이나 C 스타일 스트링으로 표현할 수 있다. RegEx 매개변수는 매치의 기준이 될 정규표현식을 지정한다. 옵션으로 지정하는 Flags 매개변수는 적용할 교체 알고리즘을 지정한다.

rege_replace() 알고리즘 중 두 가지 버전은 다음 형식을 따른다.

OutputIterator rege_replace(OuputIterator, BidirectionalIterator first, BidirectionalIterator last, RegEx, FormatString[, Flags]);

이 두 가지 버전은 지정한 출력 반복자에 교체한 결과를 쓴 다음 그 출력 반복자를 리턴한다. 그리고 입력 시퀀스는 시작과 끝 반복자로 지정한다.

regex_replace() 예제

(예시 생략)

[ssba]

The author

지성을 추구하는 사람/ suyeongpark@abyne.com

댓글 남기기

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.