C++ 문자열 완벽 가이드 | SSO·string_view 성능 비교·C 문자열 연동
💡 핵심 개념: 두
const char*를==로 비교하면 내용이 아니라 주소다. 내용 비교는std::string의==또는strcmp. 읽기만 하는 함수 인자는string_view를 우선 떠올리라. 11-1 파일 I/O와 순서 바꿔 읽어도 무방하다.
들어가며: 문자열 비교에서 == 쓰하다가 크래시
”C 문자열 두 개를 ==로 비교했는데 항상 같다고 나와요”
C 스타일 문자열(char* 또는 const char*)을 사용하는 레거시 API와 연동할 때, ==로 비교하면 포인터 주소가 비교됩니다. 문자열 내용이 아니라 “같은 메모리 주소를 가리키는지”만 확인하게 됩니다.
자주 겪는 문제 시나리오들
strcmp 반환값 잘못 해석
strcmp는 “같으면 0, 작으면 음수, 크면 양수”를 반환하는데, if (strcmp(a, b))처럼 쓰면 “같을 때” false가 됩니다. C++에서 if (x)는 x가 0이 아니면 true이므로, strcmp가 0(같음)을 반환할 때 false가 되어 의도와 반대입니다.
문자열 연결 시 성능 저하
for 루프 안에서 result += str를 반복하면, 매번 재할당이 발생할 수 있습니다. reserve로 미리 공간을 잡아 두지 않으면 작은 문자열을 수천 번 연결할 때 수 초가 걸릴 수 있습니다.
substr로 잘라낸 뒤 원본 수정
std::string_view로 받은 문자열의 일부를 가리킬 때, 원본이 수정되거나 삭제되면 댕글링 참조가 됩니다. string_view는 복사하지 않고 “보기만” 하므로, 참조하는 메모리가 유효한 동안만 사용해야 합니다.
C API에 std::string 넘기기
printf("%s", str)처럼 C 함수에 std::string을 넘기면 컴파일 에러입니다. C API는 const char*를 기대하므로 str.c_str()를 사용해야 합니다.
정의를 풀어 쓰면 std::string은 C++ 표준 문자열 클래스로, 크기가 자동으로 늘어나고 메모리를 자동 관리합니다. string_view(C++17)는 문자열을 복사하지 않고 “보기만” 하는 경량 뷰입니다. 비유하면 std::string은 “내가 소유한 노트”이으며, string_view는 “노트의 특정 페이지를 가리키는 북마크”입니다.
문제의 코드:
const char* a = "hello";
const char* b = "hello";
if (a == b) { // ❌ 포인터 비교! 내용은 같아도 주소가 다를 수 있음
std::cout << "Same\n";
}
위 코드 설명: a와 b는 서로 다른 메모리 주소를 가리킬 수 있습니다. 컴파일러가 문자열 리터럴을 “같은 주소”로 합치는 최적화(string pooling)를 할 수도 있지만, 런타임에 만든 문자열이나 다른 번역 단위에선 보장되지 않습니다. 내용 비교가 목적이면 strcmp를 써야 합니다. 올바른 비교:
#include <cstring>
// 변수 선언 및 초기화
const char* a = "hello";
const char* b = "hello";
if (strcmp(a, b) == 0) { // ✅ 내용 비교
std::cout << "Same\n";
}
이 글을 읽으면
- std::string의 주요 연산을 이해할 수 있습니다.
- C 문자열과 std::string의 비교·변환을 올바르게 할 수 있습니다.
- string_view로 불필요한 복사를 제거할 수 있습니다.
- 자주 겪는 에러와 해결법을 알 수 있습니다.
- 프로덕션에서의 문자열 패턴을 배울 수 있습니다. 문자열 타입 선택을 요약하면 아래와 같습니다.
flowchart TB
subgraph choice[문자열 타입 선택]
A[문자열이 필요할 때] --> B{용도}
B -->|소유·수정 필요| C["std string"]
B -->|읽기만·함수 인자| D["std string_view"]
B -->|C API 연동| E["const char*"]
end
subgraph caution[주의]
C --> F["reserve로 재할당 최소화"]
D --> G["원본 수명 확인"]
E --> H["strcmp로 비교"]
end
1. 문제 시나리오: 실제로 겪는 문자열 문제들
시나리오 1: 로그 파싱에서 “user”와 “User”가 같은 것으로 처리됨
대소문자를 구분하지 않는 비교가 필요할 때, ==는 대소문자를 구분합니다. "user" == "User"는 false입니다.
해결: std::transform으로 소문자 변환 후 비교하거나, strcasecmp(POSIX)를 사용합니다.
// 복사해 연결하여넣은 뒤: g++ -std=c++17 -o case_compare case_compare.cpp && ./case_compare
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
bool equalsIgnoreCase(const std::string& a, const std::string& b) {
if (a.size() != b.size()) return false;
return std::equal(a.begin(), a.end(), b.begin(),
{
return std::tolower(static_cast<unsigned char>(ca)) ==
std::tolower(static_cast<unsigned char>(cb));
});
}
int main() {
std::string a = "user";
std::string b = "User";
std::cout << (equalsIgnoreCase(a, b) ? "Same" : "Different") << "\n";
return 0;
}
실행 결과:
Same
시나리오 2: JSON 키에서 “null” 문자열과 실제 null 구분 실패
파싱된 JSON에서 "null"(문자열)과 null(JSON null 값)을 구분해야 할 때, 문자열 비교를 잘못하면 혼동됩니다.
std::string value = getJsonString(key);
if (value == "null") { // JSON의 null 문자열
// ...
}
// value가 비어 있거나 파싱 실패와 구분 필요
시나리오 3: HTTP 헤더에서 “Content-Type” vs “content-type”
HTTP 헤더는 대소문자를 구분하지 않는 경우가 많습니다. ==로 비교하면 “Content-Type”과 “content-type”이 다르게 처리됩니다.
시나리오 4: 파일 경로에서 슬래시/백슬래시 혼용
Windows 경로 "C:\\Users\\file.txt"와 Linux 스타일 "C:/Users/file.txt"를 같은 것으로 처리하려면 정규화가 필요합니다.
2. std::string 완전 가이드
기본 생성과 초기화
// 복사해 연결하여넣은 뒤: g++ -std=c++17 -o string_basic string_basic.cpp && ./string_basic
#include <iostream>
#include <string>
int main() {
std::string s1; // 빈 문자열
std::string s2("hello"); // C 문자열로 초기화
std::string s3 = "world"; // 복사 초기화
std::string s4(5, 'a'); // 'a' 5개: "aaaaa"
std::string s5(s2, 1, 3); // s2의 1번 인덱스부터 3글자: "ell"
std::string s6(s2.begin(), s2.end()); // 반복자 범위
std::cout << s1 << "|" << s2 << "|" << s4 << "\n";
return 0;
}
실행 결과:
|hello|aaaaa
위 코드 설명: s1은 빈 문자열, s2는 C 문자열 리터럴로 초기화, s4는 (개수, 문자)로 반복 문자, s5는 (문자열, 시작위치, 길이)로 부분 문자열을 만듭니다.
주요 연산: 연결, 추가, 삽입
#include <string>
#include <iostream>
int main() {
std::string a = "Hello";
std::string b = "World";
// 연결: + 연산자 (새 문자열 반환)
std::string c = a + " " + b; // "Hello World"
std::string d = a + std::string("!"); // "Hello!"
// 추가: += (기존 문자열 수정)
a += " "; // a = "Hello "
a += b; // a = "Hello World"
// append
std::string e = "Hi";
e.append(" there"); // "Hi there"
e.append(3, '!'); // "Hi there!!!"
// push_back: 문자 하나 추가
e.push_back('?'); // "Hi there!!!"
std::cout << c << "\n" << a << "\n" << e << "\n";
return 0;
}
실행 결과:
Hello World
Hello World
Hi there!!!
주의: + 연산자는 새 문자열을 반환하므로 임시 객체가 생성됩니다. 루프 안에서 result = result + piece를 반복하면 매번 새 문자열이 만들어져 비효율적입니다. += 또는 append를 사용하면 기존 버퍼에 추가합니다.
검색과 치환
#include <string>
#include <iostream>
int main() {
std::string s = "Hello World, Hello C++";
// find: 처음 발견 위치 (없으면 npos)
size_t pos = s.find("Hello");
std::cout << "First 'Hello' at: " << pos << "\n"; // 0
pos = s.find("Hello", 1); // 1번 인덱스부터 검색
std::cout << "Second 'Hello' at: " << pos << "\n"; // 12
// rfind: 처음부터 역순 검색 (마지막 발견 위치)
pos = s.rfind("Hello");
std::cout << "Last 'Hello' at: " << pos << "\n"; // 12
// find_first_of: 문자 집합 중 하나라도 처음
pos = s.find_first_of("aeiou");
std::cout << "First vowel at: " << pos << "\n"; // 1 (e)
// replace: 치환
s.replace(0, 5, "Hi"); // 0번부터 5글자를 "Hi"로
std::cout << s << "\n"; // "Hi World, Hello C++"
return 0;
}
실행 결과:
First 'Hello' at: 0
Second 'Hello' at: 12
Last 'Hello' at: 12
First vowel at: 1
Hi World, Hello C++
substr: 부분 문자열 추출
#include <string>
#include <iostream>
int main() {
std::string s = "Hello World";
// substr(시작): 시작부터 끝까지
std::string sub1 = s.substr(6); // "World"
// substr(시작, 길이): 시작부터 지정 길이
std::string sub2 = s.substr(0, 5); // "Hello"
// substr은 새 문자열을 반환 (복사)
std::cout << sub1 << " " << sub2 << "\n";
return 0;
}
실행 결과:
World Hello
비교 연산
#include <string>
#include <iostream>
int main() {
std::string a = "apple";
std::string b = "banana";
// ==, !=, <, <=, >, >= 모두 지원
std::cout << (a == b) << "\n"; // 0 (false)
std::cout << (a < b) << "\n"; // 1 (true, 사전순)
// compare: 0=같음, <0=a가 작음, >0=a가 큼
int cmp = a.compare(b);
std::cout << "compare: " << cmp << "\n";
// C 문자열과도 비교 가능
std::cout << (a == "apple") << "\n"; // 1 (true)
return 0;
}
실행 결과:
1
compare: -1
1
숫자 변환 (C++11)
#include <string>
#include <iostream>
int main() {
// 문자열 → 숫자
std::string s1 = "42";
int i = std::stoi(s1);
std::string s2 = "3.14";
double d = std::stod(s2);
// 숫자 → 문자열 (C++11)
std::string s3 = std::to_string(42);
std::string s4 = std::to_string(3.14);
std::cout << i << " " << d << " " << s3 << " " << s4 << "\n";
return 0;
}
실행 결과:
42 3.14 42 3.140000
주의: stoi는 변환 실패 시 std::invalid_argument 또는 std::out_of_range 예외를 던집니다. 사용자 입력처럼 잘못된 값이 자주 들어오는 경로에서는 std::from_chars(C++17) 또는 strtol+에러 검사가 안전합니다.
3. C 문자열 비교와 변환
strcmp 사용법
// 복사해 연결하여넣은 뒤: g++ -std=c++17 -o strcmp_demo strcmp_demo.cpp && ./strcmp_demo
#include <iostream>
#include <cstring>
int main() {
const char* a = "apple";
const char* b = "banana";
const char* c = "apple";
// strcmp(a, b): a < b 이면 음수, a == b 이면 0, a > b 이면 양수
std::cout << "strcmp(a,b): " << strcmp(a, b) << "\n"; // 음수
std::cout << "strcmp(a,c): " << strcmp(a, c) << "\n"; // 0
// 올바른 비교: == 0일 때 "같음"
if (strcmp(a, c) == 0) {
std::cout << "a and c are equal\n";
}
// ❌ 잘못된 사용: strcmp(a,c)가 0이면 false
// if (strcmp(a, c)) { ... } // "다를 때" 실행됨!
return 0;
}
실행 결과:
strcmp(a,b): -1
strcmp(a,c): 0
a and c are equal
strncmp: 길이 제한 비교
#include <cstring>
#include <iostream>
int main() {
const char* a = "apple";
const char* b = "application";
// 앞 3글자만 비교
if (strncmp(a, b, 3) == 0) {
std::cout << "First 3 chars match\n";
}
// strncmp는 null 종료 전까지만 비교
// strncmp("app", "apple", 5) → "app"은 3글자만 있으므로 3글자까지만 비교
return 0;
}
strcasecmp: 대소문자 무시 비교 (POSIX)
#include <cstring>
#include <iostream>
int main() {
const char* a = "Hello";
const char* b = "hello";
#ifdef _POSIX_C_SOURCE
if (strcasecmp(a, b) == 0) {
std::cout << "Equal (case-insensitive)\n";
}
#else
// Windows: _stricmp
std::cout << "Use platform-specific or manual tolower\n";
#endif
return 0;
}
std::string ↔ C 문자열 변환
#include <string>
#include <iostream>
#include <cstring>
int main() {
// std::string → const char*
std::string s = "hello";
const char* cstr = s.c_str(); // null 종료 보장
// C API에 넘길 때
printf("%s\n", s.c_str());
// 주의: c_str() 반환값은 s가 수정되면 무효화됨
s += " world"; // 재할당 가능
// printf("%s", cstr); // ❌ 댕글링 가능
// C 문자열 → std::string
const char* input = "from C";
std::string s2(input);
// std::string이 null 포함할 수 있음 (C++11)
std::string s3 = "hello";
s3 += '\0';
s3 += "world";
std::cout << s3.size() << "\n"; // 11 (null 포함)
return 0;
}
4. std::string_view (C++17)
string_view란?
std::string_view는 문자열을 소유하지 않고 “보기만” 하는 경량 타입입니다. 복사 없이 std::string, const char*, 리터럴을 받을 수 있어, 함수 인자로 전달할 때 효율적입니다.
// 복사해 연결하여넣은 뒤: g++ -std=c++17 -o string_view_basic string_view_basic.cpp && ./string_view_basic
#include <iostream>
#include <string>
#include <string_view>
void print(std::string_view sv) {
std::cout << sv << " (size=" << sv.size() << ")\n";
}
int main() {
std::string s = "Hello World";
const char* cstr = "C string";
const char* literal = "Literal";
print(s); // std::string 복사 없음
print(cstr); // C 문자열
print(literal); // 리터럴
print("Inline"); // inline 리터럴
// substring: 복사 없음
print(std::string_view(s).substr(0, 5)); // "Hello"
return 0;
}
실행 결과:
Hello World (size=11)
C string (size=8)
Literal (size=7)
Inline (size=6)
Hello (size=5)
string_view 주의사항: 수명
#include <string>
#include <string_view>
#include <iostream>
std::string_view getBadView() {
std::string s = "temporary";
return s; // ❌ 위험! s가 파괴되면 댕글링
}
std::string_view getGoodView() {
static std::string s = "static";
return s; // ✅ s가 프로그램 종료까지 유효
}
int main() {
// std::string_view bad = getBadView(); // ❌ 댕글링
std::string_view good = getGoodView();
std::cout << good << "\n";
return 0;
}
핵심: string_view가 가리키는 원본이 string_view보다 먼저 파괴되면 안 됩니다. 함수 반환값으로 string_view를 반환할 때, 그 안에서 만든 지역 std::string을 반환하면 댕글링입니다.
string_view 주요 연산
#include <string_view>
#include <iostream>
int main() {
std::string_view sv = "Hello World";
// substr: 복사 없음, 뷰만 생성
std::string_view sub = sv.substr(0, 5); // "Hello"
// find, rfind, find_first_of 등 std::string과 유사
size_t pos = sv.find(' ');
std::cout << "Space at: " << pos << "\n";
// remove_prefix, remove_suffix: 뷰 범위 조정 (C++20)
#if __cplusplus >= 202002L
sv.remove_prefix(6); // "World"
std::cout << sv << "\n";
#endif
return 0;
}
5. 완전한 문자열 예제 모음
예제 1: CSV 한 줄 파싱
// 복사해 연결하여넣은 뒤: g++ -std=c++17 -o csv_parse csv_parse.cpp && ./csv_parse
#include <sstream>
#include <string>
#include <vector>
#include <iostream>
std::vector<std::string> splitCSV(const std::string& line) {
std::vector<std::string> result;
std::stringstream ss(line);
std::string cell;
while (std::getline(ss, cell, ',')) {
result.push_back(cell);
}
return result;
}
int main() {
std::string line = "apple,banana,cherry";
auto cells = splitCSV(line);
for (const auto& c : cells) {
std::cout << "[" << c << "] ";
}
std::cout << "\n";
return 0;
}
실행 결과:
[apple] [banana] [cherry]
예제 2: trim (앞뒤 공백 제거)
#include <string>
#include <iostream>
std::string trim(const std::string& s) {
size_t start = s.find_first_not_of(" \t\n\r");
if (start == std::string::npos) return "";
size_t end = s.find_last_not_of(" \t\n\r");
return s.substr(start, end - start + 1);
}
int main() {
std::string s = " hello world ";
std::cout << "[" << trim(s) << "]\n";
return 0;
}
실행 결과:
[hello world]
예제 3: 문자열 연결 (reserve 활용)
#include <string>
#include <vector>
#include <iostream>
std::string join(const std::vector<std::string>& parts, const std::string& sep) {
if (parts.empty()) return "";
size_t total = 0;
for (const auto& p : parts) total += p.size();
total += sep.size() * (parts.size() - 1);
std::string result;
result.reserve(total); // 한 번에 공간 확보
result = parts[0];
for (size_t i = 1; i < parts.size(); ++i) {
result += sep;
result += parts[i];
}
return result;
}
int main() {
std::vector<std::string> v = {"a", "bb", "ccc"};
std::cout << join(v, ", ") << "\n";
return 0;
}
실행 결과:
a, bb, ccc
예제 4: string_view로 토큰 파싱 (복사 없음)
#include <string>
#include <string_view>
#include <vector>
#include <iostream>
std::vector<std::string_view> splitView(std::string_view sv, char delim) {
std::vector<std::string_view> result;
while (!sv.empty()) {
size_t pos = sv.find(delim);
if (pos == std::string_view::npos) {
result.push_back(sv);
break;
}
result.push_back(sv.substr(0, pos));
sv.remove_prefix(pos + 1);
}
return result;
}
int main() {
std::string s = "one,two,three";
auto tokens = splitView(s, ',');
for (auto t : tokens) {
std::cout << "[" << t << "] ";
}
std::cout << "\n";
return 0;
}
위 코드 설명: remove_prefix는 C++17 std::string_view 멤버로, 뷰의 시작 위치를 앞으로 당깁니다. 복사 없이 토큰을 나눌 수 있어 대용량 파싱에 유리합니다.
6. 자주 발생하는 에러와 해결법
에러 1: C 문자열을 ==로 비교
증상: str1 == str2가 항상 false 또는 예상과 다르게 동작
원인: const char*의 ==는 포인터 주소 비교
해결:
// ❌ 잘못된 코드
const char* a = "hello";
const char* b = "hello";
if (a == b) { /* ... */ }
// ✅ 올바른 코드
if (strcmp(a, b) == 0) { /* ... */ }
// 또는 std::string 사용
std::string sa(a); std::string sb(b);
if (sa == sb) { /* ... */ }
에러 2: strcmp 반환값 잘못 해석
증상: “같은 문자열”인데 조건이 false로 처리됨
원인: if (strcmp(a, b))는 “다를 때” true
해결:
// ❌ 잘못된 코드
if (strcmp(a, b)) {
std::cout << "Same\n"; // 반대! 다를 때 실행됨
}
// ✅ 올바른 코드
if (strcmp(a, b) == 0) {
std::cout << "Same\n";
}
에러 3: c_str() 반환값을 오래 보관
증상: 크래시 또는 쓰레기 값
원인: std::string이 수정되면 내부 버퍼가 재할당되어 c_str() 이전 반환값이 무효화됨
해결:
// ❌ 잘못된 코드
const char* p = s.c_str();
s += " more"; // 재할당 가능
use(p); // ❌ 댕글링
// ✅ 올바른 코드
s += " more";
use(s.c_str()); // 수정 직후 호출
에러 4: string_view가 댕글링
증상: 크래시 또는 쓰레기 값 원인: string_view가 가리키는 원본이 먼저 파괴됨 해결:
// ❌ 잘못된 코드
std::string_view getView() {
std::string s = "temp";
return s; // s 파괴 후 댕글링
}
// ✅ 올바른 코드: std::string 반환 또는 원본 수명 보장
std::string getString() {
return "temp";
}
에러 5: stoi 예외 미처리
증상: "abc" 같은 입력 시 프로그램 종료
원인: stoi는 변환 실패 시 예외 던짐
해결:
// ❌ 잘못된 코드
int n = std::stoi(user_input); // "abc" → 예외
// ✅ 올바른 코드
try {
int n = std::stoi(user_input);
} catch (const std::invalid_argument&) {
// 잘못된 입력 처리
} catch (const std::out_of_range&) {
// 오버플로우 처리
}
// 또는 std::from_chars (C++17, 예외 없음)
int n;
auto [p, ec] = std::from_chars(user_input.data(),
user_input.data() + user_input.size(), n);
if (ec != std::errc{}) {
// 에러 처리
}
에러 6: 인덱스 범위 초과
증상: 크래시 또는 undefined behavior
원인: s[i] 또는 s.substr(start, len)에서 범위 초과
해결:
// ❌ 잘못된 코드
std::string s = "hi";
char c = s[10]; // undefined behavior
// ✅ 올바른 코드
if (i < s.size()) {
char c = s[i];
}
// 또는 at() 사용 (범위 검사, 예외)
char c = s.at(i); // 범위 초과 시 std::out_of_range
7. 성능 최적화 팁
tip 1: reserve로 재할당 최소화
// ❌ 비효율: 매번 재할당 가능
std::string result;
for (const auto& piece : parts) {
result += piece;
}
// ✅ 효율: 한 번에 공간 확보
std::string result;
size_t total = 0;
for (const auto& piece : parts) total += piece.size();
result.reserve(total);
for (const auto& piece : parts) {
result += piece;
}
tip 2: 읽기 전용 인자는 string_view
// ❌ std::string 복사 발생
void process(const std::string& s);
// ✅ 복사 없음 (std::string, const char*, 리터럴 모두 수용)
void process(std::string_view s);
tip 3: 작은 문자열은 SSO 활용
대부분의 구현에서 SSO(Small String Optimization)를 사용합니다. 보통 15~23바이트 이하 문자열은 힙 할당 없이 객체 내부에 저장됩니다. 따라서 짧은 문자열은 std::string을 그대로 사용해도 됩니다.
tip 4: 반복 연결 시 += 대신 reserve + append
std::string result;
result.reserve(estimated_size);
for (const auto& s : parts) {
result.append(s);
}
tip 5: from_chars로 숫자 변환 (C++17)
std::from_chars는 예외를 던지지 않으며, stoi/stod보다 빠릅니다.
#include <charconv>
#include <iostream>
int main() {
std::string s = "12345";
int value;
auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value);
if (ec == std::errc{}) {
std::cout << "Parsed: " << value << "\n";
} else {
std::cout << "Parse failed\n";
}
return 0;
}
8. 프로덕션 패턴
패턴 1: 문자열 인자로 string_view 사용
// 함수가 문자열을 읽기만 할 때
std::string findAndReplace(std::string_view text,
std::string_view find,
std::string_view replace) {
std::string result;
result.reserve(text.size());
size_t pos = 0;
while (true) {
size_t found = text.find(find, pos);
if (found == std::string_view::npos) {
result.append(text.substr(pos));
break;
}
result.append(text.substr(pos, found - pos));
result.append(replace);
pos = found + find.size();
}
return result;
}
패턴 2: 로그/에러 메시지 조립
#include <sstream>
#include <string>
std::string formatError(const std::string& filename, int line, const std::string& msg) {
std::ostringstream oss;
oss << filename << ":" << line << ": " << msg;
return oss.str();
}
// C++20: std::format
// return std::format("{}:{}: {}", filename, line, msg);
패턴 3: 문자열 풀 (공통 문자열 캐싱)
#include <string>
#include <unordered_set>
class StringPool {
std::unordered_set<std::string> pool;
public:
std::string_view intern(const std::string& s) {
auto it = pool.find(s);
if (it != pool.end()) {
return *it;
}
auto [inserted, _] = pool.insert(s);
return *inserted;
}
};
패턴 4: 안전한 C API 래퍼
void legacyApi(const char* str);
void safeCall(const std::string& s) {
legacyApi(s.c_str()); // 호출 시점에만 c_str() 사용
}
void safeCall(std::string_view sv) {
std::string temp(sv); // null 종료 필요하면 임시 복사
legacyApi(temp.c_str());
}
패턴 5: 환경 변수/설정 파싱
#include <string>
#include <iostream>
#include <cstdlib>
std::string getEnvOrDefault(const std::string& key, const std::string& def) {
const char* val = std::getenv(key.c_str());
return val ? std::string(val) : def;
}
9. 구현 체크리스트
문자열을 다룰 때 확인할 항목입니다.
- 비교: C 문자열은
strcmp사용,==는 포인터 비교 - strcmp:
== 0일 때 “같음” - 연결: 루프 안에서는
+=또는append,reserve로 재할당 최소화 - c_str(): 반환값을 오래 보관하지 말 것
- string_view: 원본 수명이 string_view보다 길어야 함
- stoi/stod: 예외 처리 또는
from_chars사용 - 인덱스: 범위 검사 또는
at()사용 - 함수 인자: 읽기 전용이면
std::string_view
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ vector 성능 | “100만 개 넣는데 10초” 문제와 reserve
- C++ stringstream | 문자열 파싱·변환·포맷팅
- C++ 문자열 파싱 완벽 가이드 | stringstream·getline·제로카피·성능 벤치마크
이 글에서 다루는 키워드 (관련 검색어)
C++ std::string, 문자열 비교, strcmp, string_view, 문자열 연결, reserve, SSO, C 문자열 변환, 문자열 파싱 등으로 검색하시면 이 글이 도움이 됩니다.
정리
| 항목 | 문법/용도 | 주의 |
|---|---|---|
| std::string | 소유·수정 | reserve로 재할당 최소화 |
| string_view | 읽기 전용 | 원본 수명 확인 |
| C 문자열 | const char* | strcmp로 비교 |
| c_str() | C API 연동 | 반환값 단기 사용만 |
| stoi/stod | 숫자 변환 | 예외 처리 |
| 핵심 원칙: |
- C 문자열은
strcmp로 비교 - 읽기 전용 인자는
string_view - 연결 시
reserve로 재할당 최소화 c_str()반환값은 즉시 사용 후 버림string_view는 원본보다 오래 살지 않도록
초보자를 위한 체크리스트
- C 스타일 문자열 비교에
strcmp/strncmp를 썼는가? -
string_view를 댕글링 없이(원본string수명) 넘기고 있는가? - 연결·반복 삽입 전에
reserve로 재할당을 줄였는가?
💡 초보자 팁: 본문 9. 구현 체크리스트와 함께 보면 빠짐없이 점검하기 좋습니다.
참고 자료
자주 묻는 질문 (FAQ)
Q. std::string과 string_view 중 어떤 것을 써야 하나요?
A. 문자열을 소유하고 수정할 때는 std::string, 읽기만 할 때는 std::string_view를 사용합니다. 함수 인자로 문자열을 받을 때 대부분 string_view가 적합합니다.
Q. C 문자열과 std::string 비교 시 성능 차이는?
A. std::string의 ==는 내용 비교를 하므로 O(n)입니다. C 문자열 strcmp도 O(n)입니다. string_view는 복사 없이 비교할 수 있어, 큰 문자열을 여러 번 전달할 때 유리합니다.
Q. SSO란?
A. Small String Optimization. 짧은 문자열(보통 15~23바이트 이하)은 힙에 할당하지 않고 std::string 객체 내부에 저장하는 최적화입니다. 구현에 따라 다릅니다.