제 27강) C언어의 파일 입출력2 |
파일 입출력의 두번째 시간입니다.
4) 무작위 접근(fseek, ftell) |
먼저 fseek 함수부터 살펴보겠습니다.
원형: int fseek ( FILE * stream, long int offset, int origin );
// 스트림, 위치, 위치가 시작되는
fseek 함수는 해당 스트림의 위치 지정자를 지정된 위치로 옮기는 역할을 하는데요.
이 fseek 함수는 3가지의 인수를 필요로 합니다.
무작위로 접근할 스트림(stream)과 해당 스트림의 접근이 시작될 위치(origin), 그리고 그 위치(origin)로 부터 얼마나 떨어져 있는지에 대한 정도(offset)가 들어가게 됩니다.
반환 값은 성공적으로 접근했을 시 0, 그게 아닐 시에는 0이 아닌 값을 반환합니다.
먼저 예제를 봅시다.
// 예제 출처: http://www.cplusplus.com/reference/cstdio/fseek/
/* fseek example */
#include <stdio.h>
int main()
{
FILE *pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET);
fputs(" sam", pFile);
fclose(pFile);
return 0;
}
(위의 예제는 CPlusPlus.com에서 가져왔습니다.)
8번 줄에서 example 이라는 파일을 이진접근으로 생성합니다.
9번 줄에서 해당 파일에 "This is an apple." 이라는 문장을 넣습니다.
10번 줄에서 현재 열려있는 파일을 SEEK_SET 위치로부터 9만큼 떨어진 위치로 지정자를 조정합니다.
fseek 함수에서 사용하는 시작 위치(origin) |
SEEK_SET : 파일의 맨 앞 SEEK_CUR: 현재 파일 포인터 위치 SEEK_END: 파일의 끝 |
즉, 10번줄을 다시 설명하자면 현재 열려있는 파일을 맨 앞에서 9번째 위치한 지점으로 위치 지정자를 조정합니다.
이때 9번째 위치한 자리란
이 지점을 뜻합니다.
1부터 시작하는 것이 아닌 0부터 시작하므로 배열처럼 주의가 필요합니다.
11번 줄에서 현재 위치(9)에 " sam"을 추가합니다.
그렇게 되면
9번 위치부터 하나씩 새로운 것으로 채우게 되고 파일 위치 지정자는 13에 위치하게 됩니다.
그래서 결과는 다음과 같습니다.
fseek 함수 사용시 주의 사항 |
fseek 함수의 offset 값을 설정시 이진모드로 접근하는 것이 아니라면 조심해야합니다. (즉, 텍스트 모드로 접근 시 가급적 0을 넣어서 사용해야 합니다.) (텍스트 모드에서는 환경에 따라 처리 기본 단위가 달라지기 때문이죠.) |
다음은 ftell 함수를 봅시다.
원형: long int ftell ( FILE * stream );
성공적으로 실행했다면 현재 파일 스트림의 위치 지정자의 위치를 반환합니다.
실패 했다면 -1L을 반환합니다.
(L은 Long형 변수의 값을 뜻합니다.)
바로 위에서 본 예제에 덧붙여 봅시다.
#include <stdio.h>
int main()
{
FILE *pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET);
fputs(" sam", pFile);
long curPosition = ftell(pFile);
printf("현재 위치: %d\n", curPosition);
fclose(pFile);
return 0;
}
8번 줄 까지는 동일합니다.
10번 줄에서 현재 파일 위치 지정자의 위치를 curPosition이라는 Long 형 변수에 넣습니다.
그리고 11번 줄에서 출력합니다.
위의 fseek예제를 볼때 마지막으로 파일 위치 지정자의 위치는 13이라고 했습니다.
정말로 그렇군요.
이 ftell 함수는 fseek의 offset에도 넣어서 사용할 수 있습니다.
단, 가급적 위에서 설명했듯이 이진 접근으로 처리 할 상황에서만 사용하세요.
(즉, 한글 처리같은 것 제외)
5) 이진 파일 입출력(fread, fwrite) |
이 두 함수는 이진 접근할 시에 많이 사용하는 파일 입출력 함수입니다.
원형: size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
데이터 버퍼, 데이터하나의크기, 데이터 개수, 스트림
fread는 데이터 버퍼(ptr)에 데이터 크기에 맞는(size) 데이터를 count개 만큼 stream을 이용하여 읽어들입니다.
반환하는 값은 데이터가 전부 다 read되었다면 그 데이터의 개수(count)를 반환합니다.
원형: size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
데이터 버퍼, 데이터하나의크기, 데이터 개수, 파일 스트림
fwrite는 데이터 버퍼에 있는 size크기의 데이터(ptr) count개를 stream에 씁니다.
반환하는 값은 데이터가 전부 다 write되었다면 그 데이터의 개수(count)를 반환합니다.
예제를 보면서 자세히 익혀봅시다.
#include <stdio.h>
#include <stdlib.h>
typedef struct _POINT
{
char* name;
int xpos;
int ypos;
}POINT;
int main()
{
POINT point = { "point1", 12, 14 };
FILE *pFile = fopen("example.txt", "wb"); // 2진 쓰기 모드
// 파일 스트림(pFile)에 데이터(point) 1개가 POINT 크기로 잘 들어갔는지 판단
if (fwrite(&point, sizeof(POINT), 1, pFile) != 1) // 제대로 들어가지 않았다면
{
printf("에러: 파일 입력이 잘못되었습니다.\n");
exit(1);
}
fclose(pFile); // 쓰기 모드로 열린 파일 스트림 닫기
pFile = fopen("example.txt", "rb"); // 2진 읽기 모드
POINT copyPoint;
// 파일 스트림(pFile)에서 POINT 크기의 데이터 1개를 가져와서 copyPoint에 잘 넣었는지 판단
if (fread(©Point, sizeof(POINT), 1, pFile) == 1) // 제대로 데이터가 잘 들어왔다면
{
printf("읽어온 데이터: %s(%d, %d)\n", copyPoint.name, copyPoint.xpos, copyPoint.ypos);
}
else
{
printf("데이터를 읽어오지 못했습니다.\n");
}
fclose(pFile); // 읽기 모드로 열린 파일 스트림
return 0;
}
위의 예제는 POINT 구조체의 자료를 2진모드로 데이터를 기록하고, 다시 불러오는 예제입니다.
위의 예제에서는 데이터를 1개로 했기 때문에 fread나 fwrite의 count에 1만 들어갔습니다.
만일 배열이라면 어떻게 count를 해야할까요?
#include <stdio.h>
#include <stdlib.h>
#define MAX 3 // 배열의 크기
typedef struct _POINT
{
char* name;
int xpos;
int ypos;
}POINT;
int main()
{
POINT point[MAX] = { {"point1", 12, 14}, {"point2", 24, 11}, {"point3", 5, 114} };
FILE *pFile = fopen("example.txt", "wb");
if (fwrite(point, sizeof(POINT), MAX, pFile) != MAX) // 배열이기 때문에 &생략
{
printf("에러: 파일 입력이 잘못되었습니다.\n");
exit(1);
}
fclose(pFile);
pFile = fopen("example.txt", "rb");
POINT copyPoint[MAX];
if (fread(copyPoint, sizeof(POINT), MAX, pFile) == MAX) // 배열이기 때문에 &
{
for (int i = 0; i < MAX; i++)
{
printf("읽어온 데이터 %d: %s(%d, %d)\n", i + 1, copyPoint[i].name, copyPoint[i].xpos, copyPoint[i].ypos);
}
}
else
{
printf("데이터를 읽어오지 못했습니다.\n");
}
fclose(pFile);
return 0;
}
배열이기 때문에 fwrite와 fread의 count를 해당 배열의 크기로 대체하고 데이터 버퍼를 배열명으로 해주면 됩니다.
6) 텍스트와 이진의 차이 |
바로 위에서 본 fread, fwrite예제를 실행하면 example.txt 파일이 생성됩니다.
그리고 실행해보면
위와 같이 이상하게 저장(이진값)되어 있는 것을 볼 수 있습니다.
하지만 프로그램을 실행했을 때는 결과가 잘 나왔었죠.
그럼 위의 예제를 fprintf, fscanf로 바꾸어봅시다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 3
typedef struct _POINT
{
char name[10];
int xpos;
int ypos;
}POINT;
int main()
{
POINT point[MAX] = { {"point1", 12, 14}, {"point2", 24, 11}, {"point3", 5, 114} };
FILE *pFile = fopen("example2.txt", "w");
for (int i = 0; i < MAX; i++)
{
fprintf(pFile, "%s %d %d ", point[i].name, point[i].xpos, point[i].ypos);
}
fclose(pFile);
pFile = fopen("example2.txt", "r");
POINT copyPoint[MAX];
char tmp[10]; // 문자열(name)을 가져와서 구조체에 넣기위한 임시 문자열 버퍼
for (int i = 0; i < MAX; i++)
{
fscanf(pFile, "%s", tmp); // 처음 문자열(name) 부분을 가져와서 tmp에 저장
strcpy(copyPoint[i].name, tmp); // tmp에 저장된 값을 구조체의 name으로 복사
fscanf(pFile, "%d %d ", ©Point[i].xpos, ©Point[i].ypos); // 나머지 정수 2개를 가져와서 구조체의 xpos, ypos에 저장
printf("읽어온 데이터 %d: %s(%d, %d)\n", i + 1, copyPoint[i].name, copyPoint[i].xpos, copyPoint[i].ypos);
memset(tmp, 0, 10); // 임시 문자열 버퍼를 초기화
}
fclose(pFile);
return 0;
}
이진 접근과 비교하기 위해서 파일 이름을 example2로 하였습니다.
이제 이진 접근으로 생성된 파일과 텍스트 접근으로 생성된 두 파일을 비교해봅시다.
텍스트는 우리가 읽을 수 있지만 이진은 쉽게 읽을 수 없습니다.
이진 접근과 텍스트 접근은 이러한 차이가 있습니다.
7) feof와 ferror |
먼저 feof는
원형: int feof ( FILE * stream );
현재 파일 스트림(stream)의 위치 지정자가 파일의 끝(EOF)에 있는가 유무를 판단합니다.
위치 지정자가 EOF라면 0이 아닌 값을, EOF가 아니라면 0을 반환합니다.
ferror는
원형: int ferror ( FILE * stream );
파일 입출력 함수 사용중 에러가 발생한 경우를 판단합니다.
에러가 발생하였다면 0이 아닌 값을, 에러가 발생하지 않았다면 0을 반환합니다.
다음 시간에는 |
지난 시간에 이어서 이번 시간에도 역시 파일 입출력에 대해서 알아보았습니다.
지난 시간과 이번 시간의 내용을 적절히 익혀서 파일 입출력을 수월하게 해보세요.
다음 시간에는 전처리기에 대해서 알아봅시다.
'Study > C언어' 카테고리의 다른 글
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 29 [소스파일의 분할(헤더파일)] (0) | 2018.10.28 |
---|---|
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 28 [전처리기(include, define, ifdef)] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 23 [메모리 구조와 메모리 할당, 배열 동적할당(malloc, realloc, calloc, free)] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 24 [구조체와 형식 정의 지정자(struct, typedef)] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 25 [공용체와 열거형(union, enum)] (0) | 2018.10.28 |