본문 바로가기
휴지통/C 언어

파일입출력2

by 신재권 2021. 2. 7.

int fputc(int c, FILE *stream); //문자 출력

int fgetc(FILE *stream); //문자 입력

int fputs(const char* s, FILE *stream);  //문자열 출력

char * fgets(char* s, int n, FILE *stream);  //문자열 입력

 

예전에는 stream부분에 표준 입출력을 의미하는 stdin이나 stdout을 인자로 전달함으로써 키보드와 모니터를 대상으로 입출력을 진행하였는데, 매개변수의형이 의미하듯이 매개변수 stream에 FILE 구조체 포인터를 인자로 전달하여 파일대상의 입출력을 진행할 수 있다. 

#include <stdio.h>

int main()
{
	FILE *fp = fopen("simple.txt", "wt");
    if(fp==NULL)
  	{
    	puts("파일 오픈 실패");
        return -1;
    }
    
    fputc('A', fp);
    fputc('B', fp);
    fputs("My name is Hong \n", fp);
    fputs("Your name is Yoon \n", fp);
    fclose(fp);
    return 0;
}

이렇게 코드를 입력하면 프로젝트 폴더 파일에 simple.txt파일이 생성된다 .

쓰기 모드로 개방하였으니 

당연히 코드를 통해 파일에 데이터를 쓸 수 있다. 

 

이어서 읽어오는 코드도 작성한다.

 #include <stdio.h>
 
 int main()
 {
 	char str[30];
    int ch;
    FILE *fp = fopen("simple.txt", "rt");
    if(fp==NULL)
    {
		puts("파일 오픈 실패");
        return -1;
    }
 
 	ch = fgetc(fp);
    printf("%c \n", ch);
    ch = fgetc(fp);
    printf("%c \n", ch);
    
    fgets(str, sizeof(str), fp);
    printf("%s", str);
    fgets(str, sizeof(str), fp);
    printf("%s", str);
    
    fclose(fp);
    return 0;
}
============================실행결과================================
A
B
My name is Hong
Your name is Yoon

위 예제는 읽어 오는 예제인데, 따로 파일의 경로를 지정안했으니 위에서 만들었던 쓰는 예제랑 같은 폴더에 있어야 simple.txt 파일을 정상적으로 찾을 수 있다.

 

위 둘의 예제에서 주목할 사실은 , 첫번째 예제인 쓰는 함수에서 문자열의 형태에 마지막에 \n문자가 존재한다는 것이다.

문자열이 파일에 저장될 때에는 문자열의 끝을 의미하는 널 문자는 저장되지 않는다. 때문에 파일에서는 개행을 기준으로 문자열을 구분하낟. 즉 읽어오는 예제에서는 총 두개의 문자열을 읽어 들이기 위해서 fgets 함수를 두 번 호출했는데, 매번 호출이 될 때마다 개행 문자를 만날 때 까지 문자열을 읽어들이게 된다. 

따라서 fgets 함수의 호출을 통해서 읽어 들일 문자열의 끝에는 반드시 \n문자가 존재해야 한다.

 


때로는 파일의 마지막에 저장된 데이터까지 모두 읽어들여야 하는 상황이 존재한다. 그리고 이를 위해서는 파일의 끝을 확인하는 방법이 필요하다.

#include <stdio.h>

int feof(FILE *stream);

-> 파일의 끝에 도달한 경우 0이 아닌 값 반환

 

이 함수는 인자로 전달된 FILE구조체의 포인터를 대상으로 , 더 이상 읽어 들일 데이터가 존재하지 않으면(파일을 끝까지 모두 읽어들인 상태이면) 0이 아닌 값을 반환한다. 따라서 다음의 파일 복사 프로그램과 같이 파일의 끝을 확인하는 경우에 유용하게 사용이 된다.

 

#include <stdio.h>

int main()
{
	FILE * src = fopen("src.txt", "rt");
    FILE * des = fopen("dst.txt", "wt");
    int ch;
    
    if(src ==NULL || des ==NULL){
    	puts("파일 오픈 실패!");
        return -1;
    }
    
    while((ch=fgetc(src))!=EOF)
    	fputs(ch, des);
        
    if(feof(src)!=0)
    	puts("파일 복사 완료 !");
    else
    	puts("파일 복사 실패 !");
   
   	fclose(src);
    fclose(des);
    return 0;
}

위의 예제는 문자단위로 복사하는 예제이다 .

 

이어서 문자열 단위로 복사를 하는 예제를 작성한다.

#include <stdio.h>

int main()
{
	FILE* src =fopen("src.txt", "rt");
    FILE* des =fopen("des.txt", "wt");
    char str[20];
    
    if(src==NULL || des==NULL){
    	puts("파일 오픈 실패");
        return -1;
    }
    
    while(fgets(str, sizeof(str), src)!=NULL)
    	fputs(str, des);
        
   	if(feof(src)! =0)
    	puts("파일 복사 완료!");
    else 
    	puts("파일 복사 실패!");
        
    fclose(src);
    fclose(des);
    return 0;
}

fgets 함수는 파일의 끝에 도달해서 더 이상 읽을 데이터가 존재하지 않거나 오류가 발생하는 경우에 NULL을 반환한다. 

때문에 파일복사의 성공을 확인하기 위해서 feof함수를 호출하였다.


바이너리 데이터의 입출력 

지금까지 설명한 입출력 함수들은 텍스트 데이터의 입출력을 진행하는 함수들 이었다. 반면에 이번에 설명하는 함수들은 바이너리 데이터의 입출력을 진행하는 함수들이다. 참고로 바이너리 데이터의 입출력은 텍스트 데이터의 입출력보다 단순하기 때문에 다양한 함수들을 제공하지 않는다. 

 

바이너리 데이터 입력에 사용되는 fread함수

#include <stdio.h>

size_t fread(void *buffer, size_t size, size_t count, FILE *stream);

-> 성공시 전달인자 count, 실패 또는 파일의 끝 도달 시 count보다 작은 값 반환

 

위 함수는 다음과 같이 호출이 된다.

int  buf[12];

fread((void*)buf, sizeof(int), 12, fp);

위의 호출문은 

"sizeof(int)크기의 데이터 12개를, fp로부터 읽어 들여서 배열 buf에 저장해라" 라는 뜻이다.

 

즉 fread함수는 두 번째 전달인자와 세 번째 전달인자의 곱의 바이트 크기만큼 데이터를 읽어 들이는 함수이다. 따라서 위의 fread 함수 호출을 통해서 int형 데이터 12개를 fp로 부터 읽어서 배열 buf에 저장하게 된다.

 

그리고 이 함수는 실제로 읽어들인 데이터의 갯수를 반환하는데(읽어 들인 바이트 수가 아니라 갯수), 위 문장은 sizeof(int) 크기의 데이터 12개를 읽어 들이는 경우이니, 함수의 호출이 성공을 하고 요청한 분량의 데이터가 모두 읽혀지면 12가 반환된다. 

반면 함수에 호출이 성공했지만 파일의 끝에 도달해서 12개를 모두 읽어 들이지 못했거나 오류가 발생하는 경우에는 12보다 작은 값이 반환된다.  

 

 

바이너리 데이터 출력에 사용되는 fwrite함수

#include <stdio.h>

size_t fwrite (const void * buffer, size_t size,  size_t count, FILE *stream):

->성공시 전달인자 count가, 실패시 count보다 작은 값 반환

 

int buf[7] = {1,2,3,4,5,6,7};

fwrite((void*)buf, sizeof(int), 7, fp);

위의 호출문은

"sizeof(int)크기의 데이터 7개를 buf로 부터 읽어서 fp에 저장해라"

 

위의 두함수를 이용해서 바이너리 파일을 복사하는 프로그램을 작성하겠다.

#include <stdio.h>

int main()
{
	FILE *src =fopen("src.bin", "rb");
    FILE *des =fopen("dst.bin", "wb");
    char buf[20];
    int readCnt;
    
    if(src ==NULL || des==NULL){
    	puts("파일 오픈 실패!");
        return -1;
    }
    
    while(1)
    {
    	readCnt = fread((void*)buf, 1, sizeof(buf), src);
        
        if(readCnt <sizeof(buf))
        {
        	if(feof(src)!=0)
            {
            	fwrite((void*)buf, 1, readCnt, des);
                puts("파일 복사 완료");
                break;
            }
            else
            	puts("파일복사 실패!);
               
           break;
    	}
        fwrite((void*)buf, 1, sizeof(buf), des);
 	}
 
	fclose(src);     
    fclose(des);
	return 0;
}

 

readCnt = fread((void*)buf, 1, sizeof(buf),src);

"src에서 1크기의  sizeof(buf)만큼 데이터를 읽어 들여 buf배열에 저장하는 뜻"

즉 sizeof(buf) x 1 만큼의 바이트 크기를 읽어들인다.

 

if(readCnt<sizeof(buf)) 

이문은 fread 함수가 sizeof(buf)의 반환 값보다 작은 값을 반환했을 때 참이 된다. 그런데 이는 오류가 발생했거나, 파일의 끝에 도달한 상황이니 feof함수의 호출을 통해서 어떠한 상황인지 판단해야 한다.

 

feof가 있는 if문 부터는 sizeof(buf)의 반환 값보다 적은 수의 바이트가 읽혀졌을 때 실행되는 영역이다. 비록 적은 수의바이트가 읽혀졌다 했을지라고 이 역시 파일의 일부이므로 fwrite 함수 호출을 통해서 복사를 해야한다. 그래서 23행에서 마지막 데이터를 출력하고 있다.

 

마지막 fwrite에서는 읽어들인 , 배열 buf를 꽉 채운 데이터를 그대로 파일에 저장하고 있다.

 


하나의 파일을 대상으로 입출력할 데이터가 텍스트 데이터와 바이너리 데이터 둘로 이뤄져있다면 어떻게하는가?

 

서식에 따른 입출력 : fprintf, fscanf

 

두 개의 텍스트와 하나의 바이너리 데이터를 입출력 해야 하는 상황에서 제일 먼저 생각할 수 있는 것은 fscanf와 fprintf함수의 호출이다. 이 두함수는 scanf와 printf함수와 유사하다. 다만 입출력의 대상이 콘솔이 아닌 파일이라는 점에 차이가 있다. 

 

char name[10]= "홍길동" //텍스트 데이터

char sex = 'M' //텍스트 데이터

int age =24;  //바이너리 데이터

fprintf(fp, "%s %c %d", name, sex, age);

 

위의 fprintf 함수의 호출문이 printf함수의 호출문과 차이를 보이는 부분은 FILE 구조체의 포인터가 첫 번째 전달인자라는 점이다. 그래서 printf함수와 달리 fprintf 함수는 첫 번째 인자로 잔달된 FILE  구조체의 포인터가 지칭하는 파일로 출력이 이뤄진다. 위의 함수 호출문으로인해

"홍길동 M 24"

라는 문자열이 만들어진다. 

이렇게 만들어진 문자열이 첫 번째 전달인자가 가리키는 파일에 저장이 된다 .결국 텍스트 데이터와 바이너리 데이터를 하나의 문자열로 묶어서 저장하는 것이다. 

 

#include <stdio.h>

int main()
{
	char name[10];
    char sex;
    int age;
    
    FILE * fp = fopen("friend.txt", "wt");
    int i;
    
    for(i=0; i<3; i++)
	{
    	printf("이름 성별 나이 순 입력 : ");
        scanf("%s %c %d", naem &sex, &age);
        getchar();		//버퍼에 남아있는 \n의 소멸
        fprintf(fp, "%s %c %d", name, sex, age);
 	}
    fclose(fp);
    return 0;
}
===========================실행결과===============================
이름 성별 나이 순 입력 : 홍길동 M 22
이름 성별 나이 순 입력 : 김길순 M 27
이름 성별 나이 순 입력 : 이순자 F 32

fprintf함수는 printf함수와 마찬가지로 문자열을 구성하여 출력하는 함수이다. 따라서 텍스트 파일로 개방을해야한다.

scanf함수는 엔터키의 입력을 읽어 들이지 않고 입력버퍼에 남겨둔다. 따라서 입력버퍼에 남아있는 엔터키를 소멸하기 위해 getchar() 함수를 호출했다.

 

실행 후 입력을 통해 fp가 지칭하는 파일로 출력을 한다.

 

위의 예제를 실행후 생성되는 텍스트 파일을 보면 , 입력한 정보가 저장되어 있는 것을 확인할 수 있다.

 

fprintf함수의 호출을 통해서 저장된 데이터는 , 동일하게 서식을 지정해서 fscanf 함수의 호출을 통해서 읽어 들일 수 있다.

 

char name[10];

char sex;

int age;

fscanf(fp, "%s %c %d", name, &sex, &age);

 

위의 fscanf함수 호출문이 scanf함수 호출문과 차이를 보이는 부분은 첫 번째 인자로 FILE 구조체의 포인터가 전달된다는 것이다. 따라서 scanf 함수와 달리 fscanf 함수는 첫 번째 인자로 전달된 포인터가 지칭하는 파일로부터 데이터를 읽어 들인다. 그리고 fscanf함수는 파일의 끝에 도달하거나 오류가 발생하면 EOF를 반환한다. 

 

#include <stdio.h>

int main()
{
	char name[10];
    char sex;
    int age;
    
    FILE *fp = fopen("friend.txt", "rt");
    int ret;
    
    while(1)
    {	
    	ret= fscanf(fp, "%s %c %d", name, &sex, &age);
        if(ret==EOF)
        	break;
       	printf("%s %c %d \n", name, sex, age);
    }
    fclose(fp);
 	return 0;
}

이 예제를 위에 예제에 있는 폴더랑 같은 곳에서 실행 시키면 fscanf함수는 txt파일에 저장되어 있는 문자열을 입력받아 프로그램에 저장한다.

 


텍스트와 바이너리 데이터의 집합체인 구조체 변수의 입출력도 가능하다.

 

실제 프로그램에서는 위의 예제를 구조체로 묶어서 정의하는 것이 보통이다. 때문에 구조체 변수 단위로의 파일 입출력에 대해 알아야 한다.

typedef struct fren

  char name[10];

  char sex;

  int age;

}friend;

 

구조체 변수 단위의 입출력이  구조체 멤버 단위의 입출력보다 입출력의 형태가 단순해 질 수밖에 없다.

 구조체 변수를 하난의 바이너리 데이터로 인식하고 처리하면 구조체 통째로 읽어들일 수 있고 저장할 수 있다.

그러면 frwite 함수를 통해서 통째로 저장혹, fread함수를 통해서 통째로 복원할 수 있다.

#include <stdio.h>
typedef struct fren
{
	char name[10];
    char sex;
    int age;
}Friend;

int main()
{
	FILE *fp;
    Friend myfren1;
    Friend myfren2;
    
    //file write
    fp = fopen("friend.bin", "wb");
    printf("이름 , 성별, 나이 순 입력 : ");
    scanf("%s %c %d", myfren1.name, &(myfren1.sex), &(myfren.age));
    fwrite((void*)&myfren1, sizeof(myfren1), 1, fp);
    fclose(fp);
    
    //file read
    fp= fopen ("friend.bin", "rb");
    fread((void*)&myfren2, sizeof(myfren2),1,fp);
    printf("%s %c %d \n", myfren2.name, myfren2.sex, myfren2.age);
    fclose(fp);
    return 0;
}
===========================실행 결과===========================
이름, 성별, 나이 순 입력 : Jungs M 27
Jungs M 27

경우에 따라서 파일의 중간 또는 마지막 부분에 저장된 데이터의 일부를 읽어야 하는 경우도 있따. 그리고 이러한 경우에는 '파일 위치 지시자'라는 것을 파일의 중간 또는 마지막 부분으로 이동시켜야 한다.

 

FILE 구조체의 멤버 중에는 파일의 위치 정보를 저장하고 있는 멤버가 있다. 이 멤버의 값은 fgets, fputs 또는 fread, fwrite와 같은 함수가 호출 될 떄마다 참조 및 갱신된다. 

 

예를 들어 fgets 함수호출을 통해서 파일에 저장된 문자열을 읽어 들이는 경우, 이 멤버가 가리키는 위치를 시작으로 문자열을 읽어 들이게 되며, 총 20바이트 크기의 문자열이 읽혀졌다고 가정하면, 이 멤버는 20바이트 뒤를 가리키게 된다. 이처럼 이 멤버에 저장된 위치정보의 갱신을 통해서 데이터를 읽고 쓸 위치 정보가 유지된다.

이멤버를 가리켜 '파일 위치 지시자'라 한다.

 

'파일 위치 지시자'는 파일이 처음 개방되면 무조건 파일의 맨 앞부분을 가리킨다. 

따라서 파일의 중간 혹은 마지막 부분에서부터 데이터를 읽거나 쓰기를 원한다면 '파일 위치 지시자'를 이동시켜야 한다.

 

파일 위치 지시자의 이동

#include <stdio.h>

int fseek(FILE * stream, long offset, int wherefrom);

->성공시 0, 실패시 0이 아닌값을 반환

 

 

위의 함수는 총 세개의 인자를 요구하는데, 인자가 의미하는 바는

stream 으로 전달된 파일 위치 지시자를 wherefrom에서부터 offset 바이트 만큼 이동시켜라. 이다.

매개변수 wherefrom이 파일 위치 지시자는 ...
SEEK_SET(0) 이라면 파일 맨 앞에서부터 이동을 시작
SEEK_CUR(1) 이라면 현재 위치에서부터 이동을 시작
SEEK_END(2) 이라면 파일의 맨 끝에서부터 이동을 시작

그리고 매개변수 offset에는 양의 정수뿐만 아니라 음의 정수도 전달될 수 있다. 양의 정수가 전달되면 팡리의 마지막을 향해서 파일 위치 지시자가 이동을 하지만, 음의 정수가 전달되면 파일의 시작 위치를 향해서 파일 위치 지시자가 이동을 한다.

 

SEEK_SET 전달 시 첫 번째 바이트에서부터 이동을 시작한다

SEEK_END 전달 시  EOF에서부터 이동을 시작한다.

fseek 함수의 두번째 인자로 음수가 전달되면 왼쪽으로 앞 부분으로 이동을 한다.

 

일반적으로 SEEK_END 가 전달되면 파일의 끝에서부터 이동이 시작된다고 한다. 여기서 말하는 파일의 끝은  파일의 마지막 데이터가 아닌, 파일의 끝을 표시하기 위해서 삽입이 되는 EOF를  의미함에 주의를 해야한다.

 

#include <stdio.h>

int main()
{
	//파일 생성
    
    FILE *fp= fopen("text.txt", "wt");
    fputs(123456789", fp);
    fclose(fp);
    
    //파일 개방
    
    fp= fopen("text.txt" , "rt");
    
    //SEEK_END test
    
    fseek(fp, -2, SEEK_END);
    putchar(fgets(fp));
    
    //SEEK_SET test
    
    fseek(fp, 2, SEEK_SET);
    putchar(fgetc(fp));
    
    //SEEK_CUR test
    
    fseek(fp, 2, SEEK_CUR);
    putchar(fgetc(fp));
    
    fclose(fp);
    return 0;
}
========================실행결과===========================
836

현재 파일위치 지시자의 정보를 확인하고 싶다면 다음 함수를 호출한다.

#include <stdio.h>

long ftell(FILE * stream);

->파일 위치 지시자의 위치정보 반환

 

이 함수는 파일 위치 지시자의 위치 정보를 반환하는데, 파일 위치 지시자가 첫 번째 바이트를 가리킬 경우 0을 반환하고, 세 번째 바이트를 가리킬 경우 2를 반환한다. 이처럼 가장 앞 부분의 바이트 위치를 0으로 간주한다는 점을 주의해야한다.

 

 

#include <stdio.h>

int main()
{
	long fpos;
    int i;
    
    //파일 생성
    FILE *fp = fopen("text.txt", "wt");
    fputs("1234-", fp);
    fclose(fp);
    
    //파일 개방
    fp =fopen("text.txt", "rt");
    
    for(i=0; i<4; i++)
    {
		putchar(fgetc(fp));
        fpos= ftell(fp);
        fseek(fp, -1, SEEK_END);
        putchar(fgetc(fp));
        fseek(fp, fpos,SEEK_SET);
   	}
    fclose(fp);
    return 0;
}
==========================실행 결과=======================
1-2-3-4-

ftell 함수는 파일 위치 지시자의 정보를 임시로 저장할 때 유용하게 사용된다.

'휴지통 > C 언어' 카테고리의 다른 글

C언어 함수들1  (0) 2021.02.11
메모리 관리와 동적할당  (0) 2021.02.10
파일 입출력1  (0) 2021.02.06
구조체  (0) 2021.02.04
문자열 입력 함수  (0) 2021.02.02