Chapter 17. Input, OutPut, and Files

지환·2022년 8월 21일
0

C/C++에선 입출력을 language 자체에 내장하지 않는다.
class, template 등 기본적 기능만 정의하고, 나머지는 사용자/설계자가 구현하도록 한다.
I/O 기능 또한 표준 클래스에 구현된 기능이다.

stream이란 입출력 데이터 흐름이다.
I/O를 다루려면 stream을 프로그램과 destination 각각에 연결하면 된다.

buffer란 정보 전송률의 차이를 극복하고, disk 접근 횟수를 줄여 속도를 높여준다.
예를들어, disk에서 input을 받는다면 disk에선 512byte이상 블록단위로 data를 전송하는데 프로그램은 1byte씩밖에 처리하지 못한다.
buffer는 이 둘 사이에 껴서 disk로부터 정보를 읽어오고, 프로그램은 이 buffer에서 읽어온다.
buffer가 없다면 프로그램이 매번 1byte씩 disk에 접근해야하는데, disk 접근은 memory 접근보다 속도가 느리다.

keyboard에서 받는 input은 1byte씩 입력되므로 buffer가 필요없긴하지만,
그래도 buffer가 있으면 정보 전송 전에 잠시 보관하고 수정할 수 있다.
(아 키보드로 input할때 지우고 쓰고하는게 buffer위에서 하는거구나)
실제로 대부분 C++ program은 Enter 입력시에 buffer을 비우도록 한다.

(링크, I/O library의 class별 bitflag, 함수, 상수 등 자세하게 설명도 같이나옴.)

위 구조로 각 기능을 구현하는 class를 정의한다.
ios_base class는 해당 stream이 text/binary 인지 등 일반적 특성을 나타낸다.
ios class는 streambuf object를 가리킬 수 있는 pointer가 있다.
streambuf class는 buffer로 사용할 메모리를 제공하고 해당 버퍼를 채우고 비우고 접근하는 등 각종 함수를 제공한다.
ostream class는 ios class를 기초해 output 함수를 제공한다. istream class는 ios class를 기초해 input 함수를 제공한다.
iostream class는 istream header에 있고, istreamostream class에 기초한다.

iostream file엔 predefined object인 cin 같은 놈들만 있고,
iostream class는 istream file에 있음.

기능을 사용하려면 각 class의 object를 만들어서 활용해야한다.(혹은 이미 만들어져있는)
<iostream> header엔 istream class와 ostream class로 만든 cincout object가 존재한다.
얘네 안엔 buffer 종류라던가 각종 stream관련 정보들이 담긴다.(위 class로 만든 object이니 당연)

  • 추가로 그 stream을 다루는 함수들도 제공된다.

고찰
C와의 차이를 보자. C에선 stream을 표현하기위해, stream 정보 등을 담기위해 file structure(pointer)을 사용했다.
그리고 미리 정의된 fprintf 등 함수에 특정 file pointer을 넘겨줌으로써 stream 정보를 넘겨줄 수 있었고,
해당 함수는 그 stream 정보를 이용해 stream에 I/O 기능을 수행할 수 있었다.
C++에선 그런 stream 정보와 함수를 따로 두지 않고, 위 class들로 묶어버린 것이다.
보면 알겠지만 I/O library의 각 class가 담는 정보라던가 지원하는 기능은 다르다.
근데 전부 상속 등의 관계로 묶어서 최종적으론 cin, cout, cerr, clog라는 객체로 만듦으로써(wchar_t 버전도 각각 4개있음),
C에서 stream 정보와 I/O 기능 구현을 따로 한것과는 다르게 그 기능을 한데 묶은 것이다.
(C++ class 기능이 추가된게 그대로 구현된 셈이네)
(각 객체들과 각 standard stream도 알아서 잘 연결될(시킬) 것이다.)
참고

C에서처럼 command line에서 redirection이 가능하다. 사실 redirection은 OS에서 제공.
standard input, output, error

cout

operator<< functions
<< operator에 대해 모든 numeric types을 overload해뒀다.
char, int, unsigned int, long, ...

참고로 char은 문자 형태로 출력된다.

뿐만아니라 const signed char *, const unsigned char *, const char *도 overload했는데,
얘넨 '\0'이 나올때까지 문자열을 출력한다.

void*도 overload했다. 이 경우 주소를 표현한다.
그래서 char*인데 주소 표현하고싶으면 (void*)로 casting해야한다.

모두 ostream &를 return하므로 연달아 사용할 수 있다.
std::cout << 1 << 2 << std::endl;

put function
ostream & put(char);
(wchar_t 버전도 있음, template으로 만듦)

보면알겠지만 cout << 처럼 연달아 사용 가능
cout.put('a').put('b');

write function
basic_ostream<charT, traits> & write(const char_type* s, streamsize n);
뒤에 n이 기술한 크기까지 계속 print한다.
null character 나와도 안멈춤.

flush
cout에 byte들을 보내면 바로 destination으로 보내지 않고, buffer에 우선 쌓는다.
그러다가 buffer가 꽉차면 내용을 destination으로 보낸다.

보통 buffer의 크기는 512 bytes이거나 그 배수이다.
표준 I/O가 disk와 연결된다면 buffer를 쓰는게 시간적으로 좋다.
512bytes를 읽어오기위해 disk에 512번 접근하는 것보다 한번에 읽어오는게 빠르다.

disk에 접근한다면 이런 buffering이 좋지만,
화면 출력시엔 buffer에 쌓기만 하면 안된다.
("입력하세요:" 같은걸 출력하기위해 512bytes를 채우는게 낭비임)

이럴땐 buffer을 채울때까지 기다리지않고 (1)new-line character가 들어오면 buffer는 자동으로 비워진다.
혹은 (2)즉시 입력을 받아야하는 경우도 buffer는 자동으로 비워진다.

cout<<"입력하세요: ";
cin>>num;

이때 output buffer가 비워지지 않는다면, 프로그램은 메시지 출력없이 입력만 기다릴 것이다.

flushendl maipulator을 사용할 수도 있다.
전자는 버퍼를 비우고, 후자는 버퍼를 비우고 new-line character도 삽입한다.(그래서 '\n'보다 느리네..)

cout<<"안녕!"<<flush;
cout<<"안녕!"<<endl;
사실 두 manipulators는 실제로 "함수"이다.
★그래서 `flush(cout);`으로도 쓸 수 있지만, `<<` 이용하는게 연달아쓸 수 있으니 편하긴함.
manipulator을 `<<`와 쓸 수 있는 이유는 당연히 `<<`를 overload했기 때문이다.★

얘네는 본인들 자체(함수)를 manipulator로 바로 활용가능한 경우이고,
showbase 같은 manipulator(함수)는 내부적으로 setf를 호출해서 구현함.(밑에 관련내용 또 나옴)

cout 출력 형식 조작 정리 (정리 잘돼있음)
추가로, C/C++은 형식보단 데이터 보존을 우선시한다.
즉 width를 작게 설정했을때 data가 그걸 넘어서도 전부다 출력된다.
setf 함수에서 fmtflags type을 return하는데, 이는 이전 모든 flag가 저장된 값이다.(이를 이용해 기존 flags 저장해뒀다가 다시 복구하는데 쓰인다.)
showpoint같이 setf에 넘겨줘서 형식 지정할때 사용하는 상수들은 ios_base class에 정의된 상수이므로 꼭 ios_base::를 앞에 붙여야 한다.
(using declaration없으면 std::까지 붙여야 됨)

setf 함수도 ios_base의 멤버함수이다.(근데 상속돼서 그런지 cout 객체 이용해서 바로 사용가능)
fmtflags는 ios_base에 정의된 "type"이니 사용하려면 앞에 ios_base::를 붙여줘야한다.(아니면 iostream까지 상속되니까 그 사이에 있는 class이름 붙여줘도됨. iostream::fmtflags a;라고해도 잘 선언된다.)(setf는 멤버함수니까 object로 호출한거고, 얘는 그냥 type이니까 당연히 scope에 맞게 가져와서 선언해야한다.)

ios_base class엔 이런 형식을 저장하는 member를 protected에 갖고있다.
해당 data의 각 비트가 어떤 형식을 어떻게할지 결정한다.
그 bit를 수정하는 것이 setf(setflag 약어)함수인 것이다.
수정하고자 하는 것을 미리 표현된 상수(ex. showpoint)로 넘겨주면 수정된다.

이런 bitmask type은 integer/enumeration/STL bitset 들로 표현할 수 있다.

setf 함수는 두가지 버전이 있다.
1. fmtflags setf(fmtflags);
2. fmtflags setf(fmtflags, fmtflags);

on/off만 하면 되는 flag는 1번 버전을 사용한다.
그런데 겹치는 역할을 하는 bit가 있어서 나머지를 clear해줘야된다면, 2번 버전을 사용한다.
2번 버전의 첫번째 인자로 세팅할 flag를 넣어주고, 두번째 인자로 나머지들을 먼저 clear를 해준다.

예를들어 양수앞에 부호를 표시하려면 그냥 setf(ios_base::showpos);라고 해버리면 된다.
하지만 진법을 나타낸다고 해보자. 8/10/16 마다 비트가 있을텐데, 하나를 키면 나머지는 다 꺼야한다.
나머지를 끄는 역할을 2번째 인자가 해준다.
두번째 인자로 쓸 수 있는 목록은 p.1087 (hex/oct/dec manipulator(함수)도 결국 이 비트를 수정하는게 아닐까)

> unsetf 함수
void unsetf(fmtflags mask);
mask가 나타내는 bit들을 끈다.
cout.setf(0, ios_base::floatfield);cout.unsetf(ios_base::floatfield);나 같다.

> Standard Manipulators
특정 setf사용은 manipulator로 단순화시켜서 자동으로 매개변수넣어서 호출하게 한다.
예를들어 showpoint manipulator는 cout.setf(ios_base::showpoint)를 호출하도록 한다.
(표는 책 p.1090아래부터)

이 manipulator들은 내부적으로 setf 호출한단 소리.
endl은 줄바꾸고 flush 호출할 것이고, "각자 manipulators마다 내부행동은 다르겠지."
특정 행동들을 cout에 바로 연계해서 할 수 있게 만든게 manipulators.

cout에 보니 operator<<를 특정 functor 형식에 대해 overload해둠.
manipulator들은 그 형식을 따라서 정의됐을 것이다.(내부에선 각자 본래 기능 하도록)

> iomanip 헤더파일
precision이나 width, fill 같은 함수는 위 manipulators처럼 cout에 바로바로 못쓰인다.
cout<<hex<<15; 이런식의 사용이 불가능하고 따로 함수호출해서 지정해야함.
이게 불편할 수 있는데, 이런 애들이 바로 cout에 연결해 사용할 수 있도록 추가 manipulators를 제공하는 inmanip header file가 있다.

ex) setw(), setprecision(), setfill() 같은 함수들이 있음.
cout << setw(6) << "N";

기존에 있던 width 함수로 그냥 overload 해도 되지 않나? setw를 왜 따로 만들었지?
: 자세힌 모르겠다만, ostream의 operator<<와 형식이 안맞는게 아닐까.
기존에 있던 manipulator만 받을 수 있게 return/parameter type을 맞췄는데,
나중에 하려고보니 width같은애들은 type이 안맞았던거지않을까..하고추측해봄

cin

operator>> functions

cout에서와 마찬가지로 cin>>연산에 대해
short int &, signed int &, ... 등 이렇게 reference 형으로 모든 기본 type을 overload한다.
(scanf도 그랬지만, refenrence나 pointer로 받아야 값을 거기 assign해주지)
각 입력 type에 맞게 변환하여 저장한다.
입력 형식을 변경할 수도 있다. cin >> hex;라고 하면 숫자들을 16진수로 인식한다.

추가로 signed char*, char*, unsigned char*도 overload한다.(문자열 입력/마지막에 널추가)
(기본 type만하네, 그래서 string은 자기 class 내부에서 따로 정의하는구나)

또 마찬가지로 istream &를 return하므로 연쇄적으로 작성할 수 있다.

> Input with cin >>

white space가 아닌 문자가 나올때까지 모두 건너뛰고, 입력 후, whitespace 나오면 입력은 거기까지만 한다.
(C의 character 입력 함수들은 안그럼, operator>> 함수들은 전부 white space 건너뜀)

> Stream States

ios_base에는 stream state를 나타내는 data member를 가지고있고,
이는 cin, cout에까지 상속되므로 저 둘도 가진다.(첨에 말했듯 각 객체엔 stream 정보도 포함된다.)
이런 state data member는 bitmask type이다.
eofbit, badbit, failbit를 가진다.
eof는 cin이 파일끝에 도달할 경우, fail은 기대하는 문자를 읽지 못할경우 세팅된다.(int 입력받도록했는데 문자가 온다거나)
bad는 알 수 없는 원인으로 stream에 문제가 생겼을때 세팅된다.
(fail과 bad가 발생하는 상황은 겹칠수도있고 아닐수도있다.)
(각 flag는 내부적으론 overload된 함수가 if문 등으로 오류 탐지해서 flag 세팅하는 방식이겠지)

말고 goodbit도 있다. 이는 위 세 bit가 모두 괜찮은지 확인하는 bit이다.(0 값을 가짐)
이 세 bits가 모두 0이면 아무 문제가 없는 것이다.

아래는 각 객체에서 stream state를 체크하기위해 사용할 수 있는 멤버함수 목록이다.

(fail() 함수는 badbit이 set돼있어도 true return한다.)
(추가로 stream에서 예외 발생 다루는 exception(), exception(iostate ex) 멤버 함수도 있음)

clear(); : default argument 0을 이용해 모든 bit 0으로 세팅(clear)
clear(eofbit); : eofbit만 1로 세팅하고 나머지는 모두 0으로 세팅(clear)
setstate(eofbit); : eofbit을 1로 세팅, 나머지는 본래 상태 그대로 놔둠.
==> 왜 이런 함수를 쓸까? EOF 등의 사유로 오류가 세팅되고 상황에 따라 다시 입력을 받을때 주로 활용된다.

> I/O와 Exceptions

input이 이루어지며 stream state가 변경될 수 있는데, 이는 clear()나 setstate()를 사용한다.
(우리가 명시적으로 사용안해도 내부에서 저 함수를 사용하긴 한단 소리같음)
(setstate()는 claer을 사용한다고하네)
clear()는 stream state를 변경한 후, 현재 stream state를 exceptions() 함수의 return 값과 비교한다.
exceptions의 반환 값과 현재 stream state 둘 다 1인 bit가 있다면 ios_base::failure exception을 발생시킨다.(다른 exceptions처럼 class임.)
(ios_base::failurestd::exception class를 inherit하므로 what() 함수를 가진다.)

ecxeptions() 함수는 기본적으로 goodbit를 return하도록 setting되어있다.
그래서 기본적으로 input 함수가 stream state bit 3개 중 하나(goodbit 제외)를 세팅해도 예외는 발생하지 않는다.

exceptions(iostate ex)를 이용해 exceptions()가 반환하는 값을 변경할 수 있다.
만약 cin.exceptions(badbit); 라고 설정해두면, 입력시 badbit이 설정됐을때 ios_base::failure exception이 발생한다.
cin.exceptions(badbit | eofbit); 이렇게 bitwise-or(|)를 이용해 두 bit를 설정할 수도 있다.

exceptions은 예기치못한 상황에 적합하다. 내가 다 예측할 수 있다면, 기본적인 툴로도 처리할 수 있다.
하지만 깊숙한 곳에서 호출되거나, file을 다루거나 할땐 exceptions을 설정해두어 편리하게 다룰 수 있다.

> Check stream state

  1. 아무 bit도 설정 안돼있단걸 확인하려면 good() 함수를 호출하면 다 확인 할 수 있다.
  2. 아니면 if(cin)처럼 cin object를 이용해 확인할 수 있다. good() 함수처럼 모든 bit을 체크한다.
    (object가 bool로 바뀌는걸보면, 내부엔 good함수 이용하는 conversion function이 존재하는게 아닐까)

적용
stream 상태를 체크하며 입력을 받자. 숫자를 입력받아야되는데 문자가 들어오면 골치아프다.
(문자 입력 받을땐 이런 과정이 보통 필요없음, 전부 문자로 잘 처리되니까; eof나 bad는 처리해야될듯)

보통 2번 방법 이용해 while(cin >> inp){~} 처럼 실패할때까지 입력을 받는다.
중요한건, stream state bit가 설정되면 그게 해제될때까지 추가 입/출력은 금지된다.

따라서 다음 입력을 받기위해

while (cin >> inp) { ~ }
cout << "다시 입력하세요: ";
cin.clear();
while (!isspace(cin.get())) ;
while (cin >> inp) { ~ }

이렇게 중간에 다시 입력받기위해 cin.clera()을 이용해 비트를 모두 해제해준다.
그리고 while (!isspace(cin.get())) ;를 뒤에 들어온 이용해 불량 input들을 모두 제거해준다.

그런데 위는 input 형식같은게 잘못됐다고 가정했을때이다. 그게아니라 EOF나 hardware 문제라면?

while (cin >> inp) { ~ }
cout << "다시 입력하세요: ";
if (cin.fail() && !cin.eof())   //순수하게 불량 input때문에 실패한 경우
{
	cin.clear();
	while (!isspace(cin.get())) ;
}
else 							//EOF나 하드웨어적 문제
{
	cout << "계속할 수 없습니다.\n";
    exit(1);
}
while (cin >> inp) { ~ }		//위 과정 쭉 거치면 제대로 작동한다.

이렇게 단순 입력 오류인지(failbit), 아닌지(eofbit or failbit) 구분해서 그에 맞게 처리한다.
후자라면 더 이상 진행하는건 불가능하므로 끝낸다.
fail() 함수는 역사적인 이유로 failbit이나 eofbit 둘 중 하나만 세팅되도 true를 반환한다.
그래서 위처럼 eof가 아닌지도 묶어서 확인한 것이다.

> 기타 istream class methods

operator>> method는 데이터 type에 맞게 형변환도 수행하고, white space도 건너뛴다.
하지만 여기 함수들은 white space든 뭐든 단순 문자입력을 받아서 unformatted input function이라고 한다.

>> Single-Character Input
get(char &)
white space도 모두 입력받는다. 파일끝이라던가 문제가 생기면 마찬가지로 stream state bit을 설정한다.
istream & 가 return type이므로 연쇄적으로 쓰일 수 있다. 단, >>와 같이 쓰일 시 white spae를 잘 고려해야한다.
cin.get(ch1).get(ch2) >> ch3;, ch3엔 white space가 대입될 수 없다.

get(void)
보통 입력받은 문자를 int로 return한다. (그러므로 얘는 연쇄사용 X)
EOF에 도달하면 iostream header에 있는 EOF 값을 return한다.
(마찬가지로 stream state 설정도 하겠지만, 반환값을 변수에 입력받아야되니 EOF로 판단하지 주로)

>>, get(char &*), get(void) 세개 중 뭐를 사용해야하나?
: 일단 white space를 건너뛰어도 된다면 `>>`가 편하다.
모든 문자를 검사해야하면 get 종류 중 하나를 사용한다.
get(void)의 장점은 C의 getchar()와 닮았다는 것이다.

>> String Input
istream & get(char*, int, char);
istream & get(char*, int);
istream & getline(char*, int, char);
istream & getline(char*, int);

int 매개변수로는 최대 허용 문자수 +1을 작성해준다.(null때문)
그냥 배열 크기를 그대로 넣어주면 된다.(10이 배열크기면 9만큼만 문자가 들어오고 1은 null로 해주는 것)

get과 getline의 차이는, new-line character를 읽고 get은 stream에 그걸 남겨둬서 다음 input함수가 쓸 수 있게하고, getline은 stream에서 읽고 버린다는 것이다.

세번째 인자로는 delimiter을 줄 수 있다.
이걸 만나면 두번째 인자 크기만큼 도달하지 않아도 입력이 멈춘다.
마찬가지로 get은 해당 delimiter 문자를 stream에 남겨두고, getline은 읽고버린다.

<stream state설정>
두 함수 다 마찬가지로 eof를 만나면 eofbit를, 장치장애 등을 만나면 badbit을 설정한다.
추가로 얘네만의 특별한 상황이 있는데, input이 아예 없는 경우엔 둘 다 failbit을 설정한다.
(참고로 get은 엔터를 치면 failbit이 설정되지만, getline은 newline도 읽긴하기때문에 엔터쳐도 failbit 설정되지 않는다.)

getline은 길이초과하는 input일때 failbit를 설정하지만,
get은 길이초과하는 input이어도 failbit을 설정하지 않는다.(그냥 그만큼만 입력받고 stream에 남겨둠)

ignore 함수
istream & ignore(int = 1, int = EOF);
마찬가지로 cin이 사용할 수 있는 member function이다.
첫번째 인자의 개수만큼의 문자를 읽거나, 혹은 두번째 인자인 delimiter를 만날때까지 읽어서 버린다. (보면 알겠지만 기본은 1, EOF이다.)
연쇄적으로 쓰일 수 있다. cin.ignore(255, '\n').ignore(255, '\n');-> 두줄을 읽고 버림.

>> 나머지 istream methods
read(), peek(), gcount(), putback()

read는 뒤에 null character을 따로 추가하진 않고, 그대로 입력받는다.(마치 C의 fread())
주로 file I/O시 write()와 함께 사용

peek는 입력 스트림에서 추출하지않고 다음 문자를 리턴한다.

gcount는 마지막 unformatted extraction method를 이용해 읽어온 문자수를 return 한다.
즉, >>가 아니라 get, getline, ignore, read 같은 함수로 읽어온 문자수를 return 한다.
strlen() 함수로도 알 수 있겠지만, gcount()로 stream으로부터 방금 읽은 문자 수를 가져오는게 더 빠르다.

putback은 특정 문자를 다시 입력 스트림에 넣는다.
istream&를 리턴하여 입력과 연달아 사용될 수 있도록 한다.


File I/O

fstream 헤더에 ifstream, ofstream, fstream class를 제공한다.
이들은 모두 iostream 헤더의 class들로부터 derived됐으므로 이미 배운 함수들을 그대로 사용할 수 있다.

  1. (stream생성)ifstream or ofstream object를 생성한 후
    : ifstream fin;

  2. 해당 object를 특정 file과 연결한다.
    : fin.open("jar.txt");
    만들면서 동시에 연결할 수도 있다. ifstream fin("jar.txt");

  3. cout, cin이 사용하는 것과 같은 방법으로 사용.(차이는 file에 입/출력된다는 것 뿐이다.)

각 object가 만료될때 파일 연결은 자동으로 닫힌다.
혹은 fin.close();로 닫을 수 있다.

닫아도 fin이 없어지는건 아니다
연결만 끊길 뿐이고 stream기능은 유지한다, 다른 file과 연결할 수도 있다.
다시 연결할땐 `fin.clear();`을 요구하는 compiler도 있다.
(stream state가 재설정 되는지 여부에 따라 달려있다.)

> Check Stream State

file stream class들도 마찬가지로 stream state 정보를 가진다.(eof, fail, bad, good)

cin/cout에서처럼 object 이용해서 입출력 하다가
(1)각 bit check 함수 이용해서 판단하거나,
(2)object 자체를 이용해서 판단할 수 있다.

File에서만 나오는 특수한 경우는 File open할때인데,
이때 실패(ex.입력stream이 없는 파일열때)하면 failbit이 설정된다.
그래서 good함수나 object 자체를 이용해서 stream에 문제가 생겼는지 판단할 수도 있지만,
★File open의 실패는 is_open() 함수를 이용해서 판단하는게 더 낫다.
이 함수는 good 여부도 판단하지만, 추가로 파일 모드 오류 여부도 판단한다.

※Stream state※
screen이든 keyboard든 file이든, stream의 상태는 전부 다 있다.
해당 stream을 이용해 I/O 연산을 할 것인데, 어떤 I/O연산을 하냐에따라 오류가 되는 경우가 있고,
그런 경우에선 그 연산의 내부적으로 해당 상황에 stream state bit이 설정되도록 하는 것이다.
쉽게말해 ☆"모든 stream에는 각 stream의 상태에 문제가 있는지 체크하는 bits가 있는데, 그게 언제 설정될지는 수행하는 연산에 따라 정해진 상황을 따른다."

C에서처럼 main 함수의 argument로 argc argv 받을 수 있음.

> File Mode

ctor과 open 함수의 두번째 인자로 optional하게 넣을 수 있다.
(그냥 file이름으로만 open하면 default argument가 들어감)

이렇게 각 mode가 ios_base에 정의돼있다.
ifstreamopen()함수와 ctor은 ios_base::in을 default로 사용하고,
ofstreamopen()함수와 ctor은 ios_base::out | ios_base::trunc를 default로 사용한다.
(그래서 위에선 아무것도 안적어도 됐던 것)
fstream은 class의 함수들은 위처럼 default mode로 지정된게 없어서 얘네는 무조건 명시해야한다.

중요한건, 위 모드들을 위 조합 중 하나로만 사용할 수 있다는 것이다.
왜냐하면 C++에서의 file open은 ANSI C처럼 되도록 표준에서 정의하기 때문이다.
즉, istream fin(filename, c++mode);는 마치 fopen(filename, cmode);처럼 구현된다는 것이다.
(자세한 구현사항은 몰라도 같게 행동하도록 명시하나봄)

reading하는데 file이 없으면 error지만, output할땐 해당 이름의 file이 없으면 새로 만들어짐.

여기서 잘못된 조합으로 file을 열면 is_open() mothod가 detect하는 것이다.
다른 good 같은 애들은 거기까진 탐지 못함. 그래서 file open시엔 is_open() 사용

> binary file/mode

다시 한번 정리하고 가자.

Binary File VS Text File
text는 말 그대로 문자인 file, binary는 그 외의 file.
즉 둘 다 내부적으론 1010101011... 의 이진수 형태로 데이터가 저장돼있을 것이다.
어떤 File이냐, 즉 어떤 종류의 확장자를 갖냐는 것은 그 file을 어떻게 해석하냐는 것이다.
같은 1011이라는 수를 두고도 mp3 file은 특정 소리로 해석하고, text file은 특정 문자로 해석하는 것이다.
(이렇게만보면 text도 결국 다른 file처럼 text만의 형식이 있는 것이기때문에 사실상 binary file에 text file이 포함되는 범주가 아닌가 생각이드네)

Binary Mode VS Text Mode
C/C++ 프로그램 내에서 어떤 Mode로 file을 열 것인가 결정한다.
두 mode의 차이는 OS별 End-Of-File/new-line 처리 방법에 따라 데이터를 "변환할지말지"이다.
text file이라면 new-line character나 end of file이 의미있으므로 그걸 자동으로 변환해주는 것이고,
binary file이라면 있는 그대로 입출력을 하는 것이다.
참고로 이건 file이 어떤 file이든 상관없다. 우리가 그 file을 프로그램에 가져올때 어떻게 할지를 정하는 것이다.
(text를 binary mode로 열어도 된다, 단 new-line이랑 EOF가 제대로 처리되지 않을 수 있지만..)

링크

Functions
그럼 프로그램에서 binary/text 정보를 저장하고 싶다면 어떻게 구분해서 저장해야되나?
file은 어떻게 해석하냐(display이든 스피커이든)의 차이이고,
mode는 EOF/new-line 변환할지말지만을 정한다면,
binary 데이터가 저장되는지 text 데이터가 저장되는지 어떻게 구분하지?
그건 프로그램 내의 data type이나 함수에따라 달라지는 것이다.
binary file을 binary mode로 열어도 C의 fprintf 같은 함수를 쓴다면 데이터는 formatted string으로 변환되어 들어갈 수 밖에 없는 것이다.

그래서 보통 text file/text mode를 다룰때는 일반적인 <<<<, get 같은 함수를 쓰는 것이고,
binary file/binary mode를 다룰때는 곧바로 데이터 복사가되는 write(), read()를 쓰는 것이다.
(C에선 fwrite(), fread())

간단하게 정리하자면, 파일 입출력 데이터 종류는 사실상 사용하는 함수에 달렸다. (open mode는 newline 해석 방식만 다르게 해줄뿐인데 그마저도 linux에선 의미없음.)

-----정리 끝-----
binary 언제 사용할까?
책에선 structure을 그대로 저장했다가 불러오기위해,
1. binary mode로 file을 열어서
2. write() 함수를 이용해서 작성했다.

 binary mode로 file을 열지 않는다면,
 text 파일도 아닌데 멋대로 EOF나 new-line으로 인식하고 데이터가 변형될 수 있다.
 
 write() 안쓰고, operator<<(char*) 쓰면..
 문자열로 해석하더라도 결국 되긴될 것 같긴한데, write()쓰는게 맞지.
 
 쉽게말해 그냥 binary는 binary 함수들로 열고 쓰고 읽고..
 text면 text 함수들로 열고 쓰고 읽고..
 맞춰서 하도록.

(Tip)
여기 보면 struct 안에 char[]로 문자열을 구현한다.
char[] 대신 string class로 구현하면 file에 옮겨적는게 의미가 없을 수 있다.
string은 문자열 멤버를 포인터로 관리하므로, file에 그대로 옮겨적으면 주소가 적히는 꼴이다.
나중에 다시 불러와서 쓸때 유효하지 않을 수 있다.

Random Access (File Positioning)

file을 순차적으로 진행하는 것이 아니라 파일 내 포인터를 특정 위치로 바로 이동.
주로 database file 처리할때 쓰인다.
파일 내 포인터는 실제 파일 내에 위치하지 않고 보통 buffer내의 위치를 지시한다.

> seekg 함수
istream class에 정의돼있다.
== ifstream classfstream class도 사용가능(상속관계)
input pointer의 위치를 수정한다.

  • istream & seekg(streamoff, ios_base::seekdir);
    : 두번째인자론 ios_base::beg, ios_base::cur, ios::end 중 하나만 들어갈 수 있다.
    첫번째인자는 두번째 인자로부터 떨어진 byte수를 명시해준다.(cplusplus.com에보니까 그냥 integral type이라고 나온)
    ex) finout.seekg(-1, ios_base::cur);-> 포인터를 현재에서 뒤로 1byte 이동시킴

  • istream & seekg(streampos);
    : streampos type이 class일 수 있다. class라면 streamoff type과 정수 type 사용하는 ctor을 가진다.
    그래서 fin.seekg(112); 라고하면 112byte에 위치시키므로, 순서로 보자면 113번째 byte 위치를 가리키게 된다.

(원래 template인데 위는 char형 instantiations임.)

> seekp 함수
ostream class에 정의돼있다.
== ofstream classfstream class도 사용가능(상속관계)
output pointer의 위치를 수정한다.

  • ostream & seekp(streamoff, ios_base::seekdir);
  • ostream & seekp(streampos);
seek도 실패하면 failbit를 설정한다.

> tellg 함수
> tellp 함수
file pointer의 현재 위치를 streampos 값으로 return

(번외)
fstream class는 iostream으로부터 derived되는데, iostreamistream, ostream을 상속받으므로, fstream 또한 두 class의 methods를 모두 가진다.
ifstream이 file input을 위한 class이고, ofstream이 output을 위한 class라면 fstream은 I/O 모두를 처리하는 class이다.
입력 buffer와 출력 buffer도 하나씩 가지는데, fstream에선 두 buffer 다루는걸 동기화시킨다. 다시말해 input buffer pointer와 output buffer pointer가 같이 움직이도록 한다.
ex) fstream finout; finout.open(filename, ios_base::in | ios_base::out);

임시 파일을 만들때 이름 정하기 쉽지않음. 겹치면 안되니
그럴땐 knk에서 배운 stdio.h(cstdio)의 tmpnam 사용하면 됨
자세한건 knk ch22 정리해둔거 보는게 나을듯
여기도 관련 macro들이랑 해서 p.1141에 설명은 함.

Incore Formatting

C의 sprintf, snprintf, scanf 같은 것이다.
string을 stream으로(처럼) 쓰는 것이다.
string object에 iostream 계열 인터페이스를 사용할 수 있게 한다.
즉, ★program과 string object 사이에 I/O를 제공하는 것이다.

해당 기능을 제공하는 class는 ssream headerostringstreamistringstream이다.
(전자는 ostream class를 inherit, 후자는 istream class를 inherit)

ex1)

ostringstream outstr;
double price = 380.0;
char * ps = " for a copy of the ISO/EIC C++ Standard!";
outstr.precision(2);  //이런거
coutstr << fixed;     //이런거
outstr << "Pay only $" << price << ps << endl;  //얘도
//이런걸 iostream interface를  string object에 적용할 수 있음

ex2)

string lit = "ABCDE"
			 "FGHIJ";   //이문법까먹었으면 knk ch13다시보기
istringstream instr(lit);
string word;
while (instr >> word)     //istringstream(string)에 이런거 가능
	cout << word << endl;

Summary

stream은 프로그램 안팎으로 왔다갔다하는 bytes의 흐름이다.
buffer는 프로그램과 I/O device 사이 매개 역할을하는 memory에 있는 임시공간이다.
I/O device와 buffer는 큰 덩어리 단위로 information을 주고받고,
buffer와 program은 byte 단위로 information을 주고 받는다.

iostream header를 include하는 프로그램은 자동으로 8개의 stream을 만든다. 각각 cin, cout, ... 으로 다루고, 각 stream은 standard input, standard output, ... 과 default로 연결된다.

portability 측면에선 binary file보다 text file이 더 낫다.
숫자, 특히 floating point를 저장하는데 있어선 binary file이 더 낫다.
read()write()이 binary I/O를 지원한다.

sstream header의 class는 string과 program 간의 format information I/O, iostream interface의 함수를 사용할 수 있도록 제공한다.


Chapter Review

cout<<showbase; or cout.setf(ios_base::showbase)
쓰면 숫자 앞에 진법에따라 접두어가 붙음. ex) 16진수: 0x15, 8진수: 012


큰 그림 정리

세세하게 어떤 구조와 요소로 I/O library가 구성됐는지 아직은 잘 모르겠다.
일단 큰 틀만 잡아서 정리해보자.
실제로 주로 사용할 헤더는 iostream, fstream, ifstream, ofstream, sstream이다.
이 헤더들에는, 입출력시 필요한 정보(버퍼, 스트림, 스트림상태 등)I/O시 필요한 연산(put() 등)이 정의된 class(혹은 그 class에서 나온 predefined object)가 있다.
iostream headeristream classostream class를 이용해 만든 다양한 object가 있다.
나머지 header들엔 class들이 정의돼있는데 그걸 이용해 필요한 정보를 담아 stream과 상호작용할 수 있는 object를 만들 수 있다.
같은 input 연산이라면 같은 함수들을 사용할 수 있다.(실제로 ifstream classistream class를 base class로 하기때문에 우리가 cin에 하는 모든 연산은 ifstream class로 만든 모든 object에 적용할 수 있다.)

연산
cin>> operator을 overloading하고 get, getline 등의 멤버 함수가 있다.
>> operator는 모든 기본 type에 대해 overload돼있다.
cout<< operator을 overloading하고(마찬가지로 모든 기본 type에 적용) put, write 등의 멤버함수가 있다.
(정확히말하자면 istream classostream class에 정의돼있는거지)
그리고 cout에는 추가로 output format을 다루는데 사용되는 type, 함수, manipulator가 있다.

구조 (간략)
이 I/O library의 class들은 공통점이 많다.
일단 모두 stream과 buffer 정보를 가져야하고, 위에서 말했듯이 Input은 Input끼리 Output은 Output끼리 공통되는 연산도 있다.
그래서 그런 공통 부분을 매 class마다 적기보단 각 파트마다 따로 적혀서 상속관계로 만들어져있다.
Inheritance diagram
ios_base는 formatting flag, I/O exceptions 등을 다루고, basic_streambuf는 실제 device를 추상화 하는 등 각각 역할마다 분리돼있다.
istream class 같은 애들은 이런 정보를 모두 포함해야되니, 이런 각 조각을 모아(상속) 구현되는 것이다.
(결국 우리가 직접 가져와서 쓰는건 말단의 iostream같은 놈들이지만 알고는 있자)
또 우리가 char type만 I/O를 하진 않기때문에 위 class들은 사실 basic_을 접두사로 하는 class template으로 작성된다.
우리가 봐온 istream class 같은 애들은 사실 typedef basic_istream<char> istream;의 산물인 것이다.
(이외에도 wchar_t type을 위한 class도 template으로 찍어낸다.)

cout 포맷 정리

0개의 댓글