소년코딩

05.04 - 열거형, enum

C++에는 많은 자료형이 내장되어 있다. 하지만 이 자료형들이 원하는 걸 표현하기에 항상 충분하지는 않다. 그래서 C++은 프로그래머들이 자신만의 자료형을 만들 수 있게 해 주는 기능을 포함하고 있다. 이러한 자료형을 사용자 정의 자료형이라고 한다.

아마도 가장 간단한 사용자 정의 자료형은 열거된 유형일 것이다. 열거된 유형(열거형이라고도 함)은 가능한 모든 값이 기호 상수(열거형)로 정의되는 자료형이다. 열거형은 enum 키워드를 통해 정의된다.

// Color라는 새로운 열거형(enum)을 정의한다.
enum Color
{
    // 열거자(enumerator)
    // 각 열거자는 세미콜론(;)이 아니라 쉼표(,)로 구분된다.
    COLOR_BLACK,
    COLOR_RED,
    COLOR_BLUE,
    COLOR_GREEN,
    COLOR_WHITE,
    COLOR_CYAN,
    COLOR_YELLOW,
    COLOR_MAGENTA,
}; // 그러나 enum 자체는 세미콜론으로 끝나야 한다.

// 열거형 Color의 변수들 정의
Color paint = COLOR_WHITE;
Color house(COLOR_BLUE);
Color apple { COLOR_RED };

열거형을 정의해도 메모리는 할당되지 않는다. 열거된 유형의 변수가 정의된 경우, 해당 변수에 대해 메모리가 할당된다.

각 열거자는 쉼표(,)로 구분되고, 전체 열거는 세미콜론(;)으로 끝난다.


Naming enums

enum 식별자는 대문자로 시작하는 경우가 많으며, 열거자(enumerator)는 종종 모두 대문자로 이름이 지어진다. 열거자는 열거와 같은 네임스페이스에 배치되므로, 열거자 이름은 같은 네임스페이스 내의 여러 열거(enum)에서 사용할 수 없다.

enum Color
{
    RED,
    BLUE, // BLUE is put into the global namespace
    GREEN
};

enum Feeling
{
    HAPPY,
    TIRED,
    BLUE // error, BLUE was already used in enum Color in the global namespace
};

따라서 이름 충돌을 방지하기 위해 열거자 앞에 ANIMAL 또는 COLOR와 같은 접두어를 붙이는 것이 일반적이다.


Enumerator values

각 열거자는 열거 목록의 위치에 따라 정수 값이 자동으로 할당된다. 기본적으로 첫 번째 열거자에는 정수 값 0이 할당되며, 각 이후 열거자에는 이전 열거자보다 1 더 큰 값이 할당된다.

enum Color
{
    COLOR_BLACK, // assigned 0
    COLOR_RED,   // assigned 1
    COLOR_BLUE,  // assigned 2
    COLOR_GREEN, // assigned 3
    COLOR_WHITE, // assigned 4
    COLOR_CYAN,  // assigned 5
    COLOR_YELLOW,// assigned 6
    COLOR_MAGENTA// assigned 7
};

Color paint(COLOR_WHITE);
std::cout << paint;      // 4

위 프로그램은 4를 출력한다.

열거자의 값을 명시적으로 정의할 수 있다. 이러한 정수 값은 양 또는 음의 값일 수 있으며 다른 열거자와 같은 값을 공유할 수 있다. 정의되지 않은 모든 열거자는 이전 열거자보다 1 더 큰 값이 부여된다.

enum Animal
{
    ANIMAL_CAT     = -3,
    ANIMAL_DOG,    // assigned -2
    ANIMAL_PIG,    // assigned -1
    ANIMAL_HORSE   = 5,
    ANIMAL_GIRAFFE = 5, // shares same value as ANIMAL_HORSE
    ANIMAL_CHICKEN // assigned 6
};

Enum type evaluation and input/output

열거형 값은 정수로 평가되므로 정수 변수에 할당할 수 있다. std:cout은 정수 출력 방법을 알고 있으므로 정수로 출력할 수도 있다.

int mypet = ANIMAL_PIG;
std::cout << ANIMAL_HORSE; // evaluates to integer before being passed to std::cout
// 5

컴파일러는 정수를 열거형 값으로 암시적으로 변환하지 않는다. 다음과 같은 경우 컴파일러 오류가 발생한다.

Animal animal = 5; // will cause compiler error

그러나 state_cast를 통해 강제로 변환할 수 있다.

Color color = static_cast<Color>(5); // ugly

또한, 컴파일러는 std:cin을 사용하여 열거형을 입력할 수 없다.

enum Color
{
    COLOR_BLACK, // assigned 0
    COLOR_RED,   // assigned 1
    COLOR_BLUE,  // assigned 2
    COLOR_GREEN, // assigned 3
    COLOR_WHITE, // assigned 4
    COLOR_CYAN,  // assigned 5
    COLOR_YELLOW,// assigned 6
    COLOR_MAGENTA// assigned 7
};

Color color;
std::cin >> color; // will cause compiler error

한가지 해결 방법은 정수를 읽고, static_cast를 사용하여 컴파일러가 정수 값을 열거형으로 입력하도록 하는 것이다.

int inputColor;
std::cin >> inputColor;

Color color = static_cast<Color>(inputColor);

열거형은 고유한 자료형으로 간주한다. 따라서 열거형에 다른 열거형을 할당하려고 하면 컴파일 오류가 발생한다.

Animal animal = COLOR_BLUE; // will cause compiler error

상수(const) 변수와 마찬가지로 열거형은 디버거에 표시되므로 #define보다 유용하다.

또한, 열거형은 정수 자료형의 일부로 간주하므로 열거형 변수에 할당할 메모리 크기는 컴파일러에 따라 결정된다. C++ 표준에 따르면 열거형 크기는 모든 열거형 값을 나타낼 만큼 커야 한다. 대부분 열거형 변수는 표준 int와 같은 크기다.


열거형은 언제 유용할까?

열거형은 특정한 상태 집합을 나타내야 할 때 코드 문서화 및 가독성 목적으로 매우 유용하다.

예를 들어 함수는 함수 내부에 문제가 발생했을 때 오류 코드를 나타내기 위해 호출자에게 정수를 반환하는 경우가 많다. 일반적으로 오류 코드를 나타내는 데는 음수가 사용된다.

int readFileContents()
{
    if (!openFile())
        return -1;
    if (!readFile())
        return -2;
    if (!parseFile())
        return -3;

    return 0; // success
}

하지만 매직 넘버를 사용하는 것은 좋지 않다. 대신에 열거형을 사용하는 게 좋다.

enum ParseResult
{
    SUCCESS = 0,
    ERROR_OPENING_FILE = -1,
    ERROR_READING_FILE = -2,
    ERROR_PARSING_FILE = -3
};

ParseResult readFileContents()
{
    if (!openFile())
        return ERROR_OPENING_FILE;
    if (!readFile())
        return ERROR_READING_FILE;
    if (!parsefile())
        return ERROR_PARSING_FILE;

    return SUCCESS;
}

위 코드는 매직 넘버를 반환하는 것보다 훨씬 가독성이 좋다. 또한, 호출자는 해당 열거자에 대해 함수의 반환 값을 테스트할 수 있으며, 이는 특정 정수 값에 대한 반환 결과를 테스트하는 것보다 이해하기 쉽다.

if (readFileContents() == SUCCESS)
{
    // do something
}
else
{
    // print error message
}

열거형은 관련 집합을 정의할 때도 사용하기 좋다. 예를 들어, 플레이어가 아이템 하나만 가지고 다닐 수 있는 게임을 만든다고 해보자. 아이템은 여러 가지 종류가 있다.

#include <iostream>
#include <string>

enum ItemType
{
    ITEMTYPE_SWORD,
    ITEMTYPE_TORCH,
    ITEMTYPE_POTION
};

std::string getItemName(ItemType itemType)
{
    if (itemType == ITEMTYPE_SWORD)
        return std::string("Sword");
    if (itemType == ITEMTYPE_TORCH)
        return std::string("Torch");
    if (itemType == ITEMTYPE_POTION)
        return std::string("Potion");

    // Just in case we add a new item in the future and forget to update this function
    return std::string("???");
}

int main()
{
    // ItemType is the enumerated type we've defined above.
    // itemType (lower case i) is the name of the variable we're defining (of type ItemType).
    // ITEMTYPE_TORCH is the enumerated value we're initializing variable itemType with.
    ItemType itemType = ITEMTYPE_TORCH;

    std::cout << "You are carrying a " << getItemName(itemType) << "\n";

    return 0;
}

많은 프로그래밍 언어에서 불리언(boolean)을 정의하기 위해 열거형을 사용한다. 부울(bool)은 기본적으로 falsetrue의 두 열거자를 가진 열거형이다. 그러나 C++에서 truefalse는 열거자 대신 키워드로 정의되어있다.


cpp 번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/45-enumerated-types/ 입니다.


댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기