C언어의 다양한 출력 문자들

C언어에서 다양한 출력 형식을 지원합니다. 우리가 너무나 잘 알고 있는 부호있는 정수형은 %d, 문자열 출력은 %s 등이 그 출력형식인데요. 오늘은 자세하게 한번 총 정리하는 포스팅을 정리하도록 하겠습니다. 마지막에는 단순 화면에 출력하는 것이 아닌 변수에 저장하는 방법, 파일에 출력하는 방법을 알아보도록 하겠습니다. 더불어 이 형식들과 소개한 함수를 이용해서 ls가 출력하는 형식처럼 일정한 간격으로 보기 좋게 문자열을 출력하는 방법도 소개합니다.

 

출력 문자(Format Character)

출력 형식 설명 출력 예
%d 우리가 흔히 알고 있는 부호 있는 정수형을 출력해줍니다. printf("%d",-150); -150
%c 문자열 하나를 출력해줍니다. printf("%c",'A');  A
%p 주로 주소를 출력합니다. 메모리 크기만큼 자릿수가 채워집니다. 출력될때는 16진수로 표시됩니다. int a;
printf("%p",&a);
0177F95C
%x 정수를 16진수로 출력합니다. %x를 보시면 소문자인데, 16진수로 표시될때 알파벳은 소문자로 표시됩니다. printf("%x",10); a
%X 위의 %x와 동일하나 알파벳이 대문자로 표시됩니다. printf("%X",10); A
%o 8진수로 출력합니다. printf("%o", 8888); 21270
%s 문자열을 출력합니다. '\0'인 NULL문자를 만날때까지 출력이 됩니다. printf("%s","hello, world"); hello, world
%u 부호없는 정수 출력입니다. 즉, 양수를 출력하는 포맷입니다. signed bit을 정수 데이터로 취급한다는 것이죠. 만약 -1이라는 정수가 2의 보수를 거치는 과정은 이렇습니다.  0000 0000 0000 0000 0000 0000 0000 0001 -> 1111 1111 1111 1111 1111 1111 1111 1110 + 1-> 1111 1111 1111 1111 1111 1111 1111 1111 = 4294967295 이 되죠. 부호가 있다면 -1이지만 부호없으면 4294967295라고 이렇게 표시가 된다는 뜻이에요. printf("%u",1);
printf("%u",-1);
1
4294967295
%ld 부호있는 long 형 정수 출력입니다. printf("%ld", 1L); 1
%lld 부호있는 long long 형 정수 출력입니다. printf("%lld", 5294967295); 5294967295
%lu 부호없는 long 형 정수 출력입니다. printf("%lu", -12345); 4294954951
%llu 부호없는 long long 형 정수 출력입니다. printf("%llu",-12345); 18876570244468679
%f, %lf 두 개 모두 실수형을 출력합니다. 6자리까지 출력이 되면 그 이하는 반올림처리됩니다. 모자라면 0으로 채웁니다.
출력할때는 상관없지만 입력 받을때는 float 자료형에 넣을시 %f, double에 입력받을때는 %lf를 사용합니다.

float f;
double d;
scanf("%f",&f);
scanf("%lf",&d);
--------------------------
printf("%f",0.1234f);
printf("%f",0.1234567f);
0.123400
0.1234567
%e 실수를 지수 표기법으로 소문자로 표시합니다.  printf("%e", 0.11223344f); 1.122334e-01
%E 실수를 지수 표기법으로 표시할때 대문자를 사용합니다. printf("%E", 0.11223344f); 1.122334E-01

 

형식 정렬 (Format Alignment)

'%' 문자와 포맷 문자(d, x, u 등) 사이에 부호와 숫자를 넣을 수 있는데, 숫자는 공간을 의미합니다. 만약 공간이 남으면 빈 자리 만큼 공백이 생깁니다. 이때 하고 부호('-')가 있으면 왼쪽 정렬, 아니면 오른쪽 정렬입니다. 예를 보면서 확인하세요.

출력 형식 출력 설명
printf("%-4d, %d, %4d", 10,11,12); 10  , 11,   12 4자리 공간을 확보하며 10은 왼쪽 정렬, 12는 오른쪽 정렬을 하여 출력하니다.
printf("%-4d, %d, %4d", 10000,11000,12000); 10000, 11000, 12000 4자리를 넘어갈 경우 일반 출력과 같습니다.
printf("%8x",0xFFEE)     ffee 8자리 확보하고 오른쪽 정렬합니다.
printf("%08x",0xFFEE) 0000ffee 8자리를 확보하고 빈 공간은 0으로 채웁니다.
printf("%02x %02x %02x", 0xE, 0xF1, 0xDEAD);  0e f1 dead 2자리를 확보합니다. 빈자리는 0으로 채우고 넘는다면 일반 출력과 같습니다.
printf("%.3f", 3.14f);
printf("%.3f", 3.141592f);
3,14
3.142
소수점 이하 3자리까지 출력합니다. 자리수가 넘어가지 않을 경우 0으로 채우며 넘어가면 3자리에서 반올림하여 출력합니다.
printf("%10.3f", 3.14f);      3.140 10자리 확보한 이후 수를 오른쪽 정렬로 출력합니다. -는 왼쪽 정렬이겠죠?
printf("%10s", "reakwon");    reakwon 10자리 확보 후 오른쪽 정렬로 문자열을 출력합니다.

 

n진수 문자 포함 출력

8진수나 16진수일때 n진수 문자를 앞에 표기할때 문자 '#'을 사용합니다. 여기서는 16진수만 예를 들어보겠습니다.

출력 형식 출력 설명
printf("%#x", 0xFF11); 0xff11 16진수 표기법인 0x까지 출력합니다.

 

%기호를 자체 출력

%는 정말 문자 그대로 출력하려면 %% 두번쓰면 됩니다. 

출력 형식 출력 설명
printf("%%JAVA_HOME%%bin"); %JAVA_HOME%bin %를 문자로 출력했습니다. %%를 사용하였죠.

 

우리는 단순히 화면에 출력했었죠? 하지만 가끔 프로그래밍을 하다가 보면 화면에 출력하는 방식이 아닌 변수에 담거나, 파일에 직접써야하는 일이 생깁니다. 그럴때 유용한 함수들이 존재하는데 여기서 알아볼 함수는 sprintf fprintf입니다. 

sprintf - 문자 배열에 형식 문자열 write

만약 콘솔에 출력하는 대신 일반 문자열에 저장하려면 어떻게 할까요? 이런 기능을 맡는 함수가 sprintf 함수입니다. sprintf의 첫 인수는 저장할 문자 버퍼(문자 배열), 두번째는 포맷 문자열, 세번째는 형식에 대응되는 데이터들입니다.

	char str[128];
	sprintf(str, "%d + %d = %d", 10, 20, 10 + 20);
	printf("%s\n", str);
10 + 20 = 30

 

sprintf 예제 ) 문자열을 일정한 간격으로 출력

리눅스 ls명령을 보면 신기하게도 문자열을 고른 간격으로 출력하는 것을 볼 수 있죠. 신기하지 않았나요? 나만 그런가.. 여기서 sprintf를 사용하여 흉내낼 수 있습니다. 

 

어떻게 흉내낼까요? 아래의 코드가 흉내내는 코드입니다. 설명은 주석으로 충분할 것 같습니다.

#include <stdio.h>
#include <string.h>
int main() {
	int i;
	char name[][100] = { "reakwon","kim","john","lee","yu","loooooooooooooooong","hello!","good!" };
	char format[128];	//출력 형식을 담을 문자열
	int longest = 0;	//가장 긴 문자열의 길이
	
	for (i = 0; i < 8; i++) {
		int len = strlen(name[i]);
		if (len > longest)		
			longest = len;
	}

	// '%' + '-' + %u = longest+3] + s -> %-[longest+3]s : 왼쪽 정렬된 문자 공간 longest+3 자리 확보
	sprintf(format, "%%-%us", longest+3);	
	for (i = 0; i < 8; i++) {
		if (i != 0 && i % 2 == 0) printf("\n");
		printf(format, name[i]);
	}

}

 

일정한 길이의 문자열 출력

 

fprintf - 파일에 형식 문자열 출력

콘솔에 출력하는게 아니라 파일에 출력하려면 fprintf함수를 사용하면 됩니다. sprintf와 사용법은 거의 비슷한데 첫 인자를 파일로 지정하면 됩니다. 어려운것은 없죠?

	FILE* fp = fopen("file.txt", "w");

	fprintf(fp, "%s, %s", "reakwon", "tistory");
	fclose(fp);

 

fprintf 사용법

 

여기까지 C언어에서 지원하는 출력 형식(format)을 알아보았고 콘솔에 출력하지 않고 문자 배열에 저장하는 함수인 sprintf와 파일에 출력해주는 유용한 함수 fprintf에 대해서 알아보았습니다. 유용한 함수이고 어려운 것은 없으니 기억해두셔서 적재적소에 쓰시면 되겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

DecimalFormat 클래스

우리의 일상에서 가장 많이 쓰이는 진수, 10진수를 형식화하는 역할을 하는 클래스가 JAVA에서 DecimalFormat이라는 클래스입니다. 이 클래스는 NumberFormat을 상속하고 있는 클래스이죠. 10진수를 다양한 형식에 맞게 출력해줄 수 있습니다. C언어에서는 이미 fprintf 등이 그 역할을 담당하고 있죠. 이제부터 어떻게 사용되는지 배워보도록 하겠습니다.

 

Decimal Pattern 적용

DecimalFormat은 java.text 패키지안에 존재하므로 따로 import를 해야합니다.  DecimalFormat은 문자 '#'과 '0'이 숫자를 나타내는데 쓰이며 쉼표(,), 마침표(.), 대시(-) 등으로 숫자 형식을 나타낼 수 있습니다. 많이 사용패턴은 아래의 표에 정리해놓았습니다.

 

 

Format 설명
0 10진수, 값이 없는 자리는 0으로 채움
# 10진수, 값이 없는 자리는 나타나지 않음
. 소수점을 이하 나타냄
- 음수 부호를 나타냄
, 단위 구분자를 나타냄
E 지수 기호를 나타냄, E 이후 0를 써서 표현(ex E0)
% 퍼센트 기호
' escape문자, 만약 #을 문자로 나타내고 싶다면 '#' 으로 표현
그외 문자 문자로 취급

 

 

DecimalFormat을 사용하는 방법은 생성자를 통해서 형식을 지정해주는 방법이 있습니다. 아래처럼 말이죠.

DecimalFormat format=new DecimalFormat("###,###.#######");

 

또는 applyPattern을 사용하여 패턴을 적용할 수 있습니다.

format.applyPattern("###,###.#######");

 

 

 

중요한것은 우리가 패턴을 적용하고 난 후 형식에 맞는 문자열을 뽑아와야합니다. 이때 format이라는 메소드를 사용하지요. 아래와 같이 사용합니다.

double n = 11223344.5678;
String formattedStr = format.format(n);

 

패턴과 사용예

DecimalFormat의 사용법은 어렵지 않습니다. 주로 사용하는 방식은 패턴을 지정하고 난 후 format으로 지정된 형식을 가진 숫자 형식 문자열을 가져오는 용도가 대부분입니다. 아래의 코드를 통해서 어떻게 출력되는지 확인해보세요.

public static void main(String[] ar){
		
	DecimalFormat format=new DecimalFormat();
		
	String patterns[]= {
			"0",
			"#",
			"0.0",
			"000.000",				//소수점
			"000,000,000.0",
			"000,000,000.000",
			"000,000,000.000000",	//숫자가 나타나지 않는 경우 나머지 빈자리를 0으로 채워줌
			"#,#,#,#.###",			//한글자씩 ,이 붙어서 나옴
			"###,###,###.#",
			"###,###,###.###",
			"###,###,###.######",
			"-###,###,###.######",	//숫자가 나타나지 않는 경우 출력하지 않음
			"###.##E0",				//지수 형식으로 출력
			"my number: ###.##%",	//my number라는 문자열이 합쳐짐
			"'#' ###,###.####",		//escape로 #을 문자화
			"'0' 000,000.00000000",	//escape로 0을 문자화
			"###,###.000000000"	//섞어서도 쓸 수 있음
	};
		
	double number=1234123123.1234;
		
	for(int i=0;i<patterns.length;i++) {
		format.applyPattern(patterns[i]);
		System.out.println("[pattern "+patterns[i]+"] "+format.format(number));
	}
		
}

 

결과

 

 

 

[pattern 0] 1234123123
[pattern #] 1234123123
[pattern 0.0] 1234123123.1
[pattern 000.000] 1234123123.123
[pattern 000,000,000.0] 1,234,123,123.1
[pattern 000,000,000.000] 1,234,123,123.123
[pattern 000,000,000.000000] 1,234,123,123.123400
[pattern #,#,#,#.###] 1,2,3,4,1,2,3,1,2,3.123
[pattern ###,###,###.#] 1,234,123,123.1
[pattern ###,###,###.###] 1,234,123,123.123
[pattern ###,###,###.######] 1,234,123,123.1234
[pattern -###,###,###.######] -1,234,123,123.1234
[pattern ###.##E0] 1.2341E9
[pattern my number: ###.##%] my number: 123412312312.34%
[pattern '#' ###,###.####] # 1,234,123,123.1234
[pattern '0' 000,000.00000000] 0 1,234,123,123.12340000
[pattern ###,###.000000000] 1,234,123,123.123400000

 

이상으로 간단하게 DecimalFormat클래스를 소개했고 사용법을 알아보았습니다. 워낙 어렵지 않은 형식 클래스이고 편리하게 십진수를 표현할 수 있으므로 적어도 #과 0의 차이와 format() 메소드만 알고 있으면 무난히 사용할 수 있겠습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

C언어 파일 입출력

 

C언어에서 scanf와 printf 함수를 통해서 키보드로 입력을 받고 모니터로 출력해주는 그런 프로그램들을 많이 보았을 겁니다. 이런 키보드나 모니터같은 입출력 장비를 콘솔이라고 합니다. 그래서 콘솔 입출력을 해왔던 것이죠.

여기서는 파일 입출력에 대해서 설명합니다. 사실 콘솔 입출력과는 별로 다를바가 없습니다. 단지 그 대상이 모니터나 키보드가 아닌 파일이기 때문이죠. 본격적으로 파일 입출력을 설명하기 전에 우리는 스트림에 대한 개념을 먼저 알아야합니다. 

> 그전에 왜 C 표준입출력을 사용하나요?

리눅스를 배우셨던 분들은 open, read, write, close를 이용해서 파일을 다뤄보셨을 겁니다. 그때는 open에 필요에 따라 여러 플래그들을 줄 수가 있는데요. 예를 들어 O_RDONLY, O_CREAT 등 말이죠. 이거 구차하게 일일히 헤더 추가한 다음에 파일 디스크립터를 가져와서 write, read하는 것을 C 표준입출력 라이브러리에서는 stdio.h만 포함해서 사용할 수 있습니다.  아주 개꿀이라는 얘기죠. 그리고 변태같은 플래그들을 포함하지 않아도 사용하기에 적합한 플래그들을 미리 조합해놨기 때문에 상큼하게 그걸 사용하면 됩니다. 또한 내부적으로 버퍼를 사용하기 때문에 read, write 함수들을 최적으로 사용하게 됩니다.

 

스트림(Stream)

영어를 그대로 직영하게 되면 흐름이라는 건데요. 비슷하게 생각하시면 됩니다. 프로그램에서 파일이 열리면 C표준입출력은 스트림(stream)이라는 파일과 프로그램 사이의 추상적인 흐름이 일어나는 파이프를 생성합니다. 그래서 파일이 열리게 되면 개념적으로 스트림을 통해서 파일에 기록하거나 읽을 수 있습니다. 만약 파일을 읽기만 하겠다하면 읽기 전용의 스트림을 여는 것이고, 파일을 쓰기만 할 것이라면 쓰기 전용의 스트림을 열어서 거기에 기록을 하면 됩니다. 

그래서 아래와 같이 어떤 프로그램에서 File이라는 이름의 파일을 읽고 쓰기 위해서 스트림을 열면 아래와 같은 상황이 발생하게 됩니다. 그래서 바이트 단위던, 줄 단위던 입력이 흐름이 가능한 상태가 됩니다.

 

기본적으로 보통 프로그램에서는 3개의 스트림이 열려있습니다. 바로 표준 입력 스트림(stdin), 표준 출력 스트림(stdout), 표준 에러 스트림(stderr)입니다. 이 3개는 콘솔에 대해서 열려있는 스트림들입니다. 

 

stdin, stdout, stderr

키보드로 입력받고, 모니터로 출력하는 것도 C표준 입출력에서는 스트림으로 간주하게 됩니다. 그래서 우리가 stdin을 통해서 입력을 받는다면 키보드를 통해서 입력을 받는 것이고, 표준 출력 스트림으로 출력한다면 모니터 화면에다가 출력이 되는 겁니다. 그래서 파일 대신 모니터와 키보드가 스트림 끝에 놓여있는 것을 보세요.

 

 

파일의 종류 ( 텍스트 파일 , 이진 파일)

파일을 사람이 쓰고 읽냐, 컴퓨터가 쓰고 읽냐에 따라서 텍스트 파일(text-file), 이진 파일(binary-file)로 나누게 됩니다. 이와 같은 구분은 문자열로 입출력을 하느냐, 아니면 바이너리로 입출력을 하느냐를 위해서 구분합니다. 맨 처음 파일에 대해서 스트림을 생성할 때 결정이 됩니다.

 

1. 파일 열기 fopen

파일 함수는 표준입출력(stdio.h) 헤더파일에 존재합니다. 파일에 어떤 데이터를 읽고, 쓰고, 추가하려면 일단 파일을 열어야겠지요. 함수를 한번 보시죠.

FILE *fopen(const char *filename, const char *mode);

filename : 파일명을 말합니다. 절대 경로나 상대 경로로 줄 수 있습니다. 상대 경로는 그 프로젝트 위치를 기준으로 합니다.

mode : 파일을 어떤 방식으로 열건지 정합니다. 스트림 방식을 정하는 겁니다. 입력 스트림인지, 출력 스트림인지.

  -동작 모드

 모드  설명 flag 조합
 r(read)  1. 파일을 읽기 전용으로 엽니다. 
 2. 파일이 있어야합니다.
O_RDONLY
 w(write)  1. 파일을 쓰기 전용으로 엽니다.
 2. 주의해야합니다. 파일이 존재한다면 기존의 내용을 지우고 쓰기 때문이죠.

 3. 파일이 없으면 새로 생성합니다.
O_WRONLY | O_CREAT | O_TRUNC
 a(append)  1. 파일이 있으면 파일의 끝에 내용을 추가합니다.
 2. 파일이 없으면 생성해서 내용을 추가합니다.
O_WRONLY | O_CREAT | O_APPEND
 r+  1. 파일을 읽고 쓰기 위해 엽니다.
 2. 파일이 반드시 있어야 합니다.
O_RDWR
 w+  1. 파일을 읽고 쓰려고 엽니다.
 2. r+와 다르게 파일이 있는 경우 내용을 덮어쓰고 없으면 생성해서 데이터를 씁니다. 
O_RDWR | O_CREAT | O_TRUNC
 a+  1. 파일을 읽고 갱신하기 위해 엽니다.
 2. 파일이 없으면 생성해서 데이터를 추가합니다.
O_RDWR | O_CREAT | O_APPEND

 

  - 이진 또는 텍스트 모드(t, b)

텍스트모드가 기본(default)입니다. 이진 모드로 파일을 열려면 b를 추가합니다. 

ex) 이진모드로 읽기 위해 파일을 open -> rb

파일을 여는 데 성공했다면 그 파일에 대한 포인터를 return합니다.

하지만 파일을 여는 데 실패했으면 NULL을 반환하죠.

 

2. 파일 닫기 fclose

무엇이든 열었으면 닫는 것이 원칙이죠. 파일 스트림을 닫으려면 fclose를 사용하시면 됩니다.

int fclose(FILE *stream);

그냥 열었던 파일 포인터를 집어넣으면 됩니다. 성공하면 0을 반환하고 실패하면 EOF(-1)를 반환합니다.

 

3. 텍스트 파일 읽기 함수 

파일은 두 종류의 파일이 있다고 했죠? 사람이 읽을 수 있는 텍스트 형식의 파일과 컴퓨터가 읽고 처리하는 바이너리 파일, 즉 이진 파일이 있습니다. 우선 텍스트 파일을 읽는 함수는 쓰임새에 따라 여러가지가 있습니다.

    3.1 한문자 읽기 : fgetc, getc, getchar

#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

fgetc와 getc는 같은 기능을 하는 함수입니다. getchar() 함수는 키보드용 한문자 입력을 받는 함수와 같아서 getc(stdin)과 같습니다. 

getc = getc , getc(stdin) = getchar()

stream에서 한 글자를 읽어오는 함수이며, 일반적으로 반환형은 한 글자의 ASCII값인 정수형 값입니다. 파일의 끝에 도달할 시에 EOF를 return합니다. EOF는 End-Of-File로 -1입니다. 이렇게 반환형이 (signed) int인 이유는 이 EOF를 반환받기 위해서입니다. 

    3.2 한 줄 읽기 : fgets

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);

stream에서 문자 한줄을 읽어올때 사용하는 함수이며 size 이하의 문자 한줄을 s로 읽어옵니다. 이때 개행문자까지 읽어옵니다. 그래서 개행문자('\n') 다음 문자의 끝을 나타내는 문자인 NULL('\0')이 붙습니다. 간단히 사용법을 확인해볼까요? 다음은 stdin으로 콘솔(키보드)로부터 입력을 받는 단순한 예제입니다.

//fgets_test.c
#include <stdio.h>
#include <string.h>

#define BUF_SIZE 32
int main(){

        char buf[BUF_SIZE] = {0,};

        printf("입력:");
        fgets(buf, BUF_SIZE, stdin);

        printf("출력:");
        printf("%s(%ld)", buf, strlen(buf));
}
# ./a.out 
입력:hello world
출력:hello world
(12)#

여기서 보이는 "hello world"의 문자열 길이는 공백을 포함해서 11글자이지만, 개행문자를 포함했기 때문에 12글자가 되고, 문자 길이도 한 줄 밑에 출력이 되었네요. 

    3.3 서식화된 파일 입력 : fscanf

#include <stdio.h>
int fscanf(FILE *stream, const char *format, ...);

키보드 입력에 대해서 입력 포맷팅 함수는 scanf였죠? 파일에 대해서 포맷팅 함수는 fscanf입니다. 

 

4. 텍스트 파일 쓰기 함수  

텍스트 파일에 쓰는 함수는 아래와 같습니다. 위의 텍스트 파일 읽기 함수의 네이밍을 따라갑니다. 함수에 대한 소개만하고 넘어가도록 합시다. 

    4.1 한문자 쓰기 : fputc, putc, putchar

#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

   putchar(c)는 putc(stdout, c)와 같습니다.

    4.2 한 줄 쓰기 : fputs, puts

#include <stdio.h>
int fputs(const char *s, FILE *stream);
int puts(const char *s);

    4.3 서식화된 파일 출력 : fprintf

#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);

 

예제 - 텍스트 데이터 저장, 읽어오기

//writer.c

#include <stdio.h>
#include <string.h>

#define NUM 3

typedef struct _student{
        char name[16]; //이름
        unsigned int age; //나이 
        unsigned int id; //학번
} student;


int main(){
        int i;
        student s[NUM] = {
                {"park", 18, 1234},
                {"jung", 18, 1235},
                {"kim", 19, 1111}
        };
        FILE *fp;

        //텍스트 파일, 없으면 새로운 파일 생성, 있으면 내용 덮어쓰기(w+)
        fp = fopen("info.txt", "w+");
        if(fp == NULL) {
                printf("fopen error\n");
                return 1;
        }

        for(i = 0;i < NUM; i++){
                fprintf(fp, "%s %d %d\n",
                        s[i].name, s[i].age, s[i].id);
        }
        fclose(fp);
}
//reader.c
#include <stdio.h>

#define NUM 3

typedef struct _student{
        char name[16]; //이름
        unsigned int age; //나이 
        unsigned int id; //학번
} student;

int main(){
        int i;
        FILE *fp;
        student s[NUM]; 

        //테스트 파일 읽기 전용
        fp = fopen("info.txt", "r");
        if(fp == NULL){
                printf("fopen error\n");
                return 1;
        }
        for(i = 0; i < NUM; i++){
                fscanf(fp,"%s %d %d", 
                                s[i].name, &(s[i].age), &(s[i].id));

                printf("[%d]\n", i);
                printf("name : %s, age : %u, id : %u\n",
                                s[i].name, s[i].age, s[i].id);
        }
        fclose(fp);
}
# gcc reader.c -o reader
# ./writer
# cat info.txt
park 18 1234
jung 18 1235
kim 19 1111

writer만 실행해보면 info.txt가 생겨났고 그 내용은 이렇게 적혀있습니다. 사람이 알아볼 수 있죠?

# ./reader 
[0]
name : park, age : 18, id : 1234
[1]
name : jung, age : 18, id : 1235
[2]
name : kim, age : 19, id : 1111

reader라는 프로그램으로도 아주 잘 읽을 수 있습니다.

 

5. 이진 파일 읽기 fread

파일을 읽는 함수는 fread입니다. 프리드라고 읽지마세요 제발. 앞에 f는 모두 file의 f입니다. 앞에 f가 붙은 함수는 거의 다 파일에 대한 함수라는 것을 기억하세요.  

#include <stdio.h>
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);

 

stream으로부터 자료형인 size를 count만큼 읽어서 buffer에 저장합니다. buffer가 void*인 이유는 어떤 자료형이건 받아와야하기 때문입니다.  파일을 읽은 길이(count)만큼 반환합니다. 

만약에 단순 바이너리를 읽는다면, 그러니까 바이트 단위를 읽는다면 size는 1입니다. 그러면 만약 크기 16바이트인 구조체를 3개를 읽는다면 아래와 같이 호출이 됩니다. 

fread(buffer, 16, 3, fp);

6. 이진 파일 쓰기 fwrite

파일에 쓰는 함수입니다. fread와는 반대 기능이죠.

#include <stdio.h> 
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);

buffer에 담긴 내용을 기록하는데 size만큼의 count 만큼 버퍼로부터 stream쪽으로 씁니다. 성공하면 count를 return하고 실패한다면 count가 아닐 수 있습니다.

 

예제 - 이진데이터 구조체 저장, 읽어오기

이진 파일을 사용할 수 있는 가장 큰 장점은 모든 자료를 이진데이터로 쓸 수 있다는 점입니다. 객체(구조체)도 그냉 냅다 쓸 수 있습니다. 모든 것을 이진 데이터로 쓰기 때문이지요. 다음은 구조체를 파일에 쓰고, 그 파일로부터 읽어오는 예제를 보여줍니다.

//info_writer.c
#include <stdio.h>

#define NUM 3

typedef struct _student{
        char name[16]; //이름
        unsigned int age; //나이 
        unsigned int id; //학번
} student;

int main(){
        FILE *fp;
        student s[NUM] = {
                {"kim", 16, 1234},
                {"lee", 16, 1235},
                {"lim", 17, 1111}
        };

        //이진(b)으로 쓰기용, 없으면 만들고 있으면 덮어쓴다(w+)
        fp = fopen("info.bin", "wb+");
        if(fp == NULL){
                printf("fopen error\n");
                return 1;
        }

        if(fwrite(s, sizeof(student), NUM, fp) != NUM){
                printf("fwrite erorr\n");
                fclose(fp);
                return 1;
        }

        printf("Student Information Saved OK \n");
        fclose(fp);
}

 

//info_reader.c
#include <stdio.h>

#define NUM 3

typedef struct _student{
        char name[16]; //이름
        unsigned int age; //나이 
        unsigned int id; //학번
} student;

int main(){
        int i;
        FILE *fp;
        student s[NUM]; 

        fp = fopen("info.bin", "rb"); //읽기 전용
        if(fp == NULL){
                printf("fopen error\n");
                return 1;
        }

        if(fread(s, sizeof(student), NUM, fp) != NUM){
                printf("fread erorr\n");
                fclose(fp);
                return 1;
        }

        for(i = 0; i < NUM; i++){
                printf("[%d]\n", i);
                printf("name : %s, age : %u, id : %u\n",
                                s[i].name, s[i].age, s[i].id);
        }
        fclose(fp);
}

 

# gcc info_writer.c -o writer
# gcc info_reader.c -o reader
# ./writer 
Student Information Saved OK 
# ./reader 
[0]
name : kim, age : 16, id : 1234
[1]
name : lee, age : 16, id : 1235
[2]
name : lim, age : 17, id : 1111

 

7. 버퍼 

버퍼는 C표준입출력에서 입력과 출력을 효율적으로 처리하기 위한 일종의 저장공간입니다. 내부적으로 write, read를 적시에 한번만 호출하기 위한 것이 목적입니다. 그런데 이러한 버퍼의 처리 방식을 잘 모르면 낭패를 볼 수 있는데요. 아래의 코드를 봅시다.

//buffer.c
#include <stdio.h>

int main(){

        char c;

        printf("아무 글자나 하나 입력:");
        scanf("%c", &c);
        printf("입력받은 글자 : %c\n", c);

        printf("다시 입력 : ");
        scanf("%c", &c);
        printf("입력받은 글자 : %c\n", c);
}

 

실행하게 되면 두 번재 scanf에 입력을 주기도 전에 프로그램이 끝나게 됩니다. 분명 scanf를 통해서 한글자 입력을 받는 코드를 작성했으에도 말이죠. 

# ./a.out 
아무 글자나 하나 입력:H
입력받은 글자 : H
다시 입력 : 입력받은 글자 : 
#

 

이 프로그램은 내부적으로 이렇게 동작하게 됩니다. 'H'라는 문자를 입력하면 내부적으로 엔터에 해당하는 개행 문자 '\n'도 입력이 됩니다.

 

결국 버퍼에는 H와 '\n'이 입력이 되게 되며 변수 c에는 'H'가 담기게 되겠죠. 버퍼에 남아있는 건 개행문자 '\n'입니다. 그래서 다음 scanf는 이 개행문자를 입력받아 입력이 끝나게 되는 겁니다. 

 

위는 줄 단위 버퍼링의 사례로 결국에는 남아있는 버퍼를 비워줘야합니다. 버퍼를 비워주는 방법에는 여러 가지 방법이 있는데요.

7.1 버퍼를 비우는 방법들

- 간단한 방법은 단순히 문자 하나 입력받는 거죠. 아래와 같이말이죠. 

printf("아무 글자나 하나 입력:");
scanf("%c", &c);
getchar();
printf("입력받은 글자 : %c\n", c);

printf("다시 입력 : ");
scanf("%c", &c);
getchar();
printf("입력받은 글자 : %c\n", c);

 

- fflush 함수 사용

#include <stdio.h>
int fflush(FILE *stream);

fflush 함수를 사용할 수가 있는데, 이 방법은 표준이 아니므로 권장되지 않습니다. 실제 제 ubuntu시스템에서는 동작하지 않습니다.

 

- scanf에서 공백 사용

scanf("%c", &c) -> scanf(" %c", &c)

 

앞에 공백 문자 하나를 넣어주세요. 

 

- 개행문자가 나올때까지 제거 

while(getchar() != '\n');

어떤 시스템에는 \r\n으로 개행합니다. 그게 윈도우즈인데, 이럴 때는 getchar()만 사용하게 되면 \r만 제거 됩니다. 그래서 아예 \n까지 제거 할 수 있도록 while문을 도는 방식을 사용할 수 있습니다. 약간 고급진 말로 '\r'은 커서를 맨 앞으로 돌리는 CR(Carriage return)이라 하며 '\n'은 커서는 그자리이며 라인만 바꾸는 LF(Line Feed)라고 합니다.  

반응형
블로그 이미지

REAKWON

와나진짜

,