
String Stream
I/O stream과 비슷하게 string 전용 stream 클래스도 존재한다
string stream은 istream, ostream과 같이 데이터를 담아두는 buffer를 제공한다, string stream의 주된 용도는 바로 나중에 표시할 문자열 출력을 미리 버퍼에 담아두는것이다 (쉽게 말해 보이지 않는 콘솔창에 미리 입력해놓는것)
string stream 클래스는 총 6가지가 존재한다
이러한 string stream 클래스를 사용하기 위해서는 < sstream > 헤더를 include 해야 한다
그렇다면 이러한 string stream에 데이터는 어떻게 넣을까?
첫번째로 <<을 사용하는것이다
#include <sstream>
std::stringstream ss;
ss << "Kelvin! \n";
std::cout << ss.str();
return 0;
std::stringstream 타입 객체를 만들고 << 연산자를 통해 string 데이터를 집어 넣었다, 결국 이 문자열 데이터는 std::stringstream의 내부 버퍼에 쌓이게 된다
<<는 stringstream 내부 버퍼에 내용을 완전히 지우고 덮어씌우는 방식이 아닌 추가하는 방식이다
str() 멤버함수로 현재 stringstream 내부의 모든 데이터를 return할 수 있다 (std::string 타입으로 return한다)
두번째는 str(string)을 사용하는것이다
std::stringstream ss;
ss.str("Kelvin! \n");
std::cout << ss.str();
이 방식은 <<와 다르게 내부 버퍼에 내용을 완전히 지우고 새로운 string으로 덮어씌우는 방식이다
operator>> 를 사용하여 stringstream 내부의 데이터를 순차적으로 추출할 수 있다
std::stringstream os{};
os << "12345 67.89"; // 스트림에 숫자 문자열을 삽입
std::string strValue{};
os >> strValue; // 첫 번째 값 "12345"를 추출하여 strValue에 저장
std::string strValue2{};
os >> strValue2; // 두 번째 값 "67.89"를 추출하여 strValue2에 저장
std::cout << strValue << " - " << strValue2 << '\n';
//12345 - 67.89가 출력된다
<<와 >>를 이용한 숫자 <-> 문자열 변환
<<와 >>는 모든 기본 데이터 타입을 지원하기 때문에 이러한 특성으로 문자열과 숫자 사이의 변환을 쉽게 할 수 있다
std::stringstream os{};
constexpr int ivalue{ 12345 };
constexpr double dvalue{ 67.89 };
os << ivalue << " " << dvalue;
std::string strvalue1{};
std::string strvalue2{};
os >> strvalue1 >> strvalue2;
std::cout << strvalue1 << " " << strvalue2 << std::endl;
int와 double타입의 데이터가 stringstream에 <<로 들어가면서 문자열로 변환되어 buffer에 저장된다, 그리고 >>를 통해 std::string으로 반환되는 코드이다
반대로 문자열로부터 숫자로 변환하는것도 마찬가지이다
std::stringstream os{};
os << "12345 67.89";
int intvalue1{};
double doublevalue1{};
os >> intvalue1 >> doublevalue1; //여기서 문자열이 int와 double로 암시적 캐스팅 된다
std::cout << intvalue1 << " " << doublevalue1 << std::endl;
stringstream 버퍼 비우기
str("")로 간단하게 stringstream의 buffer를 비울 수 있다
std::stringstream os{};
os << "Kelvin";
os.str("");
os << "Jacob";
std::cout << os.str(); //Jacob하나만 나오게 된다
혹은 std::string{}으로 비울수도 있다
os.str(std::string{});
하지만 stringstream의 buffer를 비울때는 단순히 buffer내용만 비우는게 아니라 stream의 상태 flag도 같이 비우는게 좋다
따라서 clear()도 사용하는게 좋다
std::stringstream os{};
os << "Kelvin";
os.str("");
os.clear();
os << "Jacob";
std::cout << os.str();
그렇다면 이러한 stream의 상태 flag들에는 어떤게 있을까?
Stream state
ios_base 클래스에는 stream을 사용할 때 발생할 수 있는 다양한 상황을 알리는데 사용되는 여러 상태 flag가 포함되어 있다
ios는 ios_base를 상속받기 때문에 std::ios::failbit 이런식으로 접근해서 사용하면 된다
ios는 이러한 상태에 편리하게 접근할 수 있도록 다양한 멤버 함수를 지원한다
std::cout << "Enter int : ";
int intvalue{};
std::cin >> intvalue;
std:: cout << std::cin.fail();
//kelvin을 입력하면 1이 나오게 된다
이렇게 stream상태가 goodbit 상태가 아니라면 그 뒤에 해당 stream에 대한 추가 작업은 전부 무시된다, 이를 clear()를 통해서 강제로 goodbit 상태로 만들고 추가 작업을 허용시킬 수 있다
Input validation
Input validation으로 사용자의 입력이 특정 기준을 충족하는지 검사할 수 있다, 보통 문자열과 숫자에 많이 사용한다
C++에는 특정 문자가 숫자인지 문자인지 판별해주는 함수를 제공한다, 이는 < cctype > 헤더에서 제공한다
std::string test{ "?!123ABCD" };
for (int i = 0; i < test.size(); ++i)
{
const char reschar{ test.at(i) };
if (std::isalpha(reschar))
{
std::cout << "Alpha" << '\n';
}
else if (std::isdigit(reschar))
{
std::cout << "Digit" << '\n';
}
else if (std::ispunct(reschar))
{
std::cout << "Punct" << '\n';
}
}
//Punct2번, Digit3번, Alpha4번이 나오게 된다
이때 std::isalnum()과 같은 함수에 숫자를 그대로 작성하게 되면 아스키 코드 문자로 변환되어 원하는 결과대로 나오지 않을 수 있기 때문에 유의해야 한다
문자열 전체가 특정 조건을 만족하는지 확인하려면 어떻게 할까? std::ranges::all_of()를 사용하면 된다
std::ranges::all_of(시작, 끝, 함수객체);로 시작부터 끝까지 함수객체로 검사할 수 있다
#include <algorithm>
#include <cctype>
bool IsValidName(std::string_view InName)
{
return std::ranges::all_of(InName.begin(), InName.end(), [](char ch) {
return std::isalpha(ch) || std::isspace(ch);
});
}
int main()
{
std::string name{};
do
{
std::cout << "Enter Name : ";
std::getline(std::cin, name);
} while (!IsValidName(name));
return 0;
}
그렇다면 숫자 input validation은 어떻게 할까?
가장 기본적인 검사법은 stream의 상태를 확인하는것이다
int intValue{};
std::cin >> intValue;
if (std::cin.fail())
{
std::cin.clear(); //flag를 비워준다
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
//stream의 max사이즈 만큼 버린다
std::cout << "Fail" << '\n';
}
return 0;
이때 치명적인 문제가 하나 발생할 수 있는데 만약 입력을 34kelvin으로 처리하면 어떻게 될까? 바로 failbit가 되지 않아 성공적인 처리가 되어버린다
34는 intValue로 들어가게 되고 kelvin은 stream에 남겨져 있기 때문에 failbit이 뜨지 않은 것이다
이렇게 되면 stream buffer에는 쓰레기 데이터가 남게 되기 때문에 다음 입출력에서 문제가 될 수 있다
따라서 입력 직후 ignore()를 통해 비워주는게 좋다
#include <iostream>
#include <limits>
int intValue{};
std::cin >> intValue;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
if (std::cin.gcount() > 1)
{
std::cout << "Fail";
}
여기서 std::cin.gcount()는 stream에서 추출된 문자 개수가 나오게 된다, enter키를 제외해야하기 때문에 1보다 큰 조건으로 설정해야 의도한대로 결과가 나온다
이러한 과정을 문자열 -> 숫자로로 변환을 통해 조금 더 쉽게 처리할 수 있다
#include <optional>
#include <charconv>
std::optional<int> stringToInt(const std::string& str)
{
int value;
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value);
//std::from_chars를 이용하여 문자열의 시작 ~ 끝까지를 int타입인 value로 변환하여 넣는다
//str.data()로 첫번째 문자를 가리키는 포인터를 얻을 수 있다
//ptr에는 파싱이 끝난 위치의 포인터값이 들어간다 (정상적이라면 문자열 끝)
//ec에는 에러코드 std::errc가 들어간다, 성공한다면 ec == std::errc()로 들어갈 수 있다
if (ec != std::errc() || ptr != str.data() + str.size())
{
return {};
}
return value;
}
int main()
{
std::optional<int> result = stringToInt("KK12345");
if (result.has_value())
{
std::cout << "Num";
}
return 0;
//KK가 껴있으면 전부 숫자가 아니기 때문에 Num이 출력되지 않는다
}
std::optional과 std::from_char로 현대적으로 전제 문자열이 숫자인지 판단할 수 있다