C++ Regex | "정규 표현식" 가이드
이 글의 핵심
C++ Regex에 대한 실전 가이드입니다.
들어가며
**C++11 <regex>**는 정규 표현식을 지원하는 표준 라이브러리입니다. 패턴 매칭, 검색, 치환 등을 통해 문자열 처리를 강력하고 유연하게 수행할 수 있습니다.
1. Regex 기본
기본 사용
정규 표현식으로 이메일 형식을 검증하는 간단한 예제입니다:
#include <regex>
#include <iostream>
#include <string>
int main() {
std::string text = "[email protected]";
// 정규 표현식 패턴 생성
// R"(...)" : Raw 문자열 리터럴 (이스케이프 불필요)
// (\w+) : 첫 번째 캡처 그룹 - 하나 이상의 단어 문자 (알파벳, 숫자, _)
// @ : 리터럴 @ 문자
// (\w+\.\w+) : 두 번째 캡처 그룹 - 도메인 (예: example.com)
// \w+ : 하나 이상의 단어 문자
// \. : 리터럴 점 (. 은 특수문자이므로 이스케이프 필요)
// \w+ : 하나 이상의 단어 문자
std::regex pattern{R"((\w+)@(\w+\.\w+))"};
// regex_match: 전체 문자열이 패턴과 일치하는지 확인
// 반환값: bool (일치하면 true, 아니면 false)
if (std::regex_match(text, pattern)) {
std::cout << "이메일 형식입니다" << std::endl;
}
return 0;
}
패턴 해석:
\w: 단어 문자 (a-z, A-Z, 0-9, _)+: 1개 이상 반복\.: 리터럴 점 (이스케이프 필요)(): 캡처 그룹 (나중에 추출 가능)
Raw 문자열 리터럴
#include <regex>
// ❌ 이스케이프 지옥
std::regex pattern1{"\\d+\\.\\d+"};
// ✅ Raw 문자열 (권장)
std::regex pattern2{R"(\d+\.\d+)"};
// 복잡한 패턴
std::regex email{R"(^[\w\.-]+@[\w\.-]+\.\w+$)"};
2. Regex 함수
regex_match (전체 매치)
regex_match는 전체 문자열이 패턴과 정확히 일치해야 true를 반환합니다:
#include <regex>
#include <iostream>
int main() {
std::string text1 = "123";
std::string text2 = "abc123";
// \d+ : 하나 이상의 숫자
std::regex pattern{R"(\d+)"};
// text1 = "123" : 전체가 숫자 → 매치 성공
std::cout << std::regex_match(text1, pattern) << std::endl; // 1 (true)
// text2 = "abc123" : "abc"가 있어서 전체가 숫자가 아님 → 매치 실패
std::cout << std::regex_match(text2, pattern) << std::endl; // 0 (false)
// regex_match는 문자열 처음부터 끝까지 전체가 패턴과 일치해야 함
// 부분 일치는 인정하지 않음
return 0;
}
사용 시나리오:
- 입력 검증: 전화번호, 이메일, 우편번호 등이 정확한 형식인지 확인
- 파일 확장자 검사: 파일명 전체가 특정 패턴인지 확인
regex_search (부분 매치)
regex_search는 문자열 어딘가에 패턴이 있으면 true를 반환합니다:
#include <regex>
#include <iostream>
int main() {
std::string text = "C++ 2026";
// \d+ : 하나 이상의 숫자
std::regex pattern{R"(\d+)"};
// "C++ 2026"에서 "2026" 부분이 패턴과 일치 → 성공
if (std::regex_search(text, pattern)) {
std::cout << "숫자 발견" << std::endl; // 출력됨
}
// regex_search는 문자열 전체를 순회하며 패턴을 찾음
// 처음 발견된 매치를 반환
// 여러 매치를 찾으려면 sregex_iterator 사용
return 0;
}
regex_match vs regex_search 비교:
| 함수 | 동작 | 예시 |
|---|---|---|
regex_match | 전체 문자열이 패턴과 일치 | "123" → \d+ → true"abc123" → \d+ → false |
regex_search | 부분 문자열이 패턴과 일치 | "123" → \d+ → true"abc123" → \d+ → true |
사용 시나리오:
- 로그 파싱: 로그 파일에서 에러 메시지 찾기
- 텍스트 검색: 문서에서 특정 패턴 찾기
- 데이터 추출: HTML/XML에서 태그 내용 추출
regex_replace (치환)
#include <regex>
#include <iostream>
int main() {
std::string text = "Hello World 2026";
std::regex pattern{R"(\d+)"};
// 숫자를 [숫자]로 치환
std::string result = std::regex_replace(text, pattern, "[$&]");
std::cout << result << std::endl; // Hello World [2026]
return 0;
}
3. 캡처 그룹
기본 캡처
#include <regex>
#include <iostream>
int main() {
std::string text = "2026-03-29";
std::regex pattern{R"((\d{4})-(\d{2})-(\d{2}))"};
std::smatch matches;
if (std::regex_match(text, matches, pattern)) {
std::cout << "전체: " << matches[0] << std::endl; // 2026-03-29
std::cout << "년: " << matches[1] << std::endl; // 2026
std::cout << "월: " << matches[2] << std::endl; // 03
std::cout << "일: " << matches[3] << std::endl; // 29
}
return 0;
}
이름있는 캡처 (C++11에서는 미지원)
#include <regex>
#include <iostream>
int main() {
std::string text = "[email protected]";
std::regex pattern{R"((\w+)@(\w+\.\w+))"};
std::smatch matches;
if (std::regex_match(text, matches, pattern)) {
std::string username = matches[1];
std::string domain = matches[2];
std::cout << "사용자: " << username << std::endl; // user
std::cout << "도메인: " << domain << std::endl; // example.com
}
return 0;
}
4. 반복자 (Iterator)
모든 매치 찾기
#include <regex>
#include <iostream>
#include <string>
int main() {
std::string text = "C++ 11, C++ 14, C++ 17, C++ 20";
std::regex pattern{R"(C\+\+ (\d+))"};
// 모든 매치 반복
auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
std::smatch match = *it;
std::cout << "전체: " << match.str() << std::endl;
std::cout << "버전: " << match[1] << std::endl;
}
// 출력:
// 전체: C++ 11
// 버전: 11
// 전체: C++ 14
// 버전: 14
// ...
return 0;
}
토큰 반복자
#include <regex>
#include <iostream>
int main() {
std::string text = "apple,banana,cherry";
std::regex pattern{R"(,)"};
// 구분자로 분리
std::sregex_token_iterator begin(text.begin(), text.end(), pattern, -1);
std::sregex_token_iterator end;
for (auto it = begin; it != end; ++it) {
std::cout << *it << std::endl;
}
// 출력:
// apple
// banana
// cherry
return 0;
}
5. 실전 예제
예제 1: 이메일 검증
#include <regex>
#include <iostream>
#include <string>
bool isValidEmail(const std::string& email) {
// 이메일 패턴 (간단 버전)
std::regex pattern{R"(^[\w\.-]+@[\w\.-]+\.\w{2,}$)"};
return std::regex_match(email, pattern);
}
int main() {
std::string emails[] = {
"[email protected]",
"invalid.email",
"[email protected]",
"@example.com"
};
for (const auto& email : emails) {
std::cout << email << ": "
<< (isValidEmail(email) ? "유효" : "무효")
<< std::endl;
}
return 0;
}
예제 2: URL 파싱
#include <regex>
#include <iostream>
struct URL {
std::string protocol;
std::string domain;
std::string path;
};
URL parseURL(const std::string& url) {
std::regex pattern{R"(^(https?)://([^/]+)(/.*)?$)"};
std::smatch matches;
if (std::regex_match(url, matches, pattern)) {
return {
matches[1].str(), // protocol
matches[2].str(), // domain
matches[3].str() // path
};
}
return {};
}
int main() {
std::string url = "https://example.com/path/to/page";
URL parsed = parseURL(url);
std::cout << "프로토콜: " << parsed.protocol << std::endl;
std::cout << "도메인: " << parsed.domain << std::endl;
std::cout << "경로: " << parsed.path << std::endl;
return 0;
}
예제 3: 로그 파싱
#include <regex>
#include <iostream>
#include <string>
#include <vector>
struct LogEntry {
std::string timestamp;
std::string level;
std::string message;
};
std::vector<LogEntry> parseLog(const std::string& log) {
std::vector<LogEntry> entries;
// 로그 패턴: [2026-03-29 14:30:25] [INFO] Message
std::regex pattern{R"(\[([^\]]+)\] \[(\w+)\] (.+))"};
std::istringstream stream(log);
std::string line;
while (std::getline(stream, line)) {
std::smatch matches;
if (std::regex_match(line, matches, pattern)) {
entries.push_back({
matches[1].str(), // timestamp
matches[2].str(), // level
matches[3].str() // message
});
}
}
return entries;
}
int main() {
std::string log =
"[2026-03-29 14:30:25] [INFO] Server started\n"
"[2026-03-29 14:30:26] [ERROR] Connection failed\n"
"[2026-03-29 14:30:27] [DEBUG] Retrying...";
auto entries = parseLog(log);
for (const auto& entry : entries) {
std::cout << entry.timestamp << " [" << entry.level << "] "
<< entry.message << std::endl;
}
return 0;
}
6. 자주 발생하는 문제
문제 1: 이스케이프
#include <regex>
// ❌ 이스케이프 지옥
std::regex pattern1{"\\d+\\.\\d+"};
// ✅ Raw 문자열 리터럴
std::regex pattern2{R"(\d+\.\d+)"};
// 복잡한 패턴도 간결
std::regex email{R"(^[\w\.-]+@[\w\.-]+\.\w+$)"};
문제 2: 성능
#include <regex>
#include <iostream>
#include <chrono>
#include <vector>
int main() {
std::vector<std::string> texts = {"test1", "test2", "test3"};
// ❌ 매번 regex 생성 (느림)
auto start1 = std::chrono::high_resolution_clock::now();
for (const auto& text : texts) {
std::regex pattern{R"(\d+)"}; // 매번 생성!
std::regex_search(text, pattern);
}
auto end1 = std::chrono::high_resolution_clock::now();
// ✅ regex 재사용 (빠름)
auto start2 = std::chrono::high_resolution_clock::now();
std::regex pattern{R"(\d+)"}; // 한 번만 생성
for (const auto& text : texts) {
std::regex_search(text, pattern);
}
auto end2 = std::chrono::high_resolution_clock::now();
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(end1 - start1).count();
auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(end2 - start2).count();
std::cout << "매번 생성: " << duration1 << " μs" << std::endl;
std::cout << "재사용: " << duration2 << " μs" << std::endl;
return 0;
}
해결책: std::regex 객체를 재사용하세요.
문제 3: 예외 처리
#include <regex>
#include <iostream>
int main() {
try {
// ❌ 잘못된 정규식
std::regex pattern{"[invalid"}; // std::regex_error
} catch (const std::regex_error& e) {
std::cerr << "정규식 에러: " << e.what() << std::endl;
std::cerr << "에러 코드: " << e.code() << std::endl;
}
return 0;
}
문제 4: 플래그
#include <regex>
#include <iostream>
int main() {
std::string text = "Hello World";
// 대소문자 구분 (기본)
std::regex pattern1{"hello"};
std::cout << std::regex_search(text, pattern1) << std::endl; // 0 (false)
// 대소문자 무시
std::regex pattern2{"hello", std::regex::icase};
std::cout << std::regex_search(text, pattern2) << std::endl; // 1 (true)
return 0;
}
7. 정규식 문법 (ECMAScript)
문자 클래스
\d // 숫자 [0-9]
\D // 비숫자
\w // 단어 문자 [a-zA-Z0-9_]
\W // 비단어 문자
\s // 공백 [ \t\n\r\f\v]
\S // 비공백
. // 모든 문자 (개행 제외)
수량자
* // 0개 이상
+ // 1개 이상
? // 0 또는 1개
{n} // 정확히 n개
{n,} // n개 이상
{n,m} // n개 이상 m개 이하
앵커
^ // 문자열 시작
$ // 문자열 끝
\b // 단어 경계
\B // 비단어 경계
그룹
() // 캡처 그룹
(?:) // 비캡처 그룹
| // OR
[] // 문자 클래스
8. 실전 예제: 텍스트 처리 유틸리티
#include <regex>
#include <iostream>
#include <string>
#include <vector>
class TextProcessor {
public:
// 이메일 추출
static std::vector<std::string> extractEmails(const std::string& text) {
std::vector<std::string> emails;
std::regex pattern{R"([\w\.-]+@[\w\.-]+\.\w+)"};
auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
emails.push_back(it->str());
}
return emails;
}
// 전화번호 추출 (한국 형식)
static std::vector<std::string> extractPhones(const std::string& text) {
std::vector<std::string> phones;
std::regex pattern{R"(\d{2,3}-\d{3,4}-\d{4})"};
auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
phones.push_back(it->str());
}
return phones;
}
// HTML 태그 제거
static std::string removeHTMLTags(const std::string& html) {
std::regex pattern{R"(<[^>]*>)"};
return std::regex_replace(html, pattern, "");
}
// 연속 공백 제거
static std::string normalizeSpaces(const std::string& text) {
std::regex pattern{R"(\s+)"};
return std::regex_replace(text, pattern, " ");
}
// URL 검증
static bool isValidURL(const std::string& url) {
std::regex pattern{R"(^https?://[\w\.-]+\.\w{2,}(/.*)?$)"};
return std::regex_match(url, pattern);
}
};
int main() {
std::string text = "Contact: [email protected] or call 010-1234-5678";
// 이메일 추출
auto emails = TextProcessor::extractEmails(text);
std::cout << "이메일:" << std::endl;
for (const auto& email : emails) {
std::cout << " " << email << std::endl;
}
// 전화번호 추출
auto phones = TextProcessor::extractPhones(text);
std::cout << "전화번호:" << std::endl;
for (const auto& phone : phones) {
std::cout << " " << phone << std::endl;
}
// HTML 태그 제거
std::string html = "<p>Hello <b>World</b></p>";
std::string plain = TextProcessor::removeHTMLTags(html);
std::cout << "태그 제거: " << plain << std::endl;
// 공백 정규화
std::string messy = "Hello World !";
std::string clean = TextProcessor::normalizeSpaces(messy);
std::cout << "공백 정규화: " << clean << std::endl;
// URL 검증
std::cout << "URL 검증: "
<< (TextProcessor::isValidURL("https://example.com") ? "유효" : "무효")
<< std::endl;
return 0;
}
정리
핵심 요약
- regex: C++11 정규 표현식 라이브러리
- regex_match: 전체 문자열 매치
- regex_search: 부분 문자열 검색
- regex_replace: 패턴 치환
- 캡처 그룹:
()로 부분 추출 - 반복자: 모든 매치 찾기
Regex 함수 비교
| 함수 | 용도 | 반환 타입 | 사용 시기 |
|---|---|---|---|
regex_match | 전체 매치 | bool | 검증 (이메일, URL) |
regex_search | 부분 매치 | bool | 검색 (숫자 찾기) |
regex_replace | 치환 | string | 변환 (마스킹, 정리) |
sregex_iterator | 모든 매치 | 반복자 | 추출 (모든 이메일) |
실전 팁
성능:
std::regex객체 재사용 (생성 비용 큼)- 간단한 패턴은
std::string함수 사용 - 대량 처리는 전용 라이브러리 고려 (RE2, PCRE)
가독성:
- Raw 문자열 리터럴 사용 (
R"(...)") - 복잡한 패턴은 주석으로 설명
- 캡처 그룹에 의미있는 변수명
안전성:
- 예외 처리 (
std::regex_error) - 입력 검증 (길이 제한)
- 타임아웃 고려 (복잡한 패턴)
다음 단계
- C++ Regex Iterator
- C++ String
- C++ Algorithm Replace
관련 글
- C++ 정규표현식 |