본문 바로가기
개인공부/C++ 기초플러스

C++ Primer 17

by 하고싶은건많은놈 2023. 2. 21.

학습목표

  • C++에서의 입출력
  • iostream 계열의 클래스
  • 리디렉션
  • ostream 클래스 메소드
  • 출력 형식 지정
  • istream 클래스 메소드
  • 스트림의 상태
  • 파일 입출력
  • 파일로부터 입력을 얻는 ifstream 클래스
  • 파일에 출력을 하는 ofstream 클래스
  • 파일 입출력을 위한 fstream 클래스
  • 명령행 처리
  • 2진 파일
  • 파일 임의 접근
  • 인코어 형식 지정

17.1 C++ 입출력의 개요

C++에서도 cstdio 헤더 파일을 통해 입출력에 사용하는 일반적인 C 함수들을 사용할 수 있음
C++ 스타일의 입출력은 iostream, fstream 헤더 파일에 정의되어있는 클래스들의 집합을 사용함

스트림과 버퍼

C++에서는 입력과 출력을 바이트들의 흐름인 스트림으로 간주함

  • 입력시 입력 스트림으로 바이트들을 추출, 출력시 출력 스트림에 바이트들을 삽입
  • 바이트들은 수치 데이터 또는 문자의 2진 표현을 나타낼 수 있음
  • 입력 스트림의 바이트들은 키보드 / 저장장치 / 다른 프로그램으로부터 흘러나올 수 있음
  • 출력 스트림의 바이트들은 디스플레이 / 프린터 / 저장장치 / 다른 프로그램으로 흘러나갈 수 있음

입력을 다루기 위해선 두가지 절차가 필요

  • 하나의 스트림을 프로그램의 입력에 연결
  • 스트림을 하나의 파일에 연결
  • 즉, 입력 스트림은 양쪽에 하나씩 두개의 연결을 필요로함

버퍼 이용시 입력과 출력을 더 효율적으로 처리할 수 있음

  • 버퍼(buffer) : 정보를 전송하기 위해 임시 저장장치로 사용하는 메모리 블록
  • 저장장치와 프로그램의 정보 전송률 차이를 극복할 수 있도록 도와줌
    • 우선 버퍼에 한꺼번에 많은 데이터를 읽어들여 저장한 후, 이후 프로그램이 버퍼로부터 데이터를 읽어들임
    • 출력시 버퍼를 가득 채운 다음 전송하고, 이후 버퍼를 깨끗하게 비우는 버퍼 비우기(flushing the buffer)를 수행함
    • 일반적으로 사용자가 Enter를 누를때 입력 버퍼를 비우고, 개행 문자를 전달받으면 출력 버퍼를 비움

스트림, 버퍼, iostream 파일

C++에서의 입출력 클래스

  • streambuf 클래스 : 버퍼로 사용할 메모리 제공, 버퍼 채우기, 버퍼 접근, 버퍼 비우기 등 버퍼 메모리를 관리하는 클래스 메소드 제공
  • ios_base 클래스 : 스트림의 일반적인 특성들을 나타냄
  • ios 클래스 : ios_base 클래스에 기초하며 streambuf 객체를 지시하는 포인터 멤버를 가짐
  • ostream 클래스 : ios 클래스에서 파생되어 출력 메소드들을 제공
  • istream 클래스 : ios 클래스에서 파생되어 입력 메소드들을 제공
  • iostream 클래스 : istream 클래스와 ostream 클래스에 기초하여 입력 메소드들과 출력 메소드들을 모두 상속받음

프로그램에 iostream 파일 포함시 자동으로 8개의 스트림 객체가 생성됨

  • cin : 표준 입력 스트림에 해당, 키보드 등의 표준 입력 장치에 연결됨
  • cout : 표준 출력 스트림에 해당, 모니터 등의 표준 출력 장치에 연결됨
  • cerr : 표준 에러 스트림에 해당, 표준 출력 장치에 연결되나 버퍼를 사용하지 않기 때문에 곧바로 출력됨
  • clog : 표준 에러 스트림에 해당, 표준 출력 장치에 연결되며 버퍼를 사용함
  • wcin, wcout, wcerr, wclog : wchar_t 형에 동작

스트림이 해당하는 객체 생성시 해당 객체는 입력 또는 출력과 관련된 모든 정보를 담고있는 데이터 멤버들을 가지게됨


리디렉션

리디렉션(redirection) : 표준 입력과 표준 출력을 위한 연결을 다른쪽으로 변경
많은 운영체제들이 리디렉션 기능을 지원함

cout으로 나타나는 표준 출력 스트림, cerrclog로 나타나는 표준 에러 스트림의 객체들은 모두 모니터로 보내짐
표준 출력을 리디렉션하는 것은 cerrclog에 영향을 주지 않기 때문에 cout 출력이 다른 곳으로 리디렉션되어있는 경우에도 에러 메시지를 화면에 출력함
운영체제에 따라 표준 에러를 리디렉션할 수 있는 경우도 있음



17.2 cout을 이용한 출력

ostream 클래스는 intfloat와 같은 수치 데이터를 문자들의 스트림으로 변환함
즉, 2진 비트 패턴으로 되어있는 수치 데이터의 내부적인 표현을 문자 바이트의 스트림으로 변환함

오버로딩된 << 연산자

ostream 클래스는 오버로딩을 통해 기본적으로 왼쪽 시프트 연산자로 사용되는 << 연산자를 ostream 클래스에 대한 출력으로 재정의함

  • 이 경우의 << 연산자는 삽입 연산자라고 부름
  • 삽입 연산자는 C++의 모든 기본 데이터형들을 인식할 수 있도록 오버로딩되어있음
  • 각각의 데이터형들에 대해 operator<<() 함수 정의를 제공함

ostream 클래스는 포인터형을 위한 삽입 연산자 함수들도 정의함

  • const signed char *, const unsigned char *, const char *, void *
  • 따라서 char * pn = "abc"; cout << pn; 구문은 정상적으로 작동함
  • 다른 데이터형을 지시할 경우 포인터를 void *형에 일치시킨 후 그 주소의 수치적 표현을 출력함

모든 삽입 연산자 함수들은 ostream &형을 리턴하도록 정의되어있음

  • 리턴하는 참조는 연산자를 호출한 객체에 대한 참조임
  • 따라서 이를 이용하여 출력을 연이어 할 수 있음

그 밖의 ostream 메소드들

ostream 클래스는 문자들을 화면에 출력하는 put() 메소드와 write() 메소드를 제공함

  • put() 메소드도 호출한 객체에 대한 참조를 리턴하므로 연이어 출력할 수 있음
  • put() 메소드에 적절한 수치형 매개변수 사용시 char형 값으로 변환하여 사용됨
  • write() 메소드는 전체 문자열을 출력하며, 첫번째 매개변수로 출력할 문자열의 주소를 받고 두번째 매개변수로 출력할 문자들의 수를 받음
// write.cpp

#include <iostream>
#include <cstring>

int main()
{
	using std::cout;
	using std::endl;
	const char * state1 = "Florida";
	const char * state2 = "Kansas";
	const char * state3 = "Euphoria";
	int len = std::strlen(state2);

	cout << "루프 인덱스 증가 : \n";
	int i;
	for (i = 1; i <= len; i++)
	{
		cout.write(state2, i);
		cout << endl;
	}

	cout << "루프 인덱스 감소 : \n";
	for (i = len; i > 0; i--)
		cout.write(state2, i) << endl;
	
	cout << "문자열 길이 초과 : \n";
	cout.write(state2, len + 5) << endl;

	return 0;
}

cout.write() 호출은 그것을 호출한 객체에 대한 참조를 리턴함
write() 메소드는 널문자에 도달하더라도 출력을 자동으로 멈추지 않음
write() 메소드는 수치 데이터에도 사용할 수 있으며, 따라서 char *형으로 캐스트한 어떤 수의 주소를 전달할 수 있음

  • 이 때 수치를 값에 해당하는 문자로 변환하는 것이 아니라 메모리에 저장된 비트 표현을 그대로 전달함
  • 수치 데이터를 가장 간결하고 정확하게 파일에 저장하는 방법을 제공함

출력 버퍼 비우기

cout 객체가 다루는 출력에는 버퍼를 사용하므로 출력은 버퍼가 가득 찰 때까지 쌓임
이후 버퍼의 내용을 목적지로 보내고 새로운 데이터 저장을 위해 버퍼를 깨끗하게 배움
단, 메모리 낭비를 방지하기 위해 개행 문자를 보낼시 버퍼가 자동으로 비워짐
긴급 입력을 받아들여야할 때도 버퍼를 비우기 때문에 문자열에 개행 문자가 들어있지 않더라도 그 뒤의 입력을 예상하여 문자열을 즉시 출력할 수 있음

사용자가 원하는 때에 출력이 되지 않을시 조정자(manipulator)를 사용하여 강제로 출력할 수 있음

  • flush : 버퍼를 비움
  • endl : 버퍼를 비우고 개행 문자를 삽입함
  • 조정자들은 실제로는 << 연산자로 오버로딩되는 함수이기 때문에 직접 호출하는 것도 가능함

cout을 이용한 출력 형식 지정

ostream 삽입 연산자는 값들을 텍스트 형식으로 변환함

  • 인쇄할 수 있는 문자에 해당하는 char형 값은 한 문자 필드 폭에 해당 문자를 출력함
  • 수치 정수형은 알맞은 크기의 필드 폭에 십진수 형식으로 출력하며, 음수인 경우 마이너스 부호도 함께 출력함
  • 문자열은 해당 문자열 길이에 맞는 크기의 필드폭에 출력함
  • 부동 소수점형은 총 6자리에 출력되며 뒤에 붙는 0들은 출력되지 않고, 지수가 6 이상이거나 -5 이하면 E 표기가 사용됨
// defaults.cpp

#include <iostream>

int main()
{
	using std::cout;
	cout << "12345678901234567890\n";
	char ch = 'K';
	int t = 273;
	cout << ch << " : \n";
	cout << t << " : \n";
	cout << -t << " : \n";

	double f1 = 1.200;
	cout << f1 << " : \n";
	cout << (f1 + 1.0 / 9.0) << " : \n";

	double f2 = 1.67E2;
	cout << f2 << " : \n";
	f2 += 1.0 / 9.0;
	cout << f2 << " : \n";
	cout << (f2 * 1.0e4) << " : \n";
  
	double f3 = 2.3e-4;
	cout << f3 << " : \n";
	cout << f3 / 10 << " : \n";

	return 0;
}

화면 출력을 위한 진법 변경

조정자를 사용하여 화면에 정수를 출력할 때 사용되는 진법을 제어할 수 있음
출력 상태를 서술하는 정보를 가지고 있는 ios_base의 멤버 함수를 사용하여 필드 폭과 소수점 아래 자릿수를 제어할 수 있음

// manip.cpp

#include <iostream>

int main()
{
	using namespace std;
	cout << "하나의 정수를 입력하십시오 : ";
	int n;
	cin >> n;

	cout << "n\tn*n\n";
	cout << n << "\t" << n*n << " (10진법)\n";

	cout << hex;
	cout << n << "\t";
	cout << n*n << " (16진법)\n";

	cout << oct << n << "\t" << n*n << " (8진법)\n";

	dec(cout);
	cout << n << "\t" << n*n << " (10진법)\n";

	return 0;
}

필드 폭 조정

width() 멤버 함수를 사용하여 서로 다른 크기의 수들을 동일한 필드 폭에 출력할 수 있음

  • int width();의 경우 현재 설정된 필드 폭을 리턴함
  • int width(int i);의 경우 현재 필드 폭을 i칸으로 설정하고 이전의 필드 폭을 리턴함
  • 바로 다음에 출력될 항목에만 영향을 미친 후 해당 항목을 지나가면 디폴트 값으로 환원됨
// width.cpp

#include <iostream>

int main()
{
	using std::cout;
	int w = cout.width(30);
	cout << "디폴트 필드 폭 = " << w << " : \n";

	cout.width(5);
	cout << "N" << ':';
	cout.width(8);
	cout << "N * N" << ":\n";

	for (long i = 1; i <= 100; i *= 10)
	{
		cout.width(5);
		cout << i << ':';
		cout.width(8);
		cout << i*i << " : \n";
	}

	return 0;
}

채움 문자

기본적으로 cout은 필드 폭의 남는 부분을 빈칸으로 채우며, fill()멤버 함수를 사용하면 다른 것으로 바꿀 수 있음

// fill.cpp

#include <iostream>

int main()
{
	using std::cout;
	cout.fill('*');
	const char * staff[2] = {"윤성일", "박순용"};
	long bonus[2] = {900, 1350};

	for (int i = 0; i < 2; i++)
	{
		cout << staff[i] << " : $";
		cout.width(7);
		cout << bonus[i] << "\n";
	}

	return 0;
}

부동 소수점수의 출력 정밀도 설정

부동 소수점수의 정밀도의 경우

  • 디폴트 모드에서는 출력되는 총 자릿수를 의미
  • 고정 소수점 표기 또는 지수 표기 모드에서는 소수점 아래 자릿수를 의미
  • precision() 이라는 멤버 함수를 사용하여 정밀도를 다르게 설정할 수 있음
// precise.cpp

#include <iostream>

int main()
{
	using std::cout;
	float price1 = 20.40;
	float price2 = 1.9 + 8.0 / 9.0;

	cout << "\"손오공 인형\" $" << price1 << "!\n";
	cout << "\"사오정 인형\" $" << price2 << "!\n";

	cout.precision(2);
	cout << "\"손오공 인형\" $" << price1 << "!\n";
	cout << "\"사오정 인형\" $" << price2 << "!\n";

	return 0;
}

뒤에 붙는 0과 소수점 출력

ios_base 클래스의 setf()함수에서 여러가지 설정이 가능함
ios_base::showpointcout이 뒤에 붙는 소수점을 출력하도록 만들며, 디폴트 부동 소수점 형식에서 뒤에 붙는 0들도 출력하게 만듬

// showpt.cpp

#include <iostream>

int main()
{
	using std::cout;
	using std::ios_base;

	float price1 = 20.40;
	float price2 = 1.9 + 8.0 / 9.0;

	cout.setf(ios_base::showpoint);
	cout << "\"손오공 인형\" $" << price1 << "!\n";
	cout << "\"사오정 인형\" $" << price2 << "!\n";

	cout.precision(2);
	cout << "\"손오공 인형\" $" << price1 << "!\n";
	cout << "\"사오정 인형\" $" << price2 << "!\n";

	return 0;
}

setf()에 대한 보충

setf()가 정의되어있는 ios_base 클래스는 하나의 protected 멤버를 가지고있으며, 해당 멤버의 각 비트들은 진법이나 뒤에붙는 0들의 출력 여부 등을 제어함
이 비트들을 키는 것(=1로 설정)을 플래그를 설정한다고 함

첫번째 원형은 fmtflags setf(fmtflags);의 형태를 가지며, fmtflags는 출력 형식 플래그를 보관하는데 사용되는 비트마스크(bitmask) 데이터형의 typedef 이름으로 ios_base에 정의되어있음

  • 매개변수는 설정할 비트를 나타내며, 모든 플래그들이 이전 설정값을 나타내는 fmtflags형 수를 리턴함
  • boolalpha, showbase, showpoint, uppercase, showpos 등의 지정 상수들이 존재함
  • 출력 형식 지정 상수들은 ios_base 클래스에 정의되어있으므로 사용 범위 결정 연산자와 함께 사용해야함
// setf.cpp

#include <iostream>

int main()
{
	using std::cout;
	using std::endl;
	using std::ios_base;

	int temperature = 63;

	cout << "오늘의 수온 : ";
	cout.setf(ios_base::showpos);
	cout << temperature << endl;

	cout << "프로그래머들에게 그 값은\n";
	cout << std::hex << temperature << endl;
	cout.setf(ios_base::uppercase);
	cout.setf(ios_base::showbase);
	cout << "또는\n";
	cout << temperature << endl;
	cout << true << "!의 값은 ";
	cout.setf(ios_base::boolalpha);
	cout << true << "이다.\n";

	return 0;
}

두번째 원형은 fmtflags setf(fmtflags, fmtflags);의 형태를 가짐

  • 첫번째 매개변수는 원하는 설정이 담긴 fmtflags 값을 사용
  • 두번째 매개변수는 해당 비트들을 먼저 깨끗하게 지우는 값을 사용
    • basefield - 진법 변경, floatfield - 표기 변경, adjustfield - 정렬 변경
// setf2.cpp

#include <iostream>
#include <cmath>

int main()
{
	using namespace std;

	cout.setf(ios_base::left, ios_base::adjustfield);
	cout.setf(ios_base::showpos);
	cout.setf(ios_base::showpoint);
	cout.precision(3);

	ios_base::fmtflags old = cout.setf(ios_base::scientific, ios_base::floatfield);
	cout << "왼쪽 정렬 : \n";
	long n;
	for (n = 1; n <= 41; n += 10)
	{
		cout.width(4);
		cout << n << "|";
		cout.width(12);
		cout << sqrt(double(n)) << "|\n";
	}

	cout.setf(ios_base::internal, ios_base::adjustfield);
	cout.setf(old, ios_base::floatfield);
	cout << "내부(internal) 정렬 : \n";
	for (n = 1; n <= 41; n += 10)
	{
		cout.width(4);
		cout << n << "|";
		cout.width(12);
		cout << sqrt(double(n)) << "|\n";
	}

	cout.setf(ios_base::right, ios_base::adjustfield);
	cout.setf(ios_base::fixed, ios_base::floatfield);
	cout << "오른쪽 정렬 : \n";
	for (n = 1; n <= 41; n += 10)
	{
		cout.width(4);
		cout << n << "|";
		cout.width(12);
		cout << sqrt(double(n)) << "|\n";
	}

	return 0;
}

setf()의 효과는 unsetf()를 호출하여 취소시킬 수 있음

  • void unsetf(fmtflags mask); 형태의 원형을 가짐
  • mask는 하나의 비트 패턴으로 mask에 1로 설정되어있는 모든 비트들이 해당 비트들의 설정을 해제하여 0으로 만듬
  • cout.setf(0, ios_base::floatfield);cout.unsetf(ios_base::floatfield);는 동일함

표준 조정자

C++은 setf()를 호출하는 몇가지 조정자를 제공하여 정확한 매개변수를 자동으로 호출함

  • boolalpha, noboolalpha
  • showbase, noshowbase
  • showpoint, noshowpoint
  • showpos, noshowpos
  • uppercase, nouppercase
  • internal, left, right
  • dec, hex, oct
  • fixed, scientific

iomanip 헤더 파일

출력 형식 지정시 iostream의 도구들은 번거로울 수 있기 때문에, iomanip 헤더파일 안에 조정자들을 제공함

  • setprecision(), setfill(), setw() 함수 등이 있음
  • 매개변수를 사용하며, cout 구문에 연결하여 사용할 수 있음
// iomanip.cpp

#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
	using namespace std;
	cout << fixed << right;

	cout << setw(6) << "N" << setw(14) << "제곱근"
		 << setw(15) << "네제곱근\n";
	
	double root;
	for (int n = 10; n <= 100; n += 10)
	{
		root = sqrt(double(n));
		cout << setw(6) << setfill('.') << n << setfill(' ')
			 << setw(12) << setprecision(3) << root
			 << setw(14) << setprecision(4) << sqrt(root)
			 << endl;
	}

	return 0;
}


17.3 cin을 이용한 입력

cin 객체는 표준 입력을 바이트들의 스트림으로 나타냄
일반적으로 문자들의 스트림은 키보드로 발생하며, 입력 스트림으로부터의 추출은 데이터형 변환을 포함
cin이 입력을 해석하는 방법은 주어지는 데이터형에 따라 달라지고 기본 데이터형들을 인식할 수 있도록 istream 클래스에 추출 연산자 >>가 오버로딩되어있음

  • 입력 데이터를 목적지가 지시하는 데이터형으로 변환하므로 형식이 설정된 입력함수(formatted input function) 이라고 부름
  • istream & operator>>(int &);의 원형을 가짐
  • 매개변수가 참조이므로 매개변수로 사용된 변수의 값을 직접 변경할 수 있음
  • 호출한 객체의 참조를 리턴하기 때문에 입력도 연이어 할 수 있음
  • cinhex, oct, dec 조정자를 함께 사용할 수 있음
  • 문자 포인터형들에 대한 오버로딩된 >> 연산자는 입력 스트림으로부터 단어를 읽은 후 널 문자를 덧붙임

cin >>은 입력을 어떻게 보는가?

추출 연산자들은 모두 동일한 방법으로 입력 스트림을 확인함

  • 화이트스페이스가 아닌 문자가 나올때까지 화이트스페이스를 건너뜀
  • 즉, 화이트스페이스가 아닌 최초의 문자에서 시작하여 원하는 데이터형과 일치하지 않는 첫 번째 문자에 도달할때까지의 문자들을 읽음
  • 입력이 프로그램이 기대하는 것과 다른 경우 추출 연산자는 변수의 값을 변경하지 않고 0을 리턴함
// check_it.cpp

#include <iostream>

int main()
{
	using namespace std;
	cout << "수를 입력하십시오 : ";

	int sum = 0;
	int input;
	while (cin >> input)
		sum += input;

	cout << "마지막으로 입력한 값 = " << input << endl;
	cout << "합계 = " << sum << endl;
	return 0;
}

스트림 상태

cin, cout 객체는 스트림 상태(stream state)를 나타내는 하나의 데이터 멤버(ios_base 클래스로부터 상속된)를 가지고있음
iostate형으로 정의되어있는 스트림 상태는 eofbit, badbit, failbit라는 세 개의 ios_base 원소로 구성됨

  • 각 원소는 1(설정) 또는 0(해제)이 될 수 있는 단일 비트
  • cin 연산이 파일의 끝에 도달한경우 eofbit를 설정
  • cin 연산이 기대하는 문자를 읽지 못한경우 failbit를 설정
  • 접근할 수 없는 파일 읽기 / 쓰기 방지된 디스크에 쓰다가 입출력 실패가 발생한 경우에도 failbit를 설정
  • 알 수 없는 원인으로 스트림이 손산되었을 경우 badbit를 설정

이외에도 상태 비트와 스트림 상태를 보고하거나 변경하는 ios_base 메소드들이 존재함

  • good() : 스트림을 사용할 수 있으면 true를 리턴하고 모든 비트를 해제
  • eof() : eofbit가 설정되면 true를 리턴
  • bad() : badbit가 설정되면 true를 리턴
  • fail() : badbitfailbit가 설정되면 true를 리턴
  • rdstate() : 스트림 상태를 리턴
  • exceptions() : 예외를 발생시킨 플래그를 식별하는 비트마스크를 리턴
  • exceptions(iostate ex) : clear()를 통해 예외를 발생시킬 상태를 설정
  • clear(iostate s) : 스트림 상태를 s로 설정하며, s의 디폴트는 0임
  • setstate(iostate s) : clear(rdstate() | s)를 호출하여 스트림 상태 비트를 s에 설정된 해당 비트로 설정함

상태 설정

clear() 메소드는 상태를 해당 매개변수로 설정

  • clear();는 디폴트 매개변수인 0으로 eofbit, badbit, failbit를 모두 해제
  • clear(eofbit);는 상태를 eofbit와 동등하게 만들어 나머지 비트를 해제함

setstate() 메소드는 해당 매개변수에 설정되어있는 비트에만 영향을 줌

  • setstate(eofbit);eofbit만 설정하고 나머지 비트에 영향을 주지 않음
  • 입력 및 출력, 함수를 통해 상태를 변경할 수 있는 수단을 제공

입출력과 예외

exceptions() 메소드는 eofbit, failbit, badbit 3가지 비트를 가지고있는 하나의 비트 필드를 리턴함
clear() 메소드로 스트림 상태가 바뀐 뒤엔 현재의 스트림 상태가 exceptions()가 리턴하는 값과 비교됨
이 때 리턴값과 현재 스트림 상태에 같은 비트가 설정되어있을시 clear()basic_ios::failure 예외를 발생시키며, 이는 std::exception 클래스로 파생되어 what() 메소드를 가지고있음

exceptions()의 디폴트 설정은 goodbit이기 때문에 예외가 발생하지 않음
이는 오버로딩된 exceptions(iostate) 함수를 사용하여 제어할 수 있음

// cinexcp.cpp

#include <iostream>
#include <exception>

int main()
{
	using namespace std;
	cin.exceptions(ios_base::failbit);
	cout << "수를 입력하십시오 : ";

	int sum = 0;
	int input;
	try {
		while (cin >> input)
			sum += input;
	} catch(ios_base::failure & bf)
	{
		cout << bf.what() << endl;
		cout << "앗! 실수!\n";
	}

	cout << "마지막으로 입력한 값 = " << input << endl;
	cout << "합계 = " << sum << endl;
	return 0;
}

스트림 상태 효과

ifwhilecin으로 리턴된 객체의 스트림 상태가 양호한 경우에만 true로 평가함
스트림 상태 비트가 설정된 후에는 해당 비트가 해제될 때까지 추가 입력 및 출력에 대해 스트림이 닫힘
이를 good 상태로 재설정하여 추가 입출력을 하고자한다면 clear() 메소드를 호출하여 해결함
단, 불량 입력이 입력 큐에 남아있는 경우 화이트스페이스에 도달할 때까지 계속해서 문자를 읽을 수 있음

입력이 파일 끝이나 하드웨어적 장애 때문에 종료되었을 시 fail() 메소드를 사용하여 문제를 해결할 수 있음
fail()failbit 또는 eofbit 둘 중 하나라도 설정될시 true를 리턴하므로 eofbit를 배제해야하는 경우가 생길 수 있음


그 밖의 istream 클래스 메소드들

get(char &)get(void) 메소드는 화이트스페이스를 건너뛰지 않는 단일 문자 입력 기능을 제공
get(char *, int, char)getline(char *, int, char) 함수는 기본적으로 행 전체를 읽어들이는 입력 기능을 제공
get()getline()은 단순히 문자 입력만 받아들이기 때문에 형식이 설정되지 않은 입력 함수(unformatted input function)이라고 부름

단일 문자 입력

get() 메소드는 다음 입력 문자가 무엇이든간에 해당 문자를 입력받음

get(char &)의 경우 입력 문자를 매개변수에 대입함

  • 자신을 호출하는데 사용된 istream 객체에 대한 참조를 리턴하기 때문에 연이어 표기할 수 있음
  • 실제 또는 키보드에서 만들어진 파일의 끝을 만나더라도 매개변수에 값을 전달하지 않으며, setstate(failbit)를 호출하여 cinfalse로 평가되게 만듬

get(void)의 경우 입력 문자를 정수형으로 변환하여 리턴값으로 사용

  • 리턴값이 객체가 아니므로 멤버 연산자를 적용시킬 수 없음
  • 실제 또는 시뮬레이트된 파일의 끝에 도달할 경우 iostream에 정의된 상수기호인 EOF를 리턴함

화이트스페이스를 건너뛸경우 >> 추출 연산자를 사용
프로그램이 모든 문자를 검사해야한다면 get() 메소드를 사용

문자열 입력 : getline(), get(), ignore()

getline()get()의 첫번째 매개변수는 입력 문자열을 저장할 메모리의 주소를, 두번째 매개변수는 최대 허용 문자 수 + 1을 나타내고 세번째 매개변수는 입력에 구획 문자로 사용할 문자로 지정
세번째 매개변수가 없을 경우엔 개행 문자를 구획 문자로 사용함
단, get()은 입력 스트림에 개행 문자를 남겨두고 getline()은 버림

ignore()는 최대 허용 문자 수 또는 개행 문자를 만날 때까지 문자를 읽고 바로 버림

  • istream & ignore(int = 1, int = EOF);의 원형을 가짐
  • 호출한 객체를 리턴하므로 연이어 사용할 수 있음
// get_fun.cpp

#include <iostream>
const int Limit = 255;

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;

	char input[Limit];

	cout << "getline()이 처리할 문자열을 입력하십시오 : \n";
	cin.getline(input, Limit, '#');
	cout << "다음과 같이 입력하셨습니다 : \n";
	cout << input << "\n1단계 완료\n";

	char ch;
	cin.get(ch);
	cout << "다음 입력 문자는 " << ch << "입니다" << endl;
	if (ch != '\n')
		cin.ignore(Limit, '\n');

	cout << "get()이 처리할 문자열을 입력하십시오 : \n";
	cin.get(input, Limit, '#');
	cout << "다음과 같이 입력하셨습니다 : \n";
	cout << input << "\n2단계 완료\n";

	cin.get(ch);
	cout << "다음 입력 문자는 " << ch << "입니다" << endl;

	return 0;
}

기대하지 않는 문자열의 입력

get(char *, int)getline()는 다른 입력 함수들과 마찬가지로 파일 끝을 만나면 eofbit를, 스트림이 손상될경우 badbit를 설정함

어떠한 문자도 성공적으로 추출하지 못한 경우 입력 문자열에 널문자를 넣고 setstate()failbit로 설정

  • 입력 메소드가 곧바로 파일 끝을 만났을 때, get(char *, int)의 경우 빈 줄을 입력했을 때 해당 상황이 발생
  • getline()은 개행문자를 저장하지 않더라도 추출하기 때문에 빈 줄을 입력받아도 failbit를 설정하지 않음
  • getline()에서 최대 허용 문자 수를 읽고난 뒤 다음 문자가 개행문자가 아닌 경우 failbit를 설정
  • 반면 get()은 최대 허용 문자 수를 읽더라도 failbit를 설정하지 않음(단 peek()을 사용하여 다음 입력 문자를 확인하는 것으로 대체할 수 있음)

그 밖의 istream 메소드

istream에는 다양한 메소드들이 존재함

  • read() : 주어진 수의 바이트들을 읽어서 지정된 위치에 저장
    • 입력에 널문자를 덧붙이지 않으며, 문자열 형태로 변환하지 않음
    • 리턴형이 istream &이기 때문에 연이어 사용할 수 있음
  • peek() : 입력 스트림으로부터 추출하지 않고 다음 문자를 리턴하기만 함
  • gcount() : 형식이 설정되지 않은 마지막 추출 메소드(>> 외의 다른 메소드들)가 읽은 문자 수를 리턴
  • putback() : 어떤 문자를 입력 스트림에 삽입하며, istream &형을 리턴하기 때문에 이어서 처리할 수 있음
// peeker.cpp

#include <iostream>

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;

	char ch;

	while (cin.get(ch))
	{
		if (ch != '#')
			cout << ch;
		else
		{
			cin.putback(ch);
			break;
		}
	}

	if (!cin.eof())
	{
		cin.get(ch);
		cout << endl << ch << "은 다음 입력 문자입니다.\n";
	}
	else
	{
		cout << "파일 끝에 도달했습니다.\n";
		std::exit(0);
	}

	while (cin.peek() != '#')
	{
		cin.get(ch);
		cout << ch;
	}
	if (!cin.eof())
	{
		cin.get(ch);
		cout << endl << ch << "은 다음 입력 문자입니다.\n";
	}
	else
		cout << "파일 끝에 도달했습니다.\n";

	return 0;
}
// truncate.cpp

#include <iostream>
const int SLEN = 10;
inline void eatline() { while (std::cin.get() != '\n') continue; }

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;

	char name[SLEN];
	char title[SLEN];
	cout << "이름을 입력하십시오 : ";
	cin.get(name, SLEN);
	if (cin.peek() != '\n')
		cout << "죄송합니다. 이름란이 좁아서 " << name
			 << "만 적어 넣었습니다." << endl;

	eatline();
	cout << name << " 씨! 직위를 입력하십시오 : \n";
	cin.get(title, SLEN);
	if (cin.peek() != '\n')
		cout << "직위도 뒷부분을 잘랐습니다.\n";
	eatline();
	cout << "이름 : " << name
		 << "\n직위 : " << title << endl;

	return 0;
}


17.4 파일 입력과 출력

C++의 입출력 클래스 패키지는 파일 입출력을 표준 입출력처럼 다룸

  • 파일에 기록시 ofstream 객체 생성 후 삽입 연산자 << 또는 write()와 같은 ostream 메소드를 사용
  • 파일로부터 읽을시 ifstream 객체 생성 후 추출 연산자 >> 또는 get()과 같은 istream 메소드를 사용
  • 이 때 새롭게 열리는 파일을 입력 스트림과 연결시켜야하며, 이러한 작업을 돕는 클래스들이 fstream 헤더 파일에 정의되어있음

간단한 파일 입출력

프로그램이 파일 기록시의 절차는 다음과 같음

  • 출력 스트림을 관리하기 위해 ofstream 객체 생성
  • open() 메소드를 사용하여 객체를 특정한 파일에 연결시킴
  • cout을 사용하는 것과 동일한 방법으로 객체를 사용함
  • 이 때 ofstream 클래스는 버퍼를 경유하는 출력을 사용하여 데이터 전송 속도를 향상시킴

파일을 읽을 때의 절차는 다음과 같음

  • 입력 스트림을 관리하기 위해 ifstream 객체 생성
  • open() 또는 ifstream 객체의 생성자를 사용하여 객체를 특정한 파일에 연결시킴
  • cin을 사용하는 것과 동일한 방법으로 객체를 사용함
  • 이 때 ifstream 클래스 역시 버퍼를 사용함

입력 스트림 혹은 출력 스트림 객체의 수명이 다했을시 파일과의 연결은 자동으로 닫힘
또는 close() 메소드를 사용해 명시적으로 파일과의 연결을 끊을 수도 있음
단, 이 때 스트림 자체는 여전히 유지됨

// fileio.cpp

#include <iostream>
#include <fstream>
#include <string>

int main()
{
	using namespace std;
	string filename;

	cout << "새 파일을 위한 이름을 입력하십시오 : ";
	cin >> filename;

	ofstream fout(filename.c_str());
	fout << "비밀 번호 노출에 주의하십시오.\n";
	cout << "비밀 번호를 입력하십시오 : ";
	float secret;
	cin >> secret;
	fout << "귀하의 비밀 번호는 " << secret << "입니다.\n";
	fout.close();

	ifstream fin(filename.c_str());
	cout << filename << " 파일의 내용은 다음과 같습니다 : \n";
	char ch;
	while (fin.get(ch))
		cout << ch;
	cout << "프로그램을 종료합니다.\n";
	fin.close();

	return 0;
}

스트림 검사와 is_open()

C++의 파일 스트림 클래스들은 ios_base 클래스로부터 하나의 스트림 상태 멤버를 상속받음
스트림에 이상이 없을시 상태는 0이며, 문제가 발생시 특정 비트가 1로 설정됨
따라서 사용자는 가장 최근의 스트림 오퍼레이션의 성공 여부를 알기 위해 스트림 상태를 검사할 수 있음


여러 개의 파일 열기

하나의 프로그램이 파일 여러개를 동시에 열 경우 각 파일마다 서로 다른 스트림을 생성해야함
반면 파일들을 차례로 처리해야 하는 경우, 하나의 스트림을 열어 각 파일에 차례로 연결시킴


명령행 처리

파일 처리 프로그램들은 일반적으로 명령행 매개변수를 사용하여 파일들을 식별함

  • 명령행 매개변수 : 명령을 입력하는 명령행에 나타나는 매개변수
  • int main(int argc, char *argv[])형태의 원형을 가진 main() 함수를 사용하여 프로그램이 명령행 매개변수에 접근할 수 있음
    • argc : 명령행 매개변수들의 수, 명령어 자체도 개수에 포함
    • argv : char 포인터를 지시하는 포인터, 즉 명령행에 있는 문자열들의 배열
// count.cpp

#include <iostream>
#include <fstream>
#include <cstdlib>

int main(int argc, char* argv[])
{
	using namespace std;
	if (argc == 1)
	{
		cerr << "사용법 : " << argv[0] << " filename[s]\n";
		exit(EXIT_FAILURE);
	}

	ifstream fin;
	long count;
	long total = 0;
	char ch;

	for (int file = 1; file < argc; file++)
	{
		fin.open(argv[file]);
		if (!fin.is_open())
		{
			cerr << argv[file] << " 파일을 열 수 없습니다.\n";
			fin.clear();
			continue;
		}
		count = 0;
		while (fin.get(ch))
			count++;
		cout << argv[file] << " 파일에 들어있는 문자 수는 " << count << "입니다.\n";
		total += count;
		fin.clear();
		fin.close();
	}
	cout << "전체 파일에 들어있는 문자 수는 " << total << "입니다.\n";

	return 0;
}

파일 모드

파일 모드는 파일이 사용되는 방법을 나타냄
파일 이름으로 파일 스트림 객체를 초기화하거나 open() 메소드를 사용하여 스트림을 파일에 연결할 때 파일 모드를 지정하는 두번째 매개변수를 사용할 수 있음

  • ifstream fin("banjo", model);과 같은 형태
  • 또는 ofstream fout; fout.open("harp", mode2);와 같은 형태

ios_base 클래스에 모드를 나타내는 openmode형이 정의되어있으며, fmtflagsiostate형과 같은 비트마스크형임

  • in : 파일을 읽기 위해 염
  • out : 파일에 쓰기 위해 염
  • ate : 파일을 열 때 파일 끝을 찾음
  • app : 파일 끝에 덧붙임
  • trunc : 파일이 이미 존재하면 파일 내용을 지움
  • binary : 2진 파일

파일 모드 지정 매개변수는 각 메소드마다 설정된 디폴트값이 있음

  • ifstreamopen() 메소드 및 생성자는 in을 디폴트로 사용
  • ofstreamopen() 메소드 및 생성자는 out | trunc를 디폴트로 사용
  • fstream 클래스는 디폴트를 지원하지 않기 때문에 명시적으로 지정해주어야 함

C++의 모드와 C의 모드의 대응 관계는 다음과 같음

  • in - r : 읽기 위해 염
  • out - w : out | trunc와 같음
  • out | trunc - w : 쓰기 위해 열고, 파일이 존재할시 내용을 비움
  • out | app - a : 쓰기 위해 열고 뒤에 덧붙임
  • in | out - r+ : 읽기/쓰기 겸용, 파일 안의 어디에나 쓸 수 있음
  • in | out | trunc - w+ : 읽기/쓰기 겸용, 파일이 존재할시 먼저 내용을 비움
  • c++mode | binary - cmodeb : c++mode 또는 상응하는 cmode 및 2진 모드로 염
  • c++mode | ate - cmode : 지신된 모드로 열고 파일의 끝으로 파일 포인터를 이동시키며, C에서는 별도의 함수 호출을 사용함

파일에 덧붙이기

// append.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>

const char * file = "guests.txt";

int main()
{
	using namespace std;
	char ch;

	ifstream fin;
	fin.open(file);
	if (fin.is_open())
	{
		cout << file << " 파일의 현재 내용은 다음과 같습니다 : \n";
		while (fin.get(ch))
			cout << ch;
		fin.close();
	}

	ofstream fout(file, ios_base::out | ios_base::app);
	if (!fout.is_open())
	{
		cerr << "출력을 위해 " << file << " 파일을 열 수 없습니다.\n";
		exit(EXIT_FAILURE);
	}

	cout << "새로운 손님 이름들을 입력하십시오(끝내려면 빈 줄 입력) : \n";
	string name;
	while (getline(cin, name) && name.size() > 0)
	{
		fout << name << endl;
	}
	fout.close();

	fin.clear();
	fin.open(file);
	if (fin.is_open())
	{
		cout << file << " 파일의 개정된 내용은 다음과 같습니다 : \n";
		while (fin.get(ch))
			cout << ch;
		fin.close();
	}
	cout << "프로그램을 종료합니다.\n";
	return 0;
}

2진 파일

파일에 데이터 저장시 데이터를 텍스트 형식 또는 2진 형식으로 저장할 수 있음
2진 형식으로 저장시 컴퓨터의 내부적인 표현 그대로, 일반적으로는 64비트의 double 표현으로 저장함

  • 텍스트 형식은 읽기가 쉬우며, 한 컴퓨터에서 다른 컴퓨터 시스템으로 쉽게 옮길 수 있음
  • 2진 형식은 수의 경우 변환 에러나 반올림 에러 없이 더 정확하게 저장 가능하며, 처리 속도가 빠르고 공간을 덜 차지함
const int LIM = 20;
struct planet
{
	char name[LIM];
	double population;
	double g;
};
planet pl;

ofstream fout("planets.dat", ios_base::out | ios_base::app);
fout << pl.name << " " << pl.population << " " << pl.g << "\n";

ofstream fout("planets.dat", ios_base::out | ios_base::app | ios_base::binary);
fout.write((char *) &pl, sizeof pl);

구조체를 텍스트 형식으로 저장시 가독성을 위해 공백을 넣고, 각 멤버를 명시적으로 지정하는 등의 작업이 수행됨
구조체를 2진 형식으로 저장시 구조체 전체를 하나의 단위로 저장하여 텍스트를 읽을 수는 없지만 정보가 더 정확하게 저장되며 코드 작성도 편리해짐

  • 이 때 ios_base::binary 상수를 사용하여 2진 파일 형식을 지정해주어야함
  • write() 멤버 함수를 사용하여 메모리에서 파일로 지정한 수만큼 바이트들을 복사함
  • 변수의 주소를 char *형으로 변환해주어야함
  • 가상 함수를 사용하지 않는 클래스들과 함께 사용할 수 있음
// binary.cpp

#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>

inline void eatline() { while (std::cin.get() != '\n') continue; }
struct planet
{
	char name[20];
	double population;
	double g;
};

const char * file = "planets.dat";

int main()
{
	using namespace std;
	planet pl;
	cout << fixed << right;

	ifstream fin;
	fin.open(file, ios_base::in | ios_base::binary);
	if (fin.is_open())
	{
		cout << file << " 파일의 현재 내용은 다음과 같습니다 : \n";
		while (fin.read((char *) &pl, sizeof pl))
		{
			cout << setw(20) << pl.name << " : "
				 << setprecision(0) << setw(12) << pl.population
				 << setprecision(2) << setw(6) << pl.g << endl;
		}
		fin.close();
	}

	ofstream fout(file, ios_base::out | ios_base::app | ios_base::binary);
	if (!fout.is_open())
	{
		cerr << "출력을 위해 " << file << " 파일을 열 수 없습니다. \n";
		exit(EXIT_FAILURE);
	}

	cout << "행성의 이름을 입력하십시오(끝내려면 빈 줄 입력) : \n";
	cin.get(pl.name, 20);
	while (pl.name[0] != '\0')
	{
		eatline();
		cout << "행성의 인구를 입력하십시오 : ";
		cin >> pl.population;
		cout << "행성의 중력가속도를 입력하십시오 : ";
		cin >> pl.g;
		eatline();
		fout.write((char *) &pl, sizeof pl);
		cout << "행성의 이름을 입력하십시오(끝내려면 빈 줄 입력) : \n";
		cin.get(pl.name, 20);
	}
	fout.close();

	fin.clear();
	fin.open(file, ios_base::in | ios_base::binary);
	if (fin.is_open())
	{
		cout << file << " 파일의 개정된 내용은 다음과 같습니다 : \n";
		while (fin.read((char *) &pl, sizeof pl))
		{
			cout << setw(20) << pl.name << " : "
				 << setprecision(0) << setw(12) << pl.population
				 << setprecision(2) << setw(6) << pl.g << endl;
		}
		fin.close();
	}
	cout << "프로그램을 종료합니다.\n";
	return 0;
}

구조체 멤버에 문자 배열 대신 string 객체가 사용될 경우, string 객체는 자체적으로 문자열을 포함하지 않고 대신 문자열이 저장되어있는 메모리 위치를 가리키는 포인터를 포함하기 때문에 구조체 복사시 주소만 복사됨
따라서 프로그램이 다시 실행될 때 해당 주소가 의미가 없어지기 때문에 원하는 결과를 얻을 수 없음


임의 접근

임의 접근 : 파일의 특정 위치에 순차적으로 접근하는 것이 아닌 직접 그 위치로 이동
파일이 같은 크기의 레코드(서로 관련이 있는 데이터들의 한 집합)들로 이루어진 집합일 경우 가장 간단하게 적용됨

seekg() : 입력 포인터를 주어진 위치로 옮김
seekp() : 출력 포인터를 주어진 위치로 옮김

  • 실제 파일에 있는 위치가 아닌 버퍼에 있는 위치를 지시함
  • istream & seekg(streamoff, ios_base::seekdir); 형태의 원형을 사용시 두번째 매개변수로 지정된 파일 위치에서 몇 바이트 떨어져있는지를 계산하여 파일 위치를 찾음
    • ios_base::beg : 시작 위치로부터의 오프셋 측정
    • ios_base::cur : 현재 위치로부터의 오프셋 측정
    • ios_base::end : 끝 위치로부터의 오프셋을 측정
  • istream & seekg(streampos); 형태의 원형을 사용시 파일의 시작 위치로부터 몇 바이트 떨어져있는지를 계산하여 위치를 찾음
  • 파일 포인터의 현재 위치는 입력 스트림의 경우 tellg(), 출력 스트림의 경우 tellp() 메소드를 사용하여 확인할 수 있음
// random.cpp

#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>

inline void eatline() { while (std::cin.get() != '\n') continue; }
const int LIM = 20;
struct planet
{
	char name[20];
	double population;
	double g;
};

const char * file = "planets.dat";

int main()
{
	using namespace std;
	planet pl;
	cout << fixed << right;

	fstream finout;
	finout.open(file, ios_base::in | ios_base::out | ios_base::binary);
	int ct = 0;
	if (finout.is_open())
	{
		finout.seekg(0);
		cout << file << " 파일의 현재 내용은 다음과 같습니다 : \n";
		while (finout.read((char *) &pl, sizeof pl))
		{
			cout << ct++ << " : " << setw(LIM) << pl.name << " : "
				 << setprecision(0) << setw(12) << pl.population
				 << setprecision(2) << setw(6) << pl.g << endl;
		}
		if (finout.eof())
			finout.clear();
		else
		{
			cerr << file << " 파일을 읽다가 에러 발생.\n";
			exit(EXIT_FAILURE);
		}
	}
	else
	{
		cerr << file << " 파일을 열 수 없습니다. 종료합니다.\n";
		exit(EXIT_FAILURE);
	}

	cout << "수정할 레코드 번호를 입력하십시오 : ";
	long rec;
	cin >> rec;
	eatline();
	if (rec < 0 || rec >= ct)
	{
		cerr << "잘못된 레코드 번호입니다. 종료합니다.\n";
		exit(EXIT_FAILURE);
	}
	streampos place = rec * sizeof pl;
	finout.seekg(place);
	if (finout.fail())
	{
		cerr << "레코드를 찾다가 에러 발생\n";
		exit(EXIT_FAILURE);
	}

	finout.read((char *) &pl, sizeof pl);
	cout << file << " 현재 레코드의 내용 : \n";
	cout << rec << " : " << setw(LIM) << pl.name << " : "
				 << setprecision(0) << setw(12) << pl.population
				 << setprecision(2) << setw(6) << pl.g << endl;
	if (finout.eof())
		finout.clear();

	cout << "행성의 이름을 입력하십시오 : ";
	cin.get(pl.name, LIM);
	eatline();
	cout << "행성의 인구를 입력하십시오 : ";
	cin>> pl.population;
	cout << "행성의 중력가속도를 입력하십시오 : ";
	cin >> pl.g;
	finout.seekp(place);
	finout.write((char *) &pl, sizeof pl) << flush;
	if (finout.fail())
	{
		cerr << "쓰다가 에러 발생.\n";
		exit(EXIT_FAILURE);
	}

	ct = 0;
	finout.seekg(0);
	cout << file << " 파일의 개정된 내용은 다음과 같습니다 : \n";
	while (finout.read((char *) &pl, sizeof pl))
	{
		cout << ct++ << " : " << setw(LIM) << pl.name << " : "
			 << setprecision(0) << setw(12) << pl.population
			 << setprecision(2) << setw(6) << pl.g << endl;
	}
	finout.close();
	cout << "프로그램을 종료합니다.\n";
	return 0;
}

C++에서 임시 파일들의 이름은 tmpnam() 함수에 의해 생성됨

  • char* tmpnam(char* pszName) 형태의 원형을 가짐
  • 생성된 이름은 pszName이 지시하는 C스타일의 문자열에 저장됨
  • cstdio에 정의되어있는 L_tmpnam은 파일 이름의 문자 수, TMP_MAX 상수는 현재 디렉토리에 중복된 파일 이름을 생성하지 않고 tmpnam()이 호출될 수 있는 최대 허용 횟수를 제한함
  • 즉, tmpnam()을 사용함으로써 이름당 L_tmpnam개까지 문자를 가질 수 있는 TMP_NAM개의 고유한 파일 이름들을 생성할 수 있음


17.5 인코어 형식의 지정

iostream 계열은 프로그램과 단말기 사이의 입출력을 지원
fstream 계열은 동일한 인터페이스로 프로그램과 파일 사이의 입출력을 지원
sstream 계열은 동일한 인터페이스로 프로그램과 string 객체 사이의 입출력을 제공

  • 인코어 형식 지정 : string 객체로부터 형식이 지정된 정보를 읽거나, string 객체에 형식이 지정된 정보를 기록하는 과정
  • sstream 헤더 파일은 ostream 클래스에서 파생된 ostringstream 클래스를 정의하며, ostringstream 객체는 cout과 함께 사용할 수 있는 메소드들을 사용할 수 있음
// strout.cpp

#include <iostream>
#include <sstream>
#include <string>

int main()
{
	using namespace std;
	ostringstream outstr;

	string hdisk;
	cout << "하드디스크의 모델명이 무엇입니까? ";
	getline(cin, hdisk);
	int cap;
	cout << "하드디스크의 용량이 몇 GB입니까? ";
	cin >> cap;
	outstr << hdisk << " 하드디스크의 용량은 "
		   << cap << " GB입니다.\n";
	string result = outstr.str();
	cout << result;

	return 0;
}

ostringstream 클래스의 str() 멤버 함수는 버퍼의 내용으로 초기화된 string 객체를 리턴하며,해당 객체를 동결시켜 더이상 정보를 써 넣을 수 없게 만듬

// strin.cpp

#include <iostream>
#include <sstream>
#include <string>

int main()
{
	using namespace std;
	string lit = "길섶 민들레 꽃씨대롱 방울로 부풀어 "
				 "여윈 목 아프게 빼어 들고 "
				 "아기씨 실어 나를 바람 기다리누나.";
	istringstream instr(lit);
	string word;
	while (instr >> word)
		cout << word << endl;
	return 0;
}

istringstream 클래스는 istream 메소드 계열을 사용하여 istringstream 객체로부터 데이터를 읽을 수 있게 해줌



연습문제

  1. 입력과 출력을 관리하는 클래스, 상수, 조정자들을 정의
    모든 프로그램에 연결되는 표준 입력 스트림과 출력 스트림들을 위해 사용되는 표준 객체들을 생성함
    객체들은 입출력에 사용되는 스트림과 버퍼를 관리
  2. 키보드 입력은 문자를 발생시키기 때문에 연속된 문자들을 묶어 하나의 숫자인 121으로 변환시켜주어야함
  3. 출력을 파일로 리디렉션할시 표준 출력은 화면이 아니라 파일에 연결되지만 표준 에러는 계속해서 화면으로 연결됨
  4. 각 기본 데이터형에 대해 오버로딩된 << 연산자를 이용함
  5. 메소드를 호출한 객체의 참조형을 리턴하는 출력 메소드들은 연이어서 출력을 할 수 있음
  6. int main()
    {
    	using namespace std;
    
    	cout << "숫자 하나를 입력하세요 : ";
    	int i;
    	cin >> i;
    	cout << i << endl
    		<< oct << i << endl
    		<< hex << i << endl;
    
    	return 0;
    }
  7. #include <iostream>
    	#include <iomanip>
    #include <string>
    
    int main()
    {
    	using namespace std;
    
    	cout << "이름을 입력하십시오 : ";
    	string name;
    	getline(cin, name);
    
    	cout << "시간당 급료를 입력하십시오 : ";
    	double pay;
    	cin >> pay;
    
    	cout << "근로 시간을 입력하십시오 : ";
    	double time;
    	cin >> time;
    
    	cout << "첫째 형식 : \n";
    	cout << showpoint << fixed << right;
    	cout << setw(30) << name << ": $ " << setprecision(2)
    		<< setw(10) << pay << ":" << setprecision(1)
    		<< setw(5) << time << "\n";
    		
    	cout << left;
    	cout << "둘째 형식 : \n";
    	cout << setw(30) << name << ": $ " << setprecision(2)
    		<< setw(10) << pay << ":" << setprecision(1)
    		<< setw(5) << time << "\n";
    
    	return 0;
    }
  8. cin >>은 화이트스페이스를 제외하고 받으므로 ct1 = 5;를 출력함
    cin.get()은 화이트스페이스를 포함하므로 ct2 = 9를 출력함
  9. while (cin.get() != '\n') continue;의 경우 행의 끝(개행 문자가 나오기 전)까지 완전하게 읽고 버림
    반면 cin.ignore(80, '\n');의 경우 입력받은 문자가 최대 허용 문자 수를 초과할시 바르게 동작하지 않으며, 처음 80개 문자를 건너뛰기만 함

'개인공부 > C++ 기초플러스' 카테고리의 다른 글

C++ Primer 17 Exercise  (0) 2023.02.21
C++ Primer 16 Exercise  (0) 2023.02.21
C++ Primer 16  (0) 2023.02.21
C++ Primer 15 Exercise  (0) 2023.02.21
C++ Primer 15  (0) 2023.02.21

댓글