커닝헌과 데니스 리치가 쓴 The C Programming Language(TCPL)에 대해서는 여기까지 여기까지 하겠습니다


본래 8장과 뒤에 부록이 있지만 그것은 범용적인 C라기 보다는 UNIX시스템에 관련된 것이고 뒤의 부록은

인터넷을 검색해보면 금방 찾을수 있을 거라 생각합니다


이책이 옛날에 쓰여진 책이고 쓰여질 당시 OS는 UNIX나 DOS라서 거기에 맞게 저자가 예졔를 짜놓아서

지금과 맞이 않는 부분이 상당하게 있네요 일단은 한번은 익히는게 목적이라 그냥 썻지만 추후에


Microsoft-> Visual C++

OS X     -> LLVM/Clang

Linux    -> gcc


버전에 맞게 또 지금은 c11까지 나왔지만 나중에 c14가 나오면 그에 맞게 제나름대로 고쳐보겠습니다


사실상 어떤 OS에서도 쓸수있는 언어라면 C/C++/JAVA 정도이지만 오라클이 JAVA를 인수한후 좀 이상? 해진 면이 있는데요 C/C++을 훨씬 오래전부터 사용해왔고 라이브러리도 많으면서 OS나 게임만들때 필수적인 언어고 다른언어들 보다 덜 변하기 때문에 한번 익혀 두고 꾸준히 공부하면 정말 대체불가능한 프로그래머가 되지 않을까 싶습니다 물론 자료구조/알고리즘도 열심히 해야겠지만요!


이책은 300페이지가 안되고 뒤에 부록을 제외하면 정말 짧은책에 제가 C를 많이 접했기때문에 정말 후딱 공부할수 있었습니다만  C++는 비야네가 쓴 책부터 1000페이지가 넘고 포인터와 객체지향을 두개다 쓰기때문에 정리하기에는 오래 걸릴것 같습니다만 실제로 모바일 이나 3d콘솔게임 같은경우 C++을 사용하는 경우가 많기때문에 게임을 만들면서 천천히 공부해야겠습니다 즐기면서요! 아마 주중에 C++/cocos2d-x 주말에 C/알고리즘/자료구조를 정리하면서 저를 완성시킬것 같습니다


한국 게임 개발자들 화이팅!

표준 라이브러리는 상당히 다양한 함수를 제공한다. 이 절에서는 그중 자주 쓰이는 것에 대해 설명하였다.

7.8.1 문자열 조작

<stdio.h>에 있는 strlen,strcpy,strcat,strcmp등의 문자열 함수에 대해서는 이미 언급하였다. 다음 예시에서 s는 char *이고 c와 n은 int 이다.

strcat(s,t)      t를 s의 끝에 연결시킨다.

strncat(s,t,n)   t중에 n개 문자를 s의 끝에 연결시킨다.

strcmp(s,t,)     s<t,s==t나 s>t에 대해,-,0,+로 리턴한다.

strncmp(s,t,n)   strcmp와 같은데 단지 처음의 n개의 문자만 비교한다.

strcpy(s,t)      s에 t를 복사한다.

strncpy(s,t,n)   t의 n개 문자를 s에 복사한다.

strlen(s)        s에 길이를 리턴한다.

strchr(s,c)      s에서 첫번째 c의 포인터를 리턴하거나 존재하지 않으면 NULL을 리턴한다.

strrchr(s,c)     s에서 마지막 c의 포인터를 리턴하거나 존재하지 않으면 NULL을 리턴한다.


7.8.2 문자분류와 변환


<ctype.h>의 여러함수는 문자를 검색하거나 변환을 수행한다 다음 c는 unsigned char로 나타낼수 있는 int 거나 EOF이다


isalpha(c)      c가 알파벳이면 0이아닌 숫자,아니면 0

isupper(c)      c가 대문자면 0이아닌 숫자,아니면 0

islower(c)      c가 소문자 0이아닌 숫자,아니면 0

isdigit(c)      c가 숫자(digit)이면 0이아닌 숫자,아니면 0

isalnum(c)      isalpha(c) 거나 isdigit(c)이면 0이아닌 숫자,아니면 0

lsspace(c)      c가 공백,탭,newline,리턴,formfeed,수직탭이면 0이 아닌 숫자

toupper(c)      c는 대문자로 변환된다.

tolower(c)      c는 소문자로 변환된다.


7.8.3 ungetc 함수


표준라이브러리에서는 제 4장에서 제시한 ungetch보다 덜 제한된 형태로 사용되는 ungetc함수가 있다.


int ungetc(int c,FILE *fp)


는 문자 c를 파일 fp에 붙이고 c나 EOF(에러인 경우)중에 하나를 리턴한다. ungetc는 scanf,getc나 getchar와 같은 입력함수의 어떤것과도 함께 사용할수 있다


7.8.4 명령 실행


system(char *s)함수는 무자열 s에 포함된 명령어 라인의 명령(UNIX 명령이나 DOS 명령등)을 실행하고 현재의 프로그램으로 다시 되돌아온다. s의 내용으로 줄수 있는 것은 각각의 운영시스템에 따라 다르다 UNIX시스템의 평범한 예로 명령문


system("date");


는 프로그램 date를 실행시킨다. 이것은 표준 출력에서 그날의 시간과날짜를 출력한다. system은 실행된 명열으로 부터 시스템 의존적인 정수상태(integer status)를 리턴한다.

UNIX시스템에서 상태리턴(status return)은 exit함수가 리턴하는 값이다.


7.8.5 기억장소 관리


malloc와 calloc 함수는 메모리 블록을 동적으로 얻어낸다.


void *malloc(size_t n)


이 함수는 사용 가능한 n바이트를 가리키는 포인터를 리턴하는데 찾을 수 없으면 NULL을 리턴한다.


void *calloc(size_t n,size_t size)


는 지정된 크기의 n개의 대상체(object)의 배열을 위한 충분한 공간의 포인터를 리턴하거나 조건이 만족되지 않으면 NULL을 리턴한다. 저장장소는 0으로 초기화 한다.

malloc나 calloc에 의해 리턴된 포인터는 기억장소 정렬(alignment)에 맞게 되어 있지만, 다음과 같이 cast를 통해 적당한 형 변환을 해 주어야 한다.


int *ip;

ip = (int *)calloc(n,sizeof(int));


free(p)는 malloc나 calloc에 의해 얻어지는 p에 의해 가리켜지는 공간을 자유공간으로 만든다. 기억장소를 자유롭게 하는데는 아무런 순서제한이 없지만 calloc나 malloc 함수에 의존하지 않는 기억장소를 자유공간으로하려면 예상치 못한 에러가 발생된다.

기억장소가 자유공간이 되 후에 이 장소를 그냥 사용하면 에러이다 리스트(list)에서 기억장소를 자유롭게 하기위해 다음과 같은 loop를 사용하면 안된다.


for (p = head; p != NULL; p = p->next) /* WRONG */

    free(p);


다음과 같이 하는 것이 올바른 방법이다.

    

for (p = head; p != NULL; p = q) {

    q = p ->next;

    free(p);

}


8.7에는 malloc와 비슷한 기억장소 할당기(storage allocator)가 소개되어 있는데 이함수는 순서에 관계없이 기억장소를 자유공간으로 만들 수 있다.


7.8.6 수학 함수


<math.h>에는 20가지 이상의 수학적인 함수(mathematical functions)가 있는데 여기서는 자주 사용하는 것을 소개하였다 하나 또는 두개의 double 매개변수를 취해서 double을 리턴한다.


sin(x)     x의 sine,x는 라디안

cos(x)     x의 cosine,x는 라디안

atan2(y,x) y/x의 arctangent,라디안

exp(x)     지수함수 e^x

log(x)     자연로그(x>0)

log10(x)   상용로그(x>0)

pow(x,y)   x^y

sqrt(x)    x의 제곱근(x>=0)

fabs(x)    x의 절대값


7.8.7 난수 발생


rand()함수는 정수형 의사난수(pseudo-random) 0에서 <stdlib.h>에서 정의한 RAND_MAX까지의 범위에서 계산한다. 0보다는 크거나 같고 1보다는 작은 랜덤 부동소수점수를 방생시키는 방법은 다음과 같다.


#define frand() ((double) rand() / (RAND_MAX+1.0))


(만일 라이프러리가 이미 부동 소수점 난수에 대한 함수를 가지고 있다면 위의 것보다 통계상 특성이 더 좋을 것일 수가 많다)

srand(unsigned)함수는 rand함수에게 줄 seed를 세트한다. rand와 srand의 포터블(portable 이식이 자유로운)한 형태의 구현은 2.7에 잘 나타나 있다.


예제 7-9 isupper와 같은 함수는 공백을 절약하거나 시간을 절약하기 위한 형태로 구현이 가능하다 두 가능성을 조사하라.




표준 라이브러리는 이전의 장들에서 사용한 getline 함수와 유사한 fgets 입력함수를 가지고 있다.


char *fgets(char *line,int maxline,FILE *fp)


fgets는 다음 입력행('\n'을 포함한)을 파일 fp로부터 문자열(즉 문자 배열)인 line에 읽어들이는데,최대 maxline-1 문자를 읽을 수 있다. 입력 완료된 라인은 '\0'에 의해 종료된다. 보통 fgets는 line을 리턴하는데,파일의 끝이너가 에러를 만났을 시에는 NULL을 리턴한다(getline 함수는 행의 길이를 리턴시키는데 이것이 더 쓸모 있는 값이다.0은 파일의 끝을 의미하게 된다.)

출력함수 fputs는 '\0'을 포함하지 않는 문자열을 파일에 출력시킨다


int fputs(char *line, FILE *fp)


에러가 발생하면 EOF를 리턴하고, 나머지 경우에는 0을 리턴한다

라이브러리 함수 gets와 puts는 fgets나 fputs와 유사하지만 stdin과 stdout에 적용되는 것이 자른 점이다. 혼란스럽게 gets는 마지막의 '\n'을 제거하지만 puts는 그것을 추가한다.

여기에서 fgets나 fputs와 같은 함수가 별로 특별한 점이 없다는 것을 보여주기 위해 프로그램 리스트를 다음에 실었다.


/* fgets: get at most n chars from iop */

char *fgets(char *s, int n, FILE *iop)

{

    register int c;

    register char *cs;

    

    cs = s;

    while (--n > 0 && (c = getc(iop)) != EOF)

        if ((*cs++ = c) == '\n')

            break;

    *cs = '\0';

    return (c == EOF && cs == s) ? NULL : s;

}


/* fputs: put string s on file iop */

int fputs(char *s, FILE *iop)

{

    int c;

    while (c = *s++)

        putc(c, iop);

    return ferror(iop) ? EOF : 0;

}


특별한 이유는 없지만 ferror과 fputs의 리턴값은 다르게 되어 있다 fgets를 사용해서 getline을 작성하는 것은 어렵지 않다.


/* getline: read a line, return length */

int getline(char *line, int max)

{

    if (fgets(line, max, stdin) == NULL)

        return 0;

    else

        return strlen(line);

}


예제 7-6 두 파일을 비교하여 내용이 다른 첫번째 행을 출력하게 하는 프로그램을 작성하라.


예제 7-7 제 5장 패턴찾는 프로그램을 수정하되 입력은 이름이 정해진 파일에서 받아들이거나, 파일 이름이 매개변수로 주어지지 않으면,쵸준 입력으로 받아들이도록 하라. 패턴이 일치하는 행이 발견될 때 파일 이름을 출력하게 할 수 있는가를 알아보라


예제 7-8 여러 개의 파일을 출력시키키는 프로그램을 작성하라. 각 프로그램은 새 페이지에서 시작 해야 하고 프로그램 위에 파일 이름이 출력되고 프로그램마다 1부터 시작하도록 페이지 번호를 붙인다.

cat 함수의 에러처리 방법은 그다지 좋은 편이 아니다 만약 파일중 하나가 어떤 이유로 액세스 될 수 없을때 에러메시지가 출력문의 끝에 나타난다. 이런 출력이 화면상에서 나타난다면 별 문제가 없지만 파이프라인을 통해 다른 파일이나 프로그래멩 들어가면 곤란하게 된다.

위의 문제를 해결하기 위해서 stderr로 불리는 또 하나의 stream을 stdin이나 stdout과 같은 방법으로 프로그램에서 지정한다.표준 출력의 방향이 다시 지정되더라도 stderr의 에러문은 화면상에 나타나게된다.

위의 것을 이용하여 cat를 표준 에러 파일에 에러메시지를 나타내도록 해보자.


#include <stdio.h>


/* cat: concatenate files, version 2 */

int main(int argc, char *argv[])

{

    FILE *fp;

    void filecopy(FILE *, FILE *);

    char *prog = argv[0]; /* program name for errors */

    

    if (argc == 1 ) /* no args; copy standard input */

        filecopy(stdin, stdout);

    else

        while (--argc > 0)

            if ((fp = fopen(*++argv, "r")) == NULL) {

                fprintf(stderr, "%s: can't open %s\n",

                    prog, *argv);

                exit(1);

            } else {

                filecopy(fp, stdout);

                fclose(fp);

            }

    if (ferror(stdout)) {

        fprintf(stderr, "%s: error writing stdout\n", prog);

        exit(2);

    }

    exit(0);

}


이 프로그램은 두가지 방법으로 에러를 알란다. 첫째 fprintf에 의해 만들어진 에러 메시지를 stderr로 보내면 에러 메시지가 사용자의 화면에 나타나게 된다. 프로그램 이름을 argv[0]으로부터 포함 했으므로 이프로그램이 다른것과 함께 사용 되더라도 에러의 근원지는 구별된다.

둘째 표준 라이브러리 함수 exit를 사용하는 방법이다. 그런데 이것이 호출되면 프로그램의 수행이 끝나게 된다. exit의 리턴 값은 다른 프로그램에서도 사용할 수 있으므로 그 프로그램 수행의 성공여부를 검사하는 프로그램을 작성할 수있다. exit가 0값을 리턴하면 모든게 잘되고 있는 것이고0이아닌 값을 리턴하면 대개 비정상적이라는 암시를 나타낸다 exit는 열린(open)파일에 대해 fclose를 호출하여 버퍼를 지우고 파일을 닫는다(close)

main 함수에서는 return expr 이라고 표현 하는 것과 exit(expire)이라고 표현하는 것은 똑같다. 그러나 exit는 다른 함수로부터 호출할 수 있는 유리한 점을 가지고 있다 함는 fp에 에러가 발생한다면 0이 아닌 값을 리턴한다.


int ferror(FILE *fp)


출력 에러는 매우 드물긴 하지만 발생할 여지가 있다(예:디스크가 꽉 차 있을경우).그렇기 때문에 프로그램에서이 점을 잘 검사해야 한다 함수 feof(FILE *)는 ferror의 유사형이다. 이 함수는 파일의 끝을 발견하면 0이 아닌값을 리턴한다.


int feof(FILE *fp)


여기서 예를 든 프로그램에서는 exit의 리턴값에 주의하지 않았지만 고수준의 프로그램에서는 리턴값 상태에 주의를 기해야만 한다.


이제까지의 예는 표준 입력과 표준 풀력에 의해 입출력 되옸는데 이들은 각 운영체제에 의해 자동연결된 것이다 다음 단계로 프로그램에 자동 연결되어 있지 않은 파일을 액세스하느 프로그램을 작성키로 하자. 이런작업을 필요로 하는 프로그램으로 cat이 있다 cat은 여러개 파일의 내용을 표준 출력으로 보낸다. 즉 몇개 파일의 내용을 모아서 화면에 표시해 준다. 예를들어 명령


cat x.c y.c


은 표준 풀력(즉,화면)에 x,c파일과 y.c의 내용을 출력한다

문제는 x,c나 y.c같은 파일 이름을 프로그램 내에서 어떻게 처리하느냐 하는 것이다.

규칙은 간단하다.읽거나 쓰기 전에 파일은 라이프러리 함수 fopen에 의해 열려야 한다 fopen는 x.c나 y.c와 같은 외부명칭을 받아들여 운영체제와 적절한 유지 및 절출(상세한 것은 생각할 필요가 없음)을 행하고 다음 조치로 파일을 읽고 쓰기 위해 사용되는 포인터들을 리턴한다.

이 포인터는 파일 포인터라고 불리며 파일에 관한 정보를 포함하는 구조체를 가리키는데 이 구조체에는 버퍼의 위치와 버퍼내에서의 문자의 위치 또한 파일이 읽기 위한 것인지 쓰기 위한것인지 에러나 EOF를 만나지 않았는지 등의 정보가 들어있다 사용자는 세부 사항을 알 필요는 없다 왜냐하면 <stdio.h>에 있는 표준 입출력 정의 중에는 FILE이라는 구조체정의(structure definition)가 있기 때문이다. 파일 포인터를 위해서 단지 다음과 같은 선언만 하면 된다.


FILE *fp;

FILE *fopen(char *name,char *mode)


여기서 fp는 FILE을 가리키는 포인터 이고 fopen은 FILE을 가리키는 포인터를 리턴한다 FILE은 구조체의 이름이 아니라 형의 이름이며 typedef에 의해 구현된다

프로그램에서 fopen의 호출은


fp = fopen(name, mode);


와 같이 한다.

fopen의 첫째 매개변수는 문자열로 표현된 파일의 이름이고 둘째 매개변수는 문자열로 표현된 모드(mode)인데,입력("r"),출력("w"),첨가("a")가 있다. 몇몇 시스템에서는 text와 2진수(binary)파일을 구별하다. 이런경우에 2진수라는 것을 나타내기 위해 "b"를 사용한다.

만약 출려과 첨가를 위한 파일이 open 되어 있지 않으면 파일은 새로 만들어 진다.출력을 위해 이미 존재하는 파일을 열면 기존의 내용이 지워진다. 존재하지 않는 파일을 읽기 위해 여는 것은 에러이며,에러에는 허가 되지 않는 파일을 읽을 경우 등 여러가지 원인이 있을수 있다. 만약 에러가 있을때 fopen은 NULL을 리턴한다(이 에러는 더 정확하게 구별될수 있다)

다음으로 필요한 것은 열린 파일을 읽거나 쓴느 방법이다 이를 위한 방법에는 여러가지가 있는데 그중 getc와 putc가 가장 간단하다. getc는 파일로부터 다음 문자를 읽어 리턴한다.getc에서는 사용하는 파일을 알려주는 파일 포인터가 필요하다.


int getc(FILE *fp)


getc은 fp에 의해 지정된 파일로 부터 입력을 한번 호출에 한문자씩 리턴한다. 파일의 끝이거나 에러가 발행하면 EOF를 리턴한다


int putc(int c, FILE *fp)


putc는 문자 c를 fp파일로 출력하고 출력된 문자를 리턴한다. 에러가 발행했다면 EOF를 출력한다 getchar와 putchar처럼 getc와 putc는 함수 대용의 매크로 일 수도 있다.

C프로그램이 처음 수행 될 때 운영시스템의 환셩은 세개의 파일을 자동적으로열고 세개파일을 위한 포인터를 배정한다. 이 파일들은 표준 풀력,표준입력과 표준 에러 출력이다 이에 대응하는 파일 포인터는 stdin,stdout,stderr로 불리고 <stdio.h> 에 선언되어있다 대개 stdin은 키보드에 연결되어 있고 stdout과 stderr는 스크린에 연결되어 있지만 stdin과 stdout은 7.1에서 설명한 것처럼 파일이나 pipe로 방향을 바꿀 수 있다

getchar와 putchar은 다음과 같이 getc,putc,stdin과 stdout로 정의 될 수도 있다.


#define getchar() getc(stdin) 

#define putchar(c) putc((c), stdout)


파일의 정형화된 입력과 출력을 수행하기 위해 fscanf와 fprintf가 사용될 수 있다. 첫번째 매개변수가 입력과 출력을 나타내는 파일 포인터인 것을 제외하면 scanf나 printf와 동일하다. 변환지정 문자열은 두번째 매개변수이다.


int fscanf(FILE *fp, char *format, ...)

int fprintf(FILE *fp, char *format, ...)


위와 같은 예비적인 지식을 갖추었으므로 이제는 파일의 내용을 원하는곳으로 보내 주는 cat프로그램을 작성할 준비가 되었다 편리하다고 인정되는 일반적인 방법을 사용하여 다음과 같은 프로그램을 작성했다. 만약 명령라인(Command line)매개변수가 있다면 이들을 파일 이름이나,기타 지시를 위한 변수로 해석하고,만약 명령 라인 매개변수가 없다면 표준 입출력으로 처리된다.


#include <stdio.h>


/* cat: concatenate files, version 1 */

int main(int argc, char *argv[])

{

    FILE *fp;

    void filecopy(FILE *, FILE *)

    if (argc == 1) /* no args; copy standard input */

        filecopy(stdin, stdout);

    else

        while(--argc > 0)

            if ((fp = fopen(*++argv, "r")) == NULL){

                printf("cat: can't open %s\n", *argv);

                return 1;

            } else {

                filecopy(fp, stdout);

                fclose(fp);

            }

    return 0;

}

/* filecopy: copy file ifp to file ofp */

void filecopy(FILE *ifp, FILE *ofp)

{

    int c;

    while ((c = getc(ifp)) != EOF) putc(c, ofp);

}


파일 포인터인 stdin과 stdout은 FILE * 형으로 되어 있다. 그들은 변수가 아니고 상수이므로 그것에 어떤 값을 지정하는 것은 불가능하다. 다음 함수는


int fclose(FILE *fp)


fopen의 역기능을 가진다 이 함수는 fopen에 의해 설정된 외부형 이름(external name)과 파일 포인터 사이의 접속상태를 해제하고 파일 포인터를 다른 파일에 재사용 가능케 한다. 대부분의 운영체제가 동시에 열 수 있는 파일 수를 제한하고 있기 때문에 cat에서처럼 사용하지 않는 파일 포인터는 다른 프로그램이 사용할 수 있게 양보하는 것이 좋다.fclose를 출력 파일에 적용시키는 또 다른 의미는 putc의 풀력문자를 저장하는 버퍼를 없애 준다는 것이다. fclose 는 프로그램이 정상적으로 끝났을 때는 각자의 열린 파일에 대해 자동적으로 호출된다(만약 stdin과 stdout이 필요치 않으면 이것을 닫아 버릴 수도 있다. 그들은 라이브러리 함수 freopen에 의해 재지정 될수 있다)


함수 scanf는 printf와 사용방법에 유사한 함수인데,출력함수가 아니고 입력함수이다.


int scanf(char *format,...)


scanf는 표준 입력으로 부터 문자를 읽어 들여서 format의 변환 형식에 따라 그들을 해석하고 매개변수에 결과를 저장한다.

변환형식 지정 매개변수가 아닌 보통의 매개변수는 입력되는 문자가 위치해야 하는 곳을 가리키는 포인터여야한다. scanf는 변환형식 지정 문자열을 다 사용했거나 형식에 맞지않게 입력이 들어왔을 때 멈추게된다. scanf는 성공리에 대응된 것들의 개수와 할당된입력 항목의 개수를 리턴한다.리턴값은 항목이 몇개나 발견되었는지를 알아내는데 사용된다.파일의 끝에 가면 EOF를 리턴한다. EOF는 다음 입력 문자가 변환형식 지정 문자열과 대응 되지 않는 입력이 들어왔을떄 나타나는 0과는 다르다. 표준 입력 대신 문자열로부터 읽어들이는 함수 sscanf도 있다.


int sscanf(char *string,char *format,arg1,arg2, ...)


이것은 format에 있는 형식을 따라 문자열을 조사하고 arg1,arg2등에 결과 값을 저장한다. 이들 매개변수는 포인터여야 한다. 변환 형식을 나타내는 문자열은 입력결과의 해석을 위해 사용된다. 변환형식 지정 문자열은 다음 항목을 포함한다.


-공백 또는 탭 이는 부시 된다.

-%를 제외한 보통 문자들

-%변환문자,변환이 없음을 알리는 * ,최대 자릿수를 표시하는 숫자,short 이냐 long이냐를 정해주는 h,l,L


문자

입력DATA:매개변수 TYPE

 d

 10진수 정수 :INT *

 i

 정수:INT *.정수는 8진수(숫자 앞에 0)또는 16진수(앞에 0x,0X)가 될 수도 있다

 o

 8진수 정수(앞에 0이 있거나 없는:int *

 u

 부호 없는 10진수 정수:unsigned int *

 x

 16진수(앞에 0x,0X가 있거나 없는):int *

 c

 문자열:char *.white space의 뛰어넘기는 하지 않는다. 다음의 non-white space 문자를 읽어 들이려면 %1s를 사용한다

 s

 문자열:char *,문자열을 위한 충분한 크기의 문자 배열을 가리킨다.문자열 끝에는 '\0'을 붙인다.

 e,f,g

 부호 10진포인터(decinal point)와 지수를 선택적으로 가질 수있는 부동소수점수:float *

 %

 문자 %:지정은 이루어지지 않는다.


보통 결과는 대응되는 매개변수가 가리키는 변수에 저장되지만 ,반환 형식 *문자에 의해 지정된다면 입력 항목은 건너 뛰게 된다. 즉 지정은 이루어지지 않는다.입력필드는 non-white space문자(공백,탭 등이 아닌 문자)의 문자열로 정의되고 다음의 white space문자까지 또는 필드 너비가 명시 되었을 경우에는 필드 폭까지 필드가 된다. 행바꿈 문자는 white-space이므로 scanf입력을 찾기 위해 행의 경계를 뛰어넘어 읽을 것 임을 의미한다 [white space문자는 공백 탭 행바꿈(newline),캐리지 리턴(carriage return)수직 탭,formfeed이다] 변환 문자는 입력 필드를 읽는 형식을 지정한다. C에서 매개변수 전달은 call by value이므로 매개변수는 반드시 포인터 여야 한다

변환운자 d,i,o,u와 x의 앞에는 h나 l을 붙일수 있는데 h는 short을 의미하고 l long을 의미한다 마찬가지로 변환문자 e,f와 g 앞에 매개 l을 붙여 double임을 나타낼 수 있다. 첫번째 예로 제 4장의 초보적인 계산기 프로그램을 scanf를 사용하도록 다시 작성한 것을 보인다


#include <stdio.h>


int main() /* rudimentary calculator */

{

    double sum, v;

    sum = 0;

    while (scanf("%lf", &v) == 1)

        printf("\t%.2f\n", sum += v); return 0;

}


다음과 같은 형태의 날짜 데이터를 읽기 원한다면


25 DEC 1988


scanf 명령문을 다음과 같이 쓴다.


int day, year;

char monthname[20];

scanf("%d %s %d", &day, monthname, &year)


&는 monthname에 사용하지 않는다 왜냐하면 배열 이름은 그 자체가 포인터 이기 때문이다. 변환형식 지정 문자열에 보통의 문자가 올 수도 있는데 ,입력에서 같은 문자가 들어와야 한다 그러므로 scanf 함수로 날짜를 mm/dd/yy 형태로 읽을수 있다.


int day, month, year;

scanf("%d/%d/%d", &month, &day, &year);


scanf는 변환형식 지정 문자열에 있는 공백과 탭을 무시한다. 또 scanf는 white space(공백,탭,newline)를 무시한다 여러가지 형태의 입력을 읽기 위해서는 한번에 한줄을 모두 읽는 것이 가장 좋다 다음에 sscanf에 의해 분해해서 읽어 들인다. 예를들어 위의 두 형태중 어떤 날짜 형태라도 읽을수 있는것을 원한다면 다음과 같이 쓸수 있다


while (getline(line, sizeof(line)) > 0)

{

    if (sscanf(line, "%d %s %d", &day, monthname, &year) == 3)

        printf("valid: %s\n", line); /* 25 Dec 1988 form */

    else if (sscanf(line, "%d/%d/%d", &month, &day, &year) == 3)

        printf("valid: %s\n", line); /* mm/dd/yy form */

    else

        printf("invalid: %s\n", line); /* invalid form */

}


scanf 함수로 읽은 다음 다른 입력 함수를 호출하면 그 함수는 scanf가 읽은 바로 다음부터 읽게 된다.

한번더 강조하는데 scanf와 sscanf의 매개변수는 반드시 포인터여야 한다. 즉


scanf("%d",n);


가 아니라


scanf("%d",&n);


로 쓰는 것이다 . 이실수는 컴파일 할 때도 일반적으로 발견되지 않는다.


예제 7-4 앞절에서의 minprint와 비슷한 scanf의 기본적인 프로그램을 작성하라.


예제 7-5 4.3절의 계산기 프로그램을 scanf,sscanf를 사용하여 다시 작성하라.


이 절에서는 printf의 최소 버전인 minprintf를 소개한다 이 함수는 가변 길이 매개변수를 포터블(portable:다른 컴퓨터 상에도 쉽게 이식 가능한)하게 처리할 수있는 함수이다.

설명하고자 하는것은 매개변수의 처리이므로 minprintf를 호출하여 사용하도록 한다.

printf에 대한 선언은 다음과 같다.


int printf(char *fmt, ...)


여기서 ...의 의미는 매개변수의 형과 개수가 변할수 있음을 뜻하며,...은 매개변수 리스트의 끝에만 나타낼 수있다. minprintf는 다음과 같이 정의한다.


void minprintf(char *ftm, ...)


표준 헤더 <stdarg.h>는 매개변수 리스트를 어떤 식으로 처리 하느냐가 정의된 매크로를 포함하고 있다. 이 헤더는 기종마다 다르게 만들어지지만 헤더를 사용하는 방법은 기종에 상관없이 동일하다.

va_list 형(type)은 매개변수를 차례로 가리키는 포인터 변수를 정의할 때 사용된다. minprintf에서 이 변수는 매개변수 포인터(argument pointer)의 첫 글자만들 따서 ap라 명명 했다. 매크로 va_start는 첫번째 이름 없는 매개변수를 가리키도록 ap의 초깃값을 설정한다. ap가 사용되기 전에 va_start를 한번 호출해야 하고 매개변수 중에서 이름을 가진것이 적어도 하나는 있어야 한다. 이름 붙여진 매개변수 중 마지막 것은 va_start를 시작하기 위해 사용된다.

va_arg를 호출하면 하나의 매개변수를 리턴하고 ap가 다음 매개변수를 가리키게 한다. va_arg는 어떤 형이 리턴될 것이며 ap가 얼마나 전진해야 될지를 결정하기 위해 형 이름을 참고한다. 마지막으로 va_end는 청소작업을 한다. 이것은 함수가 리턴되기 전에 호출되어야 한다.

이런동작을 하는 minprint 함수는 다음과 같다



#include <stdarg.h>


/* minprintf: minimal printf with variable argument list */

void minprintf(char *fmt, ...)

{

    va_list ap; /* points to each unnamed arg in turn */

    char *p, *sval;

    int ival;

    double dval;

    va_start(ap, fmt); /* make ap point to 1st unnamed arg */

    for (p = fmt; *p; p++) {

        if (*p != '%') {

            putchar(*p);

            continue;

        }

        switch (*++p) {

            case 'd':

                ival = va_arg(ap, int); printf("%d", ival); break;

            case 'f':

                dval = va_arg(ap, double); printf("%f", dval);

                break;

            case 's':

                for (sval = va_arg(ap, char *); *sval; sval++)

                    putchar(*sval);

                break;

            default:

                putchar(*p);

            break;

        }

    }

    va_end(ap); /* clean up when done */

}


예제 7-3 printf의 다른 기능을 추가하여 minprintf를 재작성하라

출력함수 printf는 내부의 수치 값을 문자로 바꾸어 준다. 이 전의 장들에서는 간단한 방법으로 printf를 사용하였다 여기서 설명된 사항들은 많이 쓰이는 전형적인 사례만을 언급하였다.전반적인 설명은 부록을 참고하자 


int printf(char *format,arg1,arg2,...)


printf는 format이라는 문자열의 제어하에서 표준 풀력 장치에 매개변수를 변환하고 형식화한 다음 출력시키고 풀력된 문자의 개수를 리턴한다. 출력형식 제어 문자열(format)은 두 종류의 대상체를 포함한다. 하나는 출력될 보통 문자이고 또 다른 하나는 출력 형식을 정해 주는 문자인데 매개변수를 지정된 형태로 변환시켜 printf로 출력하게 한다. 변환문자의 앞에는 %를 붙인다.%와 변환문자 사이에는 다음과 같은것이 순서대로 올 수있다.


-변환된 매개변수를 왼쪽에 맞추어 출력하게 하는 마이너스 부호

-최소너비를 지정하는 숫자 변환된 매개변수는 적어도 이 너비로 출력된다 만일 필요하다면 왼쪽 또는 오른쪽의 폭을 맞추기 위해 대른것(공백 또는 0)으로 체워진다.

-유효숫자를 나타내는 마침표(.)

-정밀도를 나타내는 숫자. 이숫자는 문자열을 출력하는 경우 그 최대 길이를 실수 값인 경우는 소수점 이하 숫자 길이를 정수 출력의 경우는 최소 숫자 길이를 나타낸다.

-만일 정수가 short로 출력되어야 하면 h,long으로 출력시키려면 소문자 1을 쓴다


변환문자는 아래 나타냈다 만일 %뒤의 문자가 변환문자가 아닌 경우는 동작이 정의되어 있지 않다.

                   

문자 

 변수형태

출력된 형태 

 d,i

 int

 10진수 

 o

 int

 부호없는 8진수

 x,X

 int

 부호없는 16진수(앞에 0x나 0X가 없는),10,...15에 대해 abcdef나     ABCDEF를 사용한다

 u

 int

 부호없는 10진수

 c

 int

 단일 문자 

 s

 char *

 문자열로 부터 '\0'가 있을때 까지 문자가 출력되거나 정밀도에 의해 주어진 문자수 만큼 문자가 출력된다

 f

 double

 [-]m,dddddd 여기서 d의 수는 정밀도에 의해서 주어진다(기본설정 값은 6이다.)

 e,E

 double

 [-]m,dddddd e+-xx또는,[-]m,ddddddE+-xx,여기서 d의 수는 정밀도에 의해서 주어진다(기본설정 값은 6이다.)

 g,G

 double

 만일 지수가 주어진 정밀도 이상이거나 -4보다 작으면 %e나 %E를 사용하고 그렇지 않으면 %f를 사용한다. 뒤에 붙는 0과 소수점은 출력되지 않는다.

 p

 void *

 포인터(시스템에 따라 표현이 다름)

 %

 변환되지 않는다

 %를 출력


폭(정밀도)은 *로도 표현될수 있는데 그러면 출력형태는 출력될 매개변수에 의해 결정된다 예를 들면 문자열 s로부터 max 문자를 출력한다.

printf("%.*s",max,s);


대부분의 출력형 변환은 앞에서 설명하였지만 여기서 더 자세히 설명하기로 한다.

다음의 표는 "hello,world"(12문자)를 출력 시키는데 있어서의 여러가지 변환 효과를 나타내었다. 각각 필드의 주위에 콜론(;)을 찍어서 그들의 범위를 알 수 있게 하였다.


:%s:           :hello, world:

:%10s:         :hello, world:

:%.10s:        :hello, wor:

:%-10s:        :hello, world:

:%.15s:        :hello, world:

:%-15s:        :hello, world   :

:%15.10s:      :     hello, wor:

:%-15.10s:     :hello, wor     :


printf는 매개변수의 개수와 그들의 형태가 무엇인지를 결정하기 위해 첫번째 매개변수를 이용한다. 만일 매개변수가 충분치 않거나 형(type)이 틀렸을 경우에는 혼란을 일으키게 되어 무의미한 결과를 얻게 된다. 다음의 두 호출 사이의 차이가 뭔지 잘 보기 바란다.


printf(s);         /* FAILS if s contains % */

printf("%s",s);    /* SAFE */


sprintf 함수는 printf가 행하느 같은 변환을 행하지만,출력을 문자열(string)에 저장하는 것이 다른점이다.


int sprintf(char *sting,char *format,arg1.arg2,...)


sprintf는 arg1,arg2등의 매개변수를 printf와 같은 방법으로 형식화 시킨다. 하지만 표준 입출력 대신에 문자열에 결과를 저장한다. 문자열은 결과를 수용할 만큼의 충분한 크기여야 한다.


예제 7-2 임의의 입력을 알아볼 수있는 형태로 출력시키는 프로그램을 작성하라 그래픽 문자가 들어오면 8진수나 16진수로 출력시켜야 하며,긴 행은 적당한 길이로 끊어서 출력해야 한다.



입력과 출력 기능은 C 언어 자체에서 제공하지 않으므로 그 기능을 강도하지 않았다. 그럼에도 실제적인 프로그램은 앞에서 소개한것 보다 더욱 복잡하게 주위 환경과 상호작용을 한다. 이 장에서느 표준 라이브러리와 입출력 시스템에 제공하기 뒤해 설계된 함수와 문자열 조작(string handling),기억장치관리(storage management),수학적 루틴(mathematical routines)과 C프로그램을 위한 다른 여러 함수들을 기술하고자 한다. 그중에서도 입력과 출력에 대해 집중적으로 기술한다

ANSI 표준은 이 라이브러리 함수들을 세밀하게 정의하는데 현존하는 어떠한 C 시스템과 서로 호환성을 가져야한다 라고 정의하고있다 표준 라이브러리에 의해 제공된 함수를 사용하여 만들어진 프로그램을 변경하지 않은 상태에서도 다른 시스템에서도 똑같이 동작해야 한다는 말이다.

라리브러함수의 속성은 12개이상의 헤더에서 자세히 설명하였다 <stdio.h> <string.h> <ctype.h>등인데 벌써 여러 번 소개하였다 여기서는 모든 라이브러리를 소개하지 않을 것이다 왜냐하면 이 장의 목적은 C프로그램을 작성할때 이 헤더들을 어떻게 사용하는지를 설명하는데 있기 때문이다


제 1장에서 말한 바와 같이 표준 라이브러리 함수를 사용하여 텍스트의 입력과 출력의 간단한 동작을 실행 할수 있다 문자 stream은 여러개의 행으로 구성되는데 각 행의 끝에는 행바꿈 문자가 있다. 라이브러리의 함수는 행바꿈 문자 또는 리턴키 등에 적절히 대응하는 동작을 해야한다. 가장 간단한 입력 방법은 보토의 키보드로 표준 입력ㅢ getchar를 이용하여 한번에 한 문자씩 읽어들이는 것이다


int getchar(void)


getchar는 그것이 호출 될때마다 다음의 입력문자를 넘겨주고 파일의 끝을 만나면 EOF라는 값을 넘겨준다 EOF는 상수인데 <stdio.h> 에 정의되어있다 그 값은 -1로 정의되지만 기계에 따라 다를수도 있으므로 -1을 직접사용하는 것보다는 EOF를 사용하는 것이 좋다

대부분의 환경에서는 <을 이용하여 키보드 대신에 파일을 이용할 수있게 한다.(redirection)만약 prog라는 프로그램 내에서 getchar가 사용되었다면,infile이라는 파일로 부터 prog문자를 읽어 들이게 다음과 같이 한다


prog <infile


prog 프로그램 내에서는 입력이 어디로부터 오는지에 대해 신경 쓰지 않아도 좋다. 그리고 "<infile"은 argv안에 있는 명령 라인 매개변수(argument)에 포함되지 않는다. 입력전환(redirection)은 만약 입력이 다른 프로그램으로부터 온다 해도(파이프라는 방식을 사용해서)밖에서는 보이지 않는다. 일부 시스템에서 명령라인(command line)


other | prog


은 otherprog와 prog라는 두 프로그램을 실행 시키고 otherprog의 표준 출력을 prog의 표준 입력으로 되게 한다.


함수

int putchar(int)


은 출력으로 사용된다. putchar(c)는 문자 c를 표준 풀력인 스크린에 보낸다 putchar는 문자를 출력하는데 에러 발생 시에는 EOF를 리턴한다. 출력은>filename에 의해 파일로 방향 전환이 가능하다 prog가 putchar를 사용하는 경우


prog >outfile


와 같이 해주면 표준 풀력 대신 outfile을 쓸 것이다. 만일 파이프 기능이 있다면


prog | anotherprog


은 anotherprog의 표준 입력에 prog의 표준 출력을 집어 넣는다. printf에 의해 생성되 ㄴ풀력 또한 표준 출력이 된다. putchar와 printf의 호출이 적당히 혼잡되어 있는 경우, 그들의 호출 순서에 따라 출력이 나온다.

입력/출력 라이브러리 함수를 사용하는 프로그램의 시작부분에는 다음행이 반드시 있어야 한다.


#include <stdio.h> 


이때 이름은<와>를 사용하여 묶는데 이것은 표준 지정장소에 stdio.h파일이 있다는 뜻이다

대부분의 프로그램은 하나의 입력 stream에서만 읽어 들이고 하나의 출력 stream으로만 출력시킨다,getch,putchar,printf만 사용해도 이런 프로그램을 작성할 수있다. 한 프로그램의 출력을 다음 프로그램의 입력으로 연결시키기 위한 방향 전환(redirection)을 사용한다면 입력 하나 출력 하나로도 많은 일을 할 수있다. 예를 들어 입력을 소문자로 바꾸어주는 lower라는 프로그램을 생각해보자


#include <stdio.h> 

#include <ctype.h>


int main() /* lower: convert input to lower case */

{

    int c;

    while ((c = getchar()) != EOF)

        putchar(tolower(c));

    return 0;

}


함수 tolower는 <ctype.h> 에서 정의 되어있다. 이것은 다른 문자는 건드리지 않고 대문자를 소문자로만 바꾸어준다.

앞에서 이미 언급했던 <stdio.h> 에 있는 getchar와 putchar와 같은 함수와 <ctype.h>에 있는 tolower들은 매크로(macro)이다


예제 7-1 대문자는 소문자로 또는 소문자는 대문자로 변환시키는 프로그램을 작성하되,argv[0]에서 찾아낸 이름을 사용하라.



기억 장소가 부족할 때에는 여러 대상체(object)를 하나의 기계어에 채워 넣을 필요가 있다.가장 많이 쓰이는 방법은 컴파일러의 심벌 테이블에서와 같이 각각 한 비트의 플래그를 두는 것이다. 하드웨어 장치에 대한 인터페이스 같은 외부적으로 주어진 데이터 형식은 한 단어를 여러족가으로 분리해서 처리할 수있는 능력을 요구한다.

컴파일러에서 심벌 테이블을 조사하는 경우를 생각해 보자. 프로그램에 있는 각 식별자(identifier,뱐수나 상수의 이름 함수의 이름등)는 그와 과련된 어떤 정보, 즉 그것이 키워드 인지 아닌지 또는 외부의 정적인 것인지 아닌지 등과 연관된 어떤 정보를 갖고 있다. 그러한 정보를 코드와 시키는 가장 간견한 방법은 하나의 나 1키느 짜리 플래그(flag)를 쓰는 것이다.

이를 하는 일반적인 방법은 각 연관된 비트의 위치에 적당한 역할을 부여하는 것이다.


#define KEYWORD 01

#define EXTERNAL 02

#define STATIC 04


또는


enum { KEYWORD = 01,EXTERNAL 02,STATIC = 04};


이때 숫자는 반드시 2의배수 또는 1이어야 한다 이렇게 하면 제 2장에서 설명한 시프트나 마스크 보수 연산자를 가지고 비트를 조작하는 것과 어떤 비트의 값을 알아내는 것이 가능해 진다.

다음과 같이 쓰면 플래그의 EXTERNAL과 STATIC 비트는 on 상태가 되고


flags |= EXTERNAL | STATIC;


이것을 다음과 같이 쓰면 off가 된다.


flags &= ~(EXTERNAL | STATIC);


그리고 다음과 같이 썽르 경우에는 EXTERNAL과 STATIC둘다 off일때만 참이 된다.


if ((flags & (EXTERNAL | STATIC)) == 0) ...


C에서는 이러한 비트단위 논리 연산자를 쓴느 방법 외에 단어의 필드를 직접 액세스 하고 정의하는 방법도 제공하다. 비트 필드의 줄임말인 필드는 워드라고 부르는 기억장소 내의 인접한 비트의 집합으로 이루어진다. 필드를 정의하고 액세스 하는 구문은 구조체에 근거를 두고있다. 예를 들어 앞에서 언급한 바 있는 심벌 테이블 #define은 다음과 같이 세개의 필드를 정의 해서 나타낼수 있다.


struct {

    unsigned int is_keyword : 1;

    unsigned int is_extern : 1;

    unsigned int is_static : 1;

} flags;


이렇게 하면 flags는 세개의 1비트짜리 필드를 가진 변수로 정의 된다. 콜론 뒤의 숫자는 필드의 크리를 비트수로 표시한 것이다. 여기서 필드는 부호 없는 수임을 표시하기 위해  unsigned 라고 정의된다

각 필드는 구조체의 멤버와 마찬가지로 flags.is_keywoord,flags.is_extern등으로 사용된다 또한 필드는 다른 정수와 마찬가지로 수식에서도 쓰일 수이 ㅆ는 조그만 정수이다 따라서 위의 수식은 다음과 같이 쓰느 것이 더 자연스럽다 비트를 on 시키기 위해서는 다음과 같이 하고


flags.is_extern = flags.is_static = 1;


off 시키기 위해서는 다음과 같이 하며,


flags.is_extern = flags.is_static = 0;


그 비트를 시험하기 위해서는


if (flags.is_extern == 0 && flags.is_static == 0)

...


위와 같이 작성하고 쓰면 된다

필드는 int 의 경계를 넘으면 안된다 만약 그런 일이 생긴다면(중첩된다면)필드는 다음 int의 경계에 정돈된다

필드는 이름이 없어도 된다. 기억장소의 시작위치를 맞추기 위해 콜론과 크기만 있는 이름없는 필드가 사용되기도 한다. 특수하게 크기가 0인 필드가 있는데 이것은  int의 경계와 맞추기위해 사용된다

필드의 사용에는 여러가지 주의할 점이 있는데 가장 중요한 것은 외쪽에서 오른쪽으로 지정된 기종도 있고 오른쪽에서 왼쪽으로 지정된 기종도 있다는 점이다 이것은 필드가 내부저ㅗㄱ으로 정의된 자료구조를 유지 하는데 매우 유용하지만 외부적으로 정의된 자료를 분해할때는 어느쪽 끝이 먼저인가를 고려해야 함을 의미한다. 그러한 것에 관련된 프로그램은 다른기종에 옮기기가 힘들다(호환성이 없다).필드는 단시  int만으로도 정의될수 있자만 호환성을 유지하기 위해서 signed인지 unsigned인지 명확히 구별할 필요가 있다. 그것들은 배열될 수 없으며,주소도 갖지 않기 때문에 그들에 대해서 &연산자도 적용될 수없다