Programming/C C++
1. File처리 기본

C에서 File에 접근하기 위해서는 우선 fopen함수를 통하여 다음과 같이 open해야 합니다.

fopen("file명", "mode");

fopen함수에서의 첫번째 인수는 열고자 하는 File명을 그리고 두번째 인수에서 해당 File을 어떤 상태로 open할 것인지 지정합니다.

C에서 File을 open하면 해당 File의 제어권을 File Pointer로 넘기게 되므로 open한 이후의 모든 작업을 File Pointer로 하게 됩니다. 따라서 File을 open하기전 미리 File Pointer가 선언되어 있어야 히고 선언된 File Pointer를 위에서 언급한 fopen함수와 같이 쓰는형태를 취하게 됩니다.

FILE *fl

file pointer를 선언하였습니다. 이때 Pointer이름은 개발자가 임의로 정합니다.

fl = fopen("file명", "mode");

fopen함수를 통해 File을 mode상태로 열고 제어권을 File Pointer인 fl에 넘김니다.

File을 open할때 어떤 형태로 open할것인가는 mode에서 지정한다고 하였습니다. 아래는 해당 mode의 종류와 그 기능을 설명한 표입니다.

 mode  기능
 r  읽기전용으로 Open
 w  쓰기용으로 Open(지정한 File이 존재하지 않을 경우 해당 File명으로 새로운 File을 생성합니다.)
 a  수정(추가)용으로 Open(지정한 File이 존재하지 않을 경우 해당 File명으로 새로운 File을 생성합니다.)
 rb  Binary형태의 File읽기
 wb  Binary형태의 File쓰기
 ab  Binary형태의 File추가
 r+  읽고 쓰기
 W+  쓰고 읽기

해당 mode에 따라 만일 aaa.txt라는 File을 읽기 전용으로 열고자 한다면 다음과 같이 쓸 수 있을 것입니다.

fl = fopen("aaa.txt", "r");

fopen을 통해 File을 open한뒤 원하는 동작을 수행하고 나서는 File을 닫아야 합니다.(File을 닫지않고 Program을 끝내버려도 대부분의 경우 문제가 없지만 그다지 권장할 만한 방법은 아닙니다.)

File을 닫는데는 fclose함수가 쓰입니다.

fclose(file pointer);

fclose의 인수로는 fopen으로 열려진 File의 제어권을 갖고있는 Pointer를 넘겨줍니다.

2. File의 조작함수

fopen으로 File을 open하면 해당 File을 읽거나 쓰는등의 처리가 필요할 것입니다. C에서는 이를 위해 File전용으로 읽기/쓰기를 위한 함수가 별도로 마련되어 있습니다.

 함수(함수의 필요 인수)  기능
 fgetc(file pointer)  file에서 한문자 읽기
 fputc("한문자", file pointer)  file에서 한문자 쓰기
 fgets(문자열 저장변수, 읽을 문자 수, file pointer)  file에서 문자열 읽기
 fputs("문자열", file pointer)  file에서 문자열 쓰기
 fprintf(file pointer, 쓸내용, 전달인수)  file 읽기
 fscanf(file pointer, 읽을형태, 전달인수)  file 쓰기

위 함수를 사용하여 직업 File을 다루어 보겠습니다.

3. File 한문자 읽기

먼저 hello!라는 내용을 작성된 aaa.txt File을 만듭니다.


위에서 저장한 aaa.txt 이름의 Text File을 읽기위해 다음과 C Program을 작성합니다.

#include <stdio.h>

main()
{
  FILE *fl;
  char s;
 
  if ((fl = fopen("aaa.txt", "r")) != NULL){
    while((s = fgetc(fl)) != EOF)
      printf("aaa.txt file의 내용 %c\n", s);
  }
 
  fclose(fl);
}


FILE *fl 은 fl이라는 File Pointer를 선언함을 의미합니다. 그리고 char s을 통해 File로부터 읽은 내용을 저장할 변수를 선언합니다. 이때 if ((fl = fopen("aaa.txt", "r")) != NULL)라고 한 이유는 fopen함수를 통해 r을 지정하여 해당 File을 읽기전용으로 열기위한 것입니다. 만일 지정한 File이 존재하지 않으면 fopen함수는 NULL값을 반환하게 되고 결국 if문에서 fopen의 수행결과가 NULL인지 아닌지가 판단할 수 있게 됩니다.(위의 예제에서는 NULL 아닐때(!=) File 읽기를 시도하도록 하고 있습니다.)

fopen("aaa.txt", "r")에서 File 이름을 aaa.txt라고만 하였는데 이는 aaa.txt File이 실제 Program이 존재하는 위치가 같은 경로에 있다는 것을 가정한 것입니다. 만일 aaa.txt가 C Drive의 work Folder에 있다고 가정한다면 "aaa.txt"대신 "C:\work\aaa.txt"라고 해야 합니다.

fopen수행결과가 NULL이 아니면 fgefc를 통해 File에 저장된 내용에서 한문자를 읽는 동작을 수행합니다. 이때 While에서 fgetc함수가 반복적으로 수행되는데 이는 fgetc함수를 한번만 수행할 경우 File에서 처음의 한문자만을 읽고 끝내버리기 때문입니다. 따라서 반복문(while)을 통해 File의 모든 Data를 한문자씩 읽도록 하는 처리가 필요한 것입니다.

fgetc는 File Pointer가 지정하고 있는 File의 내용을 한문자씩 읽은 후 s변수에 저장하도록 합니다.(s = fgetc(fl)) 이렇게 File에서 한문자씩 읽다가 File의 끝에 도달하면 fgetc함수는 끝(end)를 의미하는 EOF값을 반환하고 loop를 종료합니다.


4. File 한문자 쓰기

이번에는 bbb.txt라는 File을 신규생성하고 이 Text File에 한문자씩 입력하여 korea라는 내용을 저장해 보겠습니다.

#include <stdio.h>

main()
{
  FILE *fl;
  char s[6] = "korea";
  int i;
 
  if ((fl = fopen("bbb.txt", "w")) != NULL){
    for (i = 0; i <= 4; i++){
      fputc(s[i], fl);
    }
  }
 
  printf("file을 성공적으로 작성 하였습니다.\n");
  fclose(fl);
}


fputc를 통해 bbb.txt File에 korea라는 내용을 입력하고 저장합니다.(fputc()함수는 오류발생시 EOF를 반환합니다.)

fopen을 통해 File을 쓰기전용(w)으로 열면 이미 File이 있는 경우 새로운 내용으로(여기서는 korea)저장하고 File이 없는 경우 신규로 만들게 됩니다. 이때 여기에서 오류값인 NULL을 확인하는 이유는 Disk Drive가 쓰기금지되어 있거나 같은 이름의 File이 현재 사용중일 경우등등.. 여러가지 요인으로 인해 File을 쓸 수 없는 오류가 발생할 수 있기 때문입니다.

예제에서는 File에 문자를 쓰기위해 char s[6] = "korea";로 문자배열에 korea문자열을 저장하고 fputc(s[i], fl);을 통해 문자배열의 각 요소를 꺼내어 File Pointer가 지정하고 있는 File에 저장하도록 하였습니다.



위는 bbb.txt의 내용입니다.

5. File 한행 읽기

File의 내용을 한행씩 읽을때는 fgets를 사용합니다.


aaa.txt를 위와같은 내용으로 저장합니다.

#include <stdio.h>

main()
{
  FILE *fl;
  char s[7];
 
  if ((fl = fopen("aaa.txt", "r")) != NULL){
    while(fgets(s, 7, fl) != NULL){
      printf("%s\n", s);
    }
  }
 
  fclose(fl);
}


fgets를 통해 File에서 문자열을 읽어들이고 읽어들인 문자열을 출력하도록 합니다.

fgets는 첫번째 인수에 한행 읽어들인 File의 내용을 저장할 문자배열변수를 지정하고 두번째 인수에 읽어들일 문자수를 지정합니다. fgets는 한행을 읽어들인다고 하였지만 사실 읽어들일 행의 길이는 이 두번째 인수에서 지정하게 됩니다. 만일 한행의 길이가 200자일때 읽어들일 문자수를 10으로 한경우에는 200자가 아닌 단 10자만 읽어들이게 될 것입니다.


6. File 한행 쓰기

File의 내용을 한행씩 쓰기 위해서는 fputs를 사용합니다.

#include <stdio.h>

main()
{
  FILE *fl;
  char s[7] = "hello!";
  char ss[7] = "world!";
 
  if ((fl = fopen("bbb.txt", "w")) != NULL){
    fputs(s, fl);
    fputs(ss, fl);
  }
 
  fclose(fl);
}


s문자배열과 ss문자배열에 각각 hello와 world를 저장하고 fputs를 통해 bbb.txt File에 쓰기를 시도합니다.(fputs()함수는 오류발생시 EOF를 반환합니다.)


bbb.txt file의 내용입니다.

7. File읽기와 쓰기에서 Format지정하기

위에서 fputs를 통해 File에 문자열을 저장하였습니다. 이때 문자열이 저장되도록 한 의도는 다음과 같은 형태입니다.

hello!
world!

하지만 결과는

hello!world!

로 각 문자열이 붙어있는 형태를 띄게 되었습니다. 이 문제를 해결하려면 fputs를 두번째 호출하기전 fputc함수를 통해 개행을 지시하는 문자를 추가해도 되지만 이렇게 번거로운 작업을 거치지 않고 fprintf와 fscanf를 사용해 File을 다루면 개발자가 원하는 형식대로 읽거나 쓸 수 있습니다.

#include <stdio.h>

main()
{
  FILE *fl;
  char s;
 
  if ((fl = fopen("bbb.txt", "r")) != NULL){
    while(fscanf(fl, "%c", &s) != EOF){
      printf("%c\n", s);
    }
  }
 
  fclose(fl);
}


위에서 저장한 bbb.txt file을 읽기전용으로 하고 fscanf함수를 통해 한문자씩 File의 내용을 읽도록 하였습니다. 여기서 한문자를 읽는다는 것은 fscanf함수의 두번째 인수에서 File의 내용을 한문자(%c) 단위로 읽으라고 지정했기 때문입니다.(fscnaf함수는 File의 끝에 다다르면 EOF를 반환합니다.)


이번에는 fscanf함수를 통해 문자열(행)을 읽도록 해보겠습니다.

#include <stdio.h>

main()
{
  FILE *fl;
  char s[50];
 
  if ((fl = fopen("bbb.txt", "r")) != NULL){
    while(fscanf(fl, "%s", &s) != EOF){
      printf("%s\n", s);
    }
  }
 
  fclose(fl);
}


문자배열을 50(딱맞게 맞춰도 되지만 여기서는 넉넉하게 배열크기를 잡았습니다.) 으로 하고 fscanf에서 %s를 지정하여 문자열단위(행단위)로 File을 읽도록 하고 있습니다.


이번에는 반대로 fprintf를 통해 특정 Format대로 File을 작성해 보겠습니다.

#include <stdio.h>

main()
{
  FILE *fl;
  char s[18] = "The C Programming";
  char ss[9] = "language";
 
  if ((fl = fopen("bbb.txt", "a")) != NULL){
    fprintf(fl, "\n%s", s);
    fprintf(fl, "\n%s", ss);
  }
 
  printf("file을 작성하였습니다.\n");
  fclose(fl);
}


bbb..txt File을 추가용(a)으로 Open합니다. 그리고 fprintf를 통해 s배열의 문자열과 ss배열의 문자열을 bbb.txt file내용에 추가합니다.

fprintf에서 문자열을 추가할때 "\n%s"로 Format을 지정하였으므로 일단 개행(\n)한후에 각각의 문자열을 저장할 것입니다.



8. File간 복사수행하기

위에서 말씀드린 File읽기와 쓰기함수를 적절히 사용하면 File복사는 아주 쉽게 처리할 수 있습니다.

다음은 문자열단위를 통한 File복사의 예제입니다.

#include <stdio.h>

main()
{
  FILE *fli;
  FILE *flo;
 
  char ss[500];
 
  if ((fli = fopen("bbb.txt", "r")) == NULL){
    printf("복사할 file을 찾지 못했습니다.\n");
   
    return 1;
  }
 
  if ((flo = fopen("ccc.txt", "w")) == NULL){
    printf("복사 대상 file을 열수 없습니다.\n");
   
    return 1;
  }
 
  while(fgets(ss, 500, fli) != NULL)
  {
    fputs(ss, flo);
  }
  printf("file을 복사하였습니다.");
 
  fclose(fli);
  fclose(flo);
}


먼저 File의 읽기용 Pointer와 쓰기용 Pointer를 각각 하나씩 선언합니다. 하나는 File을 읽고 다른 하나는 읽은 내용을 쓰면서 File복사가 진행되도록 하기위해 읽기쓰기를 위한 두개의 File Pointer가 있어야 하기 때문입니다.

char ss[500]은 File의 내용을 한행씩 읽어들일때 행이 얼마나 긴 문자열단위인지 알 수 없는경우 읽어들인 문자열을 넉넉하게 저장할 수 있도록 하기 위해서 작성된 것입니다. 만일 File의 한행길이가 최대 1000자라고 가정한다면 최소 1001만큼의 배열 크기가 확보되어야 할 것입니다.

처음에는 fli Pointer를 통해 복사대상 File을 읽기(r)용으로 열고 두번째에는 fio Pointer로 쓰기용 File인 ccc.txt File을 Open합니다. 이렇게 해서 fli를 통해 읽은 내용은 flo를 통해 쓰면서 File을 복사하도록 하고 있습니다.(이때 ccc.txt File은 존재하지 않으므로 신규로 생성합니다. 또한 fli든 flo든 File을 여는데 실패하면 각각의 Error Message를 뿌리고 Program을 종료할 것입니다.)

모든 File의 열기가 완료되면 while문을 통해 fli에 지정한 File을 처음부터 끝까지 읽어들이게 되고 읽어들인 행은 바로 fputs를 통하여 flo이 지시하고 있는 File에 쓰게 될 것입니다.



9. 전체 File 다루기

File을 문자및 행단위가 아니라 전체내용을 한꺼번에 읽고 쓰려면 fread()와 fwrite()함수를 사용합니다. 이 함수들은 통상적으로 문자및 행단위를 처리하는 함수보다 느리긴 하지만 File크기가 큰 경우 유용하게 사용할 수 있는 함수입니다.

먼저 aaa.txt File에 다음과 같은 내용을 저장합니다.


이제 이 File의 내용을 bbb.txt로 복사하도록 fread()와 fwrite()함수를 사용하여 다음과 같이 작성합니다.

#include <stdio.h>

main()
{
  FILE *fr, *fw;
  char fs[1024];
  int fsc;
 
  fr = fopen("aaa.txt", "r");
 
  fsc = fread(fs, 1, 1024, fr);
 
  fw = fopen("bbb.txt", "w");
 
  fwrite(fs, 1, fsc, fw);
 
  fclose(fr);
  fclose(fw);
}


fread()함수를 통해 File을 읽고 fwrite()함수로 읽은 내용을 bbb.txt File로 작성합니다.

이 Program은 fread(fs, 1, 1024, fr); 부분에서 fr에 지정된 File을 읽습니다. 이때 1byte씩(8bit = 1문자) 총 1024byte만큼의 내용을 읽어서 그 내용을 fs배열에 저장합니다.(물론 File이 1024byte크기를 넘는다면 fread()함수에서의 인수와 배열크기를 더 크게 지정해 줘야 합니다.)

그런데 fread()함수에서 읽어들일 크기를 1024로 지정한다 하더라도 실제 읽어들인 크기는 다를 수도 있습니다.(더 작을 수도 있습니다.) 이에 대해 fread()함수는 해당 File로 읽어들인 정확한 Data수를 정수형태로 반환하게 되는데 예제 에서는 fread()함수에 반환되는 크기를 fsc변수에 저장하도록 하였습니다.

이 후 fwrite()함수를 호출하여 읽어들인 Data를 다른 File(bbb.txt)에 쓰도록 시도합니다. fwrite()함수는 fwrite(fs, 1, fsc, fw);의 형태로 호출되어 fw이 지정하고 있는 File에 1byte씩 fs에 있는 Data를 fsc만큼 쓰도록 하고 있습니다.(여기서 fsc는 fread()에서 반환되온 실제 Data크기 입니다.)


원본 aaa.txt File의 내용입니다.


복사된 bbb.txt File의 내용입니다.

참고:
fread()함수와 fwrite()함수는 오류가 발생하면 -값을 반환하게 됩니다.

10. File의 조작위치 지정하기

지금까지 File을 다룰때는 File의 처음부터 다루는 것을 기본으로 하였습니다. 하지만 경우에 따라 File의 특정 지점부터 처리해야하는 경우도 있을 것입니다.

이때 사용하는 함수가 ftell(), fseek(), rewind()함수입니다.

여기서 ftell()함수는 현재 File Pointer에서의 File위치를 나타내도록 하며 fseek()함수는 원하는 지점으로 File Pointer를 이동시키고 rewind()는 File Pointer를 처음으로 되돌리는 역활을 하게됩니다.


예제에서 사용될 aaa.txt File

#include <stdio.h>

main()
{
  FILE *fr;
  char c, s[28];
  fr = fopen("aaa.txt", "r");
 
  while((c = fgetc(fr)) != EOF){
    if (c == 'c'){
      printf("%d\n", ftell(fr));
      break;
    }
  }
 
  fseek(fr, 5, SEEK_SET);
 
  fgets(s, 5, fr);
 
  printf("%s\n", s);
 
  rewind(fr);
 
  fgets(s, 27, fr);
 
  printf("%s\n", s);
 
  fclose(fr);
}


위 Program에서는 우선 fgetc()함수를 통해 c aaa.txt file의 내용을 한문자씩 읽습니다. 그러다가 c 문자를 발견하면 ftell()함수를 호출하여 그때까지의 File Pointer위치를 출력하도록 합니다.('c'문자는 aaa.txt File 내용중 3번째에 존재합니다.)

이 후 loop를 빠져나온뒤 fseek()함수로 fr File Pointer의 위치를 File의 처음위치로 부터 5만큼 이동하고 fgets()함수로 해당 위치부터 지정한 크기만큼 읽고 그 내용을 다시 출력합니다.(5부터라면 File의 내용상 abcde이후부터가 됩니다.)

이때 fseek()에서 쓰인 SEEK_SET인수는 File의 어느 위치부터 정해진 위치(여기서는 5)로 볼것인가를 지정하는 부분으로 여기에는 필요에 따라 다음과 같이 다양한 인수가 올 수 있습니다.

 인수  설명
 SEEK_SET  File의 처음위치
 SEEK_CUR  현재 file pointer 위치
 SEEK_END  File의 마지막위치

예제에서 rewind()함수로 File Pointer의 위치를 처음으로 되돌리고 다시 File의 내용을 한행읽은 후 그 내용을 출력하도록 하고 있습니다.


11. File 잘라 읽기

File을 잘라 처리하려면 ungetc()함수를 사용합니다.


Sample로 사용될 aaa.txt File을 작성합니다.

#include <stdio.h>

main()
{
  FILE *fr;
  char c, s[11];
 
  fr = fopen("aaa.txt", "r");
 
  while((c = fgetc(fr)) != EOF){
    if (c == 'h'){
      ungetc(c, fr);
      break;
    }
  }
 
  fgets(s, 11, fr);
 
  printf("%s\n", s);
 
  fclose(fr);
}


fgetc()함수를 통해 File로 부터 한문자씩 읽고 읽은 내용중 'h'문자가 발견되면 ungetc()함수로 'h'문자부터 해당 File Pointer의 위치를 되돌립니다. 결국 fr File Pointer는 h문자 이후부터 시작하는 것으로 변경되어 File을 읽을때 결과적으로 h부터(File의 시작이 되므로) 읽게되는 것입니다.


참고:
ungets()함수는 오류 발생시 -값을 반환합니다.

12. File처리 관련 오류해결하기

대부분의 경우 File에 Data를 읽거나 쓰는 함수를 사용시 각 함수에서 반환하는 값으로 오류여부를 판단하고 적절히 처리하도록 하는 것이 통상적인 방법입니다.

하지만 이렇게 반환값으로 오류를 처리하는것 말고도 File처리시 오류를 판단하는 전용함수를 이용할 수도 있습니다.

#include <stdio.h>

main()
{
  FILE *fr;
  char c;
  fr = fopen("aaa.txt", "r");
 
  while(c = fgetc(fr)){
    if (feof(fr) != 0)
      return;
     
    printf("%c\n", c);
  }
}


fgetc()함수에서 EOF값 비교부분을 제거하여 계속해서 File의 내용을 읽도록 하고 있습니다. 다만 feof()함수를 통해 해당 File Pointer가 File의 끝을 가리키고 있는지 확인하여 끝에 도달하면 Loop를 빠져 나오도록 합니다.(feof()함수는 File Pointer가 끝을 가리키고 있지 않으면 0을 반환합니다.)


이번에는 Program을 일부 수정하여 다른 File관련 오류함수를 사용해 보도록 하겠습니다.

#include <stdio.h>

main()
{
  FILE *fr;
  char c;
  fr = fopen("aaa.txt", "r");
 
  while(c = fgetc(fr)){
    if (feof(fr) != 0)
      break;
     
    printf("%c\n", c);
  }
 
  fputc('a', fr);
 
  if (ferror(fr) != 0)
    printf("오류\n");
   
  clearerr(fr);
 
  if (ferror(fr) != 0)
    printf("오류\n");
  else
    printf("정상\n");
}


중간쯤 보시면 fputc()함수를 통해 File에 'a'문자입력을 시도하고 있습니다. 그러나 aaa.txt File은 "r"로 지정되어 읽기전용으로 열었기 때문에 입력이 불가능합니다.

따라서 Error가 발생하게 되고 그 오류여부를 ferror()로 판단하고 있습니다. 이때 ferror()함수는 해당 File Pointer를 확인하여 오류면 1을 정상이면 0을 반환하고 이 값에 따라 오류여부를 판단하고 있습니다.

그리고 다시 clearerr()함수로 File Pointer가 가지고 있는 오류사항을 초기화 시키고 ferror()함수로 오류상태가 계속되고 있는지를 확인합니다.


13. Binary 파일

일반적으로 File안에서 행의 끝은 0A와 0D로 표현합니다. 0A는 다음행으로 넘기는 역활이고 OD는 행의 처음으로 이동하는 것입니다. 결국 이들이 조합되어 개행을 처리한다는 것인데 반면 C언에서는 \n확장 문자열로 개행문자를 처리하고 있습니다.

즉, C언어와 File상에서의 개행을 처리하는 구분문자가 다른것입니다. 그래서 File을 작성하는 경우에는 \n과 같은 개행문자를 실제 File에서의 개행문자인 0A등으로 바꾸어 줄 필요가 있는 것입니다.(File안에서 0A가 개행문자라는 것은 OS마다 차이가 있을 수 있습니다.)

이런 상황은 읽을때도 마찬가지입니다. 파일에서는 0A를 개행으로 처리하고 있으니 C언어상에서는 이 0A를 \n으로 바꿔야 정상적인 개행이 이루어 질 수 있을 것입니다.

그러나 필요에 따라 이러한 개행문자들을 C언어의 개행형식으로 변환하지 않고 있는 그대로 읽어들일 필요가 있는데 이때 File을 Binary mode로 Open하면 원하는 처리를 할 수 있게 됩니다. C에서 File을 Binary로 읽고 쓰게 되면 말씀드린 개행문자라던가 기타 다른 구분문자들을 C언어 특유의 구분문자로 변환하지 않고 있는 그대로 처리하게 되는 것입니다.
0 0