[C++] 스트림(stream)(3) 문자열 스트림(<sstream>) 헤더

Gamchan Kang·2023년 4월 13일
0

C++

목록 보기
11/11

string을 다루다보면 문자열을 잘라야 할 경우가 많이 생긴다. 이번 포스팅에서는 <sstream> 헤더를 소개하고 문자열을 자르는 여러가지 경우를 살펴본다.
cppreference


1. <sstream>

1-1 std::stringstream


stringstream 클래스는(자식 클래스) iostream를 상속받아서(부모 클래스) iostream 메소드를 전부 사용할 수 있다. 하지만 클래스 상속만 이루어지기 때문에 <sstream> 헤더만 include 한다고 <iostream> 헤더가 include 되지는 않는다.

std::istringstreamstd::ostringstream도 있는데 부모 클래스가 각각 std::istream, std:ostream이라 쓰임이 보다 한정적이다. 이번 포스팅에서는 std::stringstream을 중점적으로 서술한다.


1-2 상속 받은 클래스

std::iostream을 상속받아서 멤버 함수와 std::iostream의 부모 클래스, 그 위 부모 클래스들의 멤버 함수 모두 사용가능하다.

std::istream


getgetline이 보이는데 <string> 헤더에 선언된 std::getline와 다르다. std::istream::getlinechar*이 argument로 들어가는 반면, std::getline은 stream이 argument로 들어간다. char*는 modern C++ 스타일이 아니므로 자세하게 짚지 않고 넘어가겠다.
get
getline

std::ostream

std::ios

std::ios_base



1-3 std::stringstream vs std::string vs char* (char[])


1-4 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;
}




2. 문자열 자르기

2-1 스페이스(' ') 자르기

std::iostreamstd::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()도 가능.


이런 결과가 출력된다.


2-2 구분자(delimiter) 기준 자르기

>> 연산자는 버퍼를 스페이스, 줄바꿈 문자를 기준으로 자른다. 만약 여기에 구분자를 변경한다면 다음과 같이 코드를 짜면 된다.

#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문이 돌아가면서 다음 타겟이 되기 때문이다.


2-3 소수점 자리수 버리기

문자열을 자를 때 기준이 꼭 문자가 되진 않는다. 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::coutstd::fixedstd::setprecision으로 소수점 자리를 고정한 것처럼, std::stringstream도 소수점 자리를 고정할 수 있다. std::iostream을 상속받았기 때문이다.

profile
Someday, the dream will come true

0개의 댓글