파일 입출력 기능을 추가한 도서 관리 프로그램 개선 4탄.
이전까지는 내가 프로그램을 켜고 도서 추가/삭제를 해도 끄고 나서 프로그램을 다시키면 여전히 내가 소스파일에 입력해놓은 초기데이터가 보였었다.
하지만 나는 프로그램 실행중에 데이터를 가공하고 껐을 때 그 데이터가 어딘가 잘 저장되어 있다가 다시 키면 읽어오는 방식으로 연속적으로 관리하고 싶어졌다.
결과물 투척
파일 읽기
FILE* fp = NULL;
errno_t err = fopen_s(&fp, "booklist.bin", "rb");
if (err == 0) {
fseek(fp, 0, SEEK_END); // 파일 포인터를 파일의 끝으로 이동시킴
size_t fileSize = ftell(fp); // 파일 포인터의 현재 위치를 얻음
rewind(fp); // 파일 포인터의 위치를 다시 맨앞으로 보냄
for (;;) {
if (fileSize > sizeof(_stBook) * listSize) { // 파일 사이즈가 구조체 배열 사이즈보다 크다면 10씩 더 메모리 확대 확보
expandM(&booklist);
}
else if (fileSize <= sizeof(_stBook) * listSize) break;
}
//파일 읽어들이기
fread((void *)booklist, sizeof(_stBook), fileSize/sizeof(_stBook), fp);
fclose(fp); // 파일 스트림 닫기
}
else {
printf("파일오픈 실패\n");
printf("에러코드 : %d\n", err);
system("pause");
}
- FILE 포인터를 만들고 NULL 값 대입
- fopen_s 으로 입력 스트림 열기. 그냥 fopen 이랑은 사용법이 좀 다르다.
fopen_s(FILE **_Stream, const char *_FileName, const char *_Mode)
fopen_s의 원형(파일포인터의 주소, "파일명", "개방모드")
- fopen은 리턴값을 파일포인터에 대입하는데, fopen_s는 인수에 이미 파일포인터의 주소를 넘겨주고 리턴값은 성공여부에 따라 성공했으면 0, 실패했으면 오류코드를 반환한다. 반환값을 errno_t 변수에 담는데 이건 이런 오류코드들을 담기위해 VS에서 제공하는 자료형 타입이라고 한다.
- 파일의 개방모드는 "rb" 바이너리 파일 읽어오기 모드이다.
- 그다음 딱 파일사이즈만큼만 읽어오기 위해 먼저 파일사이즈 구해보겠다. 파일을 열었을 때는 파일포인터가 맨앞을 가리키고 있다. fseek(fp, 0, SEEK_END); 로 파일의 끝으로 파일 포인터를 이동시킨 후 0바이트 움직인다. ftell(fp); 는 파일포인터의 현재위치를 반환하는데 이걸로 파일사이즈를 알 수 있다. (ex. 파일크기가 560바이트라면 560을 반환한다.)
- rewind(fp)로 파일포인터의 위치를 다시 맨앞으로 보내줘야 이후 읽어오는데 문제가 없다!
- 파일을 읽기 전에 만약 파일사이즈가 구조체배열크기보다 크다면 배열사이즈를 키워 메모리를 재할당 하도록 한다.
- fread로 .bin 파일 읽어오기. 파일에서 size * count 바이트만큼 읽어온다. 그리고 반환값은 읽어오는데 성공한 count 개수이다. 만약 4 byte, 10 count였을 경우 10개 다 읽어오는 데 성공했으면 10반환, 하나도 못읽어왔으면 0반환 이런식이다. 나는 여기서 반환값을 활용하진 않았다.
fread(void *_Buffer, size_t _ElementSize, size_t _ElementCount, FILE *_Stream)
fread 함수의 원형((void *형변환)읽어올 값을 저장할 곳의 주소, size, count, 파일포인터)
- fopen으로 파일스트림을 열고 나서 fclose 를 까먹으면 계속 컴퓨터자원 을 사용하므로 반드시 fclose로 닫아주자.
참고 : 파일의 개방모드
b를 붙이면 바이너리파일 모드이고 안붙이면 text파일 모드이다.
참고. 주요 모드
Mode | Description | 파일이 없는 경우 |
r | 읽기 | 에러 |
w | 쓰기 | 생성 |
a | 파일의 끝에 덧붙여 쓰기 | 생성 |
r+ | 읽기/쓰기 가능 | 에러 |
w+ | 읽기/쓰기 가능 | 생성 |
a+ | 읽기/덧붙여 쓰기 가능 | 생성 |
파일의 개방 모드 중 r+, w+, a+ 등은 읽기/쓰기가 동시에 가능하지만 읽기에서 쓰기, 쓰기에서 읽기로 작업을 변경할 때마다 메모리 버퍼를 비워줘야 하는 불편함과 더불어 사용상 잘못될 위험이 따르기 때문에 r, w, a 중 하나를 선택하여 파일 스트림을 형성하는 것이 좋다.
참고. fseek 함수의 두번째 매개변수
SEEK_SET | 파일의 처음부터 | fseek(fp, 0, SEEK_SET); // 파일 포인터를 파일의 처음으로 이동 |
SEEK_CUR | 현재 위치부터 | fseek(fp, -10, SEEK_CUR); // 파일 포인터를 현재 위치에서 10바이트만큼 역방향으로 이동 |
SEEK_END | 파일의 끝부터 | fseek(fp, 0, SEEK_END); // 파일 포인터를 파일의 끝으로 이동 |
다음 main, while문 내에서 종료버튼을 눌렀을 때 실행되는 파일 출력 부분이다.
파일 쓰기
if (menu == 6) { // 종료 버튼
FILE* fp;
errno_t err = fopen_s(&fp, "booklist.bin", "wb"); // 파일 출력스트림 개방
if (err == 0) {
fwrite((void*)booklist, sizeof(_stBook), datanum, fp); // 파일 출력
fclose(fp); // 파일 스트림 닫기
}
else {
printf("파일저장 실패\n");
printf("에러코드 : %d\n", err);
}
break;
}
- 파일포인터 만들고 NULL 대입
- fopen_s 호출. "wb" 바이너리파일 쓰기 모드로 파일 출력스트림 개방
- fwrite함수로 파일 쓰기. fwrite((void*)booklist, sizeof(_stBook), datanum, fp); booklist 주소에 있는 값을 sizeof(_stBook) * datanum 개수 byte만큼 fp 포인터가 가리키는 파일에 쓴다.
그리고 ㅂ 특수문자를 사용해서 메뉴판을 좀더 예쁘게 바꿀 수 있다.
printf("┌────────────────────────────────────────────────────────────────────────────┐\n");
#ifdef DEBUG
printf("│ ~ 테스트용 도서추가버튼 : 7, 도서삭제버튼 : 8 ~ │\n");
printf("│ │\n"); // 구조체배열 사이즈 출력
printf("│ (현재 할당된 메모리크기 / 구조체크기 : %2d) │\n", _msize(list) / sizeof(_stBook)); // _msize 힙에할당된 메모리크기 반환 함수, MSVS 전용
#endif // DEBUG
printf("│ │\n");
printf("│ 도서 검색 프로그램 │\n");
printf("│ (현재 데이터 개수 : %2d) │\n", num);
printf("│ │\n");
printf("│───────────────────────────────────MENU─────────────────────────────────────│\n");
printf("│ │\n");
printf("│ 1. 도서 추가 │\n");
printf("│ │\n");
printf("│ 2. 도서 삭제 │\n");
printf("│ │\n");
printf("│ 3. 도서 정렬 │\n");
printf("│ │\n");
printf("│ 4. 도서 검색 │\n");
printf("│ │\n");
printf("│ 5. 도서 확인 │\n");
printf("│ │\n");
printf("│ 6. 종료 │\n");
printf("│ │\n");
printf("└────────────────────────────────────────────────────────────────────────────┘\n\n");
파일입출력 기능 추가 끝!
중간에 파일쓰기 기능이 갑자기 안되서 원인을 찾느라 고생했는데 알고보니 내가 파일읽기 부분에서 이것저것 수정하다가 fclose를 주석처리 해버린 탓이었다.ㅜㅜ
참고로 인터넷에 찾아보니 fopen_s는 파일을 한번만 열 수 있어서 여러번 열어서 읽고 쓰고 하려면 _fsopen함수를 쓰라고 하더라. 나같은 경우 위에선 rb 읽기만 아래에선 wb 쓰기모드로만 열어서 fopen_s를 두 번 사용해도 문제가 없었던거 같다.
후기.
끝. 기능적인 부분은 다 완성했는데 구조체배열을 링크드리스트로 바꿔보고 싶긴하다. 다만 기존에 인덱스로 쉽게 사용하던 부분을 전부 갈아엎어야해서 뭔가 대공사가 될거 같으니 시간을 두고 해보도록하겠다. 어쩌면 이게 마지막편이 될수도 있다!
Array List 배열 |
인덱스를 통해 원하는 요소에 바로 접근할 수 있어 탐색이 빠르다. 사용되지 않는 요소만큼의 메모리의 낭비가 있다. 요소 중간에 삽입/삭제를 할 때 데이터를 한 칸씩 밀거나 땡겨야 해서 번거롭다. |
Linked List 링크드 리스트 |
크기가 고정적이지 않아서 메모리 사용이 효율적이다 삽입, 삭제가 용이하다 맨앞 노드부터 순차적으로 접근해야해서 검색이 느리다. 알고리즘이 배열보단 복잡하다. |
다음편 :
'프로그래밍 > C' 카테고리의 다른 글
scanf , scanf_s " %d "로 정수 입력 받으려다 문자(열)이 잘못 들어왔을 때 무한루프에 빠지는 문제 / 문장 씹히는 문제 해결 (0) | 2022.04.27 |
---|---|
[ C언어 ] enum 의 활용 + 사용예제 (0) | 2022.04.26 |
size_t 와 unsigned int 형의 차이 (0) | 2022.04.24 |
[ C언어 ] 프로젝트(삽질일기) : 도서 관리 프로그램 - (2) - 동적 메모리 할당 (0) | 2022.04.22 |
[ C언어 ] 프로젝트(삽질일기) : 도서 관리 프로그램 - (3) - 동적 메모리 할당 - 구조체 배열 크기 동적 조절 ( 확대 / 축소 ), #ifdef DEBUG 사용 (0) | 2022.04.22 |