2.1 동적 스트링

c를 사용 -> null로 끝나는 문자 배열로 스트링을 표현
c++ -> buffer overflow 문제 발생 가능. std::string 클래스 제공

2.1.1 c스타일 스트링

char* copyString(const char* str) {
    char* result { new char{strlen(str) + 1}};
    strcpy(result, str);
    return result;
}


char* copyString(const char* str1, const char* str2) {
    char* result { new char{strlen(str1) + strlen(str2)+ 1}};
    strcpy(result, str1);
    strcat(result, str2);

    return result;
}
  • sizeof() : 데이터 타입/변수의 크기 return
const char* text {"abcdef"};
size_t s3 {sizeof(text2)}; // 32비트 모드에서는 4, 64비트 모드에서는 8
size_t s4 {sizeof(text2)}; // 6

2.1.2 스트링 리터럴

cout << "hello" << endl;
  • "hello" 처럼 변수에 담지 않고 곧바로 값으로 표현한 스트링을 스트링 리터럴이라 부른다.
  • 메모리의 읽기 전용 영역에 저장
  • 컴파일러는 같은 스트링 리터럴이 코드에 여러 번 나오면 이에 대한 레퍼런스를 재활용하는 방식으로 메모리를 절약
    • 이를 리터럴 풀링(literal pooling이라 부른다
  • 스트링 리터러를 변수에 대입할 수는 있지만, 스트링 리터럴은 메모리의 읽기 전용 영역 뿐만 아니라 동일한 리터럴을 여러 곳에서 공유할 수 있기 때문에 변수에 저장하면 위험하다.
char* ptr {"hello"}; // 변수에 스트링 리터럴을 대입
ptr[1] = 'a'; // 결과를 예측할 수 없다.
const char* ptr {"hello"}; // 변수에 스트링 리터럴을 대입
ptr[1] = 'a'; // 컴파일 에러
char arr[] {"hello"}; // 컴파일러는 적절한 크기의 문자 배열 arr을 생성한다
arr[1] = 'a'; // 이제 스트링을 수정할 수 있다.

1 Raw String Literal

const char* str {"Hello "World"!"}; // 에러

const char* str {"Hello \"World\"!"};

const char* str {R"(Hello "World"!)"};
  • Raw String Literal은 ")"로 끝나기 때문에 이 구문을 사용하는 스트링 안에 ")"를 넣을 수 없다.

2.1.3 C++ std::string 클래스

1 C 스타일 스트링의 문제점

  • 장점
    • 간단하다. 내부적으로 기본 문자 타입과 배열 구조체로 처리한다.
    • 가볍다. 제대로 사용하면 메모리를 꼭 필요한 만큼만 사용
    • 로우 레벨. 메모리의 실제 상태를 조작하거나 복사하기가 쉽다
  • 단점
    • 스트링 데이터 타입에 대한 고차원 기능을 구현하려면 상당한 노력 필요
    • 찾기 힘든 메모리 버그

      2 string 클래스 사용법

      string a{"12"};
      string b{"34"};
      string c;
      c = a + b;
스트링 비교
char* a {"12"};
char b[] {"12"};

if(a==b) { // 항상 false return
}

if(strcmp(a,b) == 0) // 0보다 작은값, 0, 0보다 큰 값을 리턴
string a{"12"};
string b{"34"};

auto result {a.compare(b)};
if(result < 0) {cout << "less" << endl;}
if(result > 0) {cout << "greater" << endl;}
if(result == 0) {cout << "equal" << endl;}
메모리 처리
  • 연산자 오버로딩으로 string을 확장해도 메모리 관련 작업은 string 클래스가 알아서 처리해준다는 것을 알 수 있다. 따라서 메모리 overrun이 발생할 걱정을 할 필요가 없다.

  • string 객체는 모두 스택 변수로 생성. string 클래스를 사용하면 메모리를 할당하거나 크기를 조절할 일이 상당히 많긴 하지만 string 객체가 스코프를 벗어나자마자 여기에 할당된 메모리를 string 소멸자가 모두 정리한다.

C 스트링과 호환
  • string 클래스에서 제공하는 c_str() 메서드를 사용하면 c언어에 대한 호환성을 보장할 수 있다. 이 메서드는 c 스트링을 표현하는 const 포인터를 return한다.
스트링 연산
  • substr(pos, len) : 인수로 지정한 시작 위치와 길이에 맞는 서브스트링을 리턴한다.
  • find(str) : 인수로 지정한 서브스트링이 있는 지점을 리턴한다.
  • replace(pos, len, str) : 스트링에서 인수로 지정한 위치와 길이에 해당하는 부분을 str로 지정한 값을 교체한다.
  • starts_with(str)/ends_with(str) : 인수로 지정한 서브스트링으로 시작하거나 끝나면 true를 리턴한다.

3 std::string 리터럴

  • 표준 사용자 정의 리터럴s를 사용하려면 std::literals::string_literals 네임스페이스를 추가한다.
    auto string1 {"Hello World"}; // string1의 타입은 const char*
    auto string2 {"Hello World"s}; // string2의 타입은 std::string
    
    

// 다음 중 하나로 작성해도된다
using namespace std;
using namespace std::literals;
using namespace std::string_literals;
using namespace std::literals::string_literals;


- 기본적으로 인라인 네임스페이스에 선언된 것은 모두 자동으로 부모 네임스페이스에도 추가된다.

#### 4 std::vector와 스트링의 CTAD
```cpp
vector names {"John", "Sam", "Joe"}; // vector<const char*>로 추론
vector names{"John"s, "Sam"s, "Joe"s}; // 각 스트링 리터럴 끝에 s를 붙인다

2.1.4 숫자 변환

1 하이레벨 숫자 변환

숫자를 string으로 변환
string to_string(T val);

2 로우 레벨 숫자 변환

  • 로우 레벨 숫자 변환에 대한 함수도 다양하게 제공 <charconv> 헤더에 정의
  • 고성능과 로케일 독립성에 최적화
    • 하이레벨 숫자 변환 함수에 비해 처리 속도가 엄청나게 빠르다
  • 퍼펙트 라운드 트리핑 방식으로 설계되었다
to_chars_result to_chars(char* first, char* last, IntegerT value, int base = 10);

from_chars_result from_chars(const char* first, const char* last, IntegerT& value, int base = 10);

2.1.5 std::string_view 클래스

  • c++17 이전에는 읽기 전용 스트링을 받는 함수의 매개변수 타입을 쉽게 결정할 수 없었다.
  • const char*로 지정하면 std::string을 사용할 때 c_str()이나 data()로 const char*를 구해야 한다.
  • 더 심각한 문제는 이렇게 하면 std::string의 객체지향 속성과 여기서 제공하는 헬퍼 메서드를 제대로 활용할 수 없다.
  • 매개변수로 const std::string&으로 지정하면? 그렇게 하면 항상 std::string만 사용해야 함.
    • 스트링 리터럴을 전달하면 컴파일러는 그 스트링 리터럴의 복사본이 담긴 string 객체를 생성해서 함수로 전달하기 때문에 오버헤도가 발생
  • c++에서 추가된 std::string_view 클래스를 사용하면 고민 해결
    • string_view는 실제로 const string& 대신 사용할 수 있으며 오버헤드도 없다
      string_view extractExtension(string_view filename) {
      return filename.substr(filename.rfind('.'));
      }

1 std::string_view와 임시 스트링

  • 임시 스트링에 대한 view를 절대로 std::string_view로 지정하면 안된다

2 std::string_view 리터럴

auto sv {"My string_view"sv};

2.1.6 비표준 스트링

  • c++ 프로그래머 상다수가 c++ 타입 스트링을 잘 사용하지 않는다.
    • c++ 규격에 명확히 나오지 않기 때문에 string이라는 타입이 있는 줄도 모르는 이도 있다.
    • c++ string을 사용하다가 자신이 원하는 기능이 없거나 인코딩을 무시한다는 사실이 마음에 안들어서
    • 가장 큰 이유는 마이크로소프트 MFC의 CString 클래스 처럼 개발 프레임워크나 운영체제에서 나름대로 정의한 스트링을 제공하기 때문

1 C 스트링은 사용하지 않는다
2 MFC나 Qt 등에서 기본적으로 제공하는 스트링처럼 현재 사용하는 프레임워크에서 제공하는 스트링을 프로젝트의 표준 스트링으로 삼는다
3 std::string으로 스트링을 표현한다면 함수의 매개변수로 전달할 읽기 전용 스트링은 std::string_view로 지정한다

2.2 스트링 포맷 지정

auto s1 {format("Read {} bytes from {}", n, "file.txt")};

2.2.1 포맷 지정자

[[fill]align][sign][#][0][width][.precision][type]

1 width

2 [fill]align

2.2.2 포맷 지정자 에러

2.2.3 커스텀 타입 지원

BELATED ARTICLES

more