C++ - iostream
C++
표준 스트림과 C++ iostream
표준 스트림
컴퓨터 프로그램에서 입출력 데이터의 흐름 경로다.
그 경로는 보통 콘솔(터미널)을 통해 이루어지지만, 리디렉션이나 파이프를 사용하면 다른 매체로 바뀔 수 있다.
표준 스트림의 목적
장치에 대한 독립성
- 표준 스트림은 프로그램이 입출력 장치와 직접적으로 상호작용하는 대신, 추상화된 경로를 통해 데이터를 처리하도록 한다.
- 추상화함으로 프로그램은 입력과 출력을 장치에 의존하지 않고, 장치가 무엇인지에 상관없이 동일한 방법으로 데이터를 처리할 수 있게 된다.
유연성 제공
- 표준 스트림은 기본적으로 콘솔(터미널)과 연결되어 있지만, 리디렉션 기능을 통해 다른 매체(파일 네트워크 등)로 입출력을 쉽게 전환할 수 있다.
- 콘솔 프로그램이 파일 입력, 출력을 처리하려면, 파일 스트림을 사용해야하지만, 표준 입력과 표준 출력을 사용하면, 프로그램은 매체에 구애 받지 않고 동일한 방식으로 데이터를 처리할 수 있다.
간결한 인터페이스
- 표준 스트림을 사용하면 입출력 장치나 데이터 소스를 처리하는 세부 사항을 신경 쓸 필요 없이, 단순한 인터페이스로 데이터 처리가 가능하다.
- cin/cout 등의 표준 스트림 객체를 사용하면 입출력을 간단한 함수 호출로 처리할 수 있다.
플랫폼 독립성
- 표준 스트림은 다양한 운영 체제와 환경에서 동일한 방식으로 동작하도록 설계되었다.
- 윈도우나 리눅스와 같은 운영 체제에서 표준 스트림을 통해 입출력 처리를 동일하게 할 수 있다.
스트림의 기본적인 동작
- 스트림을 사용하려면 먼저 열기 작업을 수행해야 한다.
- ex) 파일을 읽으려면 파일 스트림을 열어야 한다.
- 스트림의 함수를 호출해 입출력을 처리한다.
- 사용이 끝난 스트림을 자원 관리를 위해 닫아준다.
- 파일 스트림의 경우, 파일을 읽고 쓸 때 운영 체제에서 파일 핸들을 할당하기에, 닫지 않으면 계속 사용하게 된다.
- 또한 일부 운영 체제나 파일 시스템에선 파일을 열면 잠금이 걸려 다른 프로그램이나 프로세스에서 해당 파일을 사용하지 못한다.
코드
1
2
3
4
5
6
7
8
void WriteTest()
{
ofstream outFile("test.txt"); // 파일 열기
outFile << "Test" << endl; //데이터 쓰기
outFile.close(); //파일 닫기
}
결과
표준 입력
- 표준 입력은 프로그램에 입력되는 데이터의 표준적인 출처를 뜻하며 stdin이라고 불린다.
- 유닉스 쉘에선 표준 입력이 키보드로 설정되어 있다.
표준 출력
- 표준 출력은 프로그램에서 출력 될 데이터의 표준적인 방향을 뜻하며, stdout과 stderr로 구분 가능하다.
- stdout은 정상적인 출력이 반환되는 방향을 말한다.
- stderr는 프로그램의 비정상 종료 시에 반환되는 방향이다.
C++의 표준 스트림
c++의 표준 스트림은 iostream 이다.
iostream은 크게 두 그룹으로 나눌 수 있다.
바이트 지향
- 기존의 바이트 형식의 ASCII 코드나 char 타입의 문자를 처리하는 데 사용하는 그룹이다.
- cin, cout, cerr 및 clog 가 있다.
와이드 문자 지향
- 유니코드와 wchar 타입의 문자를 처리하는 그룹
- wcin, wcout, wcerr 및 wclog가 있다.
iostream 헤더의 표준 스트림 객체들은 정적 객체들보다 먼저 생성된다.
그리고 정적 객체들의 소멸자가 실행되기 전에 소멸되지 않는다.
(사용자 정의 스트림은 사용자가 정의한 객체의 생명 주기에 의존)
cin
표준 입력 스트림
특징
- 기본적으로 키보드 입력을 처리한다.
- 데이터를 읽을 때 기본적으로 공백으로 구분된 입력을 처리한다.
- 문자열 입력 시 공백 포함 데이터를 읽으려면 std::getline을 이용해야한다.
- 입력된 데이터를 변수의 타입에 맞게 변환하는데, 실패하면 실패 상태로 설정한다.
실패 상태 발생 조건
- 사용자가 입력한 데이터가 변수 타입과 맞지 않은 경우
- 파일 끝(EOF)에 도달한 경우
- 입력 스트림이 손상된 경우
실패 상태의 cin
- 이후의 모든 cin 작업은 아무것도 수행하지 않고 실패한다.
- 스트림이 자동으로 복구되지 않으므로, 명시적으로 복구 작업을 해줘야한다.
cin의 fail, good 등으로 실패 상태인지 확인 가능하다.
실패 상태 복구
- cin.clear() 로 실패 상태를 정상 상태로 복원
- 입력 버퍼에 남아 있는 잘못된 데이터를 제거
- cin.ignore 혹은 getline으로 입력을 소모
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Cin()
{
int input;
cout << "입력 : ";
cin >> input; // 기본적인 입력 (공백을 기준으로 가장 앞 토큰을 input에 저장)
cout << "입력 결과 : " << input << endl;
cout << "잘못된 입력 해보기 : ";
cin >> input;
if (cin.fail()) // cin이 현재 실패 상태인지 확인
{
cout << "잘못된 입력" << endl;
cin.clear(); // cin을 정상 상태로 복구
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 입력 버퍼에서 개행문자로 설정한 \n까지의 모든 문자를 건너뛰게 함
cout << "정상 복구 완료" << endl;
}
cout << "다시 입력 : ";
cin >> input;
cout << "입력 결과 : " << input << endl;
}
결과
cout
표준 출력 스트림
특징
- 기본적으로 콘솔 화면에 데이터를 출력한다.
- 데이터를 출력할 때 다양한 데이터 타입을 자동으로 처리해준다.
- 출력도 입력과 마찬가지로, 출력 버퍼에 저장된 후, flush 될 때 화면에 출력된다.
- 수동으로 플러시 하는 방법으로는 cout.flush()과 endl이 있다.
flush와 endl의 차이
- flush는 줄바꿈 없이 버퍼만 플러시
- endl은 줄바꿈 포함해 버퍼를 플러시 두 경우 모두 출력 버퍼가 비워지며 화면에 즉시 출력된다.
출력 형식 조정
- iomanip 헤더를 사용해 출력 형식(포맷)을 조정할 수 있다.
- std::oct, std::hex 로 숫자 형식을 변경 가능
- std::setw 각 출력 값의 너비를 설정 가능, 출력 값이 너비보다 짧으면 나머지 공간이 공백으로 채워진다.
- std::right 출력 값을 오른쪽 정렬로 변경
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iomanip>
void Cout()
{
cout << "cout의 << 연산자가 cout(ostream) 객체와 << 오른쪽의 데이터를 연결한다." << std::endl;
cout << "각 데이터 타입에 맞는 형 변환을 통해 출력할 수 있게 된다." << std::endl;
cout << "출력은 화면에 바로 나타는 것이 아니라, 출력 버퍼에 먼저 저장된다." << std::endl;
cout << "버퍼에 저장된 데이터는 버퍼 플러시를 통해 출력된다." << std::endl;
cout << "버퍼는 flush 함수 호출이나, endl로 플러시 가능하다." << std::endl;
cout << std::endl; // 공백 줄 추가
// flush와 endl 차이점 설명
cout << "flush와 endl의 주요 차이점:" << endl;
cout << setw(30) << left << "- flush는 줄바꿈 없이 버퍼만 플러시한다." << endl;
cout << setw(30) << left << "- endl은 줄바꿈을 포함하여 버퍼를 플러시한다." << endl;
cout << endl;
cout << "두 경우 모두 출력 버퍼가 비워지며 화면에 즉시 출력된다." << std::endl;
cout << endl;
// flush 예시
std::cout << "flush 함수 플러시 결과";
std::cout.flush(); // 버퍼 플러시
std::cout << "(flush 이후)" << endl << endl;
// endl 예시
cout << "endl 함수 플러시 결과" << endl;
cout << "(endl 이후)" << endl << endl;
// iomanip 헤더를 사용한 출력 포맷 조정
cout << "iomanip 헤더를 사용하면 출력 포맷 조정이 가능하다" << endl;
cout << "- std::oct와 std::hex를 이용한 포맷 조정" << endl;
int num = 255;
cout << " - 10진법 : " << num << endl;
cout << " - 8진법 : " << std::oct << num << endl;
cout << " - 16진법 : " << std::hex << num << endl;
cout << "- setw와 right를 이용하면 출력 너비와 정렬 순서를 변경 가능하다." << endl;
cout << setw(00) << std::right << " cout << setw(00) << std::right" << endl;
cout << setw(40) << std::right << " cout << setw(40) << std::right" << endl;
cout << setw(50) << std::right << " cout << setw(50) << std::right" << endl;
}
결과
cerr
표준 오류 출력 스트림
특징
- 버퍼링되지 않고 출력 즉시 플러시한다.
- 오류 메시지나 경고를 출력하는 데 사용된다
코드
1
2
3
4
5
void Cerr()
{
cerr << "cerr는 출력 버퍼를 거치지 않고 바로 출력된다." << endl;
cerr << "즉시 화면에 표시되기에 오류 메시지가 출력되는 시점에 사용자가 바로 인지할 수 있게 된다." << endl;
}
clog
표준 로그 스트림
특징
- 출력은 버퍼가 플러시될 때 발생
- 주로 디버깅이나 로그 정보를 출력하는 데 사용된다.
- clog는 자동 플러시가 되지 않는다.
- clog가 소멸할 때, clog의 출력 버퍼가 가지고 있는 남은 데이터를 플러시한다.
IDE 디버그 모드에서 버퍼링 설정
Visual Studio의 디버그 모드에선, 스트림들이 버퍼링을 하지 않고 자동으로 플러시 된다.
만약 디버그 모드에서 테스트하려면, setvbuf 함수를 사용해 특정 스트림에 대한 버퍼링을 설정해줘야 한다.
setvbuf(stdout, nullptr, _IOFBF, 1024);
대상: stdout 버퍼링 모드 : _IOFBF (전체 버퍼링 모드)로 버퍼가 가득차거나 endl, flush로 명시적 플러시를 수행할 때. 크기 : 1024만큼의 버퍼 크기를 할당해준다.
cout은 stdout과 연결되어 있고, clog는 stderr와 연결되어있다. clog도 동일하게 setvbuf(stderr, nullptr, _IOFBF, 1024); 로 버퍼링하도록 해줬다.
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
void Clog()
{
clog << "clog는 로그 메시지를 출력할 때 사용한다." << endl;
clog << "cout과 유사하게 버퍼링하는 출력 스트림이다." << endl;
clog << "대신 자동으로 플러시되지 않는다." << endl;
setvbuf(stdout, nullptr, _IOFBF, 1024);
setvbuf(stderr, nullptr, _IOFBF, 1024);
clog << "1. clog 명시적 플러시 X\n";
cout << "2. cout 플러시" << endl;
clog << "3. clog 명시적 플러시 X\n";
}
결과
동기화
C++ 표준 스트림들은 C 표준 스트림들과 각각 동기화 되어있다.
동기화 상태의 C++ 스트림들
- 동기화된 C++ 스트림들이 대응되는 C 스트림 버퍼를 사용한다.
- C와 C++의 입출력 방식을 자유롭게 혼용 가능하다.
- 동기화된 스트림들은 thread-safe를 보장한다.
- std::ios_base::sync_with_stdio 함수를 통해 동기화를 끊을 수 있다.
비동기화 상태의 C++ 스트림들
- C++ 스트림은 독립적인 버퍼를 가지게 된다.
- C 스트림들과 동기화하지 않아 속도가 상승한다.
- 대신 C와 C++ 스트림을 섞어 사용하면 출력 순서를 보장하지 않는다.
- 또한 thread-safe를 보장하지 않는다.
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <cstdio>
void Sync_with_stdio()
{
setvbuf(stdout, nullptr, _IOFBF, 1024);
if (std::ios_base::sync_with_stdio(false)) // 동기화 해제
{
cout << "동기화 해제" << endl;
}
for (int i = 0; i < 50; i++)
{
cout << "a from cout\n"; //순서가 섞일 수 있다.
printf("b from printf\n");
cout << "c from cout\n";
}
}
tie
tie 함수로 다른 스트림과 연결이 가능하다.
cin와 cout의 경우 기본적으로 tie 되어있다.
cin에서 입력을 받들 때, cout 버퍼에 데이터가 있으면 자동으로 플러시된다.
1
2
3
4
5
6
7
8
9
10
void Tie()
{
setvbuf(stdout, nullptr, _IOFBF, 1024);
std::cin.tie(nullptr); // cin과 cout의 자동 연결 해제
std::cout << "cin 전에 cout 호출했음\n"; // 이 출력은 플러시되지 않음
int x;
std::cin >> x; // 입력을 받기 전에 cout이 플러시되지 않음
std::cout << "연결이 해제되어 cout 버퍼에 데이터가 남아도 플러시 되지 않음: " << x << std::endl; // 이 출력은 플러시됨
}
리다이렉션
프로그램의 표준 입출력, 에러 출력을 파일이나 다른 출력 장치로 보내는 방법이다. C++에서 파일 스트림을 사용하여 stdou과 stderr를 리다이렉션할 수 있다.
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Redirection()
{
FILE* f = nullptr; // 열려있는 파일 스트림을 저장할 포인터
if (freopen_s(&f, "redirection.txt", "w", stdout)) //세번째 매개변수는 파일 열기 모드(Write), 네번째는 리다이렉션하는 스트림
{
cout << "실패하면 0 이외의 값" << endl;
}
else
{
std::cout << "리다이렉션해 파일에 저장" << std::endl; // 프로그램 종료 시 "redirection.txt" 가 닫힌다.
}
fclose(f);
}