string
을 다루다보면 문자열을 잘라야 할 경우가 많이 생긴다. 이번 포스팅에서는 <sstream>
헤더를 소개하고 문자열을 자르는 여러가지 경우를 살펴본다.
cppreference
<sstream>
std::stringstream
stringstream
클래스는(자식 클래스) iostream
를 상속받아서(부모 클래스) iostream
메소드를 전부 사용할 수 있다. 하지만 클래스 상속만 이루어지기 때문에 <sstream>
헤더만 include 한다고 <iostream>
헤더가 include 되지는 않는다.
std::istringstream
과 std::ostringstream
도 있는데 부모 클래스가 각각 std::istream
, std:ostream
이라 쓰임이 보다 한정적이다. 이번 포스팅에서는 std::stringstream
을 중점적으로 서술한다.
std::iostream
을 상속받아서 멤버 함수와 std::iostream
의 부모 클래스, 그 위 부모 클래스들의 멤버 함수 모두 사용가능하다.
std::istream
get
과 getline
이 보이는데 <string>
헤더에 선언된 std::getline
와 다르다. std::istream::getline
은 char*
이 argument로 들어가는 반면, std::getline
은 stream이 argument로 들어간다. char*
는 modern C++ 스타일이 아니므로 자세하게 짚지 않고 넘어가겠다.
get
getline
std::ostream
std::ios
std::ios_base
std::stringstream
vs std::string
vs char* (char[])
std::stringstream::str
std::stringstream::str
은 문자열 스트림 버퍼 속 문자열을 나타내는 메소드 함수이다.
#include <iostream>
#include <sstream>
int main() {
std::string s("Hello World");
std::stringstream ss(s);
std::cout << ss.str() << std::endl;
return 0;
}
' '
) 자르기std::iostream
과 std::stringstream
은 스페이스(' '
)와 줄바꿈('\n'
)를 만나면 버퍼 입력이 종료되고 버퍼에 저장된 값이 >>
혹은 <<
연산자로 배출된다.
이때 문자열을 스페이스를 포함해서
1. 표준 입력
2. std::string
저장된 문자열을 자르고 싶을 경우가 있다.
1번 케이스를 구현하면 다음과 같다.
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string inputStr, tmp;
std::stringstream ss;
std::getline(std::cin, inputStr);
ss.str(inputStr);
while(ss >> tmp) {
std::cout << tmp << std::endl;
}
return 0;
}
std::getline(std::cin, inputStr);
std::getline()
으로 입력을 받는 이유는 앞서 말한 std::cin
이 스페이스와 줄바꿈으로 버퍼 입력을 종료하기 때문이다. 라인별로 입력을 받아 ss >> tmp
에서 실제로 자르는 동작이 이루어진다.
2번 케이스를 구현하면 다음과 같다.
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string inputStr("Split this string"), tmp;
std::stringstream ss(inputStr);
while(ss >> tmp) {
std::cout << tmp << std::endl;
}
return 0;
}
std::stringstream ss(inputStr);
에서 생성자로 std::string
형 데이터가 들어갔다. 하지만 스트림에 들어갈 때 잘리는 것이 아닌 >>
연산자로 잘린다. 만약 위 코드에서 while
문을 다음 코드로 대체하면
std::cout << ss.str() << std::endl;
// ss.str() -> ss.rdbuf()도 가능.
이런 결과가 출력된다.
>>
연산자는 버퍼를 스페이스, 줄바꿈 문자를 기준으로 자른다. 만약 여기에 구분자를 변경한다면 다음과 같이 코드를 짜면 된다.
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string inputStr("split this string"), tmp;
std::stringstream ss(inputStr);
while(std::getline(ss, tmp, 'i')) {
std::cout << tmp << std::endl;
}
return 0;
}
여기서 유의해야 할 점은 std::getline()
에서 구분자는 char
형 변수만 들어갈 수 있다. 이말은 std::getline()
에서는 구분자를 여러 가지로 설정할 수 없다는 뜻이다. 만약 구분자가 문자열일 경우, 다음과 같은 코드로 해결할 수 있다.
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
int main() {
std::string input_string = "apple!@#$orange!@#$!banana!@#$!pineapple!@#$!mango";
std::string delimiter = "!@#$";
std::stringstream ss(input_string);
std::string token;
while (std::getline(ss, token, delimiter[0])) {
for (char c: delimiter.substr(1)) {
token.erase(std::remove(token.begin(),
token.end(),
c),
token.end());
}
if (!token.empty() ){
std::cout << token << std::endl;
}
}
return 0;
}
위 코드를 간단하게 보면, std::getline()
과 std::string::erase()
, std::remove()
로 문자열을 자르고 있다.
<algorithm>
헤더의 std::remove()
는 제거한 위치 바로 뒤 위치를 return해, std::string::erase()
는 제거한 뒤 위치에 있는 모든 문자를 지운다. 문자열|delimiter|문자열
일 경우 delimiter
뒤 문자열은 while
문이 돌아가면서 다음 타겟이 되기 때문이다.
문자열을 자를 때 기준이 꼭 문자가 되진 않는다. std::string::substr()
은 특정 index 위치부터 임의의 크기까지 문자를 자를 수 있다.
std::string dest = src.substr(i, n) // i index부터 n개 문자 자르기
소수점 자리수를 조절하는 경우를 생각해보자. 소수점 자리는 std::string::substr()
으로 하기 곤란하다. 숫자의 길이가 정해지지 않았기 때문이다. 다음 코드를 보자.
#include <iostream>
#include <sstream>
#include <cmath>
#include <iomanip>
int main() {
std::stringstream ss;
ss << std::fixed << std::setprecision(2);
ss << M_PI;
std::cout << M_PI << std::endl;
std::cout << ss.str() << std::endl;
return 0;
}
std::cout
을 std::fixed
와 std::setprecision
으로 소수점 자리를 고정한 것처럼, std::stringstream
도 소수점 자리를 고정할 수 있다. std::iostream
을 상속받았기 때문이다.