10.C언어 표준 라이브러리

본장에서는 C 언어에서 제공하는 기본 함수들에 대한 설명입니다. 지금까지는 C 언어 문법에 대하여 공부하였습니다. 이제 C언어에서 제공하는 여러 함수들에 대한 내용과 그 함수들을 이용하는 방법에 대해서 설명하겠습니다. C에서 제공하는 함수들은 보통 표준 파일입출력 함수, 메모리관련함수,문자열 처리함수, 수학함수, 표준 입출력함수등이 있습니다. 이중에서 표준 입출력함수 및 파일입출력함수는 다음장에서 설명을 하며 메모리 관련함수와 문자열처리함수,수학함수 등에 대해서 본장에서 설명을 합니다.

라이브러리 만들기

라이브러리란 자주사용하는 함수들을 다시 재사용할수 있도록 하기위한 함수 묶음을 말합니다. 예를 들어서 덧셈,뺄셈,나눗셈,곱셈을 하는 함수를 A프로그램제작시에도  사용을 하고  B프로그램 제작시에도 사용을하고 등등 여러 프로그램에서 사용한다고 가정을 합시다. 그렇다면 덧셈,뺄셈,나눗셈,곱셈을 A프로그램 만들 때 새로 만들고 B프로그램 제작시에 새로만들고 C프로그램 만들 때 또새로 제작한다면 이것은 낭비입니다. 즉 덧셈,뺄셈,나눗셈,곱셈을 하는 함수를 만들어서 이것을 라이브러리화 하여 A,B,C 등등의 프로그램에서 호출을 하면 한번 만든 함수들을 재사용할수 있다는 잇점이 있습니다. C에서 라이브러리화 한다는 것은 함수를 컴파일 하여 기계어 코드로 만든다음 이것을 LIB파일로 저장을 한다는 의미입니다. 보통 C소스 파일을 컴파일하면 OBJ파일이 만들어 집니다. 예를 들어서 test.c파일을 컴파일하면 test.obj파일이 만들어 집니다. 이파일들을 묶어서 lib로 만들 수 있는데 이것을 라이브러리 파일이라고 합니다. 예를 들어서 test1.c test2.c test3.c라는 3개의 파일에 있는 함수들을 라이브러리화 하여 test.lib로 만들수가 있다는 것입니다.

Exam49는 라이브러리르 만드는 예제입니다. 먼저 프로젝트를 만들 때 그림 1과 같이 Win32 Static Library를 선택합니다.

(그림 1)LIB용 프로젝트 만들기

Win32 Static Library로 설정하면 프로그램이 컴파일 되고 프로젝트 명에 해당하는 LIB파일을 만들어 줍니다. Exam49라고 프로젝트를 만들었기 때문에 Exam49.lib라는 파일이 만들어지게 되는것이지요.

이렇게 프로젝트를 만든다음에 지금까지 한것처럼 Exam02.c를 프로젝트에 삽입을 하지 않습니다. 이곳에서는 새로 파일을 만든다음 함수만 기록하게 됩니다.

Exam49.c라는 파일을 만든다음 다음과 같이 덧셈,뺄셈,곱셈,나눗셈 함수를 기록합니다.

(프로그램 소스)

///////////////////////////////////////////

//Exam49.c

//덧셈 뺄셈 곱셈 나눗셈 라이브러리 만들기

#include <windows.h>

#include <stdio.h>

#include "Exam49.h"

//덧셈 함수

int Add(int a,int b)

{

return a+b;

}

//뺄셈 함수

int Sub(int a,int b)

{

return a-b;

}

//곱셈 함수

int Mul(int a,int b)

{

return a*b;

}

//나눗셈 함수

int Div(int a,int b)

{

return a/b;

}

(프로그램 소스끝)

위와 같이 소스를 기록한후에 소스안에 있는 함수 리스트를 설정할 헤더가 필요합니다. Exam49.h를 만들어서 다음과 같이 기록합니다.

(프로그램 소스)

//Exam49.h 함수 리스트

int Add(int a,int b);

int Sub(int a,int b);

int Mul(int a,int b);

int Div(int a,int b);

(소스끝)

위와 같이 한후에 컴파일 하면 Debug(또는 Release)디렉토리안에 Exam49.lib라는 라이브러리가 만들어 질것입니다.

5장 함수와 헤더에서 설명을 하였듯이 함수가 다른 파일로 만들어 지만 그함수의 형을 *.h라는 즉 헤더 파일에 기록한다고 하였습니다. 마찬가지고 Exam49를 컴파일하여 만들어진 Exam49.lib안에있는 Add,Sub,Mul,Div 라는 함수 4개를 단지 Exam49.lib만 가지고는 알수가 없습니다. 보통 이 lib와 함께 이함수들의 리스트를 헤더파일로 쌍으로 제공을 합니다. 따라서 우리도 Exam49.lib파일과 함께 이 라이브러리 파일에 있는 함수 리스트를 Exam49.h로 제공을 하는 것입니다.

라이브러리 이용하기

Exam50은 Exam49에서 만든 라이브러리를 이용하는 예제입니다. 이번 프로젝트는 지금까지 제작한방식 즉 Win32 Application 방식으로 프로젝트를 만듭니다. 프로젝트를 만들고 난후에 Exam02.c를 프로젝트를 만든 디렉토리에 복사를 하고 마찬가지로 Project에서 Add to Project를 이용하여 파일을 프로젝트에 삽입합니다.

이제 다음과 같은 순서로 Exam49.lib를 프로젝트에 삽입합니다.

(1)Exam49.lib를 Exam50 프로젝트를 만든 디렉토리에 복사를 합니다.

(2)Project 메뉴에서 Setting를 선택하면 그림 2와 같은 카드철이 나올것입니다. 그림 2는 카드철 메뉴에서 Link항목을 선택하였을 때입니다.

(그림 2) 프로젝트에 LIB파일 링크

보통 라이브러리를 자신의 프로그램에 링크시킬경우에 Project의 Setting에서 Object/library modules 라는 항목 입력란에 LIB파일명을 입력해주면 됩니다.

우리가 사용하고자 하는 파일은 Exam49.lib이므로 이것을 그림2와 같이 Object/library modules의 란에 입력하여 줍니다.

(3)라이브러리를 링크시켰다고 해서 함수를 사용할수 있는 것은 아닙니다. 헤더가 필요합니다. 그래서 컴파일이 되니까요. Exam49.h를 Exam50프로젝트가 있는 디렉토리에 복사를 합니다.

이와 같이 하면 라이브러리를 이용하는 모든 준비가 완료 된것입니다.

Exam50.c는 (3)까지 작업이 끝난후 제작한 프로그램 소스입니다.

(프로그램 소스)

//라이브러리 이용하기 Exam50.c

#include <windows.h>

#include <stdio.h>

#include "Exam49.h"//헤더를 포함한다.

void main(HDC hdc)

{

int a,b;

char temp[80];

a=30;

b=15;

//덧셈함수 이용

sprintf(temp,"a+b=%d",Add(a,b));

TextOut(hdc,0,0,temp,strlen(temp));

//뺄셈 함수 이용

sprintf(temp,"a-b=%d",Sub(a,b));

TextOut(hdc,0,20,temp,strlen(temp));

//곱셈 함수 이용

sprintf(temp,"a*b=%d",Mul(a,b));

TextOut(hdc,0,40,temp,strlen(temp));

//나눗셈 함수 이용

sprintf(temp,"a/b=%d",Div(a,b));

TextOut(hdc,0,60,temp,strlen(temp));

}

(프로그램 소스끝)

위에서 #include "Exam49.h"라는 항목을 기록한 이유는 Exam49.lib안에 있는 함수를 이용하기 위해서 헤더를 설정한것입니다.

위와 같이 한후에 컴파일하여 실행하면 그림 3과 같은 내용이 출력이 될것입니다.

(그림 3) Exam50 출력 결과

표준 라이브러리 함수

C컴파일러와 함께 이미 프로그래머가 프로그래밍을 하기 편리하도록 몇가지 기본 함수를 LIB파일로 제공합니다. (또는 OBJ 파일로도 제공합니다)

기본적으로 C컴파일로와 제공하는 표준 함수들을 표준 라이브러리 함수라고 합니다. 보통 문자처리,메모리관리,수학함수,표준 입출력함수등등 이있습니다.

예를 든다면 지금까지 사용한 sprintf같은 함수는 표준 라이브러리 함수입니다. 즉 C와 함께 제공하는 최소 기본함수인것입니다. 어떤 컴파일러든지 이 표준함수는 꼭 들어있습니다. 기본적으로 C 컴파일러라고 하면요. 이것이 C언어 안에 내장되어 있는 것이 아니라 컴파일러 제조업자들이 직접 위의 라이브러리 만들기 처럼 만들어서 LIB파일로 제공한것입니다. 상업용 C컴파일러에는 이것 외에서 굉장히 많은 다른 여러 함수들도 제공합니다. Visual C++은 표준 함수는 기본이고 이외 GUI를 이용하는 함수,멀티미디어 이용함수,등등 수많은 함수를 라이브러리로 제공합니다. 표준 라이브러리 함수외의 다른 함수를 이용하는 방법은 본책을 통해서 전반적으로 설명을 하겠습니다. 본장에서는 대표적으로 사용하는 표준라이브러리 함수중 중요 함수를 설명합니다.

지금까지 소스중에서 우리가 이해를 하지못한 또하나의 부분을 설명하겠습니다.

선행처리기 #include를 이용하여 우리가 프로그램을 제작할 때 아래와 같은 코드를 꼭 삽입하였습니다.

#include <windows.h>

#include <stdio.h>

stdio.h는 표준 입출력 함수 리스트가 들어 있는 헤더입니다. 이헤더를 포함시킬 때 □<□ 기호와 □>□를 사용한 이유는 본 헤더는 컴파일러와 함께 제공하는 함수 헤더이며 이함수 헤더는 컴파일러가 설치된 디렉토리에서 헤더만 설정한 디렉토리에서 찾아라 하는 말입니다. 보통 INCLUDE라는 디렉토리에 이 헤더들이 있습니다. 컴파일러는 <> 기호를 읽고나서 stdio.h를 헤더들이 들어 잇는 디렉토리에서 찾아서 포함을 하게 됩니다.

windows.h는 윈도우에서 사용하는 SDK 함수 리스트가 들어 있는 헤더입니다. SDK는 Standard Developer Kit의 약자입니다. 즉 윈도우에서 프로그래밍을 할 때 기본적으로 사용하는 GUI(Graphic User Interface)함수 리스트입니다.

지금까지 우리는 프로그램을 할 때 단순한 윈도우 하나를 출력하였습니다. 이런 윈도우를 출력할 때 사용하는 함수들의 리스트는 window.h 에 있으며 sprintf같은 표준함수는 stdio.h에 있는 것입니다.

문자열 처리 함수

문자열 처리 함수란 문자열을 복사하거나 또는 현재 문자열에 새로운 문자열을 삽입하거나 하는등 문자열을 컨트롤 할수 있는 함수를 말합니다. 문자열 처리함수는 헤더는  string.h입니다. 따라서 문자열 처리함수를 사용하기 위해서는 다음과 같이 string.h를 삽입해야 합니다.

#include <string.h>

표1은 문자열 처리함수 리스트입니다.

(표1) 문자열 처리함수 리스트

char *str,des;  int n;  일 경우

함수	설명
size_t strlen(str)	str 문자의 길이를 리턴
char *strcpy(src,des)	src에 des에 있는 문자열을 복사한다.
char *strcat(src,des)	src 에 설정된 문자열에 뒤로 des문자열을 복사한다.
char *strncpy(src,des,int n)	src 에 des문자열을 n바이트 만큼 복사한다.
char *strncat(src,des,int n)	src에 설정된 문자열 뒤로 des 문자열을 n 바이트 만큼 복사한다.

지금까지 우리가 사용했던 strlen(temp);의 의미를 여기서 아실수 있을겁니다. 즉 strlen(temp)라는 의미는 전에 설명하였듯이 문자열의 길이를 리턴하는 함수이고 이것은 표준 라이브러리 함수에 속해 있는 것입니다.

strcpy라는 함수는 문자열을 복사하는 함수입니다. 다음은 strcpy의 예입니다.

char src[80];

char *des;

des="안녕하세요";

strcpy(src,des);

위와 같이하면  src 문자열안에 "안녕하세요□라는 값이 들어가게 됩니다.

strcat라는 함수는 현재 설정된 문자열 뒤로 삽입을 하는 함수입니다. 다음은 strcat함수의 예입니다.

char src[80];

char des[80];

strcpy(src,"안녕");

strcpy(des,"하세요□);

strcat(src,des);

위와 같이 하면  src 문자열 끝에 des의 문자열이 삽입됩니다 위프로그램 수행후에 src안에는 □안녕하세요□라는 문자열이 입력되게 됩니다.

strncpy 문자열을 인자로 넘겨준 수만큼 복사를 합니다. 다음은 strncpy의 예입니다.

char src[80];

char *des="abcdefg";

strncpy(src,des,3);

위와 같이하면 src에는 □abc"가 복사됩니다.

문자열 처리 함수 예제 Exam51

Exam51은 문자열을 복사하는 예제입니다.

(프로그램 소스)

//문자열 처리 표준함수

//Exam51.c

#include <windows.h>

#include <stdio.h>

#include <string.h>

void main(HDC hdc)

{

char src[80];

char des[80];

char temp[80];

//src에 abcdefg복사

strcpy(src,"abcdefg");

//des에  src값 복사

strcpy(des,src);

//src와 des출력

sprintf(temp,"strcpy=> src=%s des=%s",src,des);

TextOut(hdc,0,0,temp,strlen(temp));

//des에 test복사

strcpy(des," test");

//src문자열 끝에 des복사

strcat(src,des);

sprintf(temp,"strcat=>src=%s des=%s",src,des);

TextOut(hdc,0,20,temp,strlen(temp));

//src에 abcdefg복사

strcpy(src,"abcdefg");

//des에 src를 3바이트만 복사

strcpy(des,"");

strncat(des,src,3);

sprintf(temp,"strncat=>src=%s des=%s",src,des);

TextOut(hdc,0,40,temp,strlen(temp));

}

(프로그램 소스끝)

Exam51출력 결과는 그림 4와 같습니다.

(그림 4) Exam51 출력 결과

메모리 관련함수

지금까지는 정적 배열을 만들고 이것을 포인터 변수에 연결시키는 방법으로 변수를 만들었습니다. 이방법은 현재의 데이터 크기를 이미 알고 있을때의 방법입니다. 만일 프로그램 도중에 데이터 크기를 알수 있을 경우 정적 배열을 사용할때는 문제가 발생됩니다. 예를 들어서 파일의 내용을 화면에 출력하는프로그램에서 사용자가 파일을 선택할 때 마다 파일의 크기가 변하게 되고 그크기에 맞는 문자 배열을 만들어야 합니다. 이럴 경우 포인터만 설정하고 필요시에 메모리에 설정하는 함수가 메모리 관련함수입니다. 표2는 메모리 처리함수 리스트입니다.

(표2) 메모리 처리함수 리스트

함수	내용
void *malloc(size_t size)	size크기만큼 메모리를 할당한다
void *calloc(size_t num,size_t size)	size크리만큼 num만큼 메모리를 할당하고 이메모리를 모두 0값으로 채운다.
void *realloc(void *p size_t size)	할당된 메모리 p에 size만큼 다시 할당한다.
void free(void *p)	할당된 메모리 p를 해제한다.

예를 들어서 정적 배열의 다음과 같은 루틴이 있을 경우

char data[80];

char *p;

p=data;

data라는 문자열 정적 배열 80개를 포인터 p가 가르키고 있습니다. 이방식은 80개의 정적배열만 사용할수 있습니다. 만일 동적으로 배열을 만들고자 할 경우 malloc함수를 이용하여 다음과 같이 할수 있습니다.

char *p;

p=(char *)malloc(80);

:

free(p);

p=(char *)malloc(160);

:

배열이 없이 p라는 포인터만 가지고 있는 형태에서 malloc(80)를 사용하여 p에 문자열 80개의 배열을 설정합니다. 여기에서 (char *)형전환을 한이유는 malloc함수는 리턴값이 void * 이기 때문에 이것을 문자열로 바꾸기 위해서입니다. 만일 p가 int * 이라면 형전환을 (int *)로 해주셔야 합니다.

우의 예는 처음에는 포인터 p에 80개의 문자열을 설정하였습니다. 그후 포인터 p에 160개의 문자열을 매모리를 설정할 필요에 의해서 현재 설정한 메모리를 free함수를 이용하여 해제하고 다시 malloc함수를 이용하여 160개의 메모리를 할당한것입니다. 위의 예에서 free와 malloc(160)을 합쳐서 realloc함수로 대치할수 있습니다.

char *p;

p=(char *)malloc(80);

:

p=(char *)remalloc(p,160);

:

free(p);

메모리를 할당하면 할당된 메모리안에 있는 쓰레기값들이 p에 설정됩니다. calloc는 이값들을 지우고 0으로 채우는 기능을합니다. 위의 malloc함수를 calloc함수로 전환하면 다음과 같습니다.

char *p;

p=(char *)calloc(1,80);

:

p=(char *)remalloc(p,160);

:

free(p);

메모리를 할당한후에는 꼭 메모리를 해제 해주어야 합니다. 이때 사용하는 함수가 free입니다. 이메모리 처리 함수를 사용하기 위해서는 헤더를 stdlib를 설정해주어야 합니다.

ex)#include <stdlib.h>

자료 변환 관련 함수

자료변환은 문자열의 값을 소수로 또는 정수로 또는 long형으로 바꿀 때 사용하는 함수를 말합니다. 이것은 형전환이 아닙니다. 예를 들어서 □1234□라는 문자열을 1234의 정수로 전환하고자 할 경우 사용하는 것입니다. 만일 형전환을 한다면 1234의 값은 무시되고 이전체를 묶어서 정수로 나타내기 때문에 1234 값이 나오지 않습니다. 표3은 자료 변환에 관련된 함수 리스트입니다.

(표3) 자료변환 관련 함수 리스트

함수	내용
int atoi(char *s)	s문자열을 정수형으로 바꾼다.
double atof(char *s)	s문자열을 소수형으로 바꾼다.
long atol(char *s)	s문자열을 long형으로 바꾼다.

ex)

char tem[80];

int gab;

strcpy(temp,"1234");

gab=atoi(temp);

위의 예는 temp라는 문자열에 들어있는 □1234□라는 값을 gab안에 1234정수형으로 바꾼것입니다. 윈도우 프로그램에서는 int와 long가 같기 때문에 atoi와 atol은 같은 값이 출력됩니다. atof는 소수 형을 리턴하는 함수로써 다음은 atof의 예입니다.

ex)

char tem[80];

double gab;

strcpy(temp,"1234.1234");

gab=atof(temp);

파일포인터를 이용한 표준 파일입출력

표준 파일 입출력함수는 파일에 데이터를 넣고 데이터를 읽고할 경우 사용하는 함수입니다. 이파일 입출력은 크게 파일포인터를 사용하는 방법과 파일 핸들을 사용하는 방법이있습니다.

파일 포인터 함수 리스트

파일 포인터를 사용하는 방법은 FILE라는 파일 포인터를 이용하여 파일을 개방하고 데이터를 읽고 쓰고할경우입니다. 이때 사용하는 함수리스트는 표4와 같습니다.

(표 4) 파일포인터 함수리스트

함수형	내용
FILE *fopen( const char *filename, const char *mode );	filename의 파일명을 mod의 값에 의해서 개한다.
int fclose( FILE *stream );	파일을 닫는다
int fgetc( FILE *stream );	파일에서 한문자를 입력받는다
int fputc(int c, FILE *stream);	파일에 한문자를 쓴다.
int fgets( char *s,int n,FILE *stream);	파일에서 n크기만큼 문자열을 읽어서 s에 저장한다.
int fputs(char *s,FILE *stream);	파일에 s문자를 기록한다
int fprintf(FILE *fp,const char *f,...);	파일에 printf의 기능으로 기록한다.
int fscanf(FILE *fp,const char *f,...);	파일에 scanf의 기능으로 기록한다
size_t fread(void *buf,size_t size, size_t n, FILE *stream);	파일에서 size*n만큼의 크기를 buf에 읽는다
size_t fwrite(void *buf,size_t size, size_t n,FILE *stream);	파일에 buff의 size*n만큼 크기를 기록한다.
int fseek(FILE *fp,long offset,int origin)	파일에서 origin에서부터 offset로 설정한 위치까지 이동한다.
int ftell(FILE *fp)	파일 fp에서 파일 커서의 현재 위치를 찾는다.

파일 포인터 함수 개방과 닫기

파일포인터 함수를 사용하여 파일을 열고자할 경우 다음과 같이 할 수 있습니다.

ex)

FILE *out;

out=fopen("test.dat","r");

:

fclose(out);

위의 예는 test.dat라는 파일을 읽기 전용으로 오픈을 하자는의미입니다. fopen의 두 번째 인자는 파일을 개방할 때 설정하는 속성입니다. 이속성은 표5와 같습니다.

모드	내용
□r"	읽기 전용
□w"	쓰기 전용 이모드를 사용하면 기존의 파일을 삭제하고 새로 만들게 된다.
□a"	기존에 파일에 추가하여 기록한다
□rb","wb","□ab□	2진 파일로 읽고 쓰고 추가한다
□r+","w+","a+"	읽고 쓴다.

(표 5) 파일 오픈 모드

□w" 모드는 기존에 파일의 내용을 삭제하고 새로 갱신합니다. 기존의 파일에 새로운 데이터를 넣기 위한 방법의 개방은 "a"를 사용하면 됩니다. □r+"나 "w+"는 읽고 쓰고자 할 때 사용합니다. 이 두모드는 기존의 내용을 삭제하고 파일을 새로 갱신하지만 "a+"는 기존의 파일에 기록하게 됩니다. 파일을 개방한후에는 꼭 닫아주어야 합니다. 그렇지 않으면 새로 기록한 데이터가 사라지게 됩니다. 닫을 때 사용하는 함수가 fclose이며 사용법은 위 예를 참조하세요

문자를 파일 포인터에서 읽고 쓰기

파일에서 한 개의 문자를 읽을경우에는 fgetc이고 한 개의 문자를 쓰고자 할경우에는 fputc입니다. 다음은 문자를 읽고 쓰는 예입니다.

char c;

FILE *out;

out=fopen("test.dat","w"); //파일을 열고

c='t';  //c에 □t'라는 문자값을 넣은다음

fputc(c,out);//파일에 넣는다..

fclose(out);//파일을 닫는다.

out=fopen("test.dat","r");//파일을 열고

c=fgetc(out);//파일에서 한문자를 읽는다.

fclose(out);//파일을 닫는다.

파일을 개방한다음  fputc를 이용하여 □t'라는 문자를 파일에 기록하고 다이 파일을 개방한다음 문자를 하나 읽는 예입니다. fputc함수를 이용하여 텍스트 형태로 데이터를 읽고 쓰고자 할경우에는 파일을 열고 닫고를 반복해야 합니다. 그이유는 FILE핸들은 닫기 전에는 그데이터를 파일에 저장되지 않기 때문입니다. 위에서 fputc를 한후에 fgetc를 하여도 데이터를 읽지 못합니다. 이이유는 파일에 저장이 안된 이유도 있으며 fputc함수에서 의해 문자를 기록하고 현재 위치는 □t'라는 문자를 기록한 다음 위치로 이동했기 때문입니다.

문자열을  파일 포인터에서 읽고 쓰기

다음은 파일에서 문자열을 읽는 예입니다.

FILE *out;

int i;

char buff[100][100];

out=fopen("test.dat","r");

for(i=0;i<100;i++)

{

if(fgets(buff[i],100,out)==NULL)

break;

:

}

fgets는 out라는 파일에서 100개의 문자를  읽어서 buff[i]에 기록합니다. 만일 100개의 문자전에 개행 '\n' 코드가 있게 되면 이곳까지 읽습니다. 결국 fgets함수는 텍스트에서 한라인을 읽는 함수 라고 보셔도 됩니다. 위의 예는 100의 크기를 가진 문자열 배열 buff에 test.dat 에 있는 데이터를 한라인씩 읽는 예입니다.

fgets는 파일이 끝날경우에는 NULL를 리턴합니다.

다음은 파일에 문자열을 쓰는 예입니다.

FILE *out;

int i;

char temp[80];

sprintf(temp,"이것은 테스트입니다\n□);

out=fopen("test.dat","w");

fputs(temp,100,out);

:

위의 예에서 fputs는 test.dat에 □이것은 테스트입니다□라는 문자열을 첫줄에 기록하는 예입니다. fputs는 한줄의 문자열을 기록할 때 자주사용됩니다.

fprintf와 scanf를 이용하여 파일에서 데이터 읽기

다음은 fprintf함수를 이용하여 데이터를 쓰는 예입니다.

FILE *out;

char *temp="안녕하세요□;

int data=1111;

out=fopen("test.dat","w");

fprintf(out,"%s %d\n",temp,data);

:

위의예는 test.dat라는 파일 첫줄에 "안녕하세요 1111□이라는 데이터를 기록하는 예입니다. fprintf는 지금까지 사용한 sprintf와 비슷합니다. 다만 sprintf는 문자열 버퍼인 반면 fprintf는 파일 포인터에 기록하는 것이 차이입니다.

다음은 fscanf함수를 이용하여 데이터를 쓰는 예입니다.

FILE *out;

char temp[80];

int data;

out=fopen("test.dat","r");

fscanf(out,"%s %d",temp,&data);

fscanf는 문자열데이터에서 두 번째 있는 인자의 형식으로 데이터를 읽는 함수입니다. 위의에에서 "%s %d"라고 한 것은 첫 번째는 문자열형식으로 두 번째는 정수형 형식으로 읽으라는 이야기입니다. 이때 fscanf함수는 문자열과 정수형의 분리늘 공백으로 합니다. 만일 데이터가 □안녕하세요 1111□ 이라고 한다면 □안녕하세요□다음 공백문자를 보고 □안녕하세요□는 temp에 1111은 data에 저장합니다. 만일 데이터가 □안녕하세요 나는 111□ 이라고 설정되어 있을경우에는 □안녕하세요□는 temp에 □나는□이 data에 설정됩니다. 결국 data에는 이상한 값이 설정되게 되는것입니다. 그이유는 fscanf함수는 공백문자를 기준으로 분리하기 때문입니다. 또한 예로 "안녕하세요□ 라는 문자열만 있다면 data에는 설정될 데이터가 없기 때문에 에러가 발생됩니다. fgets함수는 한라인을 읽습니다. 이때 '\n' 이라는 new line 코드까지 읽게되나 fscanf는 '\n'의 코드를 제외한 한라인을 읽고 이것을 인자들의 형식에 의거하여 분리하여 전달합니다.

파일 커서의 개념

파일에도 일반 에디터의 개념으로 커서가 있습니다. 예를 들어서 다음과 같은 내용이 기록 되어있다고 본다면

[파일에 기록된 내용]

1234567890

0987654321

위의 파일을 "r"모드로 파일을 오픈하였을 경우 파일커서는 1에 위치하게 됩니다.이때 fgetc함수를 이용하여 문자한개를 읽으면 □1□ 이라는 문자를 읽은후에 파일커서는 2에 위치하게 됩니다. 또한 fgets 함수를 이용하여 한라인을 읽게 되면 파일커서는 한줄을 읽고 다음줄 첫문자인 0에 위치하게 됩니다. 가끔 우리는 이런 파일 커서 위치를 이동시키고자 할경우가 있습니다. 즉 첫 번째 줄의 4위치에 가서 문자한개을 읽고 다시 2위치에서 문자한개를 읽을 경우도 있다는 것입니다.

이럴 때 사용하는 함수가 fseek입니다. fseek함수의 origin 는 어느위치에서부터 시작하여 커서 위치를 이동할것인가의 모드값입니다. 이값은 stdio.h에 다음과 같이 정의 되어 있습니다.

SEEK_SET :  파일 처음부터

SEEK_CUR : 파일 현재 위치부터

SEEK_END : 파일 마지막부터

위의 파일에서 4번째 위치로 가서 한 개의 문자를 읽고자 한다면 다음과 같이 할수 있습니다.

fseek(out,4,SEEK_SET);

code=fgetc(out);

위의 예대로 4위치에가서 한 개의 문자를 읽으면 이때 □5□의 문자를 읽게 되면 현재 위치는 5가 됩니다. 이유는 fseek에 의해서 4의 위치로 이동하였으며 fgetc에 의해서 한문자를 읽고 커서가 한칸 이동했기 때문입니다.

현재 커서의 위치를 알고자 할경우에는 ftell함수를 사용하면 됩니다. 다음은 현재 위치를 알고 현재 위치에서 5위치 뒤로 이동한 예입니다.

long pos;

:

pos=ftell(out);

fseek(out,5,SEEK_CUR);

:

블럭 단위로 데이터 읽기

지금까지는 텍스트 데이터를 기준으로 파일에서 데이터를 읽는 방법을 설명하였습니다. sscanf는 개행라인 바로전까지 fgets는 개행값까지 포함하여 한라인을 읽습니다. 2진 파일로 파일을 열고 데이터를 기록할경우에는 sscanf 나 fgets를 이용하여 데이터를 읽는다는 것은 옳지 못합니다. 그이유는 2진 파일은 □\n'으로 분리되어 있지 않고 블록단위로 구성되어 있기 때문입니다. 블록 단위로 데이터를 읽고 쓸때는 fwrite난 fread함수를 이용합니다. 예를 들어서 char *buff; 라는 버퍼에 그림 데이터가 512정도 들어 있다고 가정을합시다. 이 data를 쓰고자 한다면 다음과 같습니다.

out=fopen("test.dat","wb□);

fwrite(buff,512,1,out);

:

fwrite함수에서 두 번째 인자는 블록데이터의 크기이며 세 번째 인자는 블록데이터의 수입니다. 위의 예는 블록데이터가 1개이기 때문에 세 번째 인자를 1로 그리고 블록 사이즈가 512이기 때문에 두 번째 인자를 512로 하였습니다. 실제로 기록되는 데이터는 size*n의 값 즉 블록데이터크기 값과 블록데이터 수입니다.

위와 같이 데이터를 블록단위로 기록하였을 때 이데이터를 읽고자 할경우에는 fread함수를 이용합니다. 다음은 위에서 기록한 test.dat파일에서 512블럭의 데이터를 읽는 예입니다.

out=fopen("test.dat","rb");

fread(buff,512,1,out);

2진파일에서는 데이터를 기록하고  읽고할경에는 fread와 fwrite를 주로 사용합니다.

파일핸들을 이용한 표준 파일입출력

파일 핸들을 이용한 표준 파일 입출력이란 FILE라는 포인터를 사용하는 것이아니란 int값의 파일 핸들 번호를 사용하여 파일을 읽고 쓰는 방법을 말합니다. 일반적으로 고수준 파일 입출력은 이방법을 많이 사용합니다. 표 6은 파일 핸들을 이용한 표준 파일 입출력함수입니다.  파일 핸들을 이용한 파일입출력을 할 경우 다음과 같은 4개의 헤더를 포함시켜야 합니다.

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <io.h>

함수명	내용
int _creat( const char *filename, int pmode );	pmode로 filename을 만든다.
int _open( const char *filename, int oflag);	oflag모드로 filename의 파일을 오픈한다.
int _read( int handle, void *buffer, unsigned int count );	handle 파일에 buff의 count만큼의 크기를 기록한다.
int _write( int handle, const void *buffer, unsigned int count );	handle 파일에서 count만큼의 데이터를 buffer에 읽는다.
long _lseek( int handle, long offset, int origin );	handle 파일에서 origin위치에서 offset만큼 파일 커서를 이동한다.
int _close( int handle );	handle 파일을 닫는다.

(표 6) 파일 핸들을 이용한 파일 입출력함수

파일 만들기,열기,닫기

파일포인터를 사용하였을때는 fopen에 모드값을 □w" 로 하면 새로운 파일이 만들어지는 반면 표준 파일 핸들러 함수에는 _creat라는 함수가 따로 있습니다.

이 creat의 pmod는 다음과 같습니다.

_S_IWRITE  : 쓰기용으로 파일을 만듬Writing permitted

_S_IREAD : 기록용으로 파일을 만듬

_S_IREAD | _S_IWRITE : 쓰기와 읽기 용으로 파일을 만듬

예를 들어서 □test.dat" 라른 파일을 읽기와 쓰기용으로 만들고자 한다면 다음과 같습니다.

int fd;

fd=_creat("test.dat",_S_IREAD| _S_IWRITE);

기존의 파일을 오픈하고자 한다면 _open함수를 사용합니다. 이함수에서 oflag값은 다음과 같습니다.

_O_BINARY: 2진 파일형으로 파일을 오픈한다.

_O_CREAT : 파일을 새로 만들면서 오픈한다.

_O_RDONLY : 읽기 전용으로 파일을 오픈한다.

_O_RDWR : 읽기와 쓰기 전용으로 파일을 오픈한다.

_open함수에서 oflag값에 _O_CREAT를 사용하면 새로 파일을 만들면서 오픈됩니다. 이것은 _creat를 한것과 같은 형태입니다.

다음은 "test.dat"를 읽기와 쓰기 그리고 2진 파일형태로 오픈한 예입니다.

int fd;

fd=_open("test.dat",_O_BINARY|_O_RDWR);

:

_close(fd);

_open함수나 또는 _creat 함수를 이용하여 파일을 만들거나 오픈하였을 경우 _close함수를 이용하여 닫아주어야 합니다.

파일에 데이터 쓰기와 읽기

핸들을 이용한 함수에서는 블록단위로 데이터를 읽는 _read와 블록단위로 데이터를쓰는 _write 만 있습니다. 파일 포인터를 이용한 fgetc나 fgets 또는 fprintf같은 기능은 없습니다. 그러나 이두함수를 이용하여 다기능적으로 데이터를 읽고 쓸수 있습니다.

예를 들어서 파일 첫 번째 에는 int 정수값을 그다음에 문자버퍼 char* 형태로 20바이트 데이터를 기록하고자 할 경우 이값을 바로 파일에 기록할수 있습니다. 다음은 이예입니다.

int gab=1234;

int fd;

char *p;

p=(char *)malloc(20);

strcpy(p,"test data");

fd=_open("test.dat",_O_BINARY|_O_RDWR);

_write(fd,&gab,4);

_write(fd,p,20);

:

_close(fd);

위의 예에서_write(fd,&gab,4);라는 의미는 gab이라는 정수형을 그대로 파일에 기록하자는 의미입니다. int는 4바이트입니다. 이것을 파일에 그대로 기록한다면 4바이트 만큼 기록하게 하면 됩니다. 같은 방법으로 p라는 포인터에 문자열 버퍼를 20 할당하고 "test data"라는 값을 넣은다음 20만큼 쓰면 □test data"가 기록됩니다. 여기에서 기억해야 할 것은 "test data"는 9바이트입니다. 그러나 실제 파일에 기록되는 것은 "test data"뒤에 있는 0x00의 값과 그외 값들까지 포함해서 20바이트 만큼 기록되게 되는 것입니다. _write를 이용하면 구조체도 파일에 바로 기입할수 있습니다. 다음은 구조체를 파일에 기록하는 예입니다.

typedef struct test{

int gab;

char data[80];

} Test;

:

Test da;

da.gab=1234;

strcpy(da.data,"test data");

fd=_open("test.dat",_O_BINARY|_O_RDWR);

_write(fd,&da,84);

_close(fd);

위의 예에서 _write(fd,&da,84)에서 84라는 값을 설정한 것은 Test구조체의 data크기가 80이며 gab의 크기가 4이므로 총 84이기 때문입니다. 이 내용을 sizeof함수를 이용하여 다음과 같이 바꿀수 있습니다.

Test da;

da.gab=1234;

strcpy(da.data,"test data");

fd=_open("test.dat",_O_BINARY|_O_RDWR);

_write(fd,&da,sizeof(da));

_close(fd);

_write함수는 파일에 데이터를 기록하면 기록한 데이터의 값이 리턴됩니다. 때로는 파일에 데이터를 기록하고자 할 때 용량이 모잘라 기록을 하지 못할경우가 있습니다. 이럴경우에는 다음과 같은 방법으로 에러를 체크할수 있습니다.

int size;

size=_write(fd,&da,sizeof(da));

if(size!=sizeof(dat))//기록된 데이터가 da의 크기와 같지 않으면

{

기록실패 에러!

}

기록 성공!

_write함수에 의해서 리턴된 기록된 값은 size에 들어가게 되고 이값이 sizeof(da)값과 같게 되면 데이터를 기록하는데 성공한 것이며  그렇지 못할경우에는 디스크 용량이 모자르거나 또는 다른 시스템 에러에 의해서 쓰기가 실패한것입니다.

파일에서 데이터를 읽는방법은 쓰기의 방법과 비슷합니다. 위에서 test.dat에 Test구조에의 값을 기록했다고 가정을 하고 이값을 읽고자 할 때는 다음과 같습니다.

Test da;

fd=_open("test.dat",_O_BINARY|_O_RDWR);

_read(fd,&da,sizeof(da));

_close(fd);

만약 위의 쓰기 예제를 수행한 후에 다시 읽기 예제를 수행하면 da라는 값에 "test dat"와 1234라는 값이 설정될것입니다. _read또한 읽은 데이터의 길이를 리턴합니다.

파일에서 커서 이동

_lseek함수는 파일에서 커서를 이동하는 함수입니다. 이것은 fseek와 거히 같습니다. 다음은 데이터를 기록하고 커서를 다시 처음으로 이동하여 기록한 데이터 값을 읽는 예제입니다.

Test da;

da.gab=1234;

strcpy(da.data,"test data");

fd=_open("test.dat",_O_BINARY|_O_RDWR);

_write(fd,&da,sizeof(da));

_lseek(fd,0,SEEK_SET);//파일처음으로 이동한다.

_read(fd,&da,sizeof(da));

_close(fd);

위와 같이하면 da에 □test data"와 1234값이 test.dat에 기록되고 이때 파일커서는 sizeof(da)만큼 파일 처음에서 이동이 됩니다. 즉 84번째로 이동하게 되는것입니다. 이때 _lseek함수를 이용하여 다시 파일 처음으로 이동한뒤에 da값을 읽으면 전에 기록한 그값이 그대로 읽어 질것입니다.

수학 함수

C언어의 표준 라이브러리에는 다양한 수학함수가 있습니다. 이함수는 math.h에 정의 되어 있습니다. 표7은 다양한 수학함수 리스트입니다.

(표 7) 다양한 수학함수 리스트

함수명	설명
double acos (double x)	arc cosine
double asin (double x)	arc sine
double atan (double x)	arc tangent
double atan2 (double y,x)	arc tangent(y/x)
double ceil (double x)	x보다 크거나 같은 최적의 정수
double cos (double x)	cosine
double cosh(double x)	hyperbolic cosine
double exp (double x)	[eqn: exp^x]
double frexp(x,int *exp)	지수부와 가수부 분리
double log (double x)	자연 로그
double log10 (double x)	사용 로그
double pow (double x,y)	[eqn: x^y]
double sin (double x)	sine
double sinh(double x)	hyperbolic sine
double sqrt (double x)	[eqn: ROOT{x}]
double tan (double x)	tangent
double tanh (double x)	hyperbolic tangent

sin이나 cos또는 tan등에 설정되는 x의 값은 각도가 아니라 라디안입니다. 따라서 각도를 라디안으로 변경하여 설정하여야 합니다. 다음은 sin함수를 이용하여 값을 얻는 예입니.

#define PI 3.14159265

double gab;

gab=cos( 90.*PI/180.0); //cos(90)의 값을 출력하는 경우

그밖의 함수에 대하여

이외에도 C언어에서 사용하는 다양한 함수가 매우 많습니다. 본책에서 가장적게 설명한 부분이 있다면 그것은 표준 입출력 함수입니다. 이부분은 뒤의 C++에서 약간 설명을 할것입니다. 표준 입출력함수란 콘솔모드 (DOS모드)에서 키보드의 데이터를 입력받고 화면에 데이터를 출력하는 함수들입니다. 다른 C언어 책에는 이부분에 대해서 매우 많은 내용이 언급되어 있을것입니다. 그러나 윈도우 프로그래밍에서는 이 콘솔모드의 표준 파일 입출력이 전혀 사용되지 않습니다. 과거 DOS모드에서는 매우 큰힘을 발휘했던 이함수들이 윈도우에서는 골동품이 되었습니다. 그이유는 그래픽모드에서는 자신만은 입출력 함수가 있기 때문입니다. 요즘에는 모든 프로그램이 GUI 환경입니다. 유닉스나 리눅스에서는 X윈도우  환경에서 일반적인 프로그램밍이 이루어지고  일반 PC에서는 윈도우95 또한 GUI환경이며  애플의 맥킹토시는 처음부터 GUI였습니다. GUI환경에서는 표준 입출력함수를 전혀 사용하지 않기 때문에 점점 골동품화 되어가는 함수라고 볼 수 있습니다. 혹이 □이곳에서 설명하지 않은 함수 안배우면 큰일나지 않나?□ 하는 생각을 하실분이 있을까봐 말씀드립니다. 지금까지 배운 함수만 가지고도 충분하다고 봅니다.

이외에 설명하지 않은 C언어의 다량의 함수는 윈도우 프로그래밍에서는 윈도우에서 제공하는 함수로 모두 대치됩니다. 그렇기 때문에 윈도우 프로그래밍을 전문으로 하고자한다면 배워봤자 소용없게 되는 경우가 많습니다. 이유는 그함수들이 윈도우에서는 새로운 함수로 사용되기 때문입니다. 예를 든다면 malloc나 calloc같은 함수들도 윈도우에서는 거히 사용하지 않고 GlobalAlloc로 대치되어 사용됩니다.

이제 언어를 끝내고 본격적인 윈도우 프로그램에 도전하시기 바랍니다!