본문 바로가기

C++

[TIL 15장] 파일 입출력(feat. errno_t, 경로) 및 지원함수 텍스트 모드(.txt) & 바이너리 모드(.dat)

오늘의 TIL 목차 (22.08. 12)

 

  • 파일 입출력
  • 파일 입출력 지원 함수
  • 바이너리 입출력 지원 함수

파일 입출력


[ 파일 입출력 ]

fopen_s(FILE** _Stream, char const* _FileName, char const* _Mode);
fopen_s(파일 포인터의 주소(파일 핸들), 파일 경로, 모드);
FILE* fp = nullptr;
errono_t fInput = fopen_s(&fp, "..\C220812\Text.txt", "wt"); 
// C220812 폴더 안에 텍스트 모드의 쓰기 파일 Text.txt 생성
// errono_t 자료형 fInput에 대입하는 이유는 파일 생성 성공 여부 확인을 위해서

 

▶ errono_t 자료형

: 다양한 함수의 반환 값으로 사용되며 값이 0이면 성공, 값이 0이 아니면 실패를 의미한다. ( C 시절의 bool 대체품형 int )

 

 

▶ 경로

 

  • 절대 경로 : 드라이브 명에서 현재 파일이 있는 위치까지를 표현하는 경로
  • 상대 경로 : 프로젝트 파일을 기준으로 찾고자 하는 파일이 있는 위치까지를 표현하는 경로
☞ 해당 VS 파일을 기준으로 ../ 는 상위 폴더 ./ 는 현재 폴더 / 는 최상위 폴더이다.
D:\Code\P220812
-> D드라이브 > Code > P220812 라는 현재 작업 중인 VS 파일을 기준으로
D:\Code\C220812 의 위치에 Text.txt 파일을 생성하고 싶다면.
..\ 로 상위 폴더인 Code로 나와서 /로 Code 안의 C220812로 접근
-> ..\C220812\Text.txt // C220812 안에 Text 파일 생성

※ 비쥬얼스튜디오의 경우 .vcxproj 파일을 기준으로 상대경로

 

▶ 모드

w (write, 쓰기), r (read, 읽기), a (add, 추가)
t ( text, 텍스트 모드 ), b ( binaray, 바이너리 모드 )
-> 이 두개를 혼용하여 사용한다. ex) wt, rb ,,,

 

▶ 파일 입출력 순서

 

1. 파일을 개방 : 스트림을 생성 -> '파일 핸들'을 개방
2. 파일 쓰기, 읽기
3. 파일을 소멸 - fclose(파일); 필수 

파일 입출력 지원 함수


1. fseek()     2. ftell()     3. feof()

[ fseek() ]

: 커서를 강제를 이동시키는 함수

fseek(파일 스트림, 이동할 바이트 수, 이동할 시작 지점) // int형

 

※ 이동할 시작 지점 ( 에 들어갈 종류 )

  • SEEK_SET (1) : 파일의 처음 위치
  • SEEK_CUR (2) : 현재 파일의 커서 위치
  • SEEK_END (3) : 파일의 끝 위치

[ ftell() ]

: 커서의 위치를 알려주는 함수, 0부터 이동하여 몇 번째 위치에 있는지 인덱스 값을 반환

ftell(파일 스트림); // long형

 

[ feof() ]

: 파일 지시자가 eof에 도달했는지 알려주는 함수. eof에 도달한 경우 0이 아닌 값을 반환 = 0이면 참

if( 0 == feof(fP)) // fp 파일이 eof를 만나지 않았다면 0 반환 , int형

 

■ 예시 

FILE* fpWrite = nullptr;
	FILE* fpRead = nullptr;

	errno_t errWrite = fopen_s(&fpWrite, "../TEXT.txt", "at");
	

	if (0 == errWrite) // 0은 성공적
	{
		cout << "쓰기 파일 개방 성공" << endl; // (파일스트림, 이동할 바이트 수, 시작지점)
		fseek(fpWrite, 0, SEEK_CUR); // 커서의 위치 처음을 옮기기
		cout << ftell(fpWrite) << endl;
        // 커서 위치를 옮겨도 at 추가입력은 뒤에서부터 되는 듯함

		char* szName = new char[25];
		// cin >> szName; // cin 또는 get_s로 입력 시 배열의 크기를 넘어가도 입력되는 오버로드 발생 (오류)
		// fgets는 자동개행 해줌, cin이나 일반 입력 함수는 개행 지원 X
		// fgets는 띄어쓰기까지 인식, cin으로 받는 문자열, 문자배열은 띄어쓰기 전까지 인식
		fgets(szName, 25, stdin); // (배열명(=주소), 크기, 스트림)
		fputs(szName, fpWrite); // fpWrite에 szName 문자열을 출력
		cout << "쓰기 파일 완료" << endl;

		fclose(fpWrite);
	}

	else
		cout << "쓰기 파일 실패" << endl;

	errno_t errRead = fopen_s(&fpRead, "../TEXT.txt", "rt"); // 파일 생성이 되야 읽기로 접근 가능

	if (0 == errRead)
	{
		cout << "파일 읽기 접근" << endl;
		
		
		//char sRead[25] = {};
		//fgets(sRead, sizeof(sRead), fpRead); // fpRead 파일로부터 sizeof만큼 sRead에 읽어옴
		//fputs(sRead, stdout); // 내용이 추가되어도 sRead 크기만큼만 읽어오는 문제 발생
		

		char ch = {};

		while (0 == feof(fpRead)) // eof에 도달한 경우 0이 아닌 값 반환
		{
			ch = fgetc(fpRead); 
			fputc(ch, stdout); // 콘솔에 ch 출력
		}

		cout << endl;
		cout << "파일 읽기 성공" << endl;
		
		fclose(fpRead);
	}

	else
	{
		cout << "읽기 실패" << endl;
	}

☞ EOF를 만날 때까지 while문을 돌려 getc(), putc() 방식을 사용하는 이유 : 단순 fgets, fputs로 파일을 읽어와 출력하면 쓰기 파일에 내용이 추가되었어도 fgets한 문자열의 크기만큼만 읽어와 출력됨

 

파일 입출력 바이너리 모드


[ 파일 입출력 바이너리 ]

: 바이너리 파일 확장자 .dat , 텍스트 파일 확장자 .txt

// 바이너리 출력 함수 :
fwrite(출력할 메모리 시작 주소, 출력할 메모리 크기, 출력할 메모리 개수, 스트림)
// 바이너리 입력 함수 : 
fread(출력할 메모리 시작 주소, 출력할 메모리 크기, 출력할 메모리 개수, 스트림)

-

 

■ 예시 ( TextRPG )

void Save(tObj* _pPlayer) // 플레이어 정보 저장
{
	FILE* fpSave = nullptr;
	errno_t err_Save = fopen_s(&fpSave, "../SaveRPG.dat", "wb");

	if (0 == err_Save) // 파일 성공
	{
		fwrite(_pPlayer, sizeof(tObj), 1, fpSave);
		cout << "저장 성공" << endl;
		fclose(fpSave); // 파일 닫기 필수
	}

	else
	{
		cout << "저장 실패" << endl;
	}

	system("pause");

}

// pPlayer 포인터만 받아오면 pPlayer의 값인 주소 nullptr이 복사되어 nullptr 주소에 값을 넣고 소멸됨
// 이중포인터로 pPlayer 자체의 주소를 가져와서 안의 값 nullptr에 동적할당한 주소를 넣고 값을 변경해줘야됨
void Load(tObj** _pPlayer)  
{
	FILE* fpLoad = nullptr;
	*_pPlayer = new tObj; // 파일을 읽어서 저장하기 위해선 _pPlayer에 메모리 공간 필요(현재 할당 안된 상태)

	errno_t err_Load = fopen_s(&fpLoad, "../SaveRPG.dat", "rb");

	if (0 == err_Load) // 파일 불러오기 성공
	{
		fread((*_pPlayer), sizeof(tObj), 1, fpLoad);
		cout << "불러오기 성공" << endl;
		fclose(fpLoad);
	}

	else
	{
		cout << "불러오기 실패" << endl;
	}

	system("pause");
}