열거형 ( enum )

The enumeration data type (enum) sets up a different use of an integral data type.
열거형 데이타 타입은 그니까 정수형이다.

열거형 타입의 선언은 유한집합을 명명하고, 그 집합의 원소로서 식별자를 선언한다.
집합명은 tag 라고 하고, 집합은 { } 로 나타내며, 각 원소는 comma(,) 로 구분한다. 각 원소들은 int형 "상수" 이다.

enum tag { element_list } ;

, 로 구분된 원소들은 indentifier 들이며, 값을 assign해서 초기화 할 수 있다.  공용체의 원소들은 같은 값을 갖을 수 있다.

초기화 되지 않은 원소들은, 자신의 바로 앞(왼쪽) 원소의 값보다 1큰 수를 부여받는다. 첫번째 원소를 초기화하지 않으면 0 으로 초기화한다.



예. enum boolean { FALSE , TRUE } ;       // FALSE = 0 , TRUE = 1

     enum day { sun=1, mon, tue=1, wed, thu, fri=9, sat };   // mon=2, wed=2, thu=3, sat=10



enum 은 데이타 타입이 아니라, 열거형임을 나타내는 키워드이고, enum tag 를 묶어서 type 으로 분류한다.



열거형 "변수"의 선언은 다음과 같은 형식이다.

type variables;        // where type:=  enum tag_name

예. enum boolean a, b;     // enum boolean 타입의 변수 a, b 선언



열거형으로 선언된 변수들은 집합의 원소들을 값으로 갖을수 있다.

예. a = FALSE ;


열거형 타입의 선언과 변수선언을 한번에 할 수 있다.

예. enum day { sun=1, mon, tue, wed, thu, fri, sat } a , b ;
     a 와 b 는 enum day 타입의 변수들.


tag 는 생략가능하다(anonymous tag). 대신, tag가 없기 때문에, 처음 변수선언을 제외하고는 enum tag 의 타입으로 새로운 변수를 선언할 수 없다. (단, typedef 를 이용하면 가능하다.)

예. enum { beer, cocktail, soju } a, b ;        //같은 타입의 변수를 더이상 선언할 수 없음.


열거형 변수 선언시, 매번 열거형임을 나타내는 키워드 enum 을 쳐야되므로, typedef 를 이용해서 좀더 간단하게 하는 것이 보통이다.

예. enum alcohol { beer, cocktail, soju };
    typedef enum alcohol alcohol;      //  enum alcohol 을 alcohol 로...
     alcohol a , b ;


열거형의 선언과 typedef를 한번에 쓸수도 있다.

예. typedef enum alcohol { beer, cocktail, soju } alcohol;
     alcohol a , b ;

 

tag 가 없는 경우라도, typedef 를 사용하여 그 자체를 renaming 해버리면, 이후에도 자유롭게 사용할 수 있다.


typedef enum { element_list } identifier;        // 해당 identifier 는 tag 없는 enum { ... } 타입의 새이름이 된다.


구조체 (structure)


배열은 동종의 자료들을 표현하기 위해 파생된 타입이다. 반면, 구조체는 서로다른 타입의 자료들을 표현하는데 사용된다.  구조체를 나타내는 C의 키워드는 struct 이다.

즉, 구조체는 다양한 타입의 데이타형의 묶음이다. ( 각 타입 요소를 member 라고 한다. )

C의 struct 는 C++ 의 출발점이다. 
C의 struct 개념은 이후에 C++에서 Class 개념으로 확장된다.



structure의 선언은 형식상 enumeration data type 과 유사하다.

열거형은...  enum tag { element_list };   와 같은 꼴이었다.

구조체는 ...  struct tag { declaration_statements }; 와 같은 꼴이다.

열거형에서 {} 안에 들어가는 원소들의 구분은 콤마(,) 로 했었지만 ( 집합의 원소들이니까... )
스트럭쳐에서는 {} 안에 들어가는게 선언문들 이므로, 당연히 세미콜론(;) 으로 구분한다. (문장들 이니까...)


예. struct menu { char *item; int price; char rate; double tax, calories; } ;

열거형에서는 {} 안에 들어간것이 단순히 "정수상수"가 될 원소들이었다면, 구조체에서는 그것들이 "변수선언문"으로 대치된것이다.


구조체형 변수의 선언은 열거형과 마찬가지다.

struct tag variables ;

예. struct menu { char *item; int price; char rate; double tax, calories; } ;
    struct menu a, b, c ;


열거형과 마찬가지로, 구조체형 선언시 변수 선언도 동시에 할 수 있다.

예. struct menu { char *item; int price; char rate; double tax, calories; } a, b, c ;


tag 의 생략도 열거형과 마찬가지다.

예. struct { char *item; int price; char rate; double tax, calories; } a, b, c ;


열거형임을 명시해주는것이 귀찮아 typedef 를 쓴것과 같이, 구조체임을 명시하는 것이 귀찮아 typedef 를 많이 쓴다.

예. struct menu { char *item; int price; char rate; double tax, calories; } ;
    typedef struct menu menu;
    menu a, b, c ;


열거형때와 마찬가지로, 구조체형 선언과 typedef를 같이 할 수 있다.

예. typedef struct menu { char *item; int price; char rate; double tax, calories; } menu ;
    menu a, b, c ;



tag 가 없는 경우도 마찬가지다.

예. typedef struct { char *item; int price; char rate; double tax, calories; } menu ;
    menu a, b, c ;



구조체 타입의 배열을 선언할 수도 있고, 배열이 구조체 내부의 멤버가 될 수도 있다. 심지어 구조체의 멤버가 구조체가 될 수도 있다.
구조체 형 변수도 보통의 변수와 마찬가지로 배열을 선언할 수 있다. 즉, 이것은 동종의 구조체형 자료들을 다루기 위함이다.

예. struct menu { char *item; int price; char rate; double tax, calories; } ;
    typedef struct menu menu;
    menu a[10] ;



-----------------------------------------------------------------------------------------------

구조체 타입은 사용자가 임의로 조합해서 만든 사용자 정의 타입이라고 할 수 있다.
하지만 그것들도 전히, int 나 double 와 같은 데이터 타입이다. 물론 C에서는 키워드와 함께해야하고 연산등에 있어서 차별을 두지만, C++ 에서는 정말 차별을 두지 않는다. (적어도 C++의 철학은 그렇다.)


따라서, 그 형에 맞는 포인터를 생각할 수 있다.

가령 위의 구조체 menu 형에 대해서 그것의 포인터 형 구조체 menu 포인터 형을 생각할 수 있다.
즉, struct menu 타입의 포인터 타입은 struct menu * 타입이다. 이 포인터는 struct menu 타입 데이타들의 주소를 가리킨다.


가령, 위에서 typedef 로 간단하게 struct를 안써도 되도록 menu 타입을 셋팅했으므로 menu 타입의 포인터 타입은 menu* 타입이다.

예. menu a;
    menu *mpt;
    mpt = &a;

------------------------------------------------------------------------------------------------

열거형에서 집합의 각 원소가 정수상수였던 것과는 달리, 구조체에서는 멤버들이 변수들이므로, 값을 자유롭게 변경하게 된다. 따라서, 멤버에 접근을 할 수 있어야한다.

구조체형의 변수는 멤버라고 하는 변수들을 포함하므로, 위계구조를 갖는 변수형이라고 할 수 있다. 이러한 위계구조는 구조체의 멤버가 또다시 구조체일때 여러단계로 나타난다.

위의 예에서, 구조체 menu 형으로 선언된 변수들 a, b, c 들은 각각 멤버로 item, price, rate, tax, calories 를 갖는다.

구조체 변수명에서 멤버에 접근할때는 . 연산자를 쓴다. C++ 클래스에서 멤버에 접근할때 . 을 쓰는것도 이와 같다.
구조체형 포인터에서 멤버에 접근할때는 포인터를 해당 구조체 변수에 포인팅한후에, ->
를 사용한다.

가령 다음과 같이 선언이 되어있다고 할때...

struct menu { char *item; int price; char rate; double tax, calories; } ;
typedef struct menu menu;
menu a ;
menu *strptr;
strptr = &a;                 //  menu형 포인터 strptr 이 menu형 변수 a의 주소를 가리킴

다음과 같이 멤버접근 연산자 . 을 이용해서 a의 멤버에 접근해서 값을 할당할 수 있다.
a.item = "chiken salad";
a.price = 9800;
a.rate = 'A';
a.tax = a.price * 0.05 ;
a.calories = 480.7 ;


또한, menu형 포인터 strptr 이 menu형 변수 a의 주소를 가리키고 있으므로...
다음과 같이 포인터의 멤버접근연산자  -> 를 이용해서 a의 멤버에 접근해 값을 할당할 수 있다.

연산자 -> 는 * 와 . 의 짬뽕이다.      p -> = ( *p ).


strptr -> item = "chiken salad";  // strptr 이 가리키고있는 구조체 변수에서 멤버 item 으로 접근 후 배정
strptr -> price = 9800;
strptr -> rate = 'A';
strptr -> tax = strptr -> price * 0.05 ;      // ( strptr -> price ) * 0.05
strptr -> calories = 480.7 ;


구조체 포인터 strptr 가 현재 구조체형 변수 a의 주소이므로, 그것을 역참조하면 a 이다. 즉, (*strptr)a

따라서,  (*strptr).rate a.rate 가 된다.  이와 같이 구조체도 포인터와 역참조 연산자 * , [] 따위의 관계를 그대로 사용할 수 있다.

또한, 구조체의 멤버가 구조체인경우에는 멤버접근 연산자  .  이나  -> 를 여러단계로 사용하면 된다.





구조체 변수의 초기화
배열은 동일자료형의 패키지이고, 구조체는 다른 자료형의 패키지라는 점에서 둘은 유사하다.
또한, 초기화 구문도 배열의 그것과 유사하다.

구조체 변수의 초기화에서도 배열과 마찬가지로, 중괄호 {} 와 콤마를 이용해 상수목록을 전달한다.
멤버들의 초기화를 위해 전달되는 상수목록은 멤버의 선언순서와 대응된다.

배열에서 상수목록의 개수가 부족하면 나머지 모든 원소가 0 으로 초기화 되었던것과 마찬가지로,
구조체에서도 상수목록의 개수가 부족하면 나머지에 대해서는 0 으로 초기화시킨다.

예.
#include <stdio.h>
int main(void){
    typedef struct { int no; char *item; int price; char rate; double tax, calories; } menu;
    menu a = { 17, "Salad", 5600 , 'A' , 560.0 , 1769.56 };    // 구조체 변수 a의 초기화

    printf("Menu No.%d\nname: %s\nprice: %d\nConsumer's rate: %c\nTax: %g\nCalories: %g\n",
            a.no, a.item, a.price, a.rate, a.tax, a.calories);

 return 0;
}


만약, 위의 초기화 구문을 다음과 같이, 첫번째 목록만 남기고 나머지를 생략하면....

    menu a = { 17 };    // 첫번재 멤버는 17로, 나머지는 모두 0 으로...


첫번째 멤버를 제외한 모든멤버가 0 으로 초기화된다. 포인터인경우 NULL 포인터로 초기화된다.

따라서, 구조체의 모든 멤버를 0으로 초기화 하는 것은, 위와같은 방식으로 초기화시키되 첫번째 멤버를 0 으로 초기화시키면 된다.

예. menu a = { 0 } ;


--------------------------------------------------------------------------------------------------

구조체 변수들 사이의 대입
구조체 형도 한번 선언이 된 후에는 int 나 double 과 마찬가지로 자료형으로서의 기능을 다 하므로, 구조체형 변수들도 마찬가지로 대입문을 사용할 수가 있다.

가령, fopen() 으로 파일을 열어서, FILE* 형 변수에 배정했던것도 마찬가지다. 이때 fopen()의 리턴타입이 구조체 FILE* 형 이므로, FILE* 형 변수에 배정할 수 있었던 것이다.

즉, 같은 타입의 구조체 변수들 사이에는 = 를 이용한 대입문이 가능하다.
이때, 대입의 의미는 대응되는 모든 멤버들에 대한 대입과 같다.

예.
#include <stdio.h>
int main(void){
    typedef struct { int no; char *item; int price; char rate; double tax, calories; } menu;
    menu a = { 17, "Salad", 5600 , 'A' , 560.0 , 1769.56 };

    menu b = a ;        //   구조체 변수 b의 선언과 배정을 동시에


    printf("Menu No.%d\nname: %s\nprice: %d\nConsumer's rate: %c\nTax: %g\nCalories: %g\n",
            b.no, b.item, b.price, b.rate, b.tax, b.calories);

 return 0;
}


구조체 값들 사이의 연산은 C++ 에서 operator 들을 overloading 함으로써 가능하나, C 에서는 그와같은 간단한 방법은 없다.




계층적 구조체

하위 개념의 구조체를 멤버로 갖는 구조체를 구성함으로써 계층적인 데이터 구조를 형상화 시킬 수 있다.

다음의 예제는 구조체를 어떻게 계층적으로 사용할 수 있는지를 보여준다. ( 물론, 겉으로는 아무것도 실행되지 않지만... )


예.
#include<stdio.h>

typedef struct { int month, date, year; } DATE;
typedef struct { char *name; int month, date, year; } SDATE;
typedef struct { DATE birth, marriage, death; SDATE specialEvent; } EVENTS;
typedef struct { char *name; EVENTS event;} PERSON;

int main(void){
     PERSON a;

     a.name = "Greenwood";

     a.event.birth.year = 1903;
     a.event.birth.month = 3;
     a.event.birth.date = 19;

     a.event.marriage.year = 1942;
     a.event.marriage.month = 1;
     a.event.marriage.date = 15;
 
     a.event.specialEvent.name = "Invention of the Perpetual Mobile";
     a.event.specialEvent.year = 1951;
     a.event.specialEvent.month = 3;
     a.event.specialEvent.date = 27;
 
     a.event.death.year = 2009;
     a.event.death.month = 11;
     a.event.death.date = 6;

     return 0;
}



위에서 보듯이, 다른 구조체에서 같은 멤버명을 사용할수 있고, 또한, 구조체의 멤버로 구조체가 들어가면서 멤버구조체의 멤버에 연소적으로 접근함으로써 계층적 구조를 만들 수 있다.


------------------------------------------------------------------


공용체 ( union )

공용체는 구조체와 같은 구문형식을 갖지만, 멤버들마다 다른 메모리를 잡는것이 아니라, 멤버중 가장 큰 메모리 크기를 잡은후에, 그것을 공유한다.

즉, 다시말해서, 같은 장소에 저장된 정보를 다른형식으로 읽어들일수 있다는 말이다.

자세한 얘기는 비트단위 연산을 살펴보아야 하므로 여기서는 간단히 개념만 살펴보도록 한다.

C의 공용체 키워드는 union 이고, 형 선언 구문은 아래와 같다. 구조체와 키워드 union 만 다르다.

union tag { declaration_statements };   

이외에, 변수 선언이나, 태그없는 공용체, typedef 문의 활용 등은 모두 구조체나 열거체와 마찬가지 이므로, 생략하고, 바로 예를 살펴보겠다.

union integreal { int integer; double real; };   // union integreal 형의 정의, 메모리 8바이트 할당
union integreal x ;                               // union integreal 형 변수 x 선언



----------------------------------------------------------------------------------------------

공용체의 멤버접근도 역시 구조체와 같은 구문을 같는다.
union integreal x, *p;
x.integer = 10 ;                 
// 공용체  integreal 형 변수 x 의 멤버 integer 에 접근하여 10을 배정함.
p = &x ;                          // 공용체 integreal 형 포인터 p로 x의 주소를 포인팅
p->real = 1.542354 ;         //  포인터로부터 멤버접근후 대입


공용체의 멤버들에 각각 접근하여 배정을 하였을때, 메모리를 공유하므로 이후의 선언에 의해 앞의 선언이 덮어씌워지며 데이타의 손실이 일어날 수 있다. 그러한 것이 어떻게 진행되는지에 대한 판단과 계산은 프로그래머의 몫이다. 우리는 나중에 비트단위 연산을 통해, 그것을 좀더 자세히 살펴보도록 할 것이다.


예.

#include<stdio.h>

int main(void)
{
     union integreal{ int integer; double real;};
     union integreal x,*p=&x;
 
    x.integer = 10;
    p->real = 1.542354;

    printf("%d\n%lf\n",x.integer,x.real);

    p->real = 1.542354;
    x.integer = 10;

    printf("\n%d\n%lf\n\n",x.integer,x.real);

 return 0;
}


실행결과는 다음과 같다.

1666034994
1.542354

10
1.542354


공용체를 이용하면, 같은 데이터를 다른 싸이즈 단위로 접근하는 것이 가능하므로, 가령, 바이트단위나 비트단위로 선택적으로 접근하는 것 등을 가능케 한다.