관리 메뉴

공부 기록장 💻

[C++] 파일 입출력 (File I/O) - 텍스트/바이너리 파일 I/O, 파일 모드, 임의 접근, 파일 포인터 본문

# Language & Tools/C++

[C++] 파일 입출력 (File I/O) - 텍스트/바이너리 파일 I/O, 파일 모드, 임의 접근, 파일 포인터

dream_for 2021. 6. 2. 13:54

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

 

 

 

 

텍스트 파일 & 바이너리 파일

 

파일(File): 저장 매체에 저장되는 정보

- 바이트나 블록 단위로 입출력된다.

- 데이터 종류에 따라 텍스트(문자) / 바이너리(문자, 이미지 등) 파일로 나뉜다.

 


텍스트 파일

- 글자 혹은 문자로만 구성되는 문서 파일

- 각 글자마다 고유한 바이너리 코드(이진코드- ASCII/UNI code) 로 구성되어 있으며, 텍스트 파일엔 문자 코드만 저장됨

- ex) txt, html, xml, c/c++/java source file

 

 

 

텍스트 파일의 <Enter> 키

 

1) '\r' carriage return 제어 코드(ASCII코듸 0x0D(십진수 13)) - 커서를 현재 줄의 맨 앞으로 옮기도록 하는 지시

2) '\n' line feed 제어 코드 (ASCII코드 0x0A(십진수10)) - 커서를 현재 있는 위치에서 한 줄 밑으로 옮기도록 하는 지시

 

=> 다음 줄의 맨 앞으로 넘어가려면 carriage return 그리고 line feed가 모두 필요함

 

 

 


바이너리 파일

- 사진 이미지, 오디오, 그래픽 이미지, 컴파일된 코드 등 문자로 표현되지 않는 바이너리 정보들

- 각 파일을 만든 응용프로그램만이 해석 할 수 있다

- ex) jpeg, bmp, mp3, hwp, doc, ppt, obj, exe

 

 

 


C++ 파일 입출력

 

파일 입출력 라이브러리의 클래스들: ifstream(읽기) / ofstream(쓰기) / fstream(읽기/쓰기)

- basic_( ) 등 템플릿 클랴스에 char 타입으로 구체화하고 typedef로 선언될 클래스

 

 

 

파일 입출력 스트림: 파일 <-> 프로그램 연결

 

- ifstream, ofstream 스트림은 각각의 객체를 통해 프로그램과 파일을 연결하여 파일을 읽고, 파일에 쓰도록 한다.

- ios, iostream, ostream, iostream 클래스의 멤버 함수들인 get(), getline(), put(), read(), write() 은 스트림이 키보드가 아닌, 이번엔 '파일'에 연결되어 있다면, 스트림에 연결된 파일에서 읽고 쓰는 역할을 수행한다.

 

 

 

헤더 파일과 namespace

 

ifstream, ofstream, fstream 클래스의 객체를 생성하기 위해서는 <fstream> 헤더 파일을 추가하고.

std 이름 공간을 사용하도록 한다.

 

#include <fstream>
using namespace std;

 

 

파일 입출력 모드

 

1) 텍스트 I/O - 파일에 있는 바이트를 문자로만 해석하고, 문자들만 기록하는 입출력 방식

2) 바이너리 I/O - 바이트 단위로 바이너리 데이터를 입출력함 (텍스트/바이너리 파일 상관 x)

 

 

 


파일 입출력 - <<, >> 연산자 이용

 

 

 

<파일 쓰기>

 

1. 파일 출력 스트림 객체 생성

 

osftream fout; // 파일 출력 스트림 객체 fout 생성

 

 

2. 파일 열기 / 검사

 

파일을 연다는 것은, 출력 스트림에 파일을 연결하는 과정을 의미한다.

파일 입/출력 스트림 객체의 open() 멤버 함수를 호출하여 파일을 열고 스트림에 연결한다.

파일명을 매개 변수로 하는 함수이다.

 

fout.open("song.txt"); // song.txt 파일 열기

 

파일을 열 때, 파일이 읽기 전용(read-only) 파일로 이미 존재하거나, 디스크 용량이 모자라는 등 파일 생성하는 것이 불가능한 경우에 파일 열기는 실패한다.

위에서 "song.txt"이름의 파일이 존재하지 않으면, 빈 파일을 새로 만들고, 이미 존재한다면 기존의 파일 내용을 모두 지우고 파일의 처음부터 쓸 준비를 한다.

 

 

다음과 같이 open() 멤버 함수를 사용하지 않고,

파일 출력 스트림 객체의 생성자에 파일명을 매개변수로 전달하여 파일 열기를 함께 할 수도 있다.

ofstream fout("song.txt"); // 파일 출력 스트림 생성과 동시에 파일 열기

 

 

3. 파일 열기 성공 검사

 

파일 열기가 실패한 경우, 실패를 처리하는 코드를 위해

fout 스트림의 operator!() 연산자 함수를 실행한다. 열기가 실패한 경우에 true를 리턴한다.

 

if(!fout){ ... } // fout 스트림의 파일 열기가 실패한 경우

// if(!fout.is_open()){ ... }

 

두번째와 같이 is_open() 함수를 사용할 수도 있다.

 

 

 

3. << 연산자를 이용한 파일 쓰기

 

<< 연산자는 문자만 저장하므로, 정수를 문자열로 바꾸어 저장한다.

정수 21은 '2'와 '1'로 각각 저장된다.

 

char filename[] = "data.txt"
int age = 21;
char name[] = "Lee";
char birth_date[] = "2000/00/00";

ofstream fout(filename); // ofstream의 파일 출력 스트림 객체 fout 생성, filename 파일 oepn
if(!fout) {
	cout << "파일 열기 실패" << endl; // 열기 오류 - 실패 처리 코드
    return;
}

fout << age << '\n'; // 파일에 21과 '\n' 기록
fout << name << endl; // 파일에 "Lee"와 '\n'을 덧붙여 기록
fout << birth_date << endl; // 파일에 "2000/00/00"과 '\n'을 덧붙여 기록

 

 

 

 

4. 파일 닫기

 

파일 입출력이 끝나면 close() 함수를 호출하여 파일을 닫는다.

스트림이 파일과의 연결을 끊도록 한다.

 

fout.close();

 

 

 


<파일 읽기>

 

 

파일을 읽기 위한 파일 입력 스트림의 ifstream의 객체를 사용하여

파일을 열고 파일 스트림과 프로그램을 연결한 후 파일로부터 데이터를 가져오는 입력의 동작 방식은,

파일에 쓰기 위한 파일 출력 스트림 ofstream 의 객체가 동작한 방식과 동일하다.

 

 

 

파일 입력 스트림 객체 생성 / 파일 열기와 검사

 

ifstream의 파일 입력 스트림 클래스의 fin 객체를 생성하고, 파일을 연다.

 

ifstream fin; // 파일 입력 스트림 객체 fin 생성
fin.open("data.txt");
// ifstream fin("data.txt");

if(!fin){ ... }

 

 

>> 연산자를 이용한 파일 읽기

 

파일에 문자로 저장되어 있던 데이터를

각 데이터 타입에 맞게 변경하여 문자열 또는 정수 (또는 float형 등) 로 변환하여 저장한다.

 

char name[10];
int id;

fin >> name; // 파일에서 문자열을 읽어 name 배열에 저장
fin >> id; // 파일에서 정수를 읽어 id 정수형 변수에 저장 ('2', '0' 등의 문자를 20 정수로 변환)

 

 

4. 파일 닫기

 

fin.close();

 


 

예제

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

int main(){
	char filename[20]; // 파일 이름을 저장할 문자열
	string data("Hi~ Hello~"); // 스트링 문자열 객체
	string tmp; // 스트링 객체
	string line; // 스트링 객체
	
	cout<<"파일 이름 입력하시오 >> ";
	cin.getline(filename, 20); // 문자열 filename 입력받아 저장
	
	cout<<"파일에 출력할 한 줄 >> ";
	getline(cin, line); // line 문자열 스트링 입력받기
	
    ofstream fout(filename); // ofstream의 fout 객체 생성하고 filename 파일명의 파일 열기
	fout << data << endl; // data 스트링 문자열 객체와 줄바꿈 문자를 연결하여 파일에 출력
	fout << line << endl; // line 스트링 문자열 객체와 줄바꿈 문자를 연결하여 파일에 출력
	
	fout.close(); // fout 객체로 열었던 파일 닫기
	
    
	ifstream fin(filename); // filename 문자열의 파일 이름을 ifstream의 fin 객체로 파일 열기
	while(getline(fin, tmp)) // 파일에 저장되어 있는 한줄의 문자열을 tmp에 저장
    	cout << "읽은 데이터: " << tmp << endl; // tmp 문자열 출력
	
	fin.close(); // fin 객체로 열었떤 파일 닫기
}

 

 

 

 


파일 모드(File Mode)

파일 모드 : 파일을 열 때, 어떤 파일 입출력을 수행할 것인이 알리는 정보

 

 

<파일 모드 상수>

ios 클래스에 상수로 선언되어 있다.

 

파일 모드 설명
ios::in 파일 읽기 모드
ios::out 파일 쓰기 모드
ios::ate (at end) 쓰기 위해 파일을 염. 파일 포인터를 파일 끝에 둔다. 파일 포인터를 옮겨 파일 내의 임의의 위치에 쓸 수 있다.
ios::app 파일 쓰기시에만 적용. 자동으로 파일 포인터가 파일 끝으로 옮겨져, 항상 파일의 끝에 쓰기가 이루어짐
ios::binary 바이너리 I/O 로 파일을 연다. (디폴트는 텍스트I/O)

 

 

파일 모드 지정

파일 모드는, 파일을 열 때 생성자의 매개변수를 통해, 또는 open 함수의 매개변수를 통해 지정한다.

다음은 파일 스트림 객체의 멤버 함수인 open() 함수의 원형이다.

 

void open(const char * filename ios::openmode mode) mode로 지정된 파일 모드로 filenae의 파일을 연다.

 

 

ios클래스의 상수 mode를 여러개 지정하고자 하면, | (bit-OR) 연산자를 이용하면 된다.

 

스트림의 파일 모드 디폴트값

- ifstream의 디폴트 파일 모드 : ios::in

- ofstream의 디폴트 파일 모드 : ios::out

- fstream의 디폴트 파일 모드 : ios::in | ios::out

- 텍스트 I/O default

 

 


몇 가지의 예를 살펴보자.

 

 

파일의 끝에 데이터 저장

 

ofstream fout("data.txt", ios::out | ios::app); 

 

 

바이너리 I/O로 data.bin 파일 기록

 

fstrea fbinout("data.bin", ios::out | ios::binary);

 

 


텍스트 I/O

 

istream과 ostream 클래스의 멤버인 get()과 put()를 사용하여 파일 입출력을 해보자 가능하다.

 

 

1. 파일 열기 (텍스트 I/O 모드)

ios::binary 로 지정하는 것이 아니면, 디폴트로 텍스트I/O로 입출력이 이루어진다.

const char *file="c::\\windows\\system.ini"; // windows 파일 아래에 있는 system.ini 파일명
ifstream fin(file); // 파일 입력 스트림 객체 fin 생성하고 텍스트I/O 모드로 파일 열기
...
ofstream fout(file); // 파일 출력 스트림 객체 fout, 텍스트I/O모드

 

2. 문자 - get() - EOF / put()

 

int get()  파일에서 문자 한바이트 읽고 리턴. 파일의 끝에서 읽으면 EOF(-1) 리턴
ostream& put(char ch 파일에 문자 ch 기록

 

텍스트 I/O 모드로 읽을 때, get()은 라인의 끝에 있는 '\r\n' 의 두 바이트를 '\n' 의 한바이트로 리턴한다.

따라서 실제 저장되어 있는 값보다 모자르게 바이트를 읽는다.

 

 

get() 함수는 파일의 마지막 문자 다음인, 파일의 끝을 읽으면 EOF(-1)를 리턴하고,

스트림 내부에 eofbit 플래그를 1(true)로 설정한다.

 

get() 이 리턴한 값을 확인하여 다음과 같이 파일의 끝을 인지할 수 있다.

while(true){
	int c;
    if(c == EOF) {
    	... 
        break;
    }
    else{
    	...
    }
}

// while ((c=finl.get()) != EOF)){ ... }

 

(파일의 끝인지 fin.eof() 의 값을 확인한 후에 get() 함수를 실행하면 파일의 끝을 잘못 인지한다.)

(따라서 get() 함수로 먼저 EOF(-1)를 리턴했는지 확인한 후에 파일을 빠져나가도록 코드를 구현하는 것이 올바르다.)

 

따라서, 파일에서 문자를 읽을 때, int형으로 선언한 정수 변수로 문자를 읽자!

입력 할때는, int c; while((fin.get() ) != EOF) {...} , 출력할 때에는 (char) c 이렇게 !

 

get() 함수 다음에 -> 파일의 끝 확인하기

 

 

 

3. 파일 덧붙여 쓰기 - put()

 

파일의 끝에 문자를 추가하도록 하는 모드 ios::app 을 활용하여

어떤 파일의 문자를 읽고, 해당 파일의 끝에 문자를 추가하도록 하는 예제를 살펴보자.

 

 

data.txt 파일을 텍스트파일/읽기 모드로 열어 문자를 읽고,

mydata.txt 파일을 텍스트파일/출력/파일의 끝에 파일 포인터 이동 모드로 열어 읽은 문자를 파일 끝에 출력하도록 한다.

 

 

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

int main(){
	fstream fout("mydata.txt", ios::out | ios::app); // 파일의 끝에 추가 출력
	fstream fin("data.txt", ios::in); // 읽을 파일
	
	if(!fin){
		cout << "읽을 파일 열기 실패"<<endl;
		return 0;
	}
	if(!fout){
		cout << "출력할 파일 열기 실패"<<endl;
		return 0;
	}
	
	int c;
	while((c=fin.get())!=EOF) // fin 객체로 연 파일로부터 문자 하나를 읽어
    		fout.put(c);	// fout 객체로 연 파일에 해당 문자를 출력
	
	fout.close();
	fin.close();	 
}

 

 

다음과 같은 결과가 나타남을 확인할 수 있다.

 

 

 

 

4. 문자열 - getline()

 

문자열 단위로 텍스트 파일을 읽는 방법 두가지

1) istream의 getline(char* line, int n) - char 타입의 문자열 

2) getline(ifstream& fin, string& line) - 스트링 문자열 객체 (<string> 헤더파일의 전역 함수)

 

 

스트링 문자열 객체를 사용하는 것이 훨씬 쉽다!

 

 


바이너리 I/O

 

 

파일 열기(바이너리 I/O 모드)  / get(), put()

 

이미지 사진 파일 등 바이너리 파일에서도 텍스트 파일에서 사용한 것과 동일하게,

get(), put() 함수를 이용해 문자 단위로 바이너리 파일을 다룰 수 있다.

 

ifstream fbin(binFilename, ios::in | ios::binary); // 바이너리 파일 binFilename을 을 읽기 모드로 연다.

 

 

 

블록 단위 입출력 - read(), write()

 

istream, fstream 클래스의 멤버 함수를 상속 받아 ifstream과 ofstream에서도 

블록 단위로 파일 입출력을 할 수 있도록 한 함수는 read()와 write()이다.

 

 

istream& read(char * s, int n) 파일에서 최대 n개의 바이트를 배열 s에 읽어 들임. 파일의 끝을 만나면 읽기 중단
ostream& write(char *s, int n) 배열 s에 있는 처음 n개의 바이트를 파일에 저장
int gcount() 최근에 파일에서 읽은 바이트 수 리턴

 

 

 

예제를 살펴보자.

 

바이너리 파일에서 블록 단위로 읽을 때에는,

get() 함수로 문자를 읽을 때와 달리, 파일의 끝까지 도달해야 하므로

eof() 함수의 반환값으로 반복문의 조건을 확인하도록 한다. 

 

마지막 블록을 읽을 때에는, 지정한 블록 바이트 만큼 전부 읽지 않은 경우가 발생할 수도 있으므로,

gcount() 함수의 반환값으로 얻은 실제 읽은 바이트 수만큼 출력하도록 한다.

 

char s[32]; // 블록 단위(32바이트)로 읽어 들일 버퍼
while(!fin.eof()){
	fin.read(s, 32);
    	int n = fin.gcount(); // fin객체가 읽은 바이트 수
    	cout.write(s, n); // 버퍼의 n 개의 바이트를 화면에 출력
}

 

 

바이너리 파일을 이용해, 블록 단위로 읽으면

파일에 저장된 '\n\r' 값을 2바이트로 정확히 일어낸다.

파일의 바이트 수를 정확히 알고 싶다면, 바이너리 파일 모드로 입출력을 하자.

 

 


스트림 상태 검사 (Stream State Check)

파일 입출력 스트림은 스트림의 상태(stream state)를 저장하는 멤버 변수를 두고.

입출력이 진행되는 동안 발생한 오류(error) 정보를 유지한다.

 

 

<스트림 상태를 나타내는 비트>

 

eofbit - 파일의 끝을 만났을 때 1로 세팅

failbit - 정수를 입력받고자 했으나 문자열이 입력되는 등의 포맷 오류나 쓰기 금지된 곳에 쓰기를 시행하는 등 전반적인 I/O 실패 시 1로 세팅

badbit - 스트림이나 데이터가 손상되는 수준의 진단되지 않는 문제가 발생한 경우, 유효하지 않은 입출력 명령이 주어졌을 때 1로 세팅

 

 

<스트림 상태 검사하는 멤버 함수>

 

eof() - 파일의 끝을 만났을때 (eofbit = 1) true 리턴

fail() - failbit 나 badbit가 1로 세팅되었을때 true 리턴

bad() - badbitd이 1로 세팅되었을때 true 리턴 

good() - 스트림이 정상적(모든 비트가 0)일 때 true 리턴

clear() - 스트림 상태 변수를 0으로 지움

 

 

객체.멤버함수()


임의 접근(Random File Access), 파일 포인터(File Pointer)

 

 

파일 포인터(File Pointer)의 순차 접근(sequential file access) 방식에서

임의 접근(random file access) 방식을 사용해보자.

파일 포인터의 위치를 임의의 위치로 이동시켜보자!

 

 

C++ 파일 포인터는 다음의 두가지의 종류가 있다.

1) get pointer - 파일 내의 읽기 지점을 가리키는 파일 포인터

- ios::in 으로 열려진 fstream의 객체: get, read, getline, >>

 

2) put pointer - 파일 내의 쓰기 지점을 가리키는 파일 포인터

- ios::out으로 열려진 fstream 객체: put, write, <<

 


 

<임의 접근 방법 멤버 함수>

 

streampos: 파일 포인터의 절대적 위치 값(long 타입)

streamoff: 파일 포인터의 상대 위치 값(long 타입)

 

istream& seekg(streampos pos) 절대 위치 pos로 get pointer 이동
istream& seekg(streamoff offset, ios::seekdir seekbase) seekbase를 기준으로 offset 만큼 떨어진 위치로 get pointer 이동
ostream& seekp(streampos pos) 절대 위치 pos로 put pointer 이동
ostream& seekp(streamoff offset, ios::seekdir seekbase) seekbase를 기준으로 offset 만큼 떨어진 위치로 put pointer 이동
streampos tellg() 입력 스트림의 현재 get pointer 값 리턴
streampos teelp() 출력 스트림의 현재 put pointer 값 리턴

 

<seekbase로 사용되는 ios::seekdir 타입의 상수>

ios::beg 파일의 처음 위치를 기준으로 포인터 이동
ios::cur 현재 파일 포인터의 위치를 기준으로
ios::end 파일의 끝(EOF) 위치를 기준으로

 

 

- fin.seekg(0, ios::end) -> 파일의 맨 끝(EOF)으로 이동

- 파일의 크기: fin.seekg(0, ios::end); int fileSize=fin.tellg(); -> get pointer 값이 파일의 크기임(바이트크기)

 

728x90
반응형
Comments