2025/03/23 ch.13 C++ I/O 심층 분석
출력 스트림에서 제공하는 메서드
출력 스트림에서 가장 대표적인 연산자는 <<
put() write()
저수준 출력 메서드 - 출력 동작을 갖춘 객체나 변수를 인수로 받지 않고, 문자 하나를 받거나(put) 문자 배열 하나를 인수로 받는다(write)
const char* test { "hello there\n" };
cout.write(test, strlen(test));
cout.put('a');
flush()
일반적으로 출력 스트림은 들어온 데이터를 곧바로 쓰지 않고 버퍼에 잠시 쌓아둔다
목적지가 파일과 같은 스트림일 때는 한 문자씩 처리하기보다는 블록 단위로 묶어서 처리하는 것이 훨씬 효율적임
쌓아둔 데이터를 모두 내보내서 버퍼를 비우는 조건
- endl과 같은 경곗값에 도달할 때
- 스트림이 스코프를 벗어나 소멸될 때
- 스트림 버퍼가 가득 찼을 때
- flush()를 호출해서 스트림 버퍼를 명시적으로 비울 때
- 출력 스트림에 대응되는 입력 스트림으로부터 요청이 들어올 때(cin으로 입력 받으면 cout의 버퍼를 비움)
# 출력 스트림이라고 모두 버퍼를 사용하는건 아니고 cerr은 버퍼 사용X 바로 출력
출력 에러 처리하기
존재하지 않는 파일을 열려고 하거나 디스크가 꽉 차서 쓸 수 없으면 에러가 발생
good() - 스트림을 정상적으로 사용할 수 있는 상태인지 확인
if (cout.good()) {
cout << "All good" << endl;
}
상태 정보를 조회할 수 있지만 사용할 수 없는 상태일때 그 원인을 구체적으로 알려주지 않는다.
bad() 메서드로 자세히 볼 수 있다.
fail() - 최근(바로 직전) 수행한 연산에 오류가 발생했는지 확인 가능
cout.flush();
if (cout.fail()) {
cerr << "Unable to flush to standard out" << endl;
}
//스트림을 bool 타입으로 변환하는 연산자. !fail()과 결과가 같음
if (!cout) {
cerr << "Unable to flush to standard out" << endl;
}
스트림에 문제가 있으면 익셉션 던지도록 만들기
ios_base::failure 익셉션을 처리하도록 catch문을 작성하면 됨
what() - 현재 발생한 에러에 대한 정보를 볼 수 있음
code() - 에러 코드를 볼 수 있다.
clear() - 스트림의 에러 상태 초기화
출력 매니퓰레이터
메니퓰레이터라는 객체를 받아서 스트림의 동작을 변경할 수도 있다.
스트림의 동작을 변경하는 작업뿐만아니라 데이터를 전달하면서 동작도 변경할 수 있다.
endl도 매니퓰레이터. 데이터와 동작을 모두 담고 있다.
스트림에 전달될 때 EOL 문자를 출력하고 버퍼를 비움
메니퓰레이터는 모두 한번 설정되면 명시적으로 리셋하기 전까지 다음 출력에 계속 반영됨
# setw는 바로 다음 출력에만 적용
13.1.4 스트림을 이용한 입력
입력 스트림을 이용하면 전형 데이터 뿐만 아니라 비정형 데이터도 쉽게 읽을 수 있다.
>> 연산자로 입력 스트림에서 읽은 데이터를 변수에 저장
공백을 기준으로 입력된 값을 토큰 단위로 나눈다.
get()을 사용하면 입력값에 공백을 담을 수 있다.
cin은 cout 버퍼를 즉시 비움
입력 에러 처리하기
입력 스트림의 에러는 대부분 읽을 데이터가 없을때 발생
입력 스트림에 접근하기 전에 조건문으로 스트림의 상태를 확인하는것이 좋다
while(cin){}
while(cin >> ch) {}
입력 스트림도 good(), bad(), fail() 메서드를 호출할 수 있다.
스트림 끝에 도달하면 true를 리턴하는 eof() 메서드도 사용할 수 있다.
입력 메서드
입력 스트림도 >>연산자보다 저수준으로 접근하는 메서드를 제공
get()
>>연산자를 사용할 때 자동으로 토큰 단위로 잘리는 문제를 피하고 싶을 때 주로 get()을 사용
string readName(istream& stream)
{
string name;
while (stream) { // 또는 while (!stream.fail()) {
int next { stream.get() };
if (!stream || next == std::char_traits<char>::eof())
break;
name += static_cast<char>(next); // 문자 이어 붙이기
}
return name;
}
get()값을 int로 받은 이유 - 문자 데이터 뿐만이 아니라 eof같은 특수한 값도 받아야 하기 때문
stream을 비 const 매개변수로 받은 이유 - 스트림에서 데이터를 읽는 메서드는 실제 스트림을(특히 위치) 변경하기 때문
이 코드는 에러가 나거나 스트림에 끝에 도달하면 반복문이 종료된다
string readName(istream& stream)
{
string name;
char next;
while(stream.get(next)) {
name += next;
}
return name;
}
입력 스트림이 에러가 아닐때만 true를 리턴하므로 더 깔끔
unget()
일반적으로 입력 스트림은 한 방향으로만 진행하는 컨베이어 벨트와 같지만 unget()은 데이터를 다시 입력 소스 방향으로 보낼 수 있다.
unget()을 호출하면 스트림이 한 칸 앞으로 거슬러 올라간다.
#현재 위치가 스트림의 시작점이면 실패
putback()
unget() 처럼 입력 스트림을 한 문자만큼 되돌림.
#unget()과 달리 스트림에 되돌릴 문자를 인수로 받음
<스트림에 원하는 문자를 밀어넣는 신기한 함수>
peek()
get()을 호출할 때 리턴될 값을 미리 보여줌
# queue의 front() 처럼 버퍼의 값을 꺼내지 않고 맨 위에꺼 하나 보여주는 함수
getline()
프로그램을 작성하다보면 데이터를 한 줄 씩 읽을 일이 많으므로 전용 메서드가 있다.
한줄 크기만큼 미리 설정한 버퍼가 가득 채워질 때까지 문자를 읽음. \0(EOL) 문자도 버퍼의 크기에 포함됨
getline()은 입력 스트림에서 EOL이 나올 때까지 한 줄에 해당하는 문자들을 읽는다. EOL문자는 스트링에 담기지 않는다.
##
string의 std::getline()과 혼동하면 안된다.
istream.getline(char* buffer, streamsize) <- streamsize 버퍼 크기를 직접 설정해줘야함
std::getline(istream&, std::string&) <- 버퍼 사이즈를 설정할 필요가 없음. 좀더 고수준
둘다 마지막 매개변수로 구분자를 지정할 수 있다. 디폴트는 \n
입력 매니퓰레이터
# 입력은 로케일 설정에 영향을 받는다.
시스템 로케일이 미국이면 1,000 입력을 1000으로 받고 독일이면 1.000을 1000으로 받는다
13.1.5 객체 입출력
원하는 타입이나 클래스를 처리하도록 <<나 >>를 오버로딩
13.2 스트링 스트림
string에 스트림 개념을 추가한 것
텍스트 데이터를 메모리에서 스트림 혀태로 표현하는 인메모리 스트림을 만들 수 있다.
기본적으로 토큰화 기능을 제공하기 때문에 텍스트를 구문 분석하기도 편하다
<sstream>
string에 데이터를 쓸 때 std::ostringstream
stirng에 데이터를 읽을 때 std::istringstream
쓰고싶은 문자열을 outStream에 누적해서 받으며
화면에 출력하고 싶을때 cout으로 한번에 출력하는 것이다.
#include <iostream>
#include <sstream>
using namespace std;
int main() {
stringstream ss("123 456 789");
int a;
ss >> a; // 읽기: 123
cout << "First number: " << a << endl;
int b;
ss >> b; // 읽기: 456
cout << "Second number: " << b << endl;
}
스트링 스트림이 string보다 좋은 점은 데이터를 읽거나 쓸 지점을 알 수 있다는 것
매니풀레이터와 로케일을 다양하게 지원하므로 string보다 포맷을 융통성 있게 다룰 수 있다
조그만 스트링을 연결하는 방식으로 스트링을 만들때 일반적인 string보다 훨씬 성능이 좋다
13.3 파일 스트림
파일은 스트림 추상화에 딱 맞음. 파일을 읽고 쓸 때 항상 현재 위치를 추적하기 때문
C++는 파일 출력과 입력을 위해 std::ofstream과 std::ifstream 클래스를 제공 <fstream>헤더에 있음
파일 시스템을 다룰 때는 에러 처리가 특히 중요함. 표준 헤러 처리 메커니즘을 이용한다.
파일 스트림 생성자는 파일의 이름과 파일을 열때 적용할 모드에 대한 인수를 받는다. 디폴트는 ios_base::out
|로 여러가지를 조합해서 쓸 수도 있다.
ifstream도 디폴트로 in이 설정됨
ifstream과 ofstream 소멸자는 자동으로 연 파일을 닫으므로 close() 같은걸 호출할 필요가 없음
13.3.1 텍스트 모드와 바이너리 모드
파일 스트림은 기본적으로 텍스트 모드로 연다
파일 스트림을 생성할 때 ios_base::binary 플래그를 지정하면 파일을 바이너리 모드로 연다.
바이너리 모드는 정확히 바이트 단위로 지정한 만큼만 파일에 씀
파일을 읽을 때는 파일에서 읽은 바이트 수를 리턴
텍스트 모드는 파일에서 \n이 나올때마다 한 줄씩 읽거나 씀.(윈도우는 줄의 끝을 \r\n으로 표기하므로 \n을 자동으로 \r\n으로 바꿔서 저장함)
13.3.2 seek()과 tell() 메서드로 랜덤 액세스하기
seek() 메서드는 입출력 스트림에서 현재 위치를 원하는 지점으로 옮김
입력 스트림 - seekg() <- g는 get을 의미
출력 스트림 - seekp() <- p는 put을 의미
파일 스트림처럼 입출력을 모두 가질때가 있으므로 두개로 나눠놓음
seek들은 각각 두 버전이 있다
1. 절대 위치를 나타내는 인수 하나만 받아, 그 위치로 이동
2. 오프셋과 위치에 대한 인수를 받아 지정한 위치를 기준으로 떨어진 거리로 이동
outStream.seekp(ios_base::beg);
outStream.seekp(2, ios_base::beg);
이런식으로 쓴다고 하네요
tellx() 메서드로 스트림의 현재 위치를 알아낼 수 있다.
현재 위치를 streampos 타입의 값으로 리턴
seekx()를 호출하거나 tellx()를 다시 호출하기 전에 현재 위치를 기억하고 싶다면 앞서 tellx()에서 리턴한 값을 저장해둔다.
입출력 따로따로 tellg tellp 존재
13.4 양방향 I/O
입출력을 모두 처리하는 스트림
iostream을 상속. istream ostream을 동시에 상속하기 때문에 다중 상속의 대표적인 예
fstream 클래스는 양방향 파일 시스템을 표현
파일 안에서 데이터를 교체할 때 유용함
- 정확한 위치를 발견할때까지 데이터를 읽다가 필요한 시점에 즉시 쓰기 모드로 전환
iostream을 이용하면 매번 파일 전체를 다시 쓸 필요 없이 데이터를 검색하다가 적절한 지점을 발견하면 파일을 추가 모드로 열고 원하는 내용을 추가하면 됨
13.5 파일 시스템 지원 라이브러리
<filesystem> 헤터파일의 std::filesystem 네임스페이스 아래에 정의되어있음
path
경로를 표현
절대경로 상대경로를 표련할 수 있고 파일 이름이 포함될 수도, 빠질수도 있다
13.5.3 헬퍼 함수
copy() - 파일이나 디럭터리를 복제
create_directory() - 파일시스템에 디렉토리를 새로 만듦
exists() - 주어진 디렉터리나 파일이 실제로 존재하는지 조회
등등등...
13.5.4 디렉터리에 대한 반복문
주어진 디렉터리에 속한 파일이나 하위 디렉터리에 대해 재귀적으로 반복하는 코드를 작서앟려면 recursive_directory_iterator를 사용하면 된다
~723