프로그래밍 이야기

A Tour of C++ : 9장 문자열과 정규 표현식

원소랑 2019. 11. 18. 16:25

.

9. 문자열과 정규 표현식

9.1 소개

C++ 표준 라이브러리 std::string 타입. string_view 타입을 이용하면 string, char[] 상관없이 문자열 시퀀스 조작 가능. 정규표현식 제공.

9.2 문자열

string 타입의 활용.

s1 = s1 + ‘\n’;

s2 += ‘\n’;

string name = “Niels Stroustrup’;

string s = name.substr(6,10); // s = “Stroustrup”;

name.replace(0, 5, “nicholas”); // name = “nicholas Stroustrup”;

name[0] = toupper(name[0]); // name = “Nicholas Stroustrup”;

if ( name == “NicholasStroustrup”) // 허용

{ ... }

printf(“printf를 선호한다면: %s\n”, name.c_str());

auto s = “Cat”s; // std::string 으로 할당

auto p = “Dog”; // C 스타일 문자열: const char* 로 할당

9.2.1 string 구현

짧은 문자열 최적화를 바탕으로 구현. 짧은 문자열 값은 string 객체에 저장, 더 긴 문자열은 자유저장소에 저장.

9.3 문자열 뷰

문자열을 함수로 전달할 때, 복잡성 해결을 위한 표준 라이브러리 string_view 제공. 문자열 시퀀스를 나타내는 포인터, 길이의 쌍. string_view 가 문자열을 직접 소유하지 않음. 포인터, 참조, 반복자와 유사.

string_view : {begin(), size()}

string cat(string_view sv1, string_view sv2)

{

string res(sv1.length()+sv2.length());

char* p = &res[0];

// 복사 방법 1

for (char c: sv1)

*p++ = c;

// 복사 방법 2

copy(sv2.begin(), sv2.end(), p);

return res;

}

위 함수의 장점

- 다양한 타입의 문자 시퀀스에 적용 가능.

- C스타일 문자열을 인자로 받을 때 임시 string 객체 생성 안 됨.

- 부분 문자열 쉽게 전달.

cat( “Edward”, “Stephen”sv );

“...”sv를 쓰려면 아래를 선언해야.

using namespace std::literals::string_view_literals; // 5.4.4 절

string_view를 반환할 때는, 포인터와 유사함을 기억해야.

9.4 정규 표현식

표준 라이브러리 <regex>, 정규 표현식을 지원하기 위한 std::regex클래스와 함수들.

regexpat {R”(\w{2}\s*\d{5}(-\d{4})*)”}; // 미국 우편번호 패턴 : XXddddd-dddd와 그 변종.

\w{2}\s*\d{5}(-\d{4})*

두 문자로 시작 \w{2}

선택적 공뱅 \s*

숫자 다섯 개 \d{5}

선택적 대시와 숫자 네 개 -\d{4}

로우 문자열 리터럴 사용 R”( ... )”

백슬래시와 큰 따옴표를 직접 사용할 수 있어짐.

로우 문자열 리터럴이 아닌 일반 문자열이었다면 백슬래시를 두 개씩 써줘야 함.

<regex> 표준 라이브러리

- regex_match() : 문자열에 매칭

- regex_search() : 매칭 문자열 찾기

- regex_replace() : 데이터스트림에서 매칭 찾기

- regex_iterator() : 매치와 부분 매치를 찾아 순회

- regex_token_iterator() : 매치되지 않는 부분 순회

9.4.1 검색

스트림에서 패턴 찾기

int lineno = 0;

for (string line; getline(cin, line); ) { // 버퍼 line 에 읽기

++lineno;

smatch matches; /// 매치된 문자열 저장

if (regex_search(line, matches, pat)) // line 에서 pat 찾기

cout<<lineno<< “:” << matche[0] << ‘\’;

9.4.2 정규 표현식 표기법

regex 는 다양한 정규표현식 표기법을 인식. 여기에선 ECMAScript 의 ECMA 표준을 사용.

정규표현식 특수문자

. 모든 문자 하나(와일드카드)

[, ] 문자 클래스 시작, 끝

{, } 카운팅 시작, 끝

(, ) 그룹핑 시작, 끝

\ 다음 문자를 특수문자로 인식

* 0번 이상(접미 연산)

+ 1번 이상(접미 연산)

? 선택적(0번이나 1번)(접미 연산)

| 대안(또는 (or))

^ 행의 시작; 반전

$ 행의 끝

Q. 행의 시작이 0개 이상의 A, 다음에 1개 이상의 B, 그 뒤에 C가 선택작으로 등장

A. ?A?B+C?$

ex) AAAAAAAAAAABBBBBBBBC

ex) BC

ex) B

매치되지 않는 예

ex) AAAAA // B가없다

ex) AAABC // 맨 앞이 공백

ex) AABBCC // C가 너무 많음

(괄호에 둘러싸인) 패턴의 일부분을 부분 패턴이라고 함.

\d+?\d+ // 부분패턴 없음

\d+(?\d+) // 부분패턴 하나

(\d+)(?\d+)

반복

{n} 정확히 n번

{n,} n번 이상

{n,m} n번 이상, m번 이하

* 0번 이상, {0,}과 동일

+ 1번 이상, {1,}과 동일

? 선택적(0또는 1), {0, 1}과 동일

EX> A{3}B{2,4}C?

매칭되는 문자열

AAABBC

AAABBB

애칭되지 않는 문자열

AABBC // A가 너무 적음

AAABC // B가 너무 적음

AAABBBBBCCC // B가 너무 많음

반복 표기( ?, *, +, { } ) 다음 접미사 ?를 사용하면 패턴 매처를 지연시키거나 non-greedy로 만든다. 즉, 패턴을 발견했을 때 가장 긴 매치 대신 가장 짧은 매치를 찾는다.

기본적으로는 패턴 매처가 항상 가장 긴 매치를 찾음. Max Munch Rule(크게 베어 먹기 규칙).

ababab 아이디 활용 중.

패턴 (ab)+ 는 ababab 전체에 매치

패턴 (ab)+? 는 첫 번째 ab만 매치.

문자 분류, 문자 클래스

almun 알파벳이나 숫자

alpha 알파벳

blank 행 구분자를 제외한 공백 문자

cntrl 제어 문자

d 10진 숫자

digit 10진 숫자

graph 그래픽 문자

lower 소문자

print 출력 가능 문자

punct 문장 부호

s 공백 문자

space 공백 문자

upper 대문자

w 단어 문자(알파벳과 숫자, 밑줄)

xdigit 16진 숫자

문자 클래스는 이름을 [: :] 로 둘러싸야 한다.

ex)

\d [[:digit:]] // 모든 10진 숫자에 매치

\s [[:space:]]

\w [_[:alnum:]]

\D [^[:digit:]]

\S [^[:space:]]

\W [^_[:alnum:]]

언어 자체 제공 정규 표현식

/l : 소문자

/w : 대문자

/L : /l 이 아닌 것

/U : /u

축약어보다는 문자열 클래스 이름 정의가 좋음. 모든 가능성에 대비.

C++의 식별자 패턴.

-> 맨 앞에 밑줄이나 문자가 하나, 선택적으로 알파벳이나 숫자, 밑줄 구성.

틀린 예

[:alpha:][:alpha:]* // 틀림 : 문자열 “:alpha” 중 하나가 앞에 옴.

[[:alpha:]][[:alpha:]]* // 틀림 : 밑줄이 없음(‘_’는 alpha가 아님) alpha 는 알파벳만.

([[:alpha:]]|_)[[:alnum:]]* // 틀림 : 밑줄은 alnum에 속하지 않음. _ 를 추가해줘야함.

([[:alpha:]]|_)([[:alnum:]]|_) // OK : 그러나 지저분. 길다.

[[:alpha:]_][[:alnum:]_]* // OK : 문자클래스 안에 밑줄이 있음

[_[:alpha:]][_[:alnum:]]* // OK

[_[:alpha:]]\w* // OK : \w 는 [_[:alnum:]] 과 동일

간단 버전의 regex_match() 활용예

bool is_identifier(const string& s)

{

regex pat {“[_[:alpha:]]\\w?” };

return regex_match( s, pat );

}

일반 문자열 리터럴에서 백슬래시는 두 번 써야함. R”...” 로우 문자열 리터럴을 쓰면 하나만 써도 됨.

예제 패턴

Ax* // A, Ax, Axxxx. x가 0개 이상.

Ax+ // Ax, Axxx. | A는 매칭 안 됨. x가 1개 이상.

\d* \d // 1-2, 12. | 1--2는 매칭 안 됨

\w{2}?\d{4,5} // Ab-1234, XX-54321, 22-5432. | 숫자는 \w에 포함 됨.

(\d*:)?(\d+) // 12:3, 1:23, 123, :123. | 123:은 매칭 안 됨.

(bs|BS) // bs, BS. | bS는 매칭 안 됨.

[aeiouy] // a, o, u. | 영어 모음, x는 매칭 안 됨.

[?aeiouy] // x, k | 영어 모음이 아닌 것, e는 매칭 안 됨

[a?eiouy] // a, ?, o, u | 영어 모음 또는 ?

group(부분 패턴)은 sub_match로 표현될 수 있으며, 괄호로 구분.

부분패턴 정의 없이 괄호가 필요하면 ( 대신 (?: 을 사용.

(\s|:|,)?(\d*) // 공백 문자나 콜론, 쉼표가 0개 이상 나오고, 0개 이상의 숫자가 뒤따름.

(?:\s|:|,)?(\d*)

정규 표현식 그룹핑 예제

\d*\s\w+ 그룹 없음. 부분 패턴도 없음.

(\d*)\s(\w+) 두 그룹

(\d*)(\s(\w+))+ 두 그룹(그룹 중첩 X)

(\s?\w?)+ 한 그룹, 하나 이상의 부분 패턴. 마지막 부분 패턴만 sub_match에 저장.

<(.*)>(.*)</\1> 세 그룹. \1은 ‘그룹1과 같음’을 의미. XML 파싱에 유용한 패턴.

정규표현식은 좀 더 시간을 들여 연습하고 암기해야한다.

9.4.3 반복자

패턴 매칭하며 시퀀스 순회를 하고싶다면, regex_iterator 를 정의.

예) 공백문자로 구분된 단어를 모두 출력.

void test()

{

string input = “aa as; asd ++e^asdf asdfg”;

regex pat {R”(\s+(\w+))”};

for (sregex_iterator p(input.begin(), input.end(), pat); p != sregex_iterator{}; ++p)

cout << (*p)[1] << ‘\n’;

}

출력

as

asd

asdfg

regex pat {R”(\s+(\w+))”}; 를 단순화 해서 regex pat {R”((\w+))”}; 라면

출력

aa

as

asd

e

asdf

asdfg

regex_iterator에 쓰기를 수행할 순 없다.

9.5 조언

[2] C스타일 문자열 함수 대신 string 연산 사용하자.

[4] string 을 값으로 반환하자 (9.2, 9.2.1)

[5] 부분 문자열 읽을 때는 substr(), 쓸 때는 replace()

[8] 최적화된 성능을 원하면 at() 대신 반복자 or [] 를 사용. (9.2)

[9] string 입력은 오버플로우 되지 않는다. (9.2, 10.3)

[10] string 을 C 스타일로 표현해야 할 경우에만 c_str() 을 사용. (9.2)

[11] 문자열을 수치로 변환할 때는 stringstream 이나 (to<X>같은) 제네릭 값 추출 함수를 사용.

[13] 표준 라이브러리 string 타입으로 만들고 싶은 문자열 리터럴에는 접미가 s 를

[14] 여러 방식으로 저장된 문자 시퀀스를 읽어야 한다면 string_view 를 인자로

[15] 써야 한다면 std::string_span

[16] string_view 는 크기를 동반한 포인터로 생각.

[17] string_view 타입으로 만들고 싶은 문자열 리터럴에는 접미사 sv

[18] 정규 표현식 관례적으로 대부분의 경우 regex 를 이용.

[19] 패턴 표현 시 로우 문자열 리터럴을 사용하자. R”...”

[20] 입력 전체에 매칭하려면 regex_match()

[21] 입력 스트림에 포함된 입력 검색 regex_search()

[23] 기본 정규 표현식 표기법은 ECMAScript 따름

[26] ?를 이용하여 패턴에 지연된(lazy) 매칭 적용

.

.

.

728x90
반응형