C++의 std::string 클래스
string, string literal, 숫자와 스트링 사이의 변환, string_view
2장 - C++ std::string 클래스
1) string 클래스란
앞서 소개한 cstring의 함수와 기능은 비슷하지만, 메모리 할당 작업을 처리해주는 기능이 추가된 클래스이다. std 네임스페이스에 속한다. C 스타일 스트링은 뭐가 문제였을까?
- 장점
- 간단함 - 내부적으로 기본 문자 타입과 배열 구조체로 처리
- 가벼움 - 메모리 최소 사용
- 로우레벨 - 메모리에 대한 세부 조작이 용이
- 단점
- 고차원 기능을 구현하려면 시간이 필요
- 찾기 힘든 메모리 버그 발생 가능
- 객체지향적 특성 반영 미비
- 내부 표현 방식을 이해하고 있어야함
그렇게 장점은 유지하고 단점은 보완한 C++의 std::string이 탄생했다.
2) string 클래스 사용법
- 실제로는 클래스지만 기본 타입처럼 사용한다.
+
기호를 사용해 스트링을 덧붙이는 연산이 가능하다.- 스트링의 각 문자는
[]
를 사용해서 접근 가능하다. ==
,!=
같은 연산자를 오버로딩해서 적용할 수 있다.
**<고급>**고급>
-
스트링 할당이나 크기 조절 코드가 흩어져있어도 메모리 누수가 발생하지 않는다.
→ 스트링 객체는 모두 스택 변수로 생성되기 때문이다. 스트링 객체가 스코프를 벗어난다면 string 소멸자가 정리한다.
-
C언어에 대한 호환성을 제공한다
→ 스트링 클래스의
c_str()
을 사용해서 C와의 호환성을 보장한다. 해당 함수는 C스타일 스트링을 표현하는const char*
을 리턴한다. 다만 메모리 이동이나 객체 제거시에는 더이상 포인터를 사용할 수 없음에 유의하자.
3) string literal
C에서의 스트링 리터럴은 const char*
였다. 표준 사용자 정의 리터럴을 사용하여 타입을 std::string으로 변경할 수 있다(std::string_literals에 존재).
auto str1 = "Hello World";
auto str2 = "Hello World"s;
cout << typeid(str1).name() << endl;
cout << typeid(str2).name() << endl;
4) 숫자와 스트링 사이의 변환
숫자 → 스트링
std네임스페이스는 여러 종류의 to_string()
함수를 제공한다. 이들은 새로운 string 객체를 생성하여 리턴한다.
string to_string(int val);
string to_string(long val);
...
스트링 → 숫자
숫자→스트링 함수가 모두 이름이 같고 매개변수가 달랐다면, 스트링→숫자는 동일한 매개변수에 다른 이름과 반환 타입을 가진다. 이름의 구성은 sto
+ 바꾸려는 타입의 첫 글자
이다.
int stoi(const string& str, size_t* idx=0, int base = 10);
long stol(const string& str, size_t* idx=0, int base = 10);
long long stoll(const string& str, size_t* idx=0, int base = 10);
각 매개변수의 의미는 다음과 같다.
- str - 변환하고자 하는 원본 string
- idx - 아직 변환되지 않은 부분에서 맨 앞 문자의 인덱스를 가리키는 포인터
-
base - 변환할 수의 밑(진수), 정수형에만 존재하며 10진수가 기본이다. 스트링 내부의 값이 2진수, 8진수, 10진수와 같이 어떠한 진수로 표시되었는지를 나타낸다.
stoi("11", 0, 2)
→11은 2진수로 3이다→int 타입의 값 3으로 변환
5) std::string_view
스트링을 표현하기 위한 방법은 크게 두 가지로 구분할 수 있다.(리터럴은 제외했다)
const char*
std::string
프로그램을 작성하려면 중간에 변환 과정을 추가하거나, 양쪽 모두에 대응하게끔 오버로딩을 하곤 했었다. 얼마나 불편한가? 그래서 C++17부터 string_view를 제공한다.
string_view extractExtention(string_view fileName) {
return fileName.substr(fileName.rfind('.'));
}
int main() {
string fileName_1 = R"(c:\temp\myfile.ext)";
cout << "C++ : " << extractExtention(fileName_1) << endl;
const char* fileName_2 = R"(c:\temp\myfile.ext)";
cout << "C string : " << extractExtention(fileName_2) << endl;
cout << "Literal : " << extractExtention(R"(c:\temp\myfile.ext)") << endl;
}
fileName으로 정의된 함수의 매개변수가 string, const char*, literal 모두를 받아들이는 모습이다.
그리고 string_view를 매개변수나 리턴 타입으로 설정하면 포인터와 길이만을 값으로 주고 받는다.
string_view returnSV = extractExtention(R"(c:\temp\myfile.ext)");
cout << returnSV << endl;
string_view는 string대신에 사용 가능하다. c_str()의 제외와 몇 가지 메소드의 추가 이외에는 인터페이스가 동일하기 때문이다. 다만 둘을 서로 연결/결합할 수 없다.
string_view sv = "This is string_view";
string str = "This is string";
str = str + sv; // X
str = str + sv.data(); // O
str = str + string(sv); // O
6) 정리
나중에 문제가 발생했을 때 해결하려 하지 말고, 프로젝트 계획 단계에서 어떤 string을 사용할 건지 팀과 상의하자.