관리 메뉴

공부 기록장 💻

[C++] 표준 C++ 입출력 시스템 - 스트림(Stream), 버퍼(Buffer), ostream, istream, 포맷 입출력(Format I/O) - 포맷 함수/포맷 플래그/조작자(Manipulator) 본문

# Language & Tools/C++

[C++] 표준 C++ 입출력 시스템 - 스트림(Stream), 버퍼(Buffer), ostream, istream, 포맷 입출력(Format I/O) - 포맷 함수/포맷 플래그/조작자(Manipulator)

dream_for 2021. 5. 30. 02:02

(명품 C++ 프로그래밍 CH. 11)

 

 

C++ 표준 입출력 스트림

C++ 표준에서는 오직 스트림 입출력만 다룬다. 버퍼를 가지지 않는 저수준 입출력 방식을 다루지 않는다.

 

 

 

스트림(stream)

- 프로그램과 장치를 연결하며 바이트 단위로 입출력

  1. C++ 표준 입력 스트림 객체 : cin
  2. C++ 표준 출력 스트림 객체 : cout
  3. C++ 표준 오류 출력 스트림 객체 : cerr(버퍼 x), clog(버퍼 ㅇ)

 

 

버퍼(buffer)

- 운영체제 API를 호출하여 입출력 장치와 프로그램 사이의 데이터 전송 전 버퍼에 모아두어 API 호출의 호출 횟수를 줄인다.

  1. cin 입력 스트림 버퍼 : 입력된 데이터를 프로그램에 전달하기 전에 일시 저장하는 공간
    • <Bacspace> - 버퍼를 제어하는 제어키
    • <Enter> - 입력되면 버퍼의 키들을 전달
  2. cout 출력 스트림 버퍼 : 출력 장치로 보내기 전에 데이터를 일시 저장하는 공간
    • '\n'이 도착하거나 버퍼가 꽉 찰 때 스크린에 출력
    • cout.flush() - 출력 스트림 버퍼의 내용을 모두 출력

 


C++ 입출력 라이브러리

- 2003년 이전: 문자 하나를 한 바이트로 표현하는 언어의 문장만 입출력하도록 작성되었기 때문에, 문자 하나가 2바이트로 구성되는 한글 문자를 입력할 수 없었음

 

 

 

현 표준 C++ 입출력 라이브러리

 

- 템플릿(template)을 사용하여 C++ 입출력 라이브러리를 일반화 시킴

- 한 문자를 여러 바이트로 표현하는 다국어의 입출력 가능

- 구 표준을 기반으로 작성된 C++ 프로그램과의 호환성을 위해, 템플릿을 <char> 타입으로 구체화시키고, ios/istreamostream/iostream 등 과거의 이름들을 그대로 사용할 수 있도록 typedef 시켜놓았다.

- 그러나, 여전히 지금도 cin 으로는 한글을 문자 단위로는 읽을 수 없다.

 

 

 

 

 

<char 단위로 문자를 입출력하는 입출력 스트림 클래스>

 

클래스 설명
ios 모든 입출력 스트림 클래스들의 기본(Base) 클래스
istream, 
ostream,
iostream
istream: 문자 단위 입출력 스트림
oseram: 문자 단위 출력 스트림
iostream: 문자 단위 입출력 스트림
ifstream,
ofstream,
fstream
ifstream: 파일 읽기
ofstream: 파일 쓰기
fstream: 파일 읽고 쓰기

 

 

 


osream 클래스의 멤버 함수와 문자 출력

- << 연산자 : ostream 클래스에서 제공하는 스트림 삽입 연산자(오른쪽 피연산자를 왼쪽 cout 출력 스트림 객체에 삽입하여 화면에 출력)

 

 

 

 

<ostream 클래스의 멤버 함수>

ostream 클래스 멤버 함수 원형 설명
ostream& put(char ch) ch 문자를 스트림에 출력
ostream& write(char* str, int n) str 문자열에 있는 n개의 문자를 스트림에 출력
ostream& flush() 현재 스트림 버퍼에 있는 내용 강제 출력

 

 

 

 

cout.put(), cout.write(), cout.flush()

 

	char str[]="Hello, everyone!";
    
    	cout.put('A').put(80).put('\n'); // AP가 출력(80 ASCII 코드는 문자로 변환)
	cout.write(str,5); // str 앞의 5 문자(Hello) 만 출력
	cout.flush();

 

 


 

isream 클래스의 멤버 함수와 문자 입력

 

- >> 연산자 : istream 클래스에서 제공하는 스트림 추출 연산자(왼쪽 cin 입력 스트림 객체로부터 데이터를 읽어 오른쪽 피연산자의 변수에 저장)

- istream 멤버함수를 사용하면 >> 연산자를 이용하는 것과 달리, 공백 문자(space, tab, enter 등 white space key) 를 읽을 수 있다.

 

 

 

 

<istream의 문자 입력 멤버 함수>

 

istream 클래스 멤버 함수 원형 설명
int get() 입력 스트림에서 문자를 읽어 int형으로 리턴 => 오류, EOF 만나면 -1(EOF) 리턴
istream& get(char& ch) 입력 스트림에서 문자를 읽어 ch에 저장. 현재 입력 스트림 객체(*this)의 참조 리턴.
오류, EOF 만나면, 스트림 내부의 오류 플래그(failbit) 세팅

 

 

cin.get()

cin.get() 을 이용하여 ch 한 문자씩 읽어 버퍼에 입력하고, cout.put()으로 버퍼를 출력해보자.

 

	int ch;
	while((ch=cin.get())!=EOF){ // 키보드에서 문자 읽어 ch에 저장. EOF를 만나면 반복문 끝
		cout.put(ch); // 읽은 문자를 출력
		if(ch=='\n') break; // 공백키까지 읽으므로, 개행 문자 나오면 빠져나가는 break문 작성 

 

 

cin.get(ch)

문자를 읽어 참조 매개변수 ch에 저장하고 리턴한다.

cin.get(ch)가 EOF를 만나면 cin 스트림 내부에 eofbit 플래그를 세팅한다.

따라서 cin.eof() 함수를 통해 ch값이 EOF 값인지 아닌지 확인하도록 한다.

 

	char ch;
	while(true){ // 키보드에서 문자 읽어 ch에 저장 
		cin.get(ch); // 키를 ch로 읽음 
		if(cin.eof()) break; // EOF (ctrl-z) 가 입력되면 종료 
		cout.put(ch); // 읽은 문자를 출력
		if(ch=='\n') break; // 공백키까지 읽으므로, 개행 문자 나오면 빠져나가는 break문 작성 

 

 

 

 


<istream의 문자열 입력 멤버 함수>

 

istream 클래스 멤버 함수 원형 설명
istream& get(char* s, int n) 입력 스트림에서 (n-1)개의 문자를 읽어 배열 s에 저장하고, 마지막에 '\0' 문자를 삽입한다.
입력 도중 '\n' 를 만나면 '\0'을 삽입하고 리턴

 

 

 

 

입력 도중 개행 문자('\n')를 만나는 경우

개행 문자를 만나면, 읽기를 중단하고 리턴하여 입력 스트림 버퍼에 '\n'가 남아있게 된다.

따라서 버퍼에 남아있는 개행 문자를 제거하기 위해 cin.get() 또는 cin.ignore(1) 를 사용해서 버퍼의 문자 1개를 제거해야 한다.

 

	char s[80];
	
	while(true){ 
		cout<<"종료하려면 exit 입력 >> ";
		cin.get(s, 80); // <enter> 키 만날 때 까지 최대 79개까지 문자 읽음 
		if(strcmp(s,"exit")==0){ // #inlcude <cstring>
			cout<<"exit 만나 프로그램 종료"<<endl;
			return 0;
		}
		else
			cin.ignore(1); // cin.get(); 입력 버퍼에 남은 개행 문자 제거하여 다시 다음 입력 받음 
	}

 

 


 

<istream의 한줄의 문자열 입력 멤버 함수>

 

istream 클래스 멤버 함수 원형 설명
istream& get(char* s, int n, char delim='\n') 입력 스트림에서 (n-1)개의 문자를 읽어 배열 s에 저장하고, 마지막에 '\0' 문자를 삽입한다. 입력 도중 delim에 지정된 구분 문자를 만나면 지금까지 읽은 문자를 배열 s에 저장하고 리턴
istream& getline(char* s, int n, char delim='\n'); get() 과 동일하지만, delim에 지정된 구분 문자를 스트림에서 제거

 

 

 

 

cin.getline() - cin.ignore(1) 을 통해 마지막에 버퍼에 남아있는 개행 문자를 제거해주지 않아도 된다!

delim의 디폴트 값은 개행문자('\n')이다.

 

	char s[80];
	
	while(true){ 
		cout<<"종료하려면 exit 입력 >> ";
		cin.getline(s, 80); // 개행 문자 만날 때까지 한 줄의 문자열 입력 받음
		if(strcmp(s,"exit")==0){
			cout<<"exit 을 만나 종료"<<endl;
			return 0;
		} 
		cout<<s<<endl;
	}

 


 

 

<입력 스트림의 문자 건너 띄기>

 

istream& ignore(int n=1, int delim=EOF)
입력 스트림에서 n개 문자 제거. 도중에 elim 문자를 만나면 delim문자를 제거하고 리턴

 

 

cin.ignore()

cin.ignore(10, '\n');
// 입력 스트림에서 10개의 문자 제거. 제거 도중 개행 문자 만나면 개행 문자를 제거하고 중단

 

 

 

 

 

<읽은 문자 개수 알아내기>

 

int gcount()
최근에 입력 스트림에서 읽은 바이트 수 리턴. <enter>키도 포함

 

 

cin.getline(), cin.gcount()

 

	char s[80];
	
	while(true){ 
		cout<<"종료하려면 exit 입력 >> ";
		cin.getline(s, 80); // 개행 문자 만날 때까지 한 줄의 문자열 입력 받음
		cout<<"읽은 문자 개수 : "<<cin.gcount()<<endl; // 방금 입력 스트림에서 읽은 문자 개수 출력
		if (strcmp(s,"exit")==0){
			cout<<"exit 을 만나 종료"<<endl;
			return 0;
		} 
		cout<<s<<endl<<endl;
	}

 

다음 실행창을 확인해보면, 마지막에 입력된 <Enter>키까지 포함하여 읽은 개수를 출력하는 것을 확인할 수 있다.

한글과 같은 경우, 한 문자가 2바이트로 읽힘을 확인할 수 있다.

 

 

 

 

 

 

cin.get(), cin.gcount()

 

이번에는 getline() 멤버 함수가 아닌, get() 멤버 함수를 사용해 보았다.

마지막 개행 문자를 스트림에서 제거해버리는 getline() 과 달리

get()은 delim을 만나면 스트림 마지막에 '\0'을 삽입한다.

따라서 else 문의 cin.ignore(1)을 이용해 마지막 문자를 제거해주어야 한다.

이때 스트림에 마지막 문자가 삽입되지 않으므로, getline()을 통해 얻어 gcount() 한 결과값보다 1이 작음을 확인할 수 있다. 

	char s[80];
	
	while(true){ 
		cout<<"종료하려면 exit 입력 >> ";
		cin.get(s, 80); // 개행 문자 만날 때까지 한 줄의 문자열 입력 받음
		cout<<"읽은 문자 개수 : "<<cin.gcount()<<endl;
		if (strcmp(s,"exit")==0){
			cout<<"exit 을 만나 종료"<<endl;
			return 0;
		}
		else cin.ignore(1);
		cout<<s<<endl<<endl;
	}

 

 

 

 


포맷 입출력

 

c 언어는 포맷 입출력이 가능한 입출력 함수인 print(), scanf() 가 사용된다.

포맷 입출력이 불가능한 cout 객체와 << 스트림 삽입 연산자를 이용하는 C++ 의 입출력 시스템은

다음과 같이 3가지 방법으로 포맷 입출력을 지운한다. 

 

1. 포맷 플래그(format flag)

2. 포맷 함수(format function)

3. 조작자(manipulator)

 

 


포맷 플래그(format flag)

 

ios 클래스에 정수형 상수로 32개의 포맷 플래그들이 정의되어 있다.

하나의 플래그는 비트 한 개로 표현되며, 한 가지 포맷 정보를 표현한다.

cin, cout 객체는 입출력 시, 이 포맷 변수에 세팅디ㅗㄴ 플래그 값을 반영하여 포맷 입출력을 수행한다.

 

 

 

 

< ios 클래스에 정의된 포맷 플래그 >

 

플래그 의미
ios::left / ios::right 0x0040 / 0x0080 필드를 왼쪽 맞춤(left-align) / 오른쪽 맞춤(right-align) 형식으로 출력
ios::dec / ios::oct / ios::hex 0x0200 / 0x0400 / 0x0800 10(디폴트) / 8 / 16진수로 출력
ios::boolalpha 0x4000 설정되면, 논리값 true를 1이 아닌 "true"로, false를 0이 아닌 "false" 문자열로 출력

 

 

 

포맷을 지정하는 함수는 setf(), 지정된 플래그를 해제하는 함수는 unsetf() 이다.

포맷 플래그를 한번 설정하면, 해제할 때까지 유지된다.

 

long setf(long flags) flags를 스트림의 포맷 플래그로 설정하고 이전 플래그를 리턴
long unsetf(long flags) flags를 설정된 비트 값에 따라 스트림의 포맷 플래그를 해제하고 이전 플래그를 리턴

 

 

 

unsetf(), setf(), ios::dec, iost::hex

	cout<<"10 진수 30 = "<<30<<endl; // ios::dec default 값 
	cout.unsetf(ios::dec); // 10진수 해제
	cout.setf(ios::hex); // 16진수 플래그 설정
	cout<<"16진수 30 = "<<30<<endl; 
	cout<<"16진수 40 = "<<40<<endl; 

 

 


포맷 함수 활용

 

- ostream 클래스의 멤버 함수를 이용해 포맷 출력을 해보자.

  • 너비 설정 width()
  • 빈칸 채우기 fill()
  • 유효 숫자 자리수 지정 precision()

 

ostream 클래스의 포맷 함수 원형 설명
int width(int minWidth) 출력되는 필드의 최소 너비를 minWidth로 설정하고 이전에 설정된 너비 값 리턴
char fill(cghar cFill) 필드의 빈칸을 cFill 문자로 채우도록 지정하고 이전 문자 값 리턴
it precision(int np) 출력되는 수의 유효 숫자 자리수를 np개로 설정. 
정수 부분 + 소수점 이하의 수의 자리 모두 포함('.' 소수점 제외)

 

 

cout.width(), cout.fill(), cout.precision()

cout.width() 는 호출 직후의 하나의 필드에만 적용되고,]

cout.fill()은 계속 적용된다.

cout.precision() 을 사용할 때에는 정수+'.' 를 사용하기!

 

	cout<<"cout.width(10), cout.fill('!')"<<endl;
	cout.width(10);
	cout.fill('-'); // 한번 지정되면 계속 지정됨 
	cout<<"hello"<<"hi"<<endl<<endl; // 호출 직후의 하나의  필드에만 적용됨(오른쪽 정렬 default)
	
	cout<<"cout.width(20)"<<endl;
	cout.width(20);
	cout<<"wow"<<endl<<endl;
	
	cout<<"5./3. >> ";
	cout<<"cout.width(20), cout.fill('*'), cout.precision(5)"<<endl;
	cout.width(20);
	cout.fill('*');
	cout.precision(5);
	cout<<5./3.<<endl;

 

결과를 통해 확인해보자!

 

 

 

 

 


조작자(manipulator)를 이용하여 포맷 입출력

 

- C++ 표준 헤더 파일에 정의된 특별한 원형을 가진 함수

- 항상 <<. >> 연산자오 함께 사용된다.

- 매개 변수 없는 조작자 / 매개 변수를 하나 가지는 조작자

한 번의 입출력에만 적용되므로, 입출력마다 포맷을 지정해야함

 

 

 

1. 매개 변수 없는 조작자

 

조작자 I/O 용도
endl I 스트림 버퍼를 모두 출력(flush())하고 다음 줄로 넘어감
oct / dec / hex I 정수 필드를 8 / 10 / 16 진수 기반으로 출력
left / right I 왼쪽 / 오른쪽 맞춤으로 출력
skipws / noskipws I 입력 스트림에서 공백 문자를 읽지 않고 건너뜀 / skipws 지정 취소
boolalpha O boolean 값이 출력될 때, "true", "false" 문자열로 출력

 

 

 

 

 

2. 매개 변수를 하나 가지는 조작자

- #include <iomanip>

 

조작자 I/O 용도
setfill(char cFill) I 필드를 출력하고 남은 공간에 cFill 문자로 채움
setprecision(int np) O 출력되는 수의 유효 숫자 자리수를 np개로 설정. (소수점은 제외)
setw(int minWidth) O 필드의 최소 너비를 minWidth로 지정

 

 

 

showbase, setw(), setfill(''), dec/oct/hex, endl

0부터 9까지의 숫자를 10, 8, 16진수로 일정 넓이의 오른쪽 정렬, 진수를 나타내 형식으로 출력해보자.

 

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

int main(){
	cout<<showbase; // 16진수는 0x, 8진수는 0을 앞에 붙여 출력
	 
	cout<<setw(8)<<"Number"<<setw(10)<<"Octal"<<setw(10)<<"Hexa"<<endl;
	
	for(int i=0;i<10;i++){
		cout<<setw(8)<<setfill('.')<<dec<<i; // 10진수 (width:8, fill:'.') 
		cout<<setw(10)<<setfill(' ')<<oct<<i; // 8진수 (width:10, fill:' ')
		cout<<setw(10)<<setfill(' ')<<hex<<i<<endl; // 16진수 (width:10, fill:' ', endl) 
	}
}

 

 

 

 

 


삽입, 추출 연산자에 대해 이해하기 전, 

'연산자 중복 정의' (Operator Overriding) 그리고 friend 함수에 대한 이해가 꼭 필요하다.

 

 

https://dream-and-develop.tistory.com/92

 

[C++] 연산자 중복 함수 (Operator Overloading Function) 와 프렌드 함수 (Friend Function)

(명품 C++ 프로그래밍 7장) 프렌드 함수 (Friend Function) friend 키워드 : 클래스 외부에 작성된 함수를 클래스 내에 선언하여, 클래스의 멤버함수와 동일한 접근 자격을 부여할 수 있도록 하는 키워드

dream-and-develop.tistory.com

 

 


삽입 연산자(insertion operator)

 

출력 스트림에 데이터를 출력하는 << 출력 스트림 삽입 연산자에 대해서 자세히 살펴보자.

C++ 입출력 시스템은 다양한 값을 << 삽입 연산자를 통해 출력할 수 있도록 

ostream 클래스에 << 연산자를 중복 작성 하였다.

 

 

본래 정수를 비트 단위로 시프트(shift_ 하는 C++ 의 기본 연산자인 << 연산자를

ostream 클래스에서 연산자 중복 정의를 해놓은 것이 다음과 같다.

 

#include <iostream>
using namespace std;

class ostream : virtual public ios{
	...
public:
	// << 삽입 연산자의 중복 정의 
	ostream& operator << (int n); // 정수를 출력하는 << 연산자
	ostream& operator << (char c); // 문자를 출력하는 << 연산자
	ostream& operator << (const char* s); // 문자열을 출력하는 << 연산자
	.... 
};

 

 

이 때, cout 은 ostream 클래스의 객체라는 것을 잊지 말자!

 

 

 


삽입 연산자의 실행 과정

 

 

다음의 cout 객체에 << 삽입 연산자가 어떻게 실행되는지 살펴보자.

 

cout << 'a' << 123 << endl; // 화면 출력 : a123

 

 

1. cout << 'a' 에서 << 연산자 함수 호출

 

컴파일러는 위의 코드를 아래와 같이 변형하여 컴파일 한다.

위에서 ostream 클래스에 << 연산자가 중복 정의된 것을 보았다. 

따라서, ostream 클래스의 객체인 cout 객체 내의 문자를 매개 변수로 받는 operator<<(char c) 의 함수가 호출된다.

문자 'a'를 매개 변수에 넘겨 준다.

 

cout . << ('a') // cout 객체의 연산자 함수 operator<<(char c) 를 호출

 

 

2. cout의 연산자 함수 ostream& operator<<(char c) 실행

 

호출된  operator<<(char c) 연산자 함수는 문자 'a'를 매개 변수로 받아 해당 함수를 실행한다.

함수는 다음과 같은 코드로 작성되어 있다고 한다.

 

문자 'a'를 현재 스트림(cout) 의 버퍼에 저장하고, 버퍼가 꽉 찼다면 화면에 출력하도록 한다.

*this 를 반환하여, 자기 자신 객체, cout의 공간(참조)을 그대로 반환하게 된다.

 

ostream& operator << (char c){
	... 현재 스트림 버퍼에 문자 c('a')를 삽입한다.
   	... 버퍼가 차면 장치에 출력한다.
   	return *this; // 이 스트림의 참조를 리턴
}	

 

 

 

3. 이번엔 이어서, << 123을 실행한다.

 

위의 cout << 'a' 의 실행 결과로 현재 cout의 버퍼에는 'a'가 삽입되었고, cout 자기 자신에 대한 참조가 리턴된 상태다.

따라서 << 123 은 cout << 123을 실행하는 것과 같다.

 

이번에는 정수 123에 대한 연산을 실행하게 되므로, 그에 맞게

cout 객체의 멤버 함수인 operator<<(int n) 이 실행된다.

 

 

ostream& operator << (int n){
	... 현재 스트림 버퍼에 정수 n(123)을 삽입한다.
   	... 버퍼가 차면 장치에 출력한다.
   	return *this; // 이 스트림의 참조를 리턴
}	

 

따라서 결과는, 버퍼에 a 그리고 이후 123이 저장되며

a123으로 변경된 버퍼는 적절한 시점에 화면에 출력된다.

 

 

4. 마지막으로 endl 조작자 함수가 실행되어 버퍼의 모든 내용을 출력하고 다음 줄로 넘어간다.

 

 

 

 


 

사용자 삽입 연산자(<<) 만들기

사용자가 만든 Point 클래스의 객체를 cout << 삽입 연산자를 통해 출력하는 사용자 삽입 연산자를 만들어 보자.

 

 

아래와 같이 Point 클래스의 x, y 좌표를 (3, 4) 를 갖는 p 객체를 출력하면

좌표가 출력되도록 하는 삽입 연산자를 만들 것이다.

 

Point p(3,4);
cout << p; // (3,4) 출력

 

 

위에서 살펴본 것처럼, ostream 클래스에 중복 정의된 << 연산자는

char c, int n, char* s와 같은 문자, 정수, 문자열을 매개 변수로 받아 해당 변수를 출력하도록 정의되었지만,

사용자가 직접 만든 Point 클래스의 객체를 매개변수로 받아 출력하는 << 연산자는 정의되어 있지 않다.

 

따라서

 

728x90
반응형
Comments