-----------------
title : 씹어먹는 C++ - <17 - 2. C++ 정규 표현식(<regex>) 라이브러리 소개>
cat_title : 17 - 2. C++ 정규 표현식(<regex>) 라이브러리 소개
path : /C++
publish_date : 2020-1-3
tex_title : 정규 표현식(<regex>) 라이브러리 소개
next_page : 304
-----------------

이번 강좌에서는

* `std::regex_match` 를 이용해서 정규 표현식으로 전체 문자열 패턴 매칭하기.
* `std::regex_search` 를 이용해서 정규 표현식으로 문자열 검색하기
* `std::regex_replace` 를 이용해서 정규 표현식으로 문자열 치환하기

에 대해 다룹니다.

![](/img/ChewingCpplogo.png)

안녕하세요 여러분! 이번 강좌에서는 `C++ 11` 부터 표준에 포함된 정규 표현식(regular expression) 라이브러리에 대해서 간단하게 알아보는 시간을 가지도록 하겠습니다. 아마 프로그래밍을 오래 하신 분들이라면 정규 표현식 자체는 아마 여러 상황에서 사용해보셨으리라 생각합니다.

정규 표현식은 문자열에서 패턴을 찾는데 사용하는데, 이를 통해

* 주어진 문자열이 주어진 규칙에 맞는지 확인할 때
* 주어진 문자열에서 원하는 패턴의 문자열을 검색할 때
* 주어진 문자열에서 원하는 패턴의 문자열로 치환할 때

와 같은 경우에 매우 유용하게 사용됩니다.

그렇다면 각각의 상황에 맞게 C++ 에서 제공하는 정규 표현식 라이브러리를 어떻게 사용할지 살펴봅시다.

### 전체 문자열 매칭하기

만약에 여러분이 어떠한 서버를 관리하고 있는데, 해당 서버에서 매 시간 마다 로그 파일을 생성한다고 해봅시다. 이 때 로그 파일은 *db-(시간)-log.txt* 와 같은 형태로 생성된다고 합니다.

그러다면 여러 가지 파일들이 섞여 있는 폴더에서 우리가 원하는 로그 파일들만 어떻게 간단히 읽어들 수 있을까요? 일단 정규 표현식을 사용할 줄 아는 분들이라면 위 패턴을 보고 아래와 같은 정규 표현식을 떠올리실 것입니다.

```info
db-\d*-log\.txt
```

여기서 `\d*` 는 임의의 개수의 숫자를 의미하는 것이고, `.` 앞에 `\` 을 붙여준 이유는 `.` 을 그냥 썼을 때 *임의의 문자*로 해석되기 때문입니다.

그렇다면 해당 정규표현식을 바탕으로 C++ 을 통해 파일 이름이 주어진 패턴을 만족하는지 확인하는 프로그램을 작성해보도록 하겠습니다.

```cpp-formatted
#include <iostream>
#include <regex>
#include <vector>

int main() {
  // 주어진 파일 이름들.
  std::vector<std::string> file_names = {"db-123-log.txt", "db-124-log.txt",
                                         "not-db-log.txt", "db-12-log.txt",
                                         "db-12-log.jpg"};
  std::regex re("db-\\d*-log\\.txt");
  for (const auto &file_name : file_names) {
    // std::boolalpha 는 bool 을 0 과 1 대신에 false, true 로 표현하게 해줍니다.
    std::cout << file_name << ": " << std::boolalpha
              << std::regex_match(file_name, re) << '\n';
  }
}
```

성공적으로 컴파일 하였다면

```exec
db-123-log.txt: true
db-124-log.txt: true
not-db-log.txt: false
db-12-log.txt: true
db-12-log.jpg: false
```

와 같이 주어진 정규 표현식에 맞는 파일 이름들만 `true` 로 나옴을 볼 수 있습니다.

```cpp-formatted
std::regex re("db-\\d*-log\\.txt");
```

C++ 에서 정규 표현식을 사용하기 위해서는 먼저 위와 같이 정규 표현식 객체를 정의해야 합니다. 참고로 정규 표현식 문법의 종류와, 정규 표현식을 처리하는 엔진 역시 여러가지 종류가 있는데, 위 생성자에 추가적인 인자로 전달할 수 있습니다. 예를 들어서 `grep` 의 정규 표현식 문법을 사용하고 싶다면

```cpp-formatted
std::regex re("db-\\d*-log\\.txt", std::regex::grep);
```

과 같이 전달하면 됩니다. 만약에 인자를 지정하지 않았다면 디폴트로 `std::regex::ECMAScript` 가 들어가게 됩니다. 어떤 문법을 사용할 지 이외에도 몇 가지 특성들을 더 추가할 수 있는데, 예를 들어서 `std::regex::icase` 를 전달한다면 대소 문자를 구분하지 않게 됩니다. 이 때 특성을 추가하는 방법은 `|` 로 연결하면 됩니다. 예를 들어서

```cpp-formatted
std::regex re("db-\\d*-log\\.txt", std::regex::grep | std::regex::icase);
```

와 같이 말이지요.

참고로 정규 표현식의 성능이 중요할 경우에는 `std::regex::optmize` 를 추가적으로 전달할 수 있습니다. 이 경우 정규 표현식 객체를 생성하는데에는 시간이 좀 더 걸리지만 정규 표현식 객체를 사용하는 작업은 좀 더 빠르게 수행됩니다.

```cpp-formatted
std::cout << file_name << ": " << std::boolalpha
          << std::regex_match(file_name, re) << '\n';
```

자 그렇다면 만들어진 정규식 객체를 사용하는 부분을 살펴봅시다. `std::regex_match` 는 첫 번째 인자로 전달된 문자열 (위 경우 `file_name`) 이 두 번째 인자로 전달된 정규 표현식 객체 (위 경우 `re`) 와 **완전히** 매칭이 된다면 `true` 를 리턴합니다. 완전히 매칭 된다는 말은 문자열 전체가 정규 표현식의 패턴에 부합해야 한다는 것이지요.

### 부분 매칭 뽑아내기

앞서 `regex_match` 를 사용해서 전체 문자열이 주어진 정규 표현식 패턴을 만족하는지 알아 낼 수 있다고 하였습니다. 그렇다면 해당 조건을 만족하는 문자열에서 **패턴 일부분** 을 뽑아내고 싶다면 어떨까요?

예를 들어서 사용자로 부터 전화번호를 받는 정규 표현식을 생각해봅시다. 전화번호는 간단히 생각해서 다음과 같은 규칙을 만족한다고 생각합니다.

* (숫자)-(숫자)-(숫자) 꼴로 있어야 합니다.
* 맨 앞자리는 반드시 3 자리이며 0 과 1 로만 이루어져 있어야 합니다.
* 가운데 자리는 3 자리 혹은 4 자리 여야 합니다.
* 마지막 자리는 4 자리 여야 합니다.

예를 들어 아래와 같은 번호들이 조건을 만족하겠죠.

```info
010-1234-5678
010-123-4567
011-1234-5678
```

그렇다면 다음과 같이 정규 표현식을 작성할 수 있겠습니다.

```info
[01]{3}-\d{3,4}-\d{4}
```

맨 앞에 `[01]` 의 뜻은 0 혹은 1 이라는 의미이고, 뒤에 `{3}` 은 해당 종류의 문자가 3 번 나타날 수 있다는 의미입니다.

```cpp-formatted
#include <iostream>
#include <regex>
#include <vector>

int main() {
  std::vector<std::string> phone_numbers = {"010-1234-5678", "010-123-4567",
                                            "011-1234-5567", "010-12345-6789",
                                            "123-4567-8901", "010-1234-567"};
  std::regex re("[01]{3}-\\d{3,4}-\\d{4}");
  for (const auto &number : phone_numbers) {
    std::cout << number << ": " << std::boolalpha
              << std::regex_match(number, re) << '\n';
  }
}
```

성공적으로 컴파일 하였다면

```exec
010-1234-5678: true
010-123-4567: true
011-1234-5567: true
010-12345-6789: false
123-4567-8901: false
010-1234-567: false
```

예상한 대로 잘 작동함을 알 수 있습니다.

만약에 조건에 만족하는 전화번호 중에서 가운데 번호를 추출하고 싶다면 어떨까요? 정규 표현식을 배운 분들은 아시겠지만, 캡쳐 그룹 (capture group) 을 사용하면 된다는 것을 알고 계실 것입니다. C++ 의 경우도 마찬가지 입니다.

```cpp-formatted
std::regex re("[01]{3}-(\\d{3,4})-\\d{4}");
```

위와 같이 `()` 로 원하는 부분을 감싸게 된다면 해당 부분에 매칭된 문자열을 얻을 수 있게 됩니다.

그렇다면 매칭된 부분을 어떻게 얻을 수 있는지 살펴보도록 합시다.

```cpp-formatted
#include <iostream>
#include <regex>
#include <vector>

int main() {
  std::vector<std::string> phone_numbers = {"010-1234-5678", "010-123-4567",
                                            "011-1234-5567", "010-12345-6789",
                                            "123-4567-8901", "010-1234-567"};
  std::regex re("[01]{3}-(\\d{3,4})-\\d{4}");
  std::smatch match;  // 매칭된 결과를 string 으로 보관
  for (const auto &number : phone_numbers) {
    if (std::regex_match(number, match, re)) {
      for (size_t i = 0; i < match.size(); i++) {
        std::cout << "Match : " << match[i].str() << std::endl;
      }
      std::cout << "-----------------------\n";
    }
  }
}
```

성공적으로 컴파일 하였다면

```exec
Match : 010-1234-5678
Match : 1234
-----------------------
Match : 010-123-4567
Match : 123
-----------------------
Match : 011-1234-5567
Match : 1234
-----------------------
```

와 같이 나옵니다.

먼저 매칭된 문자열을 보관하는 객체를 정의한 부분 부터 설펴봅시다.

```cpp-formatted
std::smatch match;
```

위 `smatch` 의 경우 매칭된 문자열을 `std::string` 의 형태로 돌려줍니다. 그 외에도 `const char*` 로 돌려주는 `cmatch` 가 있습니다.

```cpp-formatted
if (std::regex_match(number, match, re)) {
```

다음에 `regex_match` 에 매칭된 결과를 보관할 `match` 와 정규 표현식 `re` 를 모두 전달합니다. 만일 `number` 가 `re` 의 패턴에 부합하다면 `match` 에 매칭된 결과가 들어 있을 것입니다.

```cpp-formatted
for (size_t i = 0; i < match.size(); i++) {
  std::cout << "Match : " << match[i].str() << std::endl;
}
```

자 그럼 이제 `match` 에서 매칭된 문자열들을 `match[i].str()` 을 통해 접근할 수 있습니다. 참고로 우리의 `match` 가 `smatch` 이므로 `match[i].str()` 은 `std::string` 이 됩니다. 반면에 `match` 가 `cmatch` 였다면 `match[i].str()` 는 `const char*` 이 되겠지요.

참고로 `regex_match` 의 경우 전체 문자열이 매칭이 된 것이기 때문에 첫 번째 결과에 전체 문자열이 나타나게 됩니다. 그 다음으로 `()` 안에 들어있던 문자열이 나타나게 되죠. 만약에 정규 표현식 안에 `()` 가 여러개 있다면 마찬가지로 `for` 문을 통해 순차적으로 접근할 수 있습니다.

### 원하는 패턴 검색하기

앞서 `regex_match` 를 통해 문자열 전체가 패턴에 부합하는지 확인하는 작업을 하였습니다. 이번에는 전체 말고 패턴을 만족하는 문자열 **일부** 를 검색하는 작업을 수행해보도록 하겠습니다.

우리가 하고 싶은 일은 HTML 문서에서 아래와 같은 형태의 태그만 읽어들이는 것입니다.

```info
<div class="sk...">...</div>
```

그렇다면 해당 조건을 만족하는 정규 표현식은 아래와 같이 작성할 수 있습니다.

```info
<div class="sk[\w -]*">\w*</div>
```

자 그렇다면 해당 정규 표현식을 사용해서 문자열에서 원하는 패턴을 어떻게 검색하는지 살펴보겠습니다.

```cpp-formatted
#include <iostream>
#include <regex>

int main() {
  std::string html = R"(
        <div class="social-login">
          <div class="small-comment">다음으로 로그인 </div>
          <div>
            <i class="xi-facebook-official fb-login"></i>
            <i class="xi-google-plus goog-login"></i>
          </div>
        </div>
        <div class="manual">
          <div class="small-comment">
            또는 직접 입력하세요 (댓글 수정시 비밀번호가 필요합니다)
          </div>
          <input name="name" id="name" placeholder="이름">
          <input name="password" id="password" type="password" placeholder="비밀번호">
        </div>
        <div id="adding-comment" class="sk-fading-circle">
          <div class="sk-circle1 sk-circle">a</div>
          <div class="sk-circle2 sk-circle">b</div>
          <div class="sk-circle3 sk-circle">asd</div>
          <div class="sk-circle4 sk-circle">asdfasf</div>
          <div class="sk-circle5 sk-circle">123</div>
          <div class="sk-circle6 sk-circle">aax</div>
          <div class="sk-circle7 sk-circle">sxz</div>
        </div>
  )";

  std::regex re(R"(<div class="sk[\w -]*">\w*</div>)");
  std::smatch match;
  while (std::regex_search(html, match, re)) {
    std::cout << match.str() << '\n';
    html = match.suffix();
  }
}
```

성공적으로 컴파일 하였다면

```exec
<div class="sk-circle1 sk-circle">a</div>
<div class="sk-circle2 sk-circle">b</div>
<div class="sk-circle3 sk-circle">asd</div>
<div class="sk-circle4 sk-circle">asdfasf</div>
<div class="sk-circle5 sk-circle">123</div>
<div class="sk-circle6 sk-circle">aax</div>
<div class="sk-circle7 sk-circle">sxz</div>
```

와 같이 잘 나옵니다.

```cpp-formatted
while (std::regex_search(html, match, re)) {
```

문자열에서 원하는 패턴을 검색하는 일은 `regex_search` 를 사용하면 됩니다. `regex_match` 처럼, 첫 번째에 검색을 하고픈 문자열을, 두 번째에 일치된 패턴을 보관할 `match` 객체를, 마지막 인자로 실제 정규 표현식 객체를 전달하면 됩니다. 만일 `html` 에서 정규 표현식과 매칭이 되는 패턴이 존재한다면 `regex_search` 가 `true` 를 리턴하게 되지요.

```cpp-formatted
std::cout << match.str() << '\n';
```

그리고 매칭된 패턴은 위와 같이 `match.str()` 을 통해서 접근할 수 있습니다. 우리의 `match` 가 `smatch` 의 객체 이므로 만들어진 `match.str()` 은 `string` 이 됩니다.

문제는 만일 그냥 `std::regex_search(html, match, re)` 를 다시 실행하게 된다면 그냥 이전에 찾았던 패턴을 다시 뱉을 것입니다. 따라서 우리는 `html` 을 업데이트 해서 검색된 패턴 바로 뒤 부터 다시 검색할 수 있도록 바꿔야합니다.

```cpp-formatted
html = match.suffix();
```

`match.suffix()` 를 하면 `std::sub_match` 객체를 리턴합니다. `sub_match` 는 단순히 어떠한 문자열의 시작과 끝을 가리키는 반복자 두 개를 가지고 있다고 보시면 됩니다. 이 때 `suffix` 의 경우, 원 문자열에서 검색된 패턴 바로 뒤 부터, 이전 문자열의 끝 까지에 해당하는 `sub_match` 객체를 리턴합니다.

![prefix 와 suffix 예시](/img/cpp/17.2.1.png)

이 때 `sub_match` 클래스에는 `string` 으로 변환할 수 있는 캐스팅 연산자가 들어 있습니다. 따라서 위와 같이 `html` 에 그냥 대입하게 되면 알아서 문자열로 변환되서 들어가게 됩니다. 덕분에 이미 찾은 패턴 뒤 부터 다시 새로운 검색을 시작할 수 있겠죠.

### std::regex_iterator

`regex_iterator` 를 사용하면 좀 더 편리하게 검색을 수행할 수 있습니다. 예를 들어서;

```cpp-formatted
#include <iostream>
#include <regex>

int main() {
  std::string html = R"(
        <div class="social-login">
          <div class="small-comment">다음으로 로그인 </div>
          <div>
            <i class="xi-facebook-official fb-login"></i>
            <i class="xi-google-plus goog-login"></i>
          </div>
        </div>
        <div class="manual">
          <div class="small-comment">
            또는 직접 입력하세요 (댓글 수정시 비밀번호가 필요합니다)
          </div>
          <input name="name" id="name" placeholder="이름">
          <input name="password" id="password" type="password" placeholder="비밀번호">
        </div>
        <div id="adding-comment" class="sk-fading-circle">
          <div class="sk-circle1 sk-circle">a</div>
          <div class="sk-circle2 sk-circle">b</div>
          <div class="sk-circle3 sk-circle">asd</div>
          <div class="sk-circle4 sk-circle">asdfasf</div>
          <div class="sk-circle5 sk-circle">123</div>
          <div class="sk-circle6 sk-circle">aax</div>
          <div class="sk-circle7 sk-circle">sxz</div>
        </div>
  )";

  std::regex re(R"(<div class="sk[\w -]*">\w*</div>)");
  std::smatch match;

  auto start = std::sregex_iterator(html.begin(), html.end(), re);
  auto end = std::sregex_iterator();

  while (start != end) {
    std::cout << start->str() << std::endl;
    ++start;
  }
}
```

성공적으로 컴파일 하였다면

```exec
<div class="sk-circle1 sk-circle">a</div>
<div class="sk-circle2 sk-circle">b</div>
<div class="sk-circle3 sk-circle">asd</div>
<div class="sk-circle4 sk-circle">asdfasf</div>
<div class="sk-circle5 sk-circle">123</div>
<div class="sk-circle6 sk-circle">aax</div>
<div class="sk-circle7 sk-circle">sxz</div>
```

와 같이 잘 나옵니다.

`std::regex_iterator` 는 주어진 문자열에서 정규 표현식으로 매칭된 문자열들을 쭈르륵 뽑아낼 수 있는 반복자 입니다.

```cpp-formatted
auto start = std::sregex_iterator(html.begin(), html.end(), re);
```

`regex_iterator` 의 경우 위와 같이 생성자를 호출함으로써 생성할 수 있습니다. 첫 번째와 두 번째 인자로 검색을 수행할 문자열의 시작과 끝을 전달하고, 마지막 인자로 사용하고픈 정규 표현식 객체를 전달하면 됩니다. 참고로 `sregex_iterator` 는 `regex_iterator` 중에서 `string` 을 사용하는 반복자 입니다.

`regex_iterator` 의 경우 처음 생성될 때와, `++` 연산자로 증감 될 때마다 `regex_search` 를 통해 초기에 주어진 문자열 범위 내에서 패턴에 맞는 문자열을 검색합니다. 또한 `*` 연산자를 통해서 역참조 하게 된다면 현재 검색된 패턴에 대한 `match_results` 객체를 얻어낼 수 있습니다.

따라서 아래와 같이 간단하게 패턴과 매칭되는 문자열들을 뽑아낼 수 있게 됩니다.

```cpp-formatted
while (start != end) {
  std::cout << start->str() << std::endl;
  ++start;
}
```

### 원하는 패턴 치환하기

마지막으로 살펴볼 기능은 정규 표현식을 통해서 원하는 패턴의 문자열을 다른 문자열로 치환(replace)하는 작업입니다. 해당 작업은 `std::regex_replace` 를 통해서 구현할 수 있습니다.

예를 들어서 `html` 에서 `sk-circle1` 과 같은 문자열을 `1-sk-circle` 로 바꿔보는 정규 표현식을 생각해봅시다. 이를 위해서 먼저 `sk-circle1` 과 같은 형태를 어떤 `regex` 로 매칭 시킬지 생각해야 하는데 이는 간단히

```info
sk-circle\d
```

로 할 수 있습니다. 그 다음에, 이를 어떠한 형태로 치환할지를 생각해야 합니다. 간단히 생각해서

```info
숫자-sk-circle
```

로 해야 하는데, 문제는 위 *숫자* 에 해당하는 부분이 매칭된 패턴에 `\d` 에 해당하는 부분이라는 점입니다. 하지만 다행이도, 캡쳐 그룹을 이용하면 이를 간단히 해결할 수 있습니다. 먼저

```info
sk-circle(\d)
```

를 통해서 숫자에 해당하는 부분을 첫 번째 캡쳐그룹으로 만듧니다. 그 다음에, 치환을 할 때 첫 번째 캡쳐 그룹을 표현하기 위해 `$1` 라고 명시할 수 있습니다. (이와 같은 요소를 *back reference* 라고 부릅니다.) 따라서

```info
$1-sk-circle
```

과 같이 표현할 수 있습니다.

이를 바탕으로 C++ 코드를 작성해봅시다.

```cpp-formatted
#include <iostream>
#include <regex>

int main() {
  std::string html = R"(
        <div class="social-login">
          <div class="small-comment">다음으로 로그인 </div>
          <div>
            <i class="xi-facebook-official fb-login"></i>
            <i class="xi-google-plus goog-login"></i>
          </div>
        </div>
        <div class="manual">
          <div class="small-comment">
            또는 직접 입력하세요 (댓글 수정시 비밀번호가 필요합니다)
          </div>
          <input name="name" id="name" placeholder="이름">
          <input name="password" id="password" type="password" placeholder="비밀번호">
        </div>
        <div id="adding-comment" class="sk-fading-circle">
          <div class="sk-circle1 sk-circle">a</div>
          <div class="sk-circle2 sk-circle">b</div>
          <div class="sk-circle3 sk-circle">asd</div>
          <div class="sk-circle4 sk-circle">asdfasf</div>
          <div class="sk-circle5 sk-circle">123</div>
          <div class="sk-circle6 sk-circle">aax</div>
          <div class="sk-circle7 sk-circle">sxz</div>
        </div>
  )";

  std::regex re(R"r(sk-circle(\d))r");
  std::smatch match;

  std::string modified_html = std::regex_replace(html, re, "$1-sk-circle");
  std::cout << modified_html;
}
```

성공적으로 컴파일 하였다면

```exec
<div class="social-login">
  <div class="small-comment">다음으로 로그인 </div>
  <div>
    <i class="xi-facebook-official fb-login"></i>
    <i class="xi-google-plus goog-login"></i>
  </div>
</div>
<div class="manual">
  <div class="small-comment">
    또는 직접 입력하세요 (댓글 수정시 비밀번호가 필요합니다)
  </div>
  <input name="name" id="name" placeholder="이름">
  <input name="password" id="password" type="password" placeholder="비밀번호">
</div>
<div id="adding-comment" class="sk-fading-circle">
  <div class="1-sk-circle sk-circle">a</div>
  <div class="2-sk-circle sk-circle">b</div>
  <div class="3-sk-circle sk-circle">asd</div>
  <div class="4-sk-circle sk-circle">asdfasf</div>
  <div class="5-sk-circle sk-circle">123</div>
  <div class="6-sk-circle sk-circle">aax</div>
  <div class="7-sk-circle sk-circle">sxz</div>
</div>
```

와 같이 잘 나옵니다.

```cpp-formatted
std::regex_replace(html, re, "$1-sk-circle");
```

`regex_replace` 로 문자열을 치환하고 싶다면 첫 번째 인자로 치환하고자 하는 문자열을, 두 번째 인자로 정규 표현식 객체를, 마지막으로 치환 시에 어떠한 패턴으로 바꿀 지 적어주면 됩니다. `regex_replace` 의 오버로딩 형태는 여러가지가 있는데 위 형태의 경우 치환된 문자열을 생성해서 돌려줍니다.

만약에 치환된 문자열을 생성하지 않고 그냥 `stdout` 에 출력하고 싶다면;

```cpp-formatted
std::regex_replace(std::ostreambuf_iterator<char>(std::cout), html.begin(),
                   html.end(), re, "$1-sk-circle");
```

와 같이 쓰면 됩니다. 이 경우 `std::regex_replace` 의 첫 번째 인자로 출력할 위치의 시작점을 가리키는 반복자를 넣어주면 됩니다.

#### 중첩된 캡쳐 그룹

만약에 더 나아가서 다음과 같이 치환을 하고 싶다 해봅시다.

```info
          <div class="sk-circle1 sk-circle">a</div>
```

에서

```info
          <div class="1-sk-circle">a</div>
```

로 말이지요. 즉 뒷 부분의 `sk-circle` 을 완전히 날려버리는 것입니다. 이를 위해서 두 개의 캡쳐그룹이 필요합니다. 일단, "" 안에 전체 문자열을 건드려야 하기 때문에

```info
(sk-circle\d sk-circle)
```

과 같이 전체 패턴에 해당하는 캡쳐 그룹이 필요하지요. 그 다음에, 숫자 부분만 다시 뽑아내야 하므로

```info
(sk-circle(\d) sk-circle)
```

와 같이 해야 됩니다. 문제는 위와 같이 캡쳐 그룹이 중첩되었을 때 어느 것이 $1 이고 $2 인지 알아야 하는데, **괄호가 열리는 순서대로** $1, $2, ... 로 진행한다고 생각하면 됩니다. 즉 해당 패턴 전체 캡쳐 그룹이 $1 이고 `\d` 에 해당 하는 캡쳐 그룹이 $2 가 되죠.

따라서 치환될 패턴은

```info
$2-sk-circle
```

이 됨을 알 수 있겠네요. 실제로 코드를 작성해보면

```cpp-formatted
#include <iostream>
#include <regex>

int main() {
  std::string html = R"(
        <div class="social-login">
          <div class="small-comment">다음으로 로그인 </div>
          <div>
            <i class="xi-facebook-official fb-login"></i>
            <i class="xi-google-plus goog-login"></i>
          </div>
        </div>
        <div class="manual">
          <div class="small-comment">
            또는 직접 입력하세요 (댓글 수정시 비밀번호가 필요합니다)
          </div>
          <input name="name" id="name" placeholder="이름">
          <input name="password" id="password" type="password" placeholder="비밀번호">
        </div>
        <div id="adding-comment" class="sk-fading-circle">
          <div class="sk-circle1 sk-circle">a</div>
          <div class="sk-circle2 sk-circle">b</div>
          <div class="sk-circle3 sk-circle">asd</div>
          <div class="sk-circle4 sk-circle">asdfasf</div>
          <div class="sk-circle5 sk-circle">123</div>
          <div class="sk-circle6 sk-circle">aax</div>
          <div class="sk-circle7 sk-circle">sxz</div>
        </div>
  )";

  std::regex re(R"r((sk-circle(\d) sk-circle))r");
  std::smatch match;

  std::string modified_html = std::regex_replace(html, re, "$2-sk-circle");
  std::cout << modified_html;
}
```

성공적으로 컴파일 하였다면

```exec
<div class="social-login">
  <div class="small-comment">다음으로 로그인 </div>
  <div>
    <i class="xi-facebook-official fb-login"></i>
    <i class="xi-google-plus goog-login"></i>
  </div>
</div>
<div class="manual">
  <div class="small-comment">
    또는 직접 입력하세요 (댓글 수정시 비밀번호가 필요합니다)
  </div>
  <input name="name" id="name" placeholder="이름">
  <input name="password" id="password" type="password" placeholder="비밀번호">
</div>
<div id="adding-comment" class="sk-fading-circle">
  <div class="1-sk-circle">a</div>
  <div class="2-sk-circle">b</div>
  <div class="3-sk-circle">asd</div>
  <div class="4-sk-circle">asdfasf</div>
  <div class="5-sk-circle">123</div>
  <div class="6-sk-circle">aax</div>
  <div class="7-sk-circle">sxz</div>
</div>
```

와 같이 잘 바뀌었음을 알 수 있습니다.

자 그럼 이것으로 C++ 정규 표현식 라이브러리를 어떻게 사용하는지 간단하게 알아보았습니다. 다음 강좌에서는 `C++ 11` 에서 새롭게 추가된 난수 생성 라이브러리 (`<random>`) 과 시간 관련 라이브러리 (`<chrono>`) 를 살펴보도록 하겠습니다.

```lec-summary
`std::regex` 를 통해서 정규 표현식 객체를 생성할 수 있습니다.
`std::regex_match` 로 전체 문자열이 패턴을 만족하는지 확인할 수 있습니다.
`std::regex_search` 를 통해 패턴에 맞는 문자열을 검색할 수 있습니다.
`std::regex_replace` 를 통해 원하는 패턴의 문자열을 다른 문자열로 치환할 수 있습니다.
```

##@ chewing-cpp-end