C 언어 | 구조체 선언 | 열거형 - enum

열거형을 이용하여 쉽게 임의의 정수에 대응하는 식별자를 만들 수 있다. 여러 항목이 있는 상수의 목록을 만들 때 유용하다.

명명된 상수

실전 프로그램에서는 함수나 시스템에 정보의 특성을 전하는 수단으로 상수를 줄 수 있다. 상수 자체는 의미가 없으며, 설계자가 상수에 대응하는 의미를 붙인다. 예를 들어 2가 전달되면 OK, 4가 전달되면 취소, 8이면 재시도라고하는 상태이다.

이러한 상수를 요구하는 경우, 상수를 직접 코드로 작성하기 보다는 상수에 이름으로 붙여 추상적으로 표현하는 것이 융통성이 있다고 설계자는 생각할 것이다. 그래서 하나의 수단으로 열거형을 사용한다.

열거형은 열거라는 명명된 상수의 집합으로 구성된다. 열거는 상수를 지정할 수 있는 모든 장소에서 지정할 수 있다. 기본적으로 논리적 의미가 있는 상수의 집합을 고유한 이름으로 추상 표현하고 싶은 경우에 효과적인 방법이다. 열거를 사용하면 개발자는 열거자가 가지는 상수가 아닌, 열거가 갖는 의미에 주목해야한다. 열거자의 의미론은 시스템 설계자에게 맡길 수 있다.

열거형은 enum 키워드를 사용하여 선언다. 구문 및 취급 방법은 구조체와 유사하기 때문에 어려운 것이 없다.

열거형 선언

enum 태그명 {
열거1 = 상수, 열거2 = 상수 ...
} 열거 변수;

태그명과 끝에 열거 변수의 선언은 구조체나 공용체와 마찬가지로 생략할 수 있다. 열거자는 상수에 미치는 식별자이다. 여기에는 C 언어 식별자 명명 규칙이 적용되지만 관행적으로 모든 대문자로 한다. C 언어의 대문자와 소문자를 구별하는 언어에 있어서, 상수와 변경 불가능한 정적 변수의 식별에는 대문자를 사용하는 관행이 있다. 이것은 다른 변수와 같은 식별자와 구별하기 위해서이다.

상수는 열거자가 보유하는 상수를 지정한다. 여기에는 숫자 상수, 또는 문자 상수를 지정할 수 있다. 열거자의 상수는 생략할 수 있으며, 상수가 생략되는 경우에는 그 이전 열거자 값을 증가 값이 자동으로 설정된다. 첫번째 열거자 상수가 생략되는 경우는 0이 주어진다.

enum Message { MSG_YES , MSG_NO };

예를 들어, 이 열거는 MSG_YES는 0을 MSG_NO는 1을 나타내는 상수로 취급할 수 있다.

enum Message { MSG_YES , MSG_NO , MSG_OK = 8 , MSG_CANCEL };

그러나, 이 경우는 MSG_OK에 8이라는 상수를 명시적으로 부여하고 있기 때문에 MSG_CANCEL는 9를 나타낸다. 열거형의 변수를 만들 수 있지만, 이 변수는 실질적으로는 int 형에 지나지 않는다. 열거는 구조체의 멤버와는 성격이 다르다. 열거자는 멤버가 아니라 단순히 상수의 별명이라고 인식한다.

코드1

#include <stdio.h>

enum Message { MSG_OK , MSG_YES = 2 , MSG_NO };

int main() {
 enum Message msg = MSG_NO;
  printf(
   "MSG_OK = %d : MSG_YES = %d : MSG_NO = %d\n" ,
   MSG_OK , MSG_YES , msg
  );
  return 0;
}

코드1에는 열거형 Message를 작성하고 있다. 이 열거형은 3가지의 열거자 MSG_OK, MSG_YES, MSG_NO을 가지고 있다. 실행 결과를 보면, 열거형의 선언으로 MSG_YES에는 2라는 상수를 명시적으로 지정하고 있기 때문에, MSG_NO은 이에 영향받아서 3이라는 값을 나타내고 있는 것을 확인할 수 있다. 이와 같이 열거 상수를 생략할 수 있기 때문에, 상수 자체가 아니라 상수에 미치는 논리적인 의미에 관심이 있는 경우에 매우 유용하다.

코드1을 보고 궁금한 것이 몇가지 있을 것이다. 먼저 열거형의 msg 변수의 존재이다. 이 변수는 초기화시 MSG_NO을 주고 있지만, 만약 다른 값을 주면 어떻게 될 것일까? 사실은 열거형 변수의 실체는 단순한 int 형이다. 열거자 이외에 것을 주어도 오류가 발생하지 않고, 컴파일러는 그것을 감시하지 않는다.

enum Message msg = 100;

이와 같이 작성해도 컴파일러는 아무 일도 없는 것처럼 컴파일이 된다. 또한 구조적인 설계를 중시하는 프로그래머는 Message.MSG_OK와 같은 상수에 대한 액세스를 선호할지 모르지만, 유감이지만 이것은 불가능하다. 열거자는 멤버가 아니므로, 구조체와 같은 액세스는 할 수 없다. 이 사양은 이상하다고 느낄 수 있지만, 열거 변수에 대입된 값을 실행시에 체크해 버리면, 속도를 중시하는 C 언어의 기본 방침에 위반되 버린다. 주어진 상수가 올바른 것인지를 조사하는 것은 개발자의 역할로 여겨진다.

코드2

#include <stdio.h>

enum { MSG_OK , MSG_YESNO };
enum { ID_OK = 1 , ID_YES , ID_NO };

int Message(char *msg , int type) {
  char ch;
  switch(type) {
  case MSG_OK:
    printf("%s\tPush Enter>" , msg);
   scanf("%c" , &ch);
    return ID_OK;
 case MSG_YESNO: 
    printf("%s y/n>" , msg);
    scanf("%c" , &ch);
    return (ch == 'y' ? ID_YES : ID_NO);
  }
 return 0;
}

int main() {
  Message("Stand by Ready!" , MSG_OK);
  if (Message("Are you sure that's enough aromr?" , MSG_YESNO) == ID_YES)
    printf("This is not your appointed time to die.\n");
 return 0;
}

이 프로그램의 Message() 함수는 사용자가 지정한 어떤 문자열을 메시지로 표시하고 입력을 기다린다. type 인수가 MSG_OK의 경우는 입력을 기다리지만 입력된 값은 평가하지 않고 ID_OK를 항상 반환한다. type 인자에 MSG_YESNO을 지정하면 y 또는 n의 입력을 촉구하는 사용자에게 선택을 하게 한다. 사용자가 입력한 결과에 따라 함수는 ID_YES 또는 ID_NO를 돌려준다.

메시지를 표시하는 목적은 통일되어 있기 때문에, 이러한 기능은 Message()라는 하나의 함수로 정리하여 상수로 동작을 분기시키는 방법이 가장 현명하다고 생각된다. 기본적인 함수의 동작이 동일하다하더라도 분할되어 버리는 것은 현명하지 않다.

코드1은 열거를 사용한 간단한 실습 예제이다. 많은 C 언어로 작성된 시스템은 이러한 상수에 논리적인 의미를 규정하여 정보의 속성과 동작의 요구 등을 할 수 있다.




최종 수정 : 2017-11-26