본문 바로가기

별걸다하는 IT/프로그래밍언어

[C/C++] 파일 입출력 함수, 파일 읽는 함수 fgets & fgetc 함수 알아보기, 파일 문자열 컨트롤 함수 stdio.h 라이브러리

[C/ C++ 포스팅 링크 모음 목차]

안녕하세요! 

저번에 파일 입출력의 시작을 알리는 스트림에 대해서 알아봤어요 ㅎㅎ

지난번 포스팅이 궁금하다면 아래 링크를 참고하세요 

 

[C언어] 파일입출력 1편-스트림(STREAM)에 대한 이해, fopen, fclose

[C/ C++ 프로그래밍 기초 목차] 오랜만에 사용해보는 기본스티커 ㅎㅎㅎ 안녕하세요. 즐거운(?) 주말이예요 ㅎㅎ 오늘은 파일입출력에 관한 포스팅을 들고 왔숩니다. 오랜만에 옛날에 배운걸 더듬더듬하려다 보니..

jhnyang.tistory.com

 

파일 입출력과의 연결다리를 열었으니, 오늘은 그 통로를 이용해서 파일을 읽는데 사용되는 함수를 알아보겠습니다.

fgetc vs fgets

가장 기초적이고 대표적으로 fgets와 fgetc가 있습니다.

다들 유추할 수 있다시피.. f는 파일의 약자이고

get는 가져오는거!

s는 string 즉 문자열을 가져오는거고 c는 character 한 글자만 가져오는 함수입니다.

즉 fgetc = 스트림으로부터 한 글자 즉 하나의 문자만 가져옴, fgets = 스트림으로부터 문자열을 가져옴 


fgetc 함수 알아보기

fgetc

스트림으로부터 한 글자만 읽어오는 함수예요 

헤더파일

헤더파일은 fgets나 fgetc나 둘 다 stdio.h 라이브러리에 속해있어요.

c++일 경우 <cstdio>가 되겠죠?! 

#include <stdio.h>

문법 syntax

int fgetc(FILE* stream);

인자

stream: 저번 시간에 배웠던 스트림을 써주는 공간입니다. 파일을 읽고쓰려면 이 부분에 파일 스트림 역할인 파일 포인터를 써주면 돼요. 만약 표준입출력으로 쓰고 싶으면 표준입력버퍼를 의미하는 stdin을 써주면 됩니다.

리턴값

반환값이 int죠. int그대로 출력하면 해당 글자랑 매핑되는 아스키코드 값 십진수로, char로 타입을 변환해줘서 출력하면 문자로 변환되어 나타납니다. ㅎㅎ


fgets 함수 알아보기 

fgets

스트림으로부터 문자열을 읽어오는 함수예요 

헤더파일

헤더파일은 fgets나 fgetc나 둘 다 stdio.h 라이브러리에 속해있어요.

c++일 경우 <cstdio>가 되겠죠?!

#include <stdio.h>

문법 syntax

char* fgets (char* str, int num, FILE* stream);

fgetc는 하나만 읽어오는거니까 굳이 길이를 지정해줄 필요가 없었지만, fgets는 문자열을 가져오는 거라 문자열을 저장할 공간과 길이를 인자로 따로 받아요 ㅎㅎ

인자

str

: string 형식이 아닌 C 타입 char*타입의 문자열을 인자로 받습니다. 정확히는 문자열을 가리키고 있는 배열의 시작주소 포인터겠죠?ㅎㅎ 스트림에서 읽어다가 요 공간에 문자열을 저장해줄거예요.

num

:길이예요 ㅎㅎ 얼마만큼의 문자열의 길이를 읽을지 지정할 수 있습니다.

문자열의 마지막을 의미하는 널문자까지 포함해서 해당 길이만큼 읽어들입니다. 

stream

:저번 시간에 배웠던 스트림을 써주는 공간입니다. 파일을 읽고쓰려면 이 부분에 파일 스트림 역할인 파일 포인터를 써주면 돼요. 만약 표준입출력으로 쓰고 싶으면 표준입력버퍼를 의미하는 stdin을 써주면 됩니다.

리턴값

char* 타입을 리턴하고 있어요 ㅎㅎ 성공적으로 읽어들였다면 인자로 받았던 str 공간에 읽은 문자열을 넣어서 반환합니다. 오류가 발생하면 NULL이 리턴돼요.


예제로 실습해보기 

▶1. 기본적인 fgetc 사용법, 한 문자 읽기 

test.txt
0.00MB

단순히 'abcdef'가 저장되어 있는 test.txt파일을 만든 후 아래 코드를 실행해볼게요.

#include <stdio.h>
#include <stdlib.h> //exit함수사용을위함
int main()
{
	char fname[20];
	printf("%s\n", "열 파일을 입력해주세요");
	scanf("%s", fname);

	FILE *fpr = fopen(fname, "r");
	if (fpr == NULL)
	{
		printf("%s\n", "ERROR: 파일명을 다시 확인해주세요 ");
		exit(1);
	}

	int first_char = fgetc(fpr);
	printf("fgetc - int: %d, char: %c\n", first_char, first_char);
	
	fclose(fpr);
	return 0;
}

결과

abcdef의 가장 맨 처음에 있는 a를 읽어들였네요. ㅎㅎ int로 출력하면 a의 아스키코드 값에 해당하는 97을 출력하고, char로 출력하면 문자'a'를 출력하는 것을 확인할 수 있어요.

 

▶2. 기본적인 fgets 사용법, 특정 길이만큼 문자열 읽기 

이번엔 fgets를 사용해서 특정 길이만큼 문자열을 읽어봅시다. 다섯글자를 읽어볼까요?

#include <stdio.h>
#include <stdlib.h> //exit함수사용을위함
int main()
{
	char fname[20];
	char str[10];
	printf("%s\n", "열 파일을 입력해주세요");
	scanf("%s", fname);

	FILE *fpr = fopen(fname, "r");
	if (fpr == NULL)
	{
		printf("%s\n", "ERROR: 파일명을 다시 확인해주세요 ");
		exit(1);
	}
	fgets(str, 5, fpr);
	printf("fgets - str :%s\n", str);

	fclose(fpr);
	return 0;
}

fgets 결과값

5글자를 읽었는데, 'abcde'가 아닌, 'abcd'만 읽인 것을 확인할 수 있습니다. 문자열 끝을 알리는 널문자가 맨 뒤에 포함되어야 하기 때문이예요. 즉 fgets는 인자로 부여한 길이(n)보다 하나 작은 n-1만큼 문자열을 읽어들입니다.

물론 5대신, 파일에 적혀있는 글자수보다 더 큰 길이를 입력하면 전체 문자열을 읽어들이고 마지막에 NULL문자를 붙입니다. 

▶3. fgetc로 읽었는데 빈 파일일  경우

위 1번 예제와 똑같은 코드인데, 열 파일에서 빈 파일을 넣워줘봅시다. 이번에 빈파일은 empty.txt로 할게요 

-1을 리턴해줘요 ㅎㅎ. 왜 -1을 리턴해주냐.

파일에는 파일을 끝을 알리는 구분자로 EOF라는 상수가 들어가게 되는데 (규칙이므로 필수입니당. 마치, 문자열 맨 마지막에 NULL을 넣음으로써 문자열의 끝을 알리듯이!)

여기서 EOF라는 상수는 -1의 값을 갖습니다. 즉, 빈 파일이라는건 결국 파일의 끝을 읽었다는 거기도 하죠 ㅎㅎ

▶4. 그럼 fgets로 읽었는데 빈 파일일 경우 어떻게 될까?

문자열이 이쁘게 초기화가 되어있지 않은 경우, 쓰레기값이 들어갑니다.

#include <stdio.h>
#include <stdlib.h> //exit함수사용을위함
int main()
{
	char fname[20];
	char str[10] = { 0x00, }; //---> NULL로 다 초기화
	printf("%s\n", "열 파일을 입력해주세요");
	scanf("%s", fname);

	FILE *fpr = fopen(fname, "r");
	if (fpr == NULL)
	{
		printf("ERROR: 파일명을 다시 확인해주세요 ");
		exit(1);
	}
	fgets(str, 10, fpr);
	printf("fgets - str :%s\n", str);

	fclose(fpr);
	return 0;
}

이렇게 문자열이 저장될 공간을 NULL로 초기화해주면, 

읽어들일 문자가 없으니 NULL을 리턴하고 종료됩니다.

▶5. fgetc 개행 출력 가능

lines.txt
0.00MB

개행이 들어갔을 경우를 한 번 테스트해봅시다.

abc

def

ghi

이렇게 세 줄을 입력해주고 lines.txt라고 저장해줬어요.

#include <stdio.h>
#include <stdlib.h> //exit함수사용을위함
int main()
{
	char fname[20];
	printf("%s\n", "열 파일을 입력해주세요");
	scanf("%s", fname);

	FILE *fpr = fopen(fname, "r");
	if (fpr == NULL)
	{
		printf("ERROR: 파일명을 다시 확인해주세요 ");
		exit(1);
	}

	int c = 0;
	for (int index = 0; index < 10; index++)
	{
		printf("index: %d:", index);
		c = fgetc(fpr);
		printf("%d-%c\n", c, c);
	}
	fclose(fpr);
	return 0;
}

정확히 무엇을 읽었는지 가시적으로 확인할 수 있게, 문자에 매칭되는 아스키코드값도 같이 출력하도록 해봤어요 ㅎㅎ

4번째 loop가 돌았을 때 , 즉 index가 3일때, 아무값도 없는 것처럼 보이지만, 아스키코드값으로 확인해보면 실제 '10'이라는 값이 들어온 것을 볼 수 있어요. 10은 아스키코드표에서 LF (line feed) 개행을 의미합니다.

개행을 읽어들인 후 printf에서 '\n'으로 개행을 한 번 더 출력해줘서 엔터가 두번들어간거예용.

개행을 읽었다고 함수가 종료되지 않고, 그대로 읽어들여서 계속 출력해주는 것을 확인할 수 있어요.

마지막에 널문자를 넣기 위해서 9개 까지만 읽었네요 ㅎㅎ

▶6. fgets 개행 만나면 출력하고 종료!

fgetc와 fgets는 개행에서 차이점이 있어요. 한 번 비교해볼까요?

#include <stdio.h>
#include <stdlib.h> //exit함수사용을위함
int main()
{
	char fname[20];
	char str[100] = { 0x00, };
	printf("%s\n", "열 파일을 입력해주세요");
	scanf("%s", fname);

	FILE *fpr = fopen(fname, "r");
	if (fpr == NULL)
	{
		printf("%s\n", "ERROR: 파일명을 다시 확인해주세요 ");
		exit(1);
	}

	fgets(str, 10, fpr);
	printf("str :%s", str);
	
	fclose(fpr);
	return 0;
}

fgets 개행

분명 10글자를 읽으라고 했는데, abc하고 개행을 읽고 출력이 종료되었어요.

fgets는 개행을 만나면 개행을 출력하면서 종료시켜버린다는 것을 알 수 있죠 ㅎㅎ

7. 표준입출력으로 읽기 

fgets와 fgetc는 스트림을 지정해서 넣어줄 수 있는 함수입니다. 따라서 스트림이 꼭 파일스트림이 아니라 표준입출력 스트림이 될 수도 있어요.

fgetc를 표준입출력 기준이면 getc함수이고 fgets를 표준입출력 스트림으로 넣은게 gets함수인가?

두 함수가 유사한 것은 사실이나 그 차이만 있는 것이 아니예요. ㅎㅎ fgets는 개행을 읽지만, gets는 개행을 읽지 않는 등, 차이가 있으니 조심하시길!

#include <stdio.h>
int main()
{
	char fname[20];
	char str[10] = { 0x00, };
	fgets(str, sizeof(str), stdin);
	printf("str :%s", str);
	printf("c :%c", fgetc(stdin));
	return 0;
}

표준입출력

표준입출력 즉, 콘솔에서 글자 또는 문자열을 읽어들이게 하고 싶을 경우 스트림인자에다가 'stdin'을 넣어주면 됩니다.

8 저장길이보다 입력한 길이가 클 경우 (버퍼 주의) 

입력한 문자열의 길이가 저장길이보다 클 경우 버퍼를 주의해줘야 해요. ㅎㅎ

7번과 똑같은 코드를 이용해주되, 입력을 'abcdefghijklmn' 14글자를 넣어줬어요 ㅎㅎ

근데 코드를 보면 fgets는 str 크기인 10자리만 읽으려 했는데 14자리를 입력으로 넣어준거죠. 

그랬더니 7번 예제와는 다르게, 콘솔에 fgetc 값을 입력해주지도 않았는데 자동으로 10글자 다음인 j를 읽어버렸어요 ㅎㅎ

즉 버퍼에 14자리 들어갔는데, 9자리 + NULL문자를 fgets가 읽고 나머지 5글자는 버퍼에 남겨둔 것을 알 수 있어요.

그리고 버퍼에 남아있는 것을 fgetc가 끌고 온거죠...

새로 입력을 받고 싶으면 fgets와 fgetc사용하기 전에 버퍼를 비워줘야 합니다.

#include <stdio.h>
int main()
{
	char fname[20];
	char str[10] = { 0x00, };
	fgets(str, sizeof(str), stdin);
	printf("str :%s\n", str);
	rewind(stdin); //버퍼비우기
	printf("c :%c", fgetc(stdin));
	return 0;
}

버퍼를 비우는 방안은 여러가지가 있습니다. 그냥 이번엔 그 방법 중 하나가 될 수 있는 rewind함수를 써줌..

버퍼 비우는 코드들에 관해서는 따로 포스팅을 하도록 할게요 

버퍼가 잘 비워져서 다시 입력을 받아 출력하는 걸 확인할 수 있습니다.

▶9 .전체파일 읽고 콘솔에 출력하기 - fgetc

파일을 끝까지 읽는건 파일 입출력의 기본이겠죠 ! ㅎㅎ

#include <stdio.h>
#include <stdlib.h> //exit함수사용을위함
int main()
{
	char fname[20];
	printf("%s\n", "열 파일을 입력해주세요");
	scanf("%s", fname);
	int c = 0;

	FILE *fpr = fopen(fname, "r");
	if (fpr == NULL)
	{
		printf("%s\n", "ERROR: 파일명을 다시 확인해주세요 ");
		exit(1);
	}
	
	while ((c = fgetc(fpr)) != EOF)
	{
		printf("%c", c);
	}

	fclose(fpr);
	return 0;
}

 아까 EOF가 파일의 끝을 의미한다고 했죠?! 파일의 끝을 나타내는 구분자를 만날때까지 쭉 한글자씩 입력받아서 바로 출력해주는 코드예요. 만약 파일 전체를 읽는게 아니라 라인 단위, 행단위로만 읽고 싶다면 EOF대신 개행을 의미하는 '\n'을 넣어주면 되겠죠 ㅎㅎ

feof함수를 사용할 수도 있고 방법은 많습니당.

▶10. 전체파일 읽고 콘솔에 출력하기 - fgets

일단 문자열의 길이를 100으로 잡아주었어요. 100으로 잡든 200으로 잡든... 400으로 잡든,, 상관 없습니당.

#include <stdio.h>
#include <stdlib.h> //exit함수사용을위함
int main()
{
	char fname[20];
	char str[100];
	printf("%s\n", "열 파일을 입력해주세요");
	scanf("%s", fname);
	
	FILE *fpr = fopen(fname, "r");
	if (fpr == NULL)
	{
		printf("%s\n", "ERROR: 파일명을 다시 확인해주세요 ");
		exit(1);
	}

	while (fgets(str, 100, fpr)) {
		printf("%s", str);
	}
	fclose(fpr);
	return 0;
}

만약 개행이 중간에 나왔다면 개행까지 입력받고 중단시키고 다시 반복문이 돌아 fgets로 다음걸 또 입력받아 출력시키겠죠.

중간에 개행이 안나온다면 100글자를 다 입력받아 출력시킨 후, 다음 루틴을 또 돌게될터이나...

파일 끝에 다다르면 null포인트가 반환됩니다. 그러면서 반복문을 종료시키게 돼요 ㅎㅎ fgetc처럼 따로 EOF를 조건에 포함시키지 않아도 돼용

주의할 점, 같이 알아두면 좋을점

◆ fgetc의 리턴 값은 기본적으로 char가 아닌 int형으로 아스키코드값을 반환하지만, 출력을 char로 지정해 자동 타입변환을 해주면 문자그대로 출력할 수 있다.

◆ fgetc는 개행도 문자처럼 읽고 출력해주는 반면, fgets는 개행을 만나면 출력후 함수를 종료시켜버리는 차어짐어 있다. 따라서 fgets는 라인을 출력해주는 함수라고 불리기도 한다.

EOF는 파일 끝을 알리는 구분자로, 상수 -1과 동일하다.

개행을 나타내는 LF (Line feed)는 아스키코드값 10을 갖는다.

공백이나 개행, 또는 제어문자 등은 출력했을 때 값이 없거나 이상한 문자를 갖는다. 따라서 정확한 값을 확인하고 싶으면 아스키코드 숫자값으로 판단하는 것이 좋다.

fgetc나 fgets나 버퍼 값을 읽어들이기 때문에 버퍼 문제가 남는다. 함수를 사용할 때 버퍼 비움을 신경써줘야 한다.

버퍼 비움에는 역으로 fgets를 한번 더 써서 비워줄 수도 있다. (버퍼를 읽어들이는 함수이기 때문)

fetc, fgets모두 stdin이라는 스트림을 인자로 지정해줌으로써 콘솔로부터 입력을 받을 수 있다.

◆ 빈 파일을 읽었을 경우 fgetc는 EOF를 의미하는 -1을 반환하지만, fgets는 널포인터를 반환한다.

◆ (fgetc-getc), (fgets-gets) 이렇게 쌍쌍의 함수들이 존재하여 file을 읽을 수 있는지 아닌지 여부 빼고 똑같을 것 같은 느낌이 있지만, 그 외의 차이점이 있으니 조심할 것.

◆ 참고로 getc와 fgetc의 차이점을 말하자면, getc의 경우 매크로로 정의될 수 있지만, fgetc의 경우 매크로로 사용할 수 없습니다. 완전 없다기보단 좀 더 정확히는

출처: https://stackoverflow.com/questions/18480982/getc-vs-fgetc-what-are-the-major-differences

라네요..ㅎㅎ

◆ fgetc fgets가 입력함수라면 출력을 대변하는 fputc fputs 함수도 있습니당

 

도움이 되셨다면 공감, 댓글 또는 광고보답은 어떠신가요?!

더 나은 질좋은 포스팅을 작성하는데 큰 힘이 됩니다. 감사합니다.