[입력과 출력] 콘솔 입출력, 파일 입출력, 파일 입출력 함수
A. 콘솔 입출력
공부하기 전 C 언어의 기본적인 입출력 방식에 대해 알아야 함
[문자와 문자열] 입출력 함수 총정리 1 : 스트림과 문자
A. 기본적인 입출력 1. 스트림 (stream) a. 스트림이란? 🌟 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름 = 운영체제에 의해 생성되는 가상의 연결 고리 C언어는 파일이나 콘솔의 입출력
monamu.tistory.com
1. 버퍼(buffer)
a. 버퍼란?
🌟 표준 입출력 함수를 사용할 때 사용하는 임시 메모리 공간
입력 작업 뿐 아니라 printf() 함수 등을 통해 모니터에 데이터를 출력할 때도 버퍼를 사용
출력하고자 하는 데이터는 일단 출력 버퍼에 저장되었다가 출력 스트림을 통해 모니터로 전송
b. 버퍼의 장단점
👍 장점
- 묶어서 전달해 전송 시간이 적게 걸려 성능이 향상
- 사용자가 문자를 잘못 입력한 경우 수정 가능
👎 단점
- 게임과 같이 빠른 반응이 요구되는 게임에서는 사용 X
👉 따라서 목적에 맞게 버퍼의 사용 여부를 판단해야 함
2. 버퍼링 방식
a. 완전 버퍼링(fully buffered)
🌟 버퍼가 가득 차면 버퍼 안의 내용을 목적지로 보내는 방식
보통 파일 입출력에서 사용
b. 라인 버퍼링(line-buffered)
🌟 입력된 문자 중 개행 문자가 나타날 때마다 버퍼 안의 내용을 목적지로 보내는 방식
보통 키보드 입력에서 사용
3. 버퍼 관련 함수
a. fflush() 함수
🌟 인수로 전달된 스트림에 연결된 버퍼를 비워줌
📘 문법
#include <stdio.h>
int fflush(FILE *stream);
- 인수는 출력 스트림과 관련(입력 스트림 X), 출력 스트림이 인수로 전달되면 출력 버퍼 안에 있는 데이터를 즉시 목적지로 보내줌
- 널 포인터가 인수로 전달되면, 해당 프로그램에서 앞서 정의한 모든 스트림에 대한 버퍼를 비움
- 성공적으로 비우면 0, 실패하면 EOF 반환
🤓 예제 : fflush()가 아닌 방법으로 버퍼 비우기
#include <stdio.h>
int main(void)
{
char str[20];
char ch;
puts("당신의 이름을 적어주세요 : ");
scanf("%s", str);
// fflush(stdin); 표준 입력 스트림의 입력 버퍼를 비움 --잘못된 방법
getchar()
puts("당신의 성별을 약자로 적어주세요 : ");
puts("(남성=M, 여성=F)");
scanf("%c", &ch);
if (ch=='m' | ch=='M')
{
printf("당신은 남성인 %s입니다.\n", str);
}
else if(ch=='f' | ch=='F')
{
printf("당신은 여성인 %s입니다.\n", str);
}
else
{
printf("%s님, 성별을 잘못 입력하셨습니다.\n", str);
}
return 0;
}
✨ 실행결과
당신의 이름을 적어주세요 :
홍길동
당신의 성별을 약자로 적어주세요 :
<남성=M, 여성=F>
홍길동님, 성별을 잘못 입력하셨습니다.
🧡 NOTICE
fflush()은 출력스트림에만 관련 (출력스트림으론 stdout, stderr이 있음)
Visual Studio 2013에서는 비표준함수인 fflush()를 허용해주고 있지만, Visual Studio2015부터 비표준함수인 fflush()가 사라졌기 때문에 위의 예제에서 fflush(stdin)을 사용하더라도 똑같은 현상이 반복됨
👉 입력 버퍼를 지우기 위해서는 fflush()함수가 아닌 getchar() 함수를 이용
B. 파일 입출력
1. 파일
a. 파일이란?
🌟 의미 있는 정보를 담고 있으며, 이름을 가지고 있는 저장 장치상의 논리적인 단위
C에선 바이트별로 따로 읽을 수 있는 연속적인 바이트의 집합으로 취급
b. 파일의 종류
1) 바이너리 파일 (binary file)
🌟 데이터의 저장과 처리를 목적으로 0과 1의 이진 형식으로 인코딩된 파일
프로그램이 이 파일의 데이터를 읽거나 쓸 때는 데이터의 어떠한 변환도 일어나지 않음
2) 텍스트 파일 (text file)
🌟 사람이 알아볼 수 있는 문자열로 이루어진 파일
이 파일의 데이터를 읽거나 쓸 때는 포맷 형식에 따라 데이터의 변환이 일어남
2. 파일의 입출력
앞서 공부했듯, C언어는 콘솔 장치에 대한 스트림은 프로그램 실행 시 자동 생성 및 소멸이지만,파일과의 연결을 위한 스트림은 사용자가 직접 생성하고 소멸시켜야 함
💙 파일에 대한 입출력 동작의 순서
1) 파일과의 스트림 생성 = fopen() 함수
2) FILE 구조체 변수의 포인터를 이용한 작업 진행
3) 파일과의 스트림 종결 = fclose() 함수
a. fopen() 함수
🌟 파일을 열어주는(파일과의 입출력을 위한 스트림을 생성) 함수
프로그램이 성공적으로 파일을 열면, FILE 구조체 변수의 포인터 반환( 실패시 널 포인터)
📘 문법
#include <stdio.h>
FILE *fopen(const char * restrict filename, const char * restrict mode);
//첫 번째 인수 : 열고자 하는 파일의 이름과 그 경로를 가지고 있는 문자열
//두 번째 인수 : 파일을 여는 데 사용할 모드를 지정하는 문자열
// 모드 문자열은 파일의 사용 용도를 결정하는 문자열과 파일의 데이터를 어떤 방식으로 입출력할지 결정하는 문자열로 구성
👉 모드 문자열은 따로 정리해놓음
[입력과 출력] 파일 입출력 : fopen() 모드 문자열
A. 모드 문자열 1. 모드 문자열 a. fopen() 함수의 모드 문자열이란? 🌟 파일의 사용 용도를 결정하고, 파일의 데이터를 어떤 방식으로 입출력할지를 결정 b. 사용 용도 r (read mode) : 읽기 전용 모드 w
monamu.tistory.com
b. FILE 구조체 변수의 포인터
🌟 해당 파일이 사용하는 버퍼 정보를 비롯한 파일에 관한 정보가 들어있는 데이터
따라서 모든 파일 입출력 함수는 FILE 구조체 변수의 포인터를 인수로 전달받아 해당 파일에 접근
c. fclose() 함수
🌟 파일을 닫아주는(파일과의 입출력을 위해 생성한 스트림을 소멸) 함수
버퍼에 남아있는 데이터를 파일로 완전히 내보내고, 파일 입출력을 위해 내부적으로 생성했던 FILE 구조체를 해제
❕ C언어에서 다 사용한 파일은 반드시 fclose() 함수를 사용하여 닫아줘야 함
닫고자 하는 파일을 가리키는 FILE 구조체 변수의 포인터를 전달받고 성공시 0, 실패시 EOF
📘 문법
#include <stdio.h>
int fclose(FILE *stream);
🤓 예제 : 단순히 파일을 여닫기
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* 파일 open */
FILE* ptr_file = fopen("example.txt", "w+");
if (ptr_file == NULL) // 파일을 열 수가 없다면, fopen() 함수는 널 포인터를 반환함.
{
puts("파일을 열 수가 없습니다!");
exit(1); // 현재의 C프로그램 자체를 완전 종료하고, 운영체제에 1을 반환함.
}
else
{
puts("파일을 성공적으로 열었습니다!");
}
/* 파일 close */
if (fclose(ptr_file)!=0) // fclose() 함수는 파일을 성공적으로 닫으면 0을 반환함.
{
puts("파일을 닫을 수가 없습니다!");
exit(1);
}
else
{
puts("파일을 성공적으로 닫았습니다!");
}
return 0;
}
✨ 실행결과
파일을 성공적으로 열었습니다!
파일을 성공적으로 닫았습니다!
📝 예제 설명
1) 모드 문자열로 전달된 "w+"의 정의에 따라, 해당 파일을 쓰는 것만이 가능한 텍스트 모드로 열음
2) 파일이 없으면 새파일을 만들고, 존재하면 모든 데이터를 지우고 파일을 열음
3) 임의의 문자열을 입력하고 저장해도 이 프로그램을 다시 실행하면 내용이 다 지워짐
C. 파일 입출력 함수
C언어에서 인수를 하나만 전달받는 대부분의 입출력 함수는 스트림이 stdin이나 stdout으로 고정
하지만 f로 시작되는 입출력함수는 입출력 스트림을 사용자가 직접 지정할 수 있음
인수로 FILE 구조체 변수의 포인터를 전달함으로써 표준 입출력 장치뿐만 아니라 파일로도 입출력을 진행
1. 표준 입출력 함수 중에서 스트림을 직접 지정할 수 있는 함수
a. fgetc() 함수
🌟 지정된 스트림으로부터 하나의 문자를 읽어 들이는 함수
💙 반환값
- 읽기에 성공 : 읽은 문자를 반환
- 파일의 끝에 도달 : EOF를 반환
📘 문법
#include <stdio.h>
int fgetc(FILE *stream);
b. fputc() 함수
🌟 지정된 스트림에 문자 하나를 출력(저장)하는 함수
💙 반환값
- 저장에 성공 : 저장한 문자를 반환
- 저장에 실패 : EOF를 반환
📘 문법
#include <stdio.h>
int fputc(int c, FILE *stream);
🤓 예제 : 파일의 모든 문자를 한 문자씩 읽어 들여 출력
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
① char ch;
/* 파일 open */
FILE* ptr_file = fopen("text_readonly.txt", "r"); // "C언어 파일 입출력" 문자열이 저장된 파일
...
if(ptr_file != NULL)
{
② while(EOF != (ch = fgetc(ptr_file))) // fgetc() 함수를 사용하여 파일로부터 문자 한 개를 읽어들임.
{
③ fputc(ch, stdout); // fputc() 함수를 사용하여 모니터에 문자 한 개를 출력함.
}
}
/* 파일 close */
...
return 0;
}
✨ 실행결과
C언어 파일 입출력
📝 예제 설명
1) ②번 라인에서는 fgetc() 함수가 반환하는 값을 char형 변수인 ch에 저장
2) 하지만 C언어의 fgetc() 함수는 char형이 아닌 int형의 값을 반환하는 함수라 반환값의 상위 3바이트가 잘려나감
3) 파일의 끝에 도달했을 때 반환되는 EOF 값인 -1의 16진수 값 '0xFFFFFFFF'이 '0xFF'로 저장됨
👉 while 문은 현재 파일의 끝인 EOF를 읽은 것인지, 16진수 데이터인 '0xFF'를 읽은 것인지를 알 수가 없게 됨
4) if 조건문에서, EOF는 int형 값이므로 변수 ch의 char ➡ int형 값으로 자동 변환
- 변수 ch가 signed char ➡ EOF : '0xFFFFFFFF'로 변환
- 변수 ch가 unsigned char ➡ EOF : '0x000000FF'로 변환되어 while 문은 무한히 반복
💥 위와 같은 문제점으로 fgetc() 함수의 반환값은 반드시 int형 변수로 저장
int main(void)
{
① int ch;
...
if(ptr_file != NULL)
{
while(EOF != (ch = fgetc(ptr_file))) // fgetc() 함수를 사용하여 파일로부터 문자 한 개를 읽어들임.
{
fputc(ch, stdout); // fputc() 함수를 사용하여 모니터에 문자 한 개를 출력함.
}
}
...
}
c. fgets() 함수
🌟 지정된 스트림으로부터 문자열을 읽어 들이는 함수
문자를 읽어 들이는 도중에 개행 문자('\n')를 만나게 되면 곧바로 읽기를 종료
지금까지 읽어 들인 문자들이 C언어에서 문자열로 인식되도록 맨 마지막에 널 문자('\0')를 자동으로 추가
💙 반환값
- 읽기에 성공 : 문자열이 저장된 주소를 반환
- 파일의 끝에 도달하거나 읽기에 실패 : NULL를 반환
📘 문법
#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream)
//첫 번째 인수 : 읽은 문자열이 저장될 주소
//두 번째 인수 : 전달받은 최대 입력 문자 개수보다 하나 적은 수의 문자를 읽거나,
// 파일의 끝에 도달할 때까지 문자
//세 번째 인수 : 스트림을 결정할 FILE 구조체 변수의 포인터
d. fputs() 함수
🌟 지정된 스트림에 문자열을 출력(저장)하는 함수
인수로 전달된 스트림이 stdout -> 모니터에 문자열을 출력 / 파일 -> 문자열을 해당 파일에 저장
💙 반환값
- 쓰기(저장)에 성공 : 음수가 아닌 값을 반환
- 쓰기(저장)에 실패 : EOF를 반환
📘 문법
#include <stdio.h>
int fputs(const char * restrict s, FILE * restrict stream);
//첫 번째 인수 : 쓰고자 하는 문자열의 주소
//두 번째 인수 : 스트림을 결정할 FILE 구조체 변수의 포인터
🤓 예제 : 파일에서 문자열을 읽어 들여 다른 파일로 옮겨 저장
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[100];
/* 파일 open */
FILE* ptr_src = fopen("text_readonly.txt", "r");
FILE* ptr_dst = fopen("text_writeonly.txt", "w");
...
while(fgets(str, 100, ptr_src) != NULL) // fgets() 함수를 사용하여 파일로부터 문자열을 읽어들임.
{
fputs(str, ptr_dst); // fputs() 함수를 사용하여 파일에 문자열을 옮겨적음.
}
puts("text_readonly.txt 파일의 모든 내용이 text_writeonly.txt 파일로 옮겨졌습니다.");
/* 파일 close */
...
return 0;
}
✨ 실행결과
파일을 성공적으로 열었습니다!
text_readonly.txt 파일의 모든 내용이 text_writeonly.txt 파일로 옮겨졌습니다.
파일을 성공적으로 닫았습니다!
e. fscanf() 함수
🌟 지정된 스트림으로부터 다양한 서식 변환 문자를 이용하여 문자열을 읽어 들이는 함수
💙 반환값
- 읽기에 성공 : 읽어 들인 변수의 개수를 반환
- 읽기에 실패 : EOF를 반환
📘 문법
#include <stdio.h>
int fscanf(FILE * restrict stream, const char * restrict format, ...);
//첫 번째 인수 : 스트림을 결정할 FILE 구조체 변수의 포인터
//두 번째 인수 : 읽어 들일 문자열의 서식
f. fprintf() 함수
🌟 지정된 스트림에 다양한 서식 변환 문자를 이용하여 문자열을 출력(저장)하는 함수
💙 반환값
- 쓰기(저장)에 성공 : 저장한 문자열의 크기를 바이트 단위로 반환
- 쓰기(저장)에 실패 : 음수를 반환
📘 문법
#include <stdio.h>
int fprintf(FILE * restrict stream, const char * restrict format, ...);
//첫 번째 인수 : 스트림을 결정할 FILE 구조체 변수의 포인터
//두 번째 인수 : 출력할 문자열의 서식
🤓 예제 : 서식에 맞춰 문자열을 읽어 들여 모니터에 출력
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int scan_num, int_num;
double double_num;
char str[100];
/* 파일 open */
FILE* ptr_file = fopen("text_fscanf.txt", "r");
...
// fscanf 함수를 사용하여 파일로부터 문자열을 서식에 맞춰서 읽어들임.
while(scan_num = (fscanf(ptr_file, "%d %lf %s", &int_num, &double_num, str)) != EOF)
{
// fprintf 함수를 사용하여 모니터에 서식에 맞춰서 문자열을 옮겨적음.
fprintf(stdout, "%d %f %s\n", int_num, double_num, str);
}
/* 파일 close */
...
return 0;
}
✨ 실행결과
파일을 성공적으로 열었습니다!
123 3.14 C언어
파일을 성공적으로 닫았습니다!