C 스타일 파일 입출력
errno_t fopen_s(
FILE** pFile,
const char *filename,
const char *mode
);
return : 성공 시 0을 리턴하고, 에러 발생 시 errno_t타입의 0이 아닌 값을 리턴
pFile : FILE타입(파일 스트림)의 이중 포인터
filename : 파일 이름. 파일 이름만 쓰고, visual studio에서 빌드 하면 프로젝트 파일이 있는 곳에 파일을 만든다
mode
- r : 파일 읽어오기
- w : 파일 쓰기(만들기)
- a : 파일에 접근하여 이어 쓰기
- r+ : 파일이 존재하면 해당 파일을 읽고 쓰기 가능하게 연다. 파일이 없으면 에러를 반환
- w+ : 파일이 존재하면 해당 파일에 읽고 쓰기 가능하게 연다. 파일이 없으면 새로 만든다
- a+ : 파일이 존재하면 해당 파일의 끝부터 읽고 쓰기가 둘다 가능하게 연다. 파일이 없으면 새로 만든다
- rt : txt 파일을 읽어온다
- rb : binary 파일을 읽어온다
- wt : txt 파일에 쓴다
- wb : binary 파일에 쓴다
- r+b 또는 rb+ : 읽고 쓰기 가능하게 binary 파일을 연다. 파일이 없으면 에러를 반환
- w+b 또는 wb+ : 읽고 쓰기 위해 비어 있는 binary 파일을 쓴다. 없으면 파일을 새로 만든다.
- a+b 또는 ab+ : 파일의 끝에 추가해서 쓰기 위해 binary 파일을 연다
반드시 fopen이나 fopen_s로 파일을 열었으면 fclose로 닫아주자!
쓰기 함수들은 fputc, fputs, fprintf 등이 있다.
#include <stdio.h>
int fputc(int c, FILE *stream); // 파일에 문자 하나 쓰기
int fputs(const char *string, FILE *stream); // 파일에 문자열 쓰기
int fprintf(FILE *stream, const char *format-string, argument-list); // 파일에 문자열 쓰기
fprintf의 특징은 문자열 포맷팅을 사용해서 숫자도 써줄 수 있다는 것이다
int main()
{
FILE* FileStream = nullptr;
fopen_s(&FileStream, "Test.txt", "wt");
int Number1 = 200;
int Number2 = 300;
fprintf(FileStream, "Number1 %d Number2 %d입니다.\n", Number1, Number2);
fclose(FileStream);
return 0;
}
파일을 읽는 함수는 쓰는 함수와 대응되도록 fgetc, fgets가 있다.
#include <stdio.h>
int fgetc(FILE *stream); // 문자 하나를 읽어오는 함수
char *fgets (char *string, int n, FILE *stream); // 문자열을 읽어오는 함수
fgets의 특이한 점은 개행 문자를 만나면 거기까지만 읽어오는 것이다.
int main()
{
FILE* FileStream = nullptr;
fopen_s(&FileStream, "Test.txt", "wt");
if (FileStream != nullptr)
{
fputs("문자 첫번째줄입니다.\n", FileStream);
fputs("문자 두번째줄입니다\n", FileStream);
fclose(FileStream);
}
fopen_s(&FileStream, "Test.txt", "rt");
if (FileStream)
{
char Line[128] = {};
fgets(Line, 127, FileStream);
std::cout << Line << std::endl;
fclose(FileStream);
}
return 0;
}
첫 번째 줄을 출력하고 개행 문자를 만나서 그 줄만 읽고 있는 것을 확인할 수 있다.
#include <stdio.h>
int fseek(FILE *stream, long int offset, int origin); // 파일 커서 위치 변경
long int ftell(FILE *stream); // 파일 커서 위치 반환
- offset : 인자 origin으로부터 몇 바이트를 이동할 것인지
- origin : 기준점
- 기준점의 종류
1. SEEK_CUR : 현재 파일 포인터의 위치이다.
2. SEEK_END : 파일의 가장 끝 위치로 이동한다.
3. SEEK_SET : 파일의 시작 위치로 이동한다.
파일을 읽고 나면 파일 내 커서가 읽은 만큼 이동한다. 따라서 나중에 읽을 때는 커서 위치부터 읽게 되므로, 읽지 않은 부분부터 읽기 시작한다. 이 커서 위치는 fseek 함수를 이용해서 바꿀 수 있으며 ftell 함수로 현재 커서 위치를 얻을 수 있다. 이 두 함수로 파일의 크기를 알 수 있다. 파일을 열면 커서 위치가 맨 처음으로 설정되어 있을 것이고 파일 커서 위치를 fseek 함수를 이용해서 맨 끝으로 설정하고, ftell 함수로 커서 위치를 반환하면 그 차이가 파일 사이즈이다.
int main()
{
fopen_s(&FileStream, "Test.txt", "rt");
if (FileStream)
{
fseek(FileStream, 0, SEEK_END);
// ftell : 현재 파일커서의 위치를 얻어온다.
int FileSize = ftell(FileStream);
fclose(FileStream);
}
return 0;
}
binary 파일은 게임에서 save/load를 할 때 자주 사용되는데, 예를 들어 플레이어의 정보를 담고 있는 구조체를 binary 파일에 저장해뒀다가 나중에 읽어올 수 있다. 확장자명은 아무렇게나 해도 된다.
enum class EJob
{
None,
Knight,
Archer,
Magicion
};
struct Player
{
char Name[32];
EJob Job;
int Attack;
int Armor;
};
int main()
{
Player player;
strcpy_s(player.Name, "마스터이");
player.Job = EJob::Knight;
player.Attack = 10;
player.Armor = 5;
FILE* FileStream;
fopen_s(&FileStream, "Test1.lol", "wb");
if (FileStream)
{
fwrite(&player, sizeof(Player), 1, FileStream);
fclose(FileStream);
}
Player player1 = {};
fopen_s(&FileStream, "Test1.lol", "rb");
if (FileStream)
{
fread(&player1, sizeof(Player), 1, FileStream);
std::cout << "이름 : " << player1.Name << std::endl;
std::cout << "직업 : ";
switch (player1.Job)
{
case EJob::Knight:
std::cout << "기사" << std::endl;
break;
case EJob::Archer:
std::cout << "궁수" << std::endl;
break;
case EJob::Magicion:
std::cout << "마법사" << std::endl;
break;
}
fclose(FileStream);
}
return 0;
}
코드를 실행하면 다음과 같이 쓰려고 했던 파일이 잘 써진 것을 확인할 수 있으며 읽어오는 것도 잘 되고 있는 것을 확인할 수 있다.
C++ 스타일 파일 입출력
C++ 스타일 파일 입출력은 읽어올 때는 ifstream 클래스, 쓸 때는 ofstream 클래스를 이용한다. std안에 존재하며, fstream 헤더에 포함되어 있다. C언어 방식과 비교했을 때 독특한 것은 클래스라는 것이며, 그 의미는 읽고 쓰는 함수는 멤버 함수로 제공된다는 의미이다.
대표적으로 쓰이는 함수들은 아래와 같다.
void open (const char* fileName, ios_base::openmode mode = ios_base::in);
bool is_open() const;
void close();
istream& get (char& c); // 문자 하나씩 읽어오는 함수
istream& getline(char* str, streamsize len); // 한 줄을 읽어오는 함수
ostream& write(const char* str, streamsize n); // 인자 str을 파일에 써주는 함수
쓰임새는 함수 이름에서도 직관적으로 알 수가 있다. 예시 코드로 아래와 같이 작성해보았다.
#include <iostream>
#include <fstream>
int main()
{
std::ofstream write;
write.open("Test.txt");
if (write.is_open())
{
const char str[128] = "가나다라\nABCD";
write.write(str, strlen(str));
}
else
{
std::cout << "file open error" << std::endl;
write.close();
return 0;
}
write.close();
std::ifstream read;
read.open("Test.txt");
if (read.is_open())
{
while (!read.eof())
{
char buf[128] = {};
read.getline(buf, 128);
std::cout << buf << std::endl;
}
read.close();
}
else
{
std::cout << "file open error" << std::endl;
read.close();
return 0;
}
return 0;
}
실제로 getline은 개행 문자를 만나면 거기까지만 읽기 때문에 while문이 두 번 돌면서 읽어오는 모습을 확인할 수 있다.
'공부 > C || C++' 카테고리의 다른 글
C++ 자식 클래스 복사 생성자 초기화 리스트에서 부모의 복사 생성자 호출하는 방법 (0) | 2021.08.17 |
---|---|
C++ return new(*this); 는 복사 생성자를 호출한다 (0) | 2021.08.17 |
C/C++ 함수 포인터를 반환하는 함수 (0) | 2021.08.06 |
C 입력 버퍼 비우기 (0) | 2021.07.30 |
C++ iterator를 reverse_iterator로 변환시 같은 element를 가리키지 않는 이유 (0) | 2021.07.30 |