6.3 구조체의 배열(Arrays of Structures)
문자열에서 C에서 사용되는 키워드(keyworkd)중에 어느것이 몇번 나왔는지 세는 프로그램을 생각해 보자 이 플그램에는 아름을 나타내는 문자열의 배열과 그 수를 세기 위한 정수의 배열이 필요하다. 그 한가지 방법은 keyworkd 와 keycount라는 두개의 배열을 사용하는 것이다
char *keyword[NKEYS];
int keycount[NKEYS];
여기서 두개의 배열 대신 구조체를 사용하는 것이 가능하다 필요한 변수는 다음과 같은 두개이다
char *word;
int count;
이 배열 쌍을 구조체로 선언하려면 다음과 같이 한다.
struct{
char *word;
int count;
} keytab[NKEYS];
이 선언은 배열 keytab이 이런 형태의 구조체라는 것을 나타내고 각각에 기억장소를 할당한다 즉 keytab은 구조체의 배열이 된다. 이것은 다음과 같이 쓸 수도 있다.
struct{
char *word;
int count;
};
struct key keytab[NKEYS];
keytab이라는 구조체는 멤버가 무엇인지 정해져 있으므로 이것들이 정의할때 초깃값을 주는 것은 쉬운일이다. 구조체에 초깃값을 주는 것은 앞의 것과 아주 비슷한데 정의 뒤에 초깃값을 중괄호 속에 나타내면 된다.
struct{
char *word;
int count;
} keytab[] = {
"auto", 0,
"break", 0,
"case", 0,
"char", 0,
"const", 0,
"continue", 0,
"default", 0,
/* ... */
"unsigned", 0,
"void", 0,
"volatile", 0,
"while", 0
};
초깃값들은 각 구조체의 멤버 쌍으로 되어있다. 각 열마다 초깃밧을 중괄호로 둘러싼느 것이 더 정확할것이다.
{ "auto", 0 },
{ "break", 0 },
{ "case", 0 },
...
그러나 초깃값이 단순 변수이거나 문자열이고 값이 모두 주어진 경우에는 중괄호가 필요없게 된다. 초깃값이 주어져 있고[]가 비어있을 때(배열의 크기가 나타나지 않을때)는 초깃값으로 준 원소의 개수가 그 배열의 크기로 지정된다
키워드 계산 프로그램은 keytab의 정의부터 시작된다. main루틴에서는 한 단어씩을 읽어들이는 getword함수를 계속해서 호출하여 입력을 읽어 들인다. 각 단어는 제 3장에서 썻던 이진탐색 함수로 keytab에 있는지를 검색한다. 키워드의 리스트는 문자 값이 증가하는 순서대로 정렬된다.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define MAXWORD 100
int getword(char *, int);
int binsearch(char *, struct key *, int);
/* count C keywords */
int main()
{
int n;
char word[MAXWORD];
while (getword(word, MAXWORD) != EOF)
if (isalpha(word[0]))
if ((n = binsearch(word, keytab, NKEYS)) >= 0)
keytab[n].count++;
for (n = 0; n < NKEYS; n++)
if (keytab[n].count > 0)
printf("%4d %s\n",
keytab[n].count, keytab[n].word);
return 0;
}
/* binsearch: find word in tab[0]...tab[n-1] */ int binsearch(char *word, struct key tab[], int n) {
int cond;
int low, high, mid;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low+high) / 2;
if ((cond = strcmp(word, tab[mid].word)) < 0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return mid;
}
return -1;
}
getword라는 함수는 단어를 찾아서 그 단어를 첫번쨰 매개변수인 배열에 복사한다 NKEYS는 keytab에 있는 키워드의 수를 나타낸다 키워드의 수는 처음부터 리스트로 주어지기 때문에 몇개인지 세어서 숫자로 정해도 되겠지만 리스트가 바뀔경우를 대비해서 프로그램에서 하기로 한다.
한가지 방법은 초깃밧들의 맨 마지막에 null 포인터를 두고 ,keytab을 반복하다가 null 포인터가 발견되면 끝내는 방법이다. 그러나 이것은 배열의 크기가 컴파일 시에 완전히 결정되기 때문에 불필요한 일을 하는 것이된다. 배열의 크기는 다음식에서 나온 값의 제곱이 된다.
keytab의 크기/ struct key의 크기
C에는 컴파일 시에 어떤 대상의 크기를 계산하는 sizeof라는 연산자가 있는데 다음과 같이 사용된다.
sizeof 대상
sizeof(형의 이름)
이 수식은 지정된 대상이나 type 의 바이트 단위의 크기가 정수로 나타난다(엄밀하게 하면 sizeof는 헤더 <stddef.h> 에서 정의된 size_t형의 unsigned 정수로 나타난다 이때 대상은 변수나 배열 구조체가 될수있다 int ,double 과 같은 기본형의 이름이나 구조체가 포인터와 같은 유도된 형태의 이름이 될수있다
우리의 경우 키워드의 수는 배열의 크기를 원소의 크기로 나눈 것과 같다. 이 계산은 #define 문에서 NKEYS의 값을 정할 때 쓰인다
#define NKEYS (sizeof key tab/sizeof(struct key))
다른 방법으로 저장된 원소의 크기로 배열의 크기를 나누는 방법이 있다.
#define NKEYS (sizeof keytab/sizeof keytab[0])
이방법은 type이 변해도 그대로 사용할 수있다는 장점이 있다
프리프로세서는 type을 분석하지 않으므로 #if문에 sizeof 를 사용할수 없다 하지만 #define은 프리프로세서가 값을 미리 구하지 않고 문자열을 치환하기만 하기 때문에 sizeof의 표현이 가능하다
getword라는 함수를 보자 우리는 필요로 하는것 이상으로 더 범용적인 getword 함수를 작성하였다 하지만 복잡한 것은 아니었다. getword는 입력(파일)으로 부터 다음 단어를 가져오는데 단어는 하나 이상의 문자의 모임이다 함수의 리턴 값은 단어의 첫 문자, 파일의 끝인 EOF또는 문자 그자체(영문자가 아닌경우)가 된다
/* getword: get next word or character from input */
int getword(char *word, int lim)
{
int c, getch(void);
void ungetch(int);
char *w = word;
while (isspace(c = getch()))
;
if (c != EOF)
*w++ = c;
if (!isalpha(c)) {
*w = '\0';
return c;
}
for ( ; --lim > 0; w++)
if (!isalnum(*w = getch())) {
ungetch(*w);
break;
}
*w = '\0';
return word[0];
}
getword는 제 4장에서 나온 getch와 ungetch를 사용한다 한 문장이 형성되었을때 getword는 한 문자를 더 읽게 된다 그 후 ungetch를 호출해서 더 읽은 문자를 다음의 호출을 위해 입력으로 되돌리게 된다. getword는 또한 공백을 건너뛰기 위해 isspace를 사용하며 문자를 확인하기 위해 isalpha를 사용하고 문자와 숫자를 확인하기 위해 isalnum을 사용하는데 이들 모두는 표준헤더인 <ctype.h>에 들어있다
예제 6-1 현재의 getword는 밑줄 _나 문자열 상수,코멘트 또는 프리프로세싱에 사용하는 제어행을 적당히 다루지 못한다. 프로그램을 계산해보라