Post

C++ - 파일 입출력

C++ 파일 입출력

파일 입출력을 할 때 C 형식으로 받아오고 쓰는 경우가 많아서 C++ 형식으로는 어떻게 하는게 좋은지 정리를 하려고 한다.

1. 도입

우리가 어떤 제품으로써 프로그램을 짜게되면 필연적으로 파일을 다루게 된다.
여기서 말하는 파일이란 txt와 같은 텍스트 파일을 말할 수도 있고, 특정 포맷으로 이루어진 바이너리 파일일 수도 있다.
때문에 파일 입출력에 대한 부분을 알아야한다.

C++에서 파일에 대해서 다루기 위해서는 파일 스트림 헤더인 fstream 헤더를 제일 상위에 포함해야한다. 이후 서술할 내용은 상위 fstream 헤더를 포함했다는 가정하에 이뤄진다.

2. 파일 열기 / 닫기

파일을 쓰고 읽기 전에 파일을 여는게 필요하다. 이는 동시성 제어를 위한 lock을 걸기 위함이기도 하다. 두 가지 방법이 있다.
해당 객체를 넘겨줄때 path를 넣어주는 것과 따로 open을 하는 방법이다. (ostream과 ifstream을 이용해 각각 쓰기와 읽기를 할 수 있지만 둘 다 한번에 가능한 fstream을 사용했다)

1
2
fstream fileOut("test.txt",ios::out);

1
2
fstream fileOut;
fileOut.open("test.txt",ios::out);

만약 쓰기를 위해 파일을 열었을경우 대상 파일이 읽기전용으로 이미 생성되어있거나, 스토리지 용량이 부족하면 에러를 반환하며. 만약 대상 파일이 이미 있지만 읽기전용이 아니라면 설정에 따라 어떻게 처리될지 정해진다. 이 설정은 open 함수에서 path 다음에 입력 가능하며 내용은 아래와 같다.

open option설명
ios::ininput
ios::outoutput
ios::ate파일 내용 위치를 가르키는 포인터를 파일의 맨 끝으로 이동
ios::binarybinary
ios::app파일 내용 이어쓰기
ios::trunc파일 내용 버리고 새로 쓰기

위 옵션들은 ios::in|ios::out 형태로 OR로 연결하여 여러개를 사용할 수 있다.
이후 제대로 파일이 열렸는지 확인을 위해 아래와 같이 확인 가능하다.

1
2
3
4
5
6
fstream fileOut;
fileOut.open("test.txt",ios::out);

if(!fileOut.is_open()){
  // 파일 열기 실패시 예외처리
}

이후 파일을 닫기 위해서는 open을 했던 객체에 아래와 같이 close 함수를 사용해주면 된다.

1
fileOut.close();

※ 파일 경로에 한국어가 들어가있을 경우

만약에 파일 경로에 한국어가 들어가있다면 그냥 fstream이나 ofstream이나 ifstream과 같은 것을 쓰면 작동을 안한다.
이 경우 아래와 같이 별도의 stream으로 운용해줘야한다.

  • 파일 쓰기
    1
    
    wofstream fileOut("폴더/파일.txt");
    
  • 파일 읽기
    1
    
    wifstream fileOut("폴더/파일.txt");
    

3. 파일 쓰기

1) 텍스트 파일 쓰기

그냥 화면에 출력하듯이 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<fstream>

using namespace std;

int main() {
    
    fstream fileOut;
    fileOut.open("test.txt",ios::out);
    
    fileOut<< "이것은 테스트" << endl;
    
    fileOut.close();
    
    return 0;
}

2) 바이너리 파일 쓰기

특정 데이터를 바이너리 형태로 넣으려면 아래와 같이쓰면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<fstream>

using namespace std;

int main() {
    
    fstream fileOut; //1
    int a = 2000; //2
    fileOut.open("test.bin",ios::out|ios::binary); //3
    
    fileOut.write(reinterpret_cast<char*>(&a),sizeof(int)); //4
    
    fileOut.close(); //5
    
    return 0;
}

천천히 한줄씩 해석해보자면

  1. file 출력 스트림 객체를 생성
  2. 파일에 넣기 위한 int 형 데이터 변수 선언 및 2000으로 초기화
  3. test.bin이라는 이름으로 파일을 열고 binary 출력 전용으로 열음
  4. 입력하고자하는 데이터를 형변환하여 char* 타입으로 변경해서 포인터로 만들고, 몇 바이트만큼 쓸지 기재(위 예시에서는 4bytes)
  5. 스트림 객체 닫기

위 코드를 실행하면 test.bin이라는 파일이 생기며 해당 파일을 헥스 에디터로 열어보면 아래와 같이 출력된다.

img.png

이는 2000을 hex 값으로 변경하면 7D0인데, 이를 리틀 엔디언 방식으로 넣기 때문에 D0 07 순으로 들어가게 되는 것이다.
만약 빅 엔디언을 쓰는 시스템을 위한 파일을 만들어야한다면 별도의 처리가 필요할 것이다.

4. 파일 읽기

1) 텍스트 파일 읽기

텍스트를 읽어 올때는 한 줄씩 읽어오는게 일반적이다.
여기서 말하는 줄이란, 개행 문자를 만날때까지가 한줄이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
#include<fstream>
#include<string>

using namespace std;

int main() {
    
    fstream filein;
    filein.open("test.txt",ios::in);
    
    string line;
    while(!filein.eof()) {
        getline(filein, line);
        cout << line << endl;
	}
    
    filein.close();
    
    return 0;
}

위 코드를 실행하면 해당 파일이 끝날때까지 loop를 돌면서 한줄씩 읽어오고 해당 줄을 읽어온 것을 콘솔에 출력해준다.

2) 바이너리 파일 읽기

바이너리 파일은 따로 read 함수로 어디에서 어느정도 까지 읽어올지를 정해주어야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<fstream>
#include<iostream>

using namespace std;

int main() {
    
    fstream filein; //1
    int a; //2
    filein.open("test.bin",ios::in|ios::binary); //3
    
    filein.read(reinterpret_cast<char*>(&a),sizeof(int)); //4
    
    cout<<a<<endl; // 5
    
    filein.close(); //6
    
    return 0;
}

천천히 한줄씩 해석해보자면

  1. file 입력 스트림 객체를 생성
  2. 파일에서 받아온 데이터를 넣기 위한 int 형 데이터 변수 선언
  3. test.bin이라는 이름으로 파일을 열고 binary 읽기 전용으로 열음
  4. 넣고자하는 위치의 포인터를 char* 타입으로 변경하고, 몇 바이트만큼 읽어올지 기재(위 예시에서는 4bytes
  5. 제대로 값을 읽어왔는지 확인
  6. 스트림 객체 닫기

참고자료

This post is licensed under CC BY 4.0 by the author.