프로그래밍 언어/C

[입력과 출력] 콘솔 입출력, 파일 입출력, 파일 입출력 함수

순정법사 2023. 8. 17. 16:14

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언어
파일을 성공적으로 닫았습니다!