Programming Language/C++

[C++] stream(istream, ostream, sstream)과 stream insertion/extraction 연산자 (>>, <<)

lumana 2024. 7. 1. 16:30

C++에서 istream, ostream, sstream는 모두 표준 라이브러리에서 제공하는 스트림 클래스입니다. 이 클래스들은 입출력 작업을 추상화하여 파일, 콘솔, 문자열 등을 손쉽게 다룰 수 있도록 도와줍니다. 각각에 대해 자세히 설명하겠습니다.

1. istream

istream은 입력 스트림을 나타내는 클래스입니다. 이 클래스는 데이터 입력을 위한 다양한 메소드를 제공합니다. istream의 주요 역할은 데이터의 입력을 관리하는 것입니다.

주요 기능

  • 연산자 오버로딩 (>>, stream insertion operator): 데이터를 입력받을 때 사용하는 연산자입니다.
  • ex) cin
    • cin은 c++ 표준 라이브러리에서 제공하는 객체로, istream 클래스의 객체임
  • int x;
    std::cin >> x; // 콘솔로부터 정수를 입력받아 x에 저장
  • 멤버 함수:
    • get(): 한 문자를 읽습니다.
    • getline(): 한 줄을 읽습니다.
    • read(): 바이너리 데이터를 읽습니다.

예시

#include <iostream>
using namespace std;

int main() {
    int num;
    cout << "Enter a number: ";
    cin >> num;
    cout << "You entered: " << num << endl;
    return 0;
}

2. ostream

ostream은 출력 스트림을 나타내는 클래스입니다. 이 클래스는 데이터 출력을 위한 다양한 메소드를 제공합니다. ostream의 주요 역할은 데이터를 출력하는 것입니다.

주요 기능

  • 연산자 오버로딩 (<<, stream extraction operator): 데이터를 출력할 때 사용하는 연산자입니다.
  • ex) cout
    • cout은 c++ 표준 라이브러리에서 제공하는 객체로, ostream 클래스의 객체임
  • int x = 10;
    std::cout << x; // 콘솔에 정수를 출력
  • 멤버 함수:
    • put(): 한 문자를 출력합니다.
    • write(): 바이너리 데이터를 출력합니다.

예시

#include <iostream>
using namespace std;

int main() {
    int num = 10;
    cout << "The number is: " << num << endl;
    return 0;
}

3. stringstream

stringstream는 문자열 스트림을 나타내는 클래스입니다. 이 클래스는 istreamostream의 기능을 모두 포함하며, 문자열을 입출력할 수 있도록 합니다. 주로 문자열을 파싱하거나 생성하는 데 사용됩니다.

주요 기능

  • 연산자 오버로딩 (<<, >>): 문자열을 입력받고 출력하는 데 사용됩니다.
    std::stringstream ss;
    ss << "123";
    int x;
    ss >> x; // 문자열 "123"을 정수 123으로 변환하여 x에 저장
  • 멤버 함수:
    • str(): 스트림의 내용을 문자열로 반환하거나 설정합니다.

예시

#include <iostream>
#include <sstream>
using namespace std;

int main() {
    stringstream ss;
    ss << 123 << " " << 456;

    int a, b;
    ss >> a >> b;

    cout << "a: " << a << ", b: " << b << endl;
    return 0;
}

이러한 스트림 클래스를 사용하면 C++에서 다양한 입출력 작업을 쉽게 처리할 수 있습니다.

 

C++에서는 stringstream 클래스를 사용하여 java.io.BufferedReader와 유사한 방식으로 데이터를 버퍼에 저장한 후, 한 번에 입출력할 수 있습니다. stringstream은 문자열 스트림을 제공하므로, 메모리 내에서 버퍼링된 입출력 작업을 수행할 수 있습니다.

예시: stringstream을 사용한 버퍼링된 입출력

코드 설명:

  • 데이터를 stringstream에 쓰고, 나중에 한 번에 출력합니다.
  • stringstream을 사용하여 문자열을 버퍼에 저장합니다.
  • 필요한 시점에 버퍼에 저장된 데이터를 출력합니다.

코드:

#include <iostream>
#include <sstream>
using namespace std;

int main() {
    // stringstream 객체를 생성
    stringstream buffer;

    // 버퍼에 데이터를 작성
    buffer << "Hello, " << "world!" << endl;
    buffer << "This is a buffered output example." << endl;

    // 더 많은 데이터를 버퍼에 작성
    for (int i = 1; i <= 5; ++i) {
        buffer << "Line " << i << ": This is some buffered text." << endl;
    }

    // 버퍼에서 데이터를 읽어와서 한 번에 출력
    cout << buffer.str();

    // 버퍼를 지우고 재사용할 수도 있습니다
    buffer.str(""); // 버퍼를 비운다
    buffer.clear(); // 상태 플래그를 초기화

    // 버퍼에 다시 데이터를 작성
    buffer << "New data after clearing the buffer." << endl;

    // 다시 버퍼의 내용을 출력
    cout << buffer.str();

    return 0;
}

설명:

  • stringstream buffer;는 문자열 스트림 객체를 생성합니다.
  • buffer << "Hello, " << "world!" << endl;은 여러 문자열을 버퍼에 차례대로 저장합니다.
  • 반복문을 통해 여러 줄의 텍스트를 버퍼에 추가로 저장합니다.
  • cout << buffer.str();은 버퍼에 저장된 모든 데이터를 한 번에 출력합니다.
  • buffer.str("");buffer.clear();은 버퍼를 비우고 상태 플래그를 초기화하여, 버퍼를 다시 사용할 수 있도록 합니다.
  • 버퍼에 새로운 데이터를 추가하고 다시 출력할 수 있습니다.

이 방식은 메모리 내에서 버퍼링된 입출력을 가능하게 하여, 파일이나 네트워크 스트림과 같은 실제 입출력 작업에서 발생하는 비용을 줄일 수 있습니다. 또한, 데이터를 버퍼에 저장한 후 나중에 한 번에 처리할 수 있어 효율적인 입출력 작업을 수행할 수 있습니다.

 

예제: 사용자 정의 클래스 Person>><< 연산자 오버로딩

사용자 정의 클래스에 >> << 연산자를 오버로딩하는 예제를 소개하겠습니다. 이를 통해 클래스 객체를 istream ostream을 통해 입출력할 수 있습니다.

클래스 정의:

  • Person 클래스는 이름과 나이를 저장합니다.
  • >> 연산자는 입력 스트림으로부터 데이터를 읽어 Person 객체에 저장합니다.
  • << 연산자는 Person 객체의 데이터를 출력 스트림에 출력합니다.

코드:

#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person() : name(""), age(0) {}
    Person(string name, int age) : name(name), age(age) {}

    // >> 연산자 오버로딩 (입력)
    friend istream& operator>>(istream& is, Person& person) {
        cout << "Enter name: ";
        is >> person.name;
        cout << "Enter age: ";
        is >> person.age;
        return is;
    }

    // << 연산자 오버로딩 (출력)
    friend ostream& operator<<(ostream& os, const Person& person) {
        os << "Name: " << person.name << ", Age: " << person.age;
        return os;
    }
};

int main() {
    Person p1;

    // >> 연산자 사용 (입력)
    cin >> p1;

    // << 연산자 사용 (출력)
    cout << p1 << endl;

    return 0;
}

설명:

  • 클래스 정의:
    • Person 클래스는 nameage 멤버를 가집니다.
    • 기본 생성자와 매개변수를 받는 생성자를 정의합니다.
  • >> 연산자 오버로딩:
    • istream& operator>>(istream& is, Person& person)istream 객체를 통해 사용자로부터 이름과 나이를 입력받아 Person 객체에 저장합니다.
    • friend 키워드를 사용하여 이 연산자가 Person 클래스의 비공개 멤버에 접근할 수 있도록 합니다.
  • << 연산자 오버로딩:
    • ostream& operator<<(ostream& os, const Person& person)Person 객체의 이름과 나이를 출력 스트림에 출력합니다.
    • friend 키워드를 사용하여 이 연산자가 Person 클래스의 비공개 멤버에 접근할 수 있도록 합니다.
  • main 함수:
    • Person 객체를 생성하고, cin >> p1;을 통해 사용자로부터 데이터를 입력받습니다.
    • cout << p1 << endl;을 통해 객체의 데이터를 출력합니다.

이 예제를 통해 사용자 정의 클래스에 >><< 연산자를 오버로딩하여 스트림 입출력을 쉽게 구현할 수 있습니다. 이 방법을 사용하면 클래스 객체를 표준 입출력 함수와 동일하게 다룰 수 있어 코드의 일관성과 가독성이 향상됩니다.

 

istreamostream 연산자 오버로딩 함수에서 반환되는 istream&ostream&는 연속적인 입출력 작업을 가능하게 하고, 스트림 상태를 유지하는 중요한 역할을 합니다. 각각의 역할을 자세히 설명하겠습니다.

istream& operator>>(istream& is, Person& person)

이 함수는 istream 객체를 통해 Person 객체에 데이터를 입력하는 역할을 합니다. 반환값으로 istream&를 반환하는 이유는 다음과 같습니다:

  1. 연속적인 입력 가능:
    • istream&를 반환함으로써 연속된 입력 연산을 할 수 있습니다.
    • 예를 들어, cin >> p1 >> p2;와 같이 여러 객체에 연속적으로 입력할 수 있습니다.
  2. 스트림 상태 유지:
    • 반환된 istream&는 스트림의 상태(예: 실패 플래그)를 유지합니다.
    • 입력 중에 오류가 발생하면, 반환된 스트림 객체에 오류 상태가 설정되어 이후의 입력 연산에 영향을 미칩니다.
  3. 연산자 체이닝:
    • istream&를 반환함으로써, 다른 연산자와의 체이닝을 가능하게 합니다.
    • 예를 들어, cin >> p1 >> p2와 같이 하나의 입력 스트림으로 여러 객체를 연속적으로 입력받을 수 있습니다.

ostream& operator<<(ostream& os, const Person& person)

이 함수는 ostream 객체를 통해 Person 객체의 데이터를 출력하는 역할을 합니다. 반환값으로 ostream&를 반환하는 이유는 다음과 같습니다:

  1. 연속적인 출력 가능:
    • ostream&를 반환함으로써 연속된 출력 연산을 할 수 있습니다.
    • 예를 들어, cout << p1 << p2;와 같이 여러 객체를 연속적으로 출력할 수 있습니다.
  2. 스트림 상태 유지:
    • 반환된 ostream&는 스트림의 상태(예: 실패 플래그)를 유지합니다.
    • 출력 중에 오류가 발생하면, 반환된 스트림 객체에 오류 상태가 설정되어 이후의 출력 연산에 영향을 미칩니다.
  3. 연산자 체이닝:
    • ostream&를 반환함으로써, 다른 연산자와의 체이닝을 가능하게 합니다.
    • 예를 들어, cout << p1 << ", " << p2와 같이 하나의 출력 스트림으로 여러 객체를 연속적으로 출력할 수 있습니다.

예시 코드의 재사용 가능성

연속적인 입출력 예시를 통해 반환된 istream&ostream&가 어떻게 사용되는지 보여드리겠습니다.

#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person() : name(""), age(0) {}
    Person(string name, int age) : name(name), age(age) {}

    // >> 연산자 오버로딩 (입력)
    friend istream& operator>>(istream& is, Person& person) {
        cout << "Enter name: ";
        is >> person.name;
        cout << "Enter age: ";
        is >> person.age;
        return is;
    }

    // << 연산자 오버로딩 (출력)
    friend ostream& operator<<(ostream& os, const Person& person) {
        os << "Name: " << person.name << ", Age: " << person.age;
        return os;
    }
};

int main() {
    Person p1, p2;

    // 연속적인 입력
    cin >> p1 >> p2;

    // 연속적인 출력
    cout << p1 << endl << p2 << endl;

    return 0;
}

 

스트림 연산자 우선순위

cin >> p1 >> p2; 구문에서 cin >> p1이 먼저 동작합니다. 이는 연산자 오버로딩과 연산자 우선순위 및 결합 규칙에 따라 결정됩니다.

연산자 우선순위와 결합 규칙

  1. 연산자 우선순위:
    • >> 연산자는 왼쪽에서 오른쪽으로 결합합니다.
    • 따라서, 가장 왼쪽의 cin >> p1이 먼저 평가됩니다.
  2. 연산자 오버로딩:
    • cin >> p1이 평가될 때, operator>>(cin, p1)이 호출됩니다.
    • 이 연산자는 istream&를 반환하므로, cin 스트림을 반환합니다.
  3. 연속적인 평가:
    • 반환된 istream&에 대해 다음 연산 >> p2가 적용됩니다.
    • 즉, cin >> p1이 평가된 후, 반환된 스트림 객체에 대해 >> p2가 평가됩니다.

예시를 통해 설명

다음은 Person 클래스에 대해 >> 연산자 오버로딩을 사용한 예제입니다.

#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    string name;
    int age;

public:
    Person() : name(""), age(0) {}
    Person(string name, int age) : name(name), age(age) {}

    // >> 연산자 오버로딩 (입력)
    friend istream& operator>>(istream& is, Person& person) {
        cout << "Enter name: ";
        is >> person.name;
        cout << "Enter age: ";
        is >> person.age;
        return is;
    }

    // << 연산자 오버로딩 (출력)
    friend ostream& operator<<(ostream& os, const Person& person) {
        os << "Name: " << person.name << ", Age: " << person.age;
        return os;
    }
};

int main() {
    Person p1, p2;

    // 연속적인 입력
    cin >> p1 >> p2;

    // 연속적인 출력
    cout << p1 << endl << p2 << endl;

    return 0;
}

동작 순서

  1. 첫 번째 평가: cin >> p1
    • operator>>(cin, p1)가 호출됩니다.
    • 사용자로부터 이름과 나이를 입력받아 p1 객체에 저장합니다.
    • 반환값으로 cin 스트림 객체가 반환됩니다.
  2. 두 번째 평가: cin >> p2
    • 첫 번째 평가의 반환값인 cin 스트림 객체에 대해 operator>>(cin, p2)가 호출됩니다.
    • 사용자로부터 이름과 나이를 입력받아 p2 객체에 저장합니다.

결과적으로, cin >> p1 >> p2; 구문은 다음과 같이 동작합니다:

  1. cin >> p1을 먼저 평가하여 p1에 데이터를 입력받습니다.
  2. 그 결과로 반환된 cin 스트림 객체에 대해 >> p2를 평가하여 p2에 데이터를 입력받습니다.

요약

cin >> p1 >> p2;에서 cin >> p1이 먼저 동작하고, 그 다음에 p1에 대한 입력이 완료된 후에 cin >> p2가 동작합니다. 이를 통해 연속적인 입력이 가능합니다.