62. 파일 문자열 쓰기/읽기
1. 키워드
- 파일 문자열
- 파일 포인터
fopen
(File Open)fprintf
(Fileprintf
)fclose
(File Close)fscanf
(Filescanf
)- 파일 스트림(File Stream)
fputs
(Fileputs
)fwrite
(File Write)fgets
(Filegets
)fread
(File Read)
2. 파일에서 문자열을 읽고 쓰기
- 프로그래밍에서 중요한 축을 차지하는 부분이 파일 처리이다.
- 파일에서 값을 쓰는 방법과 읽는 방법을 알아보자.
3. 서식을 지정하여 파일에 문자열 쓰기
- 지금까지
printf
로 서식을 지정하여 문자열을 화면에 출력하고,sprintf
로 서식을 지정하여 문자열을 생성했다. - 파일에 문자열을 쓸 때는 먼저
fopen
함수로 파일을 열어서 파일 포인터를 얻은 뒤fprintf
함수를 사용한다.
FILE *포인터이름 = fopen(파일명, 파일모드);
성공하면 파일 포인터를 반환, 실패하면 NULL을 반환
fprintf(파일포인터, 서식, 값1, 값2, ...);
성공하면 쓴 문자열의 길이를 반환, 실패하면 음수를 반환
fclose(파일포인터);
성공하면 0을 반환, 실패하면 EOF(-1)를 반환
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fprintf, fclose 함수가 선언된 헤더 파일
int main()
{
FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
// 파일 포인터를 반환
fprintf(fp, "%s %d\n", "Hello", 100); // 서식을 지정하여 문자열을 파일에 저장
fclose(fp); // 파일 포인터 닫기
return 0;
}
- 파일을 사용하기 위해서는
fopen
함수로 파일을 열어서 파일 포인터를 얻어야 한다. - 다음과 같이 읽을 파일 이름은
hello.txt
로 지정하고, 파일을 생성하여 내용을 쓸 것이므로 파일 모드를"w"
로 지정해 준다. - 파일 열기에 성공하면 파일 포인터를 반환하고, 실패하면
NULL
을 반환한다.
// ↓ 파일 포인터
FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기. 파일 포인터를 반환
// ↑ ↖ 파일 모드
// 파일 이름 또는 파일 경로
- 여기서
FILE
구조체는stdio.h
헤더 파일에 정의되어 있으며 보통FILE
과*
를 합쳐서 파일 포인터라고 부른다.
- 다음은 파일 모드의 종류이다.
- 파일 모드는 보통
"rb"
,"rt"
,"w+b"
,"w+t"
와 같이 읽기/쓰기 모드와 텍스트/바이너리 모드를 조합해서 사용한다. t
와b
는 단독으로 사용할 수 없다.
- 이제 파일 포인터를 얻었으니
fprintf
함수로 문자열을 파일에 쓴다. - 먼저 앞에서 얻은 파일 포인터를 지정하고 서식과 값을 순서대로 지정하면 된다.
- 이렇게 하면
"Hello"
와 정수100
을 합쳐서 문자열"Hello 100"
이hello.txt
파일에 저장된다.
- 파일 쓰기가 끝났으면 반드시
fclose
함수로 파일 포인터를 닫아준다. - 파일 포인터
fp
도 구조체FILE
크기만큼 동적 메모리를 할당한 것이기 때문에fclose
함수로 닫아주지 않으면 메모리 누수가 발생한다.
- 서식을 지정하여 파일에 문자열을 쓰는 과정을 그림으로 나타내면 다음과 같다.
- 한 가지 특이한 점은
fprintf
함수로도 화면에 문자열을 출력할 수 있다는 점인데,stdout
이라는 매크로를 활용하면 된다.
- 즉,
fprintf(stdout, ...);
은printf
함수와 동작이 같다.
4. 서식을 지정하여 파일에서 문자열 읽기
- 이제 앞에서 생성한
hello.txt
파일의 내용을 읽어보자. - 파일을 읽을 때도
fopen
함수로 파일을 열어서 파일 포인터를 얻은 뒤fscanf
함수로 서식을 지정하여 파일의 내용을 읽는다.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fscanf, fclose 함수가 선언된 헤더 파일
int main()
{
char s1[10];
int num1;
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
// 파일 포인터를 반환
fscanf(fp, "%s %d", s1, &num1); // 서식을 지정하여 파일에서 문자열 읽기
printf("%s %d\n", s1, num1); // 파일에서 읽은 값을 출력
fclose(fp); // 파일 포인터 닫기
return 0;
}
// Hello 100
- 먼저
fopen
함수를 사용하여hello.txt
파일을 읽기 모드(r
)로 연다.
fscanf
함수는 파일 포인터를 넣는다는 점 말고는scanf
함수와 사용법이 같다.- 즉, 파일에서 문자열을 읽은 뒤 서식에 맞춰서 값을 변수에 저장한다.
- 여기서는 서식을
"%s %d"
로 지정했으므로"Hello"
부분은 문자 배열s1
에 저장되고,100
부분은 정수형 변수num1
에 저장된다.
printf
로s1
과num1
의 값을 출력해 보면"Hello"
와100
이 나온다.
- 마찬가지로 파일 읽기 작업이 끝났다면
fclose
함수로 파일 포인터를 닫아준다.
- 서식을 지정하여 파일에서 문자열을 읽는 과정을 그림으로 나타내면 다음과 같다.
fscanf
함수는stdin
매크로를 활용하면 사용자 입력한 값을 변수에 저장할 수 있다.
- 즉,
fscanf(stdin, ...);
은scanf
함수와 동작이 같다.
파일 스트림
fprintf
,fscanf
등의 함수는 매개변수에서 파일 포인터 부분을 보면FILE *const _Stream
과 같이 스트림이라고 되어있다.- 보통 파일 포인터를 파일 스트림이라고도 하는데 스트림은 물 등의 액체가 흐르는 것을 뜻한다.
- 파이프 속에 물이 계속 흘러다니는 것처럼 파일 스트림도 파일의 데이터를 연속적으로 처리한다고 해서 스트림이다.
- 즉, 파일에서 데이터를 처리할 때마다 매번 파일을 여는 것이 아니라 파일 스트림을 한 번 생성해서 계속 데이터를 쓰거나 가져오는 방식이다.
- 여기서
fopen
으로 파일을 읽기 전용으로 열면 입력 스트림, 쓰기 전용으로 열면 출력 스트림, 읽기/쓰기로 열면 입출력 스트림이다. - 파일 모드에 따라 단방향, 양방향이 된다.
- 마찬가지로
stdin
은 입력 스트림,stdout
,stderr
는 출력 스트림이다.
5. 파일에 문자열 쓰기
fputs
함수를 사용하면 문자열을 파일에 쓸 수 있다.
- 간단하게
"Hello, world!"
문자열을 파일에 저장해 보자.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fputs, fclose 함수가 선언된 헤더 파일
int main()
{
FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
// 파일 포인터를 반환
fputs("Hello, world!", fp); // 파일에 문자열 저장
fclose(fp); // 파일 포인터 닫기
return 0;
}
- 먼저 파일에 문자열을 써야 하므로
fopen
함수를 사용하여 파일을 쓰기 모드(w
)로 연다.
- 이제 파일 포인터를 얻었으니
fputs
함수로 문자열을 파일에 쓴다. fputs
는 파일에 쓸 문자열(배열이나 동적 메모리를 할당한 포인터도 가능)과 파일 포인터fp
를 넣어준다.
- 이렇게 하면
"Hello, world!"
문자열만hello.txt
파일에 저장된다.
- 파일 쓰기가 끝났으면 반드시
fclose
함수로 파일 포인터를 닫아준다.
- 지금까지 파일에 문자열을 쓴 과정을 그림으로 표현하면 다음과 같다.
fputs
함수도 파일 포인터 대신stdout
을 지정하면 문자열이 표준 출력(화면)에 출력된다.
- 이번에는
fwrite
함수를 사용하여 문자열을 파일에 써보자.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fwrite, fclose 함수가 선언된 헤더 파일
#include <string.h> // strlen 함수가 선언된 헤더 파일
int main()
{
char *s1 = "Hello, world!";
FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
// 파일 포인터를 반환
fwrite(s1, strlen(s1), 1, fp); // strlen으로 문자열의 길이를 구함.
// 문자열의 길이만큼 1번 파일에 저장
fclose(fp); // 파일 포인터 닫기
return 0;
}
- 먼저 파일에 문자열을 써야 하므로
fopen
함수를 사용하여 파일을 쓰기 모드(w
)로 연다.
fwrite
함수는fputs
함수와는 달리 쓰기 크기와 쓰기 횟수를 지정해야 한다.- 따라서 먼저 파일에 쓸 문자열의 포인터
s1
을 지정한다. - 그리고 쓰기 크기에는
strlen(s1)
과 같이 문자열의 길이를 구해서 넣고, 쓰기 횟수에는1
을 넣는다. - 마지막에는 파일 포인터
fp
를 넣어준다.
- 이렇게 하면
"Hello, world!"
문자열만hello.txt
파일에 저장된다.
- 파일 쓰기가 끝났으면 반드시
fclose
함수로 파일 포인터를 닫아준다.
- 지금까지 파일에 문자열을 쓴 과정을 그림으로 표현하면 다음과 같다.
fwrite
함수도 파일 포인터 대신stdout
을 지정하면 문자열이 화면에 출력된다.
char *s1 = "Hello, world!";
fwrite(s1, strlen(s1), 1, stdout); // Hello, world!
// 문자열 길이만큼 표준출력(stdout)에 문자열 출력
6. 파일에서 문자열 읽기
- 이제 앞에서 생성한
hello.txt
파일의 내용을 읽어보자. - 파일을 읽을 때는
fopen
파일을 열어서 파일 포인터를 얻은 뒤fgets
함수로 파일의 내용을 읽는다.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fgets, fclose 함수가 선언된 헤더 파일
int main()
{
char buffer[20]; // 파일을 읽을 때 사용할 임시 공간
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드로 열기.
// 파일 포인터를 반환
fgets(buffer, sizeof(buffer), fp); // hello.txt에서 문자열을 읽음
printf("%s\n", buffer); // 파일의 내용 출력
fclose(fp); // 파일 포인터 닫기
return 0;
}
- 먼저 파일을 읽을 때 사용할 임시 공간(버퍼)를 선언한다.
- 여기서는 크기가
20
인char
타입 배열을 선언했다. char
타입 포인터를 선언한 뒤 동적 메모리를 할당해도 된다.
fopen
함수를 사용하여hello.txt
파일을 읽기 모드(r
)로 연다.
- 이제 파일 포인터를 얻었으니
fgets
함수로 파일의 내용을 읽는다. - 먼저 버퍼에는 앞에서 선언한
buffer
를 넣고, 버퍼 크기에는sizeof(buffer)
와 같이 버퍼의 크기를 구해서 넣는다. - 그리고 마지막에는 파일 포인터
fp
를 넣어준다.
printf
로buffer
의 내용을 출력해 보면hello.txt
에 저장되어 있던 문자열이 출력되는 것을 볼 수 있다.
- 파일 읽기가 끝났으면 반드시
fclose
함수로 파일 포인터를 닫아준다.
- 지금까지 파일에서 문자열을 읽은 과정을 그림으로 표현하면 다음과 같다.
- 한 가지 특이한 점은
fgets
함수가 파일을 읽는 방식이다. - 예를 들어 다음과 같이
hello.txt
에 문자열이 저장되어 있다.
- 이 상태에서
fgets
의 버퍼 크기를20
바이트로 지정한 뒤 파일을 읽으면 딱 버퍼 크기만큼만 읽는다. - 따라서 널 문자를 포함하여
20
바이트를 읽으며 실제 문자열은19
바이트이다.
// 파일 열기, 닫기 생략
char buffer[20];
fgets(buffer, sizeof(buffer), fp); // 버퍼 크기 20바이트
fputs(buffer, stdout); // 널 문자를 포함하여 20바이트를 읽음. 문자열은 19바이트
- 하지만 다음과 같이
hello.txt
에 줄바꿈(\n
)이 있으면 버퍼 크기와는 상관없이\n
까지만 문자열을 읽는다.
- 즉, 이 상태에서
fgets
로 파일을 읽으면buffer
에는"Hello, wo\n"
까지만 들어간다.
// 파일 열기, 닫기 생략
char buffer[20];
fgets(buffer, sizeof(buffer), fp); // 버퍼 크기 20바이트
fputs(buffer, stdout); // \n까지 문자열을 읽음(\n도 포함)
- 이처럼
fgets
함수는\n
에 따라 읽은 결과가 달라지므로 사용에 주의해야 한다.
fgets
함수도stdin
을 지정하면 사용자가 입력한 문자열을 버퍼에 저장한다.- 다음과 같이 버퍼 크기를
20
으로 지정하면 사용자가 입력한 문자열 중에서 널 문자 포함20
바이트만 버퍼에 저장한다.
char buffer[20];
fgets(buffer, sizeof(buffer), stdin); // 표준 입력(stdin)에서 20바이트만큼 문자열 읽기
// 널 문자 1바이트, 실제 문자열 19바이트
- 물론 지정한 버퍼 크기보다 작은 상태에서 엔터 키를 누르면
\n
이 되므로fgets
함수는\n
까지 입력을 받는다.
- 이번에는
fread
함수를 사용하여 파일에서 문자열을 읽어보자.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fread, fclose 함수가 선언된 헤더 파일
int main()
{
char buffer[20] = { // 파일을 읽을 때 사용할 임시 공간, 미리 0으로 전부 초기화
0,
};
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드로 열기.
// 파일 포인터를 반환
fread(buffer, sizeof(buffer), 1, fp); // hello.txt에서 버퍼 크기(20바이트)만큼 1번 값을 읽음
printf("%s\n", buffer); // 파일의 내용 출력
fclose(fp); // 파일 포인터 닫기
return 0;
}
// Hello, world!
- 파일을 읽을 때 사용할 임시 공간(버퍼)를 선언해야 하는데
fread
함수를 사용할 때는char
타입 배열을 선언한 뒤 반드시0
으로 초기화해야 한다. char
타입 포인터에 동적 메모리를 할당한 뒤0
으로 초기화해도 된다.
fopen
함수를 사용하여hello.txt
파일을 읽기 모드(r
)로 연다.
- 이제 파일 포인터를 얻었으니
fread
함수로 파일의 내용을 읽는다. - 먼저 앞에서 선언한
buffer
를 넣어준다. - 그리고 읽기 크기에는
sizeof(buffer)
와 같이 버퍼의 크기를 구해서 넣고, 읽기 횟수에는1
을 지정하여 버퍼 크기만큼 읽는다. - 마지막에는 파일 포인터
fp
를 넣어준다.
- 즉,
fread
함수에서 파일을 읽는 크기는읽기 크기 * 읽기 횟수
이다.
- 특히
fread
함수는fgets
함수와는 달리\n
이 있든 없든 무조건 지정된 크기만큼 읽는다. printf
로buffer
의 내용을 출력해 보면hello.txt
에 저장되어 있던 문자열이 출력되는 것을 볼 수 있다.
- 만약 앞에서
buffer
를0
(NULL
)으로 초기화하지 않고fread
로 파일을 읽으면"Hello, world!"
이외에도 쓸데없는 값들이 함께 출력된다. - 왜냐하면 C로 만든 프로그램의 문자열 포인터, 배열에 저장된 문자열의 끝에는
NULL
이 들어있지만, 파일에 저장된 문자열의 끝에는NULL
이 들어있지 않다. - 그러다 보니 파일에서 문자열만 읽어서
buffer
에 가져오면 이전에 메모리에 저장되어 있던 값과 합쳐지게 된다. - 즉, 문자열 끝에
NULL
이 없어서buffer
의 내용을 모두 출력하게 되는 것이다.
- 파일 읽기가 끝났으면 반드시
fclose
함수로 파일 포인터를 닫아준다.
- 지금까지 파일에서 문자열을 읽은 과정을 그림으로 표현하면 다음과 같다.
fread
함수도stdin
을 지정하면 사용자가 입력한 문자열을 버퍼에 저장한다.- 단,
fread
함수는 무조건 지정된 크기만큼만 읽으므로 버퍼 끝에NULL
이 들어갈 수 있도록 읽기 크기는 버퍼 크기보다1
이 적도록 만들어준다.
char buffer[20] = { 0, };
fread(buffer, sizeof(buffer) - 1, 1, stdin); // 표준 입력(stdin)에서 문자열 읽기
// 버퍼 끝에 NULL이 들어갈 수 있도록 sizeof(buffer) - 1을 지정
stdin
, stdout
, stderr
도 파일 포인터이다.
- 보통
stdin
은 키보드 입력,stdout
과stderr
는 콘솔 출력이다. - 특히 C에서는
stdin
,stdout
,stderr
가 파일 포인터(FILE *
)이므로fprintf
,fscanf
,fread
,fwrite
등 파일 포인터를 받는 함수에 사용할 수 있다. - 즉,
fscanf
,fgets
함수로stdin
을 읽으면 키보드 입력 값을 가져오게 되며fprintf
,fputs
함수로stdout
,stderr
에 값을 쓰면 콘솔에 값을 출력하게 된다.