36-1-라.파일 입출력

입출력 스트림으로 파일 입출력도 할 수 있는데 C에서와 마찬가지로 파일이나 콘솔이나 어차피 스트림이라는 면에서는 동일하므로 똑같은 방법으로 다룰 수 있다. C 수준에서 파일 입출력을 이미 해 본 경험이 있다면 이 항의 내용은 형식만 조금 다른 정도에 불과하므로 파일 입출력 과정에 대한 상세한 설명은 생략하고 간략하게 사용법 정도만 소개하도록 한다.

앞쪽 입출력 스트림의 클래스 계층도를 보면 파일 입출력 스트림인 basic_i(o)stream은 콘솔용 입출력 클래스로부터 상속된다. 따라서 >>, << 연산자 및 멤버 함수, 조정자 등을 그대로 사용할 수 있다. 다만 입출력 대상이 파일이라는 메모리 외부의 장치이므로 빠른 액세스를 위해 열고 닫는 동작과 섬세한 에러 처리를 해야 한다는 정도만 다르다. 일반화를 위해 파일 입출력 클래스도 템플릿으로 정의되어 있으며 이 템플릿으로부터 네 개의 특수화 버전을 정의한다.

 

typedef basic_ifstream<char, char_traits<char> > ifstream;

typedef basic_ofstream<char, char_traits<char> > ofstream;

typedef basic_ifstream<wchar_t, char_traits<wchar_t> > wifstream;

typedef basic_ofstream<wchar_t, char_traits<wchar_t> > wofstream;

 

i(o)fstream 클래스는 char 타입을 대상으로 하는 파일 입출력 클래스이며 wi(o)fstream은 wchar_t 타입을 대상으로 하는 유니코드 파일 입출력 클래스이다. 이 클래스들은 fstream 헤더 파일에 선언되어 있으므로 파일 입출력을 하려면 반드시 이 헤더 파일을 인클루드해야 한다. 먼저 파일 출력 예제부터 작성해 보자. C에서와 마찬가지로 파일 입출력을 하기 위해서는 버퍼를 준비해야 하므로 파일을 열고 닫는 처리가 필요하다.

 

: cppfilewrite

#include <Turboc.h>

#include <iostream>

#include <fstream>

using namespace std;

 

void main()

{

     ofstream f;

 

     f.open("c:\\cpptest.txt");

     f << "String " << 1234 << endl;

     f.close();

}

 

ofstream의 객체 f를 선언하고 open 함수로 출력하고자 하는 파일을 연다. open 함수의 인수로 열고자 하는 파일의 이름을 전달하는데 완전 경로를 줄 수도 있고 상대 경로를 지정할 수도 있다. open 함수는 파일이 없으면 새로 만들고 이미 존재한다면 덮어쓴다. 객체를 선언하고 open 함수로 파일을 여는 두 과정을 거치는 대신 생성자로 오픈할 파일의 이름을 곧바로 전달할 수도 있다.

 

ofstream f("c:\\cpptest.txt");

 

이 문장에 의해 출력 객체 f는 디스크상의 cpptest.txt 파일과 연결되며 이 객체로 출력을 보내면 물리적인 파일에 기록된다. 출력할 때는 << 연산자를 사용하는데 콘솔에서와 마찬가지로 모든 기본 타입에 대해 출력할 수 있으며 연쇄적인 출력도 가능하다. 출력을 완료했으면 close 함수로 파일을 닫는다. 예제를 실행하면 C 드라이브의 루트 디렉토리에 cpptest.txt라는 파일이 생성되어 있을 것이다. 다음은 이 파일을 다시 읽어 보자.

 

: cppfileread

#include <Turboc.h>

#include <iostream>

#include <fstream>

using namespace std;

 

void main()

{

     ifstream f;

     char str[128];

     int i;

 

     f.open("c:\\cpptest.txt");

     f >> str >> i;

     cout << str << i << endl;

     f.close();

}

 

파일로부터의 입력을 위해 ifstream 클래스의 객체 f를 선언하고 open 함수로 파일을 열었는데 생성자에서 바로 대상 파일을 지정하는 것도 물론 가능하다. f로부터 데이터를 읽을 때는 >> 연산자를 사용하면 된다. 예제에서는 str 배열에 문자열을 읽고 i에 정수를 읽은 후 확인을 위해 화면으로 다시 출력해 보았다. 입출력을 완료한 후 close로 파일을 닫는다.

파일은 메모리 외부에 존재하기 때문에 에러가 발생할 확률이 아주 높으므로 오픈할 때 파일이 제대로 열렸는지 항상 확인해야 한다. 에러 발생 여부는 오픈 직후에 객체의 is_open 멤버 함수로 확인할 수 있는데 이 함수는 인수를 취하지 않으며 성공 여부를 표현하는 bool값을 리턴한다. is_open 호출문을 if문의 조건식에 사용하면 성공 여부에 따른 동작을 지정할 수 있다.

 

: is_open

#include <Turboc.h>

#include <iostream>

#include <fstream>

using namespace std;

 

void main()

{

     ifstream f;

 

     f.open("c:\\neverexist.txt");

     if (f.is_open()) {

          cout << "파일 열기 성공" << endl;

          f.close();

     } else {

          cout << "파일 열기 실패" << endl;

     }

}

 

예제에서는 에러를 유도하기 위해 실제로 존재하지 않는 파일을 열어 보았는데 이 경우 is_open 함수는 열기에 실패했다는 의미로 false를 리턴한다. 프로그램은 파일 열기에 성공했을 때만 파일을 액세스해야 하며 실패했을 경우는 적절하게 에러 처리해야 한다.

파일을 여는 open 함수의 완전한 원형은 다음과 같다. 대상 파일의 경로 외에도 파일을 어떻게 열 것인지 파일 모드를 지정하는 두 번째 인수가 있으며 디폴트값이 지정되어 있다. 출력용과 입력용의 디폴트 모드가 다르게 설정되어 있는데 입력용은 읽을 수만 있고 출력용은 쓸 수만 있다.

 

void ifstream::open(const char *s, ios_base::openmode mode = ios_base::in);

void ofstream::open(const char *s, ios_base::openmode mode = ios_base::out | ios_base::trunc);

 

open 함수와 동일한 동작을 하는 생성자의 원형도 이와 같다. 파일 모드는 ios_base에 정의된 상수들이며 여러 개의 모드를 OR 연산자로 묶어서 지정할 수 있다. 파일 모드의 종류는 다음과 같다.

 

모드

설명

ios_base::out

출력용으로 파일을 연다.

ios_base::in

입력용으로 파일을 연다.

ios_base::app

파일 끝에 데이터를 덧붙인다. 데이터를 추가하는 것만 가능하다.

ios_base::ate

파일을 열자 마자 파일 끝으로 FP 보낸다. FP 임의 위치로 옮길 있다.

ios_base::trunc

파일이 이미 존재할 경우 크기를 0으로 만든다.

ios_base::binary

이진 파일 모드로 연다.

 

open 함수에 디폴트로 지정되어 있는 파일 모드를 사용하지 않으려면 두 번째 인수에 명시적으로 원하는 파일 모드를 지정하면 된다. 예를 들어 파일을 열자 마자 뒤에 덧붙이기를 하고 싶으면 out 모드와 app 모드를 같이 지정한다. 입력과 출력을 모두 할 수 있는 모드로 열고 싶으면 fstream 객체를 생성하고 in, out 모드를 모두 지정하면 된다. fstream은 istream, ostream으로부터 다중 상속된 iostream으로부터 상속을 받아 입력, 출력용 버퍼를 각각 하나씩 가지므로 입출력 겸용의 객체를 만들 수 있다.

다음 예제는 파일 입출력 객체를 사용하여 파일을 복사한다. C 드라이브의 루트에 있는 dummy.txt를 복사 원본으로 사용하므로 이 예제를 실행해 보려면 아무 파일이나 루트에 복사해 놓고 이름을 바꿔 놓아야 한다. 예제를 실행하면 똑같은 내용을 가지는 dummy2.txt 파일이 생성되어 있을 것이다.

 

: cppfilecopy

#include <Turboc.h>

#include <iostream>

#include <fstream>

using namespace std;

 

void main()

{

     ifstream src("c:\\dummy.txt",ios_base::in | ios_base::binary);

     if (!src.is_open()) {

          cout << "원본 파일이 없습니다." << endl;

     }

     ofstream dest("c:\\dummy2.txt",ios_base::out | ios_base::trunc | ios_base::binary);

     char buf[10000];

     int nread;

 

     for (;;) {

          src.read(buf,10000);

          nread=src.gcount();

          if (nread==0) break;

          dest.write(buf,nread);

     }

     src.close();

     dest.close();

}

 

원본과 대상 파일을 위해 두 개의 파일 객체를 선언하되 원본은 읽기 모드로 열고 대상은 쓰기 모드로 연다. 파일 복사는 파일에 있는 내용을 그대로 읽어서 사본을 만드는 것이므로 읽고 쓰는 중에 어떠한 변환도 할 필요가 없으며 그래서 binary 플래그를 지정하여 이진 모드로 열었다. 이진 모드의 파일을 읽고 쓸 때는 다음 함수들을 사용한다.

 

basic_istremm& read(char *s, streamsize n);

basic_ostream& write(const char *s, streamsize n);

 

읽고 쓸 데이터의 시작 번지와 크기를 인수로 전달한다. 복사하는 방법은 아주 원론적인데 원본에서 읽어서 대상 파일로 출력하기를 원본을 다 읽을 때까지 반복하면 된다. 예제에서는 10K 크기의 버퍼를 준비하고 10K 단위로 원본을 읽어 대상 파일로 보내는데 단, 파일 끝에서는 실제 읽은 바이트만큼만 출력해야 한다. read 함수가 실제 읽은 길이는 gcount 함수로 조사할 수 있다. 복사가 끝나면 close 함수로 두 파일을 모두 닫는다.

파일 액세스 함수들은 항상 파일의 현재 위치(FP)를 참조하며 읽고 쓴 후에 FP를 뒤쪽으로 옮기므로 순차적으로 파일을 액세스할 수 있다. FP를 임의의 위치로 옮길 때는 다음 두 함수를 사용하는데 입력용, 출력용의 FP를 따로 유지하므로 함수가 두 개로 나누어져 있다. 다중 상속받는 fstream 객체는 버퍼가 두 개이므로 현재 위치에 해당하는 FP도 두 개를 가진다.

 

basic_istream& seekg(off_type off, ios_base::seek_dir way);

basic_ostream& seekp(off_type off, ios_base::seek_dir way);

 

첫 번째 인수 off는 어디로 이동할 것인지 거리를 지정하며 두 번째 인수는 이동의 기준점인데 ios_base::beg는 파일의 선두를 기준으로 하며 ios_base::cur는 현재 위치, iso_base::end는 파일의 끝을 기준으로 한다. FP를 지정하는 방식은 C의 fseek 함수와 사실상 동일하다. FP의 현재 위치를 조사하는 함수는 tellp, tellg인데 역시 두 개의 FP에 대해 함수가 각각 제공된다.