devseop08 님의 블로그

[Basic] 구조체와 열거형 본문

Language/C++

[Basic] 구조체와 열거형

devseop08 2025. 7. 15. 22:59

1. 구조체

구조체, struct

std::string myName;
int myBirthYear;
int myBirthMonth;
int myBirthDay;
int myHeightInches;
int myWeightPounds;
  • 자신에 대한 정보를 함수에 전달하려면 각 변수를 개별적으로 전달해야 한다.
  • 또한, 다른 사람에 대한 정보를 저장하려면 추가된 사람마다 6개의 변수를 추가로 선언해야 한다.
  • 다행히도 C++ 에서는 고유한 사용자 정의 집계 데이터 유형(user-defined aggregate data type)을 생성할 수 있다.
  • 집계 데이터 유형(aggregate data type)은 여러 개별 변수를 함께 그룹화하는 데이터 유형이다.
  • 가장 단순한 집계 데이터 유형 중 하나는 구조체(struct)다.
  • 즉, 구조체(struct)는 하나 이상의 변수를 그룹 지어서 새로운 자료형을 정의하는 것이다.
  • 구조체 선언 및 정의
  • 구조체(struct)는 사용자 정의 자료형이기 때문에, 먼저 그것을 사용하기 전에 구조체가 어떻게 생겼는지 컴파일러에 말해야 한다.
  • 이를 위해 struct 키워드를 사용해 구조체를 선언해야 한다.
struct Employee
{
    short id;
    int age;
    double wage;
};
  • 위 코드는 컴파일러에 Employee 구조체를 정의한다고 말한다.
  • Employee 구조체는 세 개의 변수를 포함한다. (short id, int age, double wage) 구조체의 일부인 이러한 변수를 멤버(member) 또는 필드(field)라고 한다.
  • Employee는 단지 선언에 불과하다. 컴파일러에 구조체가 멤버 변수가 있을 것이라고 말하고 있지만, 지금은 어떤 메모리도 할당되지 않는다.
  • 일반적으로 구조체 이름은 대문자로 시작하여 변수 이름과 구분한다.
  • Employee 구조체를 사용하려면 Employee 타입의 변수를 정의하면 된다.
Employee joe;
  • 위 코드는 joe라는 구조체 Employee 타입의 변수를 정의한다. 일반 변수와 마찬가지로 구조체 변수를 정의하면 해당 변수에 대한 메모리가 할당된다.
  • 구조체 멤버 접근
  • Employee joe와 같이 구조체 타입 변수를 정의할 때, 변수 joe는 멤버 변수를 포함하는 전체 구조체(struct)를 참조한다.
  • 개별 멤버에 접근하기 위해 멤버 선택 연산자(member selection operator: .) 를 사용하면 된다.
  • 일반 변수처럼 구조체 멤버 변수도 초기화되지 않고 쓰레기값이 들어간다. 그러므로 수동으로 초기화해야 한다.
Employee joe; // create an Employee struct for Joe
joe.id = 14;       // assign a value to member id within struct joe
joe.age = 32;      // assign a value to member age within struct joe
joe.wage = 24.15;  // assign a value to member wage within struct joe

Employee frank;    // create an Employee struct for Frank
frank.id = 15;     // assign a value to member id within struct frank
frank.age = 28;    // assign a value to member age within struct frank
frank.wage = 18.27;// assign a value to member wage within struct frank
  • 구조체 멤버 변수는 일반 변수와 같게 작동한다.
int totalAge = joe.age + frank.age;

if (joe.wage > frank.wage)
    std::cout << "Joe makes more than Frank\n";
else if (joe.wage < frank.wage)
    std::cout << "Joe makes less than Frank\n";
else
    std::cout << "Joe and Frank make the same amount\n";

// Frank got a promotion
frank.wage += 2.50;

// Today is Joe's birthday
++joe.age; // use pre-increment to increment Joe's age by 1

구조체 초기화

  • 멤버별로 값을 지정하여 구조체를 초기화하는 작업은 매우 번거로우므로 C++은 초기화 목록(initializer list)을 사용하여 구조체를 초기화하는 더 빠른 방법을 지원한다.
  • 이렇게 하면 선언 시간에 구조체의 일부 또는 전체 멤버 변수를 초기화할 수 있다.
struct Employee
{
    short id;
    int age;
    double wage;
};

Employee joe = { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank = { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 
                            // (default initialization)
  • C++ 11에서는 유니폼 초기화(uniform initialization)를 사용할 수도 있다.
Employee joe { 1, 32, 60000.0 }; // joe.id = 1, joe.age = 32, joe.wage = 60000.0
Employee frank { 2, 28 }; // frank.id = 2, frank.age = 28, frank.wage = 0.0 
                          // (default initialization)
  • 초기화 목록에 일부 멤버의 초기값이 포함되어 있지 않으면 해당 멤버는 기본(defualt)값으로 초기화된다.
  • 위의 예제에서는 frank.wage에 명시적으로 초기값을 지정하지 않았기 때문에 frank.wage가 0.0으로 초기화된다.

C++11/14: Non-static member initialization

  • C++ 11 부터는 비-정적(non-static) 멤버 변수에 기본값을 지정할 수 있다.
struct Rectangle
{
    double length = 1.0;
    double width  = 1.0;
};

int main()
{
    Rectangle x;   // length = 1.0, width = 1.0

    x.length = 2.0; // you can assign other values like normal

    return 0;
}
  • 그러나 불행하게도, 멤버 초기화 구문이 멤버 초기화 목록 및 유니폼 초기화와 호환되지 않는다. 예를 들어. C++ 11에서는 다음 프로그램이 컴파일되지 않는다.
struct Rectangle
{
    double length = 1.0; // non-static member initialization
    double width  = 1.0;
};

int main()
{
    Rectangle x{ 2.0, 2.0 }; // uniform initialization

    return 0;
}

// Compile Error!
  • 따라서 C++ 11에서는 비-정적(non-static) 멤버 초기화와 유니폼 초기화중 무엇을 사용해야 할지 정해야 한다.
  • 그러나 C++ 14에서는 이 제한이 해제되어 두 가지 모두 사용할 수 있다. 둘 다 사용하면 초기화 목록/유니폼 초기화를 우선한다.

구조체 할당

  • C++ 11 이전에는 구조체 멤버에 값을 할당하려면 개별적으로 할당해야 했다.
struct Employee
{
    short id;
    int age;
    double wage;
};

Employee joe;
joe.id = 1;
joe.age = 32;
joe.wage = 60000.0;
  • 위 작업은 멤버가 많은 구조체에서는 매우 번거롭다.
  • C++ 11에서는 초기화 목록(initializer list)을 사용해서 구조체 멤버에 값을 할당할 수 있다.

구조체와 함수

  • 개별 변수보다 구조체의 큰 장점은 구조체를 멤버와 함께 함수에 전달할 수 있다는 것이다.
#include <iostream>

struct Employee
{
    short id;
    int age;
    double wage;
};

void printInformation(Employee employee)
{
    std::cout << "ID:   " << employee.id << "\n";
    std::cout << "Age:  " << employee.age << "\n";
    std::cout << "Wage: " << employee.wage << "\n";
}

int main()
{
    Employee joe = { 14, 32, 24.15 };
    Employee frank = { 15, 28, 18.27 };

    // Print Joe's information
    printInformation(joe);

    std::cout << "\n";

    // Print Frank's information
    printInformation(frank);

    return 0;
}

//program outputs:

ID:   14
Age:  32
Wage: 24.15

ID:   15
Age:  28
Wage: 18.27
  • 또한, 함수는 구조체를 반환함으로써 여러 변수를 반환할 수 있다.
#include <iostream>

struct Point3d
{
    double x;
    double y;
    double z;
};

Point3d getZeroPoint()
{
    Point3d temp = { 0.0, 0.0, 0.0 };
    return temp;
}

int main()
{
    Point3d zero = getZeroPoint();

    if (zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0)
        std::cout << "The point is zero\n";
    else
        std::cout << "The point is not zero\n";

    return 0;
}

// The point is zero

중첩된 구조체

  • 구조체는 다른 구조체를 포함할 수 있다.
struct Employee
{
    short id;
    int age;
    double wage;
};

struct Company
{
    Employee CEO; // Employee is a struct within the Company struct
    int numberOfEmployees;
};

Company myCompany;
  • 중첩된 구조체에 대해 중첩된 초기화 목록을 사용할 수 있다.
struct Employee
{
    short id;
    int age;
    float wage;
};

struct Company
{
    Employee CEO; // Employee is a struct within the Company struct
    int numberOfEmployees;
};

Company myCompany = {{ 1, 42, 60000.0f }, 5 };

구조체 크기와 정렬

  • 일반적으로 구조체의 크기는 모든 멤버의 크기를 합한 것이지만, 항상 그렇지는 않다
  • 성능상의 이유로 컴파일러는 때때로 구조에 간격을 추가한다. 이것을 패딩이라고 한다.
struct Employee
{
    short id;     // size: 2
    int age;      // size: 4
    double wage;  // size: 8
};

int main()
{
    std::cout << "The size of Employee is " << sizeof(Employee) << "\n";

    return 0;
}

The size of Employee is 16
  • 위의 Employee 구조체에서 컴파일러는 멤버 id 뒤에 보이지 않는 2바이트의 패딩을 추가여 구조체 크기를 14 바이트가 아닌 16 바이트로 만든다.

2. typedef

  • typedef를 사용하면 프로그래머가 타입의 별칭을 생성하고, 실제 타입 이름 대신 별칭을 사용할 수 있다.
  • 즉, C++에서 이미 정의된 자료형이나 사용자 정의 자료형보다 더 짧거나 의미 있는 이름을 지어줄 수 있다.
  • 관습적으로 typedef 별칭은 "_t" 접미사를 사용하여 선언한다.
  • typedef는 는 새로운 타입을 정의하지 않는다. 기존 타입의 별칭일 뿐이다. tydef는 일반 타입을 사용할 수 있는 곳이라면 어디서든 사용할 수 있다.
typedef long miles_t;
typedef long speed_t;

miles_t distance = 5;
speed_t mhz = 3200;

// miles_t와 speed_t는 실제로 long 타입이기 때문에, 아래 코드는 유효하다.
distance = mhz;
플랫폼 독립 코딩
  • typedef의 가장 큰 장점은 플랫폼 특정 세부 사항을 숨기는 데 사용할 수 있다는 것이다.
  • 플랫폼에 따라서, 정수는 2바이트 또는 4바이트다.
  • int를 사용해 2바이트가 넘는 정보를 저장하는 것은 잠재적으로 위험할 수 있다.
  • char, short, intlong 은 해당 크기를 나타내지 않으므로 교차 플랫폼(cross-flatform) 프로그램에서 크기(비트)를 나타내는 별칭을 정의하는 것이 매우 일반적이다
  • typedef를 사용하면 실수를 방지하고 변수의 크기에 대해 더욱 명확히 알 수 있다.
#ifdef INT_2_BYTES
typedef char int8_t;
typedef int int16_t;
typedef long int32_t;
#else
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
#endif

3. 열거형

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 };
  • 각 열거자는 열거 목록의 위치에 따라 정수 값이 자동으로 할당된다.
  • 기본적으로 첫 번째 열거자에는 정수 값 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
  • 열거자의 값을 명시적으로 정의할 수 있다.
  • 이러한 정수 값은 양 또는 음의 값일 수 있으며 다른 열거자와 같은 값을 공유할 수 있다.
  • 정의되지 않은 모든 열거자는 이전 열거자보다 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 클래스

  • C++은 열거형으로 선언된 값을 암시적으로 정수로 변환하여 평가하기 때문에 의도와 다른 결과를 도출하는 코드가 만들어질 수 있다.
#include <iostream>

int main()
{
    enum Color
    {
        RED,   // RED is placed in the same scope as Color
        BLUE
    };

    enum Fruit
    {
        BANANA, // BANANA is placed in the same scope as Fruit
        APPLE
    };

    Color color = RED; // Color and RED can be accessed in the same scope (no 
                       // prefix needed)
    Fruit fruit = BANANA; // Fruit and BANANA can be accessed in the same scope 
                          // (no prefix needed)

    if (color == fruit) // The compiler will compare a and b as integers
        std::cout << "color and fruit are equal\n"; // and find they are equal!
    else
        std::cout << "color and fruit are not equal\n";

    return 0;
}

// color and fruit are equal
  • 서로 다른 enum 자료형으로 평가할 것으로 예상했지만 예상과는 다른 결과가 도출된다.
  • 이런 문제를 보완하기 위해 C++ 11은 새로운 컨셉, 열거형 클래스(enum class)를 정의했다. (범의가 지정된 열거형 or 강력한 형식의 열거형이라고도 한다)
  • 열거형 클래스(enum class)는 강한 형식과 범위(scope)를 가진다.
  • 열거형 클래스(enum class)를 만들려면, enum 키워드 뒤에 class 키워드를 사용하면 된다.
#include <iostream>
int main()
{
    enum class Color // "enum class" defines this as a scoped enumeration instead of a standard enumeration
    {
        RED, // RED is inside the scope of Color
        BLUE
    };

    enum class Fruit
    {
        BANANA, // BANANA is inside the scope of Fruit
        APPLE
    };

    Color color = Color::RED; // note: RED is not directly accessible any more, we
                              // have to use Color::RED
    Fruit fruit = Fruit::BANANA; // note: BANANA is not directly accessible any 
                                 // more, we have to use Fruit::BANANA

    if (color == fruit) // compile error here, as the compiler doesn't know how to 
                        // compare different types Color and Fruit
        std::cout << "color and fruit are equal\n";
    else
        std::cout << "color and fruit are not equal\n";

    return 0;
}
  • 일반 열거형(enum)을 사용하면 열거자(Ex. RED)는 열거형 자체와 같은 범위에 있으므로 직접 접근할 수 있다.
  • 그러나 열거형 클래스(enum class)의 강력한 범위 규칙은 모든 열거자를 열거형 일부로 간주하므로 범위 한정자(::)를 사용하여 열거자 (Ex. Color::RED)에 접근해야 한다.
  • 이는 이름 충돌을 방지하는 데 도움이 된다.
  • 열거형 클래스(enum class)의 강력한 형식 규칙은 고유한 자료형으로 간주함을 의미한다.
  • 그래서 컴파일러가 다른 열거형의 열거자와 암시적으로 비교하지 않는다.
  • 다른 열거형 클래스에 선언된 열거형끼리 비교 연산을 수행하려 할 시 컴파일러는 컴파일 에러를 발생시킨다.
  • 그러나 같은 열거형 클래스 내의 열거자 끼리는 비교할 수 있게 돼있다.
#include <iostream>
int main()
{
    enum class Color
    {
        RED,
        BLUE
    };

    Color color = Color::RED;

    if (color == Color::RED) // this is okay
        std::cout << "The color is red!\n";
    else if (color == Color::BLUE)
        std::cout << "The color is blue!\n";

    return 0;
}

'Language > C++' 카테고리의 다른 글

[Basic] 함수와 스코프  (0) 2025.07.16
[Basic] 연산자와 흐름 제어 구문  (6) 2025.07.16
[OOP] 상속과 다형성  (0) 2025.07.15
[Basic] 상수와 리터럴  (0) 2025.07.07
[OOP] 생성자와 소멸자  (1) 2025.06.08