파일은 저장 매체에 저장된 정보로서 바이트나 블록 단위로 입출력되고 기록되는 데이터 종류에 따라 텍스트 파일과 바이너리 파일로 나뉜다. 텍스트 파일은 오직 문자들만 기록되며 문자 외에 이미지 등 다양한 정보가 기록된다면 바이너리 파일이다.
텍스트 파일은 사람들이 사용하는 글자 혹은 문자만으로 구성되는 문서 파일이다. 사람들이 사용하는 글자에는 한글, 영어 알파벳, 숫자, 기호 등 여러가지가 있으며 각 글자마다 고유한 바이너리 코드(이진코드)가 주어진다. 텍스트 파일은 문자들로만 구성되는 파일이므로, 텍스트 파일에는 문자로 해석 가능한 코드 값들만 존재한다.
메모장과 같은 텍스트 편집기들은 사용자가 입력한 Enter
키를 '\r' 과 '\n' 의 두 아스키 코드로 저장하여 코드의 값은 각각 0x0D(13), 0x0A(10) 이다. '\r' 은 캐리지 리턴으로 커서를 현재 줄의 맨 앞으로 옮기도록 하는 것이고, '\n'은 라인피드로 커서를 현재 있는 위치에서 한 줄 밑으로 옮기도록 하는 지시이다. 따라서 다음 줄의 맨 앞으로 넘어가려면 '\r' 과 '\n' 이 모두 필요하다.
사진 이미지, 오디오 등의 문자로 표현되지 않는 바이너리 정보를 저장하는 파일이며 바이너리 파일의 각 바이트는 파일을 만든 응용프로그램만이 해석할 수 있다.
C++ 에서는 ifstream
으로 파일을 읽고, ofstream
으로 파일을 쓴다. fstream
은 하나의 파일에 읽기와 쓰기를 동시에 할 때 사용된다. ifstream과 ofstream 은 파일을 프로그램과 연결하는 스트림으로 ifstream 객체를 통해 파일에서 읽고 ofstream 객체를 통해 파일 쓰기를 진행한다.
파일 입출력을 위하선 <fstream>
헤더 파일과 std의 네임스페이스가 필요하다.
텍스트 I/O : 문자들만 기록하고, 파일에 있는 바이트를 문자로만 해석하는 입출력 방식으로 텍스트파일을 읽고 쓸때만 사용된다.
바이너리 I/O : 바이트 단위로 바이너리 데이터를 입출력하는 방식으로 모든 파일을 단순히 바이트의 스트림으로 다루기 때문에 텍스트 파일이나 바이너리 파일에 상관없이 읽고 쓰기가 가능하다.
#include <iostream>
#include <fstream> //파일 입출력을 위한 헤더파일
using namespace std;
int main() {
char name[10], dept[20];
int sid;
cout << "이름 >> ";
cin >> name;
cout << "학번(숫자) >> ";
cin >> sid;
cout << "학과 >> ";
cin >> dept;
ofstream fout; //파일 출력 스트림 생성
fout.open("student.txt"); //파일을 여는데 파일이 존재하지 않는다면 만들고 존재한다면 내용을 지우고 맨 앞부터 쓰기 시작
//ofstream fout("student.txt") //파일 출력 스트림을 생성과 동시에 파일열기
if(!fout) { //파일 열기에 실패한다면
cout << "파일을 열 수 없습니다." << endl;
return 0;
}
fout << name << endl;
fout << sid << endl;
fout << dept << endl;
fout.close(); //파일 닫기
}
#include <iostream>
#include <fstream> //파일 입출력을 위한 헤더파일
using namespace std;
int main() {
char name[10], dept[20];
int sid;
ifstream fin; //파일 입력 스트림 생성
fin.open("student.txt"); //파일을 열기
if(!fin) { //파일 열기에 실패한다면
cout << "파일을 열 수 없습니다." << endl;
return 0;
}
fin >> name; //파일 읽기
fin >> sid;
fin >> dept;
cout << name << endl;
cout << sid << endl;
cout << dept << endl;
fin.close(); //파일 읽기를 마치고 닫기
}
파일 모드란 파일을 열 때 앞으로 어떤 파일 입출력으로 수행할 것인지 알리는 정보
파일 모드 | 의미 |
---|---|
ios::in | 읽기 위해 파일을 연다. |
ios::out | 쓰기 위해 파일을 연다. |
ios::ate | (at end) 쓰기 위해 파일을 열며 파일 포인터를 파일 끝에 둔다. 파일 포인터를 옮겨 파일 내의 임의의 위치에 쓸 수 있다. |
ios::app | 파일 쓰기 시에만 적용되며 파일 쓰기 시마다 자동으로 파을 포인터가 파일 끝으로 옮겨져 항상 파일의 끝에 쓰기가 이루어진다. |
ios::trunc | 파일을 열 때 파일이 존재하면 파일의 내용을 전부 지워 크기를 0으로 만든다. ios::out 모드를 지정하면 디폴트로 함께 지정된다. |
ios::binary | 바이너리 I/O로 파일을 연다. 이 파일 모드가 지정되지 않으면 디폴트는 텍스트 I/O 이다. |
파일 모드는 파일을 열 때 지정하며, open() 함수나 각 스트림의 생성자를 통해 지정한다. open()의 경우 두번째 매개변수에 파일 모드를 지정한다.
C++ 입출력 클래스에서 파일에는 한 바이트를 읽고 쓰는 get() 과 put() 함수가 있는데 각각 istream과 ostream 클래스의 멤버로 텍스트 파일이나 바이너리 파일을 막론하고 입출력이 가능하다.
#include <iostream>
#include <fstream> //파일 입출력을 위한 헤더파일
using namespace std;
int main() {
ifstream fin("student.txt", ios::in); //디폴트가 ios::in으로 생략이 가능하지만 이렇게 명시적으로 작성도 가능
if(!fin) {
cout << "파일 열기 오류" << endl;
return 0;
}
int count = 0;
int c;
while((c = fin.get()) != EOF) { //EOF를 만날때 까지 읽어서 c에 입력
cout << (char)c;
count++; //읽은 문자 카운트하기
}
cout << "읽은 바이트 수 : " << count << endl;
fin.close();
}
#include <iostream>
#include <fstream> //파일 입출력을 위한 헤더파일
using namespace std;
int main() {
fstream fout("student.txt", ios::out | ios::app); //쓰기 모드로 파일 열기
if(!fout) {
cout << "파일 열기 오류" << endl;
return 0;
}
fstream fin("test.txt");
if(!fin) {
cout << "파일 열기 오류" << endl;
return 0;
}
int c;
while((c = fin.get()) != EOF) {
fout.put(c); //c를 하나씩 읽어 다른파일에 하나씩 입력
}
fin.close();
fout.close();
}
텍스트 파일을 라인 단위로 읽기 위해 아래의 함수를 사용한다.
istream 의 getline(char* line, int n) //버퍼를 사용해 한 라인을 읽음
getline(ifstream& fin, string& line) //스트링을 사용해 한 라인을 읽음
#include <iostream>
#include <fstream> //파일 입출력을 위한 헤더파일
#include <vector>
#include <string>
using namespace std;
void fileRead(vector<string> &v, ifstream &fin) {
string line;
while(getline(fin, line)) { //getline을 이용해 라인 읽기
v.push_back(line);
}
}
void search(vector<string> &v, string word) {
for(int i = 0; i < v.size(); i++) {
int index = v[i].find(word);
if(index != -1) //찾았을 때
cout << v[i] << endl;
}
}
int main() {
vector<string> wordVector;
ifstream fin("word.txt");
if(!fin) {
cout << "파일을 열 수 없습니다." << endl;
return 0;
}
fileRead(wordVector, fin);
fin.close();
cout << "파일을 읽었습니다." << endl;
while(1) {
cout << "검색할 단어를 입력하세요 >> ";
string word;
getline(cin, word);
if(word == "exit")
break;
search(wordVector, word);
}
cout << "프로그램을 종료합니다." << endl;
}
바이너리 입출력은 파일의 각 바이트를 바이너리 값 그대로 읽거나, 변수나 버퍼의 바이너리 값을 그대로 파일에 저장하는 저수준 입출력 방식이다. 따라서 텍스트 파일의 경우 바이너리 입출력으로 읽고 쓰기가 되지만 바이너리 파일은 반드시 바이너리 입출력을 사용해야 한다.
#include <iostream>
#include <fstream> //파일 입출력을 위한 헤더파일
using namespace std;
int main() {
ifstream fin("image.png", ios::in | ios::binary); //바이너리 입력스트림 생성
if(!fin) {
cout << "파일을 열지 못했습니다." << endl;
return 0;
}
ofstream fout("copy.png", ios::out | ios::binary); //바이너리 출력스트림 생성
if(!fout) {
cout << "파일 열기 오류" << endl;
return 0;
}
int c;
while((c = fin.get()) != EOF) { //파일 끝까지 읽고
fout.put(c); //읽은 내용 쓰기
}
cout << "복사완료" << endl;
fin.close();
fout.close();
}
get()과 put()은 한바이트 단위로 입출력을 수행하지만 read()와 write()는 블록 단위로 입출력하기 때문에 실행 속도가 빠르다.
#include <iostream>
#include <fstream> //파일 입출력을 위한 헤더파일
using namespace std;
int main() {
ifstream fin("image.png", ios::in | ios::binary); //바이너리 입력스트림 생성
if(!fin) {
cout << "파일을 열지 못했습니다." << endl;
return 0;
}
ofstream fout("copy.png", ios::out | ios::binary); //바이너리 출력스트림 생성
if(!fout) {
cout << "파일 열기 오류" << endl;
return 0;
}
char buf[1024]; //1KB씩 복사
while(!fin.eof()) {
fin.read(buf, 1024); //최대 1KB를 읽어 버퍼에 저장
int n = fin.gcount(); //실제로 읽은 바이트 수 알아내기
fout.write(buf, n); //실제로 읽은 바이트 크기만큼 저장하기
}
cout << "파일 복사 완료" << endl;
fin.close();
fout.close();
}