문자열(string, string_view)

김펭귄·2025년 7월 18일

C++

목록 보기
4/20

std::string

사용법

#include <string> // std::string 사용하기 위해 include

int main()
{
    std::string a {}; // empty string
    	
    std::string b { "Alex" }; // "Alex"로 초기화
    b = "Lewis Hamilton";     // "Lewis Hamilton"으로 수정. 길이 달라도 상관 없음
    std::cout << b << '\n';	  // std::cout으로 string 출력
    
    int len { static_cast<int>(b.length()) };  // 문자열 길이
	
    constexpr std::string c{};	// compile error
}
  • b의 경우 Alex에서 Lewis Hamilton으로 수정하여, 문자열의 길이가 더 길어졌으므로, 동적할당을 통해 알아서 더 긴 문자열을 저장해준다
  • b.length()unsigned integer이므로 int형 변수로 받을 때는 casting을 해주어야 함
  • string의 경우 constexpr 사용 불가능. std::string_view는 사용 가능

string의 복잡성

#include <iostream>
#include <string>

void printString(std::string str) // str 초기화도 오래걸림
{
    std::cout << str << '\n';
}

int main()
{
 // 출력만 할 것이므로 string 사용하기엔 메모리, 시간 낭비
 std::string s{ "Hello, world!" };
 std::cout << s << '\n';
 
 Print(s);	// 역시 마찬가지
}
  • std::string은 초기화, 복사 등 과정이 오래 걸린다

std::string_view

기본 사용법

  • 문자열 읽기(참조) 전용
#include <iostream>
#include <string_view> // C++17

void printSV(std::string_view str) // string_view로 받기
{
    std::cout << str << '\n';
}

int main()
{
    std::string_view s{ "Hello, world!" }; // 초기화 및 복사 빠름
    std::cout << s << '\n';				   // "Hello, world!"
    
    printSV(s);  // 마찬가지
    s = "Hi!";	 // 포인터와 길이 정보만 바꾸어 저장
    std::cout << s << '\n';  // "Hi!"

    return 0;
}
  • 문자열이 있는 메모리의 주소를 가리키는 포인터와 문자열의 길이 정보만을 저장(메모리 효율적)
  • string과 달리 문자열 데이터의 수정 불가능. 따라서 동작은 짧게 걸림(성능 향상)
  • 따라서 문자열을 참조하는 경우엔 string_view 사용하는 것이 효율적

초기화의 호환성

void printString(std::string str) // string으로 받기
{
	std::cout << str << '\n';
}

int main()
{

	// string_view는 호환성이 좋다
	std::string_view sv1 { "Hello, world!" }; // 리터럴로 초기화 가능

	std::string s1{ "Hello, world!" };
	std::string_view sv2 { s1 };  // string으로 초기화 가능

	std::string_view sv3 { s2 }; // string_view로도 초기화 가능

	// string으로의 변환은 명시적 변환만 가능
    printString(s3);   // compile error: 암시적 변환이므로 에러
    
	std::string s2{ sv3 }; // 명시적 변환이므로 가능
	printString(s2);      // s2는 string이므로 가능

	printString(static_cast<std::string>(sv)); // 명시적 변환이므로 가능
    
    // constexpr 사용 가능
    constexpr std::string_view sv4{ "Hello, world!" }; 
}
  • string_view는 뛰어난 호환성을 보이지만, string_view에서 string은 명시적 변환을 해주어야만 사용 가능하다
  • string과는 다르게 constexpr 사용 가능

주의할 점

#include <iostream>
#include <string>
#include <string_view>

std::string getName()  // 문자열 반환 함수
{
    std::string s { "Alex" };
    return s;  // s는 값만 반환하고 메모리에서 사라짐
}

int main()
{
	std::string_view sv{};
    std::string s { "Hello" };  // string 선언
    std::string_view sv { s }; 	// s 관찰
    s = "Hi";    				// s 수정
    std::cout << sv << '\n';   	// 에러 가능
    sv = s;						// 따라서 수정되었을 경우, 다시 설정해주기
    
    
    // nested block
    {
        std::string str{ "Hello, world!" }; // nested block에서 생성
        sv = str; 							// str를 참조
    } // 이제 str은 소멸됨
    std::cout << sv << '\n';				// 에러 발생 가능


	std::string_view name { getName() };	// 반환값 s는 함수 종료되면서 사라짐
    std::cout << name << '\n'; 				// 에러 발생 가능
    
    
    using namespace std::string_literals;
    std::string_view name { "Alex"s }; 	// string 리터럴로 초기화
    std::cout << name << '\n'; 			// 에러 가능
}
  • string_view가 가리키는 문자열 데이터가 수정되거나 사라질 경우에는, 사용하면 안 됨 (dangling pointer)
  • string literal은 메모리에서 일시적으로 존재하므로, 마찬가지로 string_view 사용이 불가능
  • string_view의 초기화는 C style literal(그냥 "문자열"), string_view literal, string 이런 것들로 하기
std::string_view foo1(bool b)	// string_view로 반환
{
    std::string t { "true" }; 
    std::string f { "false" }; 

    if (b)
        return t;  // 지역변수인 t를 관찰하는 string_view를 반환
    return f; // 지역변수인 f를 관찰하는 string_view를 반환
} // 함수 종료되면 t, f는 사라짐


std::string_view foo2(bool b)
{
    if (b)
        return "true";  // "true"를 참조하는 view를 반환

    return "false"; // "false"를 참조하는 view를 반환
} // "true", "false"는 안 사라짐


std::string_view foo3(std::string_view s1)
{
    return s1;
}

int main()
{
    std::cout << foo1(true) << foo1(false);		// 에러 가능
    std::cout << foo2(true) << foo2(false);		// okay
    
    std::string a { "Hello" };
    std::cout << foo3(a); // okay
}
  • foo1이 호출되면, t, f를 관찰하는 string_view를 반환. t, f는 함수 종료되면 사라지므로 반환된 string_view에 문제 생길 수 있다.
  • foo2에서는 C style literal을 참조하는 view를 반환. C style literal은 프로그램 끝날 때까지 남아있으므로 문제 없다.
  • foo3에서는 caller에 존재하는 문자열(a)을 받아 관찰하는 view를 반환. 함수가 종료되어도 a는 남아있으니까 문제 없음
  • 따라서 문자열을 함수 인자로 받을 때, view를 주로 사용한다
  • string_view를 사용할 때 메모리에 존재하는 문자열인지 확인하며 사용해야 함

view 범위 수정

  • view는 관찰하는 문자열의 포인터와, 길이 정보만을 저장
  • 따라서 관찰하는 문자열을 직접 수정하지는 못하지만, 관찰하는 범위를 조정할 수 있다
	std::string_view str{ "Peach" };

	// 앞 글자 제거
	str.remove_prefix(1);
	std::cout << str << '\n';	// each

	// 뒷 글자 제거
	str.remove_suffix(2);
	std::cout << str << '\n';	// ea

	str = "Peach"; // 다시 초기화
	std::cout << str << '\n';	// Peach
  • 줄인 범위를 다시 늘릴 수는 없다
  • 뒷 글자 제거시strNULL도 사라짐. each\0 -> ea
  • NULL로 끝내고 싶으면 string으로 변환시키면 된다

관련 함수

to_string()

  • to_string은 문자열로 만들어 반환하지만 임시객체를 반환

  • 따라서 아래와 같이 사용할 경우 sv는 임시객체를 참조하고 있으므로 이 코드가 끝나면 이상한 값을 참조하게 됨

std::string_view sv {to_string("123")};	
  • 그러므로 임시객체를 참조하지 말고 string으로 아예 객체를 생성해서 받아야 함
std::string s {to_string("123")};

stoi()

  • 그럼 문자열을 정수형으로 바꾸려면 stoi()를 쓰면 된다

  • atoi()의 경우 char*를 인자로 받기 때문에 string 자료형을 사용할 수 없어, 대신에 stoi()를 사용

int n {stoi("-12345")};
  • 반환 자료형에 따라 stol(long), stof(float), stod(double), stoll(long long), stoui(unsigned int) ... 다양하게 사용 가능

Reference

learn.cpp
learn.cpp

profile
반갑습니다

0개의 댓글