프로그래밍/C

공용체와 열거형

길냥이 2025. 6. 13. 15:42
728x90

목차

  1. 공용체 union
  2. 열거형 enum
  3. typedef를 이용한 형 재정의

 

1. 공용체 union

공용체는 구조체와 비슷하게 선언할 수 있지만, 모든 멤버가 하나의 저장 공간을 사용합니다.

 

하지만 공용체와는 조금 다른데, 구조체는 모든 멤버들의 크기합 + 패딩만큼의 크기가 저장되지만, 

구조체는 멤버 중 가장 크기가 큰 값만큼을 저장합니다.

 

구조체가 다음과 같은 크기를 가진다면

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int  char[4] double

 

공용체는 다음과 같은 크기를 가집니다.

1 2 3 4 5 6 7 8
int        
char[4]        
double

즉, 공용체 하나를 선언하면 멤버 안에 있는 모든 해당하는 자료형을 공용체에 넣을 수 있습니다.

다만 공용체 하나에 여러개의 멤버를 넣을 수는 없어요.

 

한번 사용해봅시다.

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>

// 공용체 선언: int형, char형 배열, double형을 한 메모리 공간에서 공유함
// 가장 큰 멤버인 double형 기준으로 메모리 크기는 8바이트가 됨
union info
{
    char name[6];    // 이름 최대 5자 + null문자 저장
    int number;      // 학번 등 정수 저장
    double grade;    // 학점 등 실수형 데이터 저장
};

int main(void)
{
    // 공용체 변수 초기화: name[6]에 "Hong" 저장 (나머지 멤버는 초기화되지 않음)
    union info std = { "Hong" };  //이때 무조건적으로 첫 번째 멤버에 저장됩니다.

    printf("다음 학생의 정보를 입력하세요 : %s\n", std.name);

    //std가 공용체이므로 name에 저장된 데이터는 number에 의해 덮어쓰기 됨
    printf("학번 : ");
    scanf("%d", &std.number);
    printf("입력된 학번 : %d\n", std.number);

    //입력 시 number에 있던 데이터도 덮어써짐
    printf("학점 : ");
    scanf("%lf", &std.grade);
    printf("입력된 학점 : %.2lf\n", std.grade);

    return 0;
}

출력은 다음과 같습니다.

 

다음 학생의 정보를 입력하세요 : Hong
학번 : 2025
입력된 학번 : 2025
학점 : 3.8
입력된 학점 : 3.80

물론 필요에 따라 첫 번째 멤버 말고 다른 멤버를 초기화할 수도 있습니다.

지명 초기화 방법 ( specific member initialization) 을 이용해 name 멤버 말고 grade 멤버를 초기화할 수도 있습니다.

union info std = { .grade = 3.8 }; 처럼 말이죠.

다만 이때도 std에는 변수 하나만 들어갈 수 있다는 것을 기억하세요.

 

공용체의 멤버는 다른 멤버에 의해 언제든지 바뀔 수 있다는 단점이 있을 수 있습니다. 

하지만 같은 공간에 저장된 값을 여러 가지 형태로 사용할 수 있다는 장점이 있답니다!

 

2. 열거형 enum

그동안 무언가의 값을 반환할 때, 문자열로는 반환하기 어려워서 숫자 0,1, 2, 3... 등을 이용했습니다.

하지만 이런 경우에는 성별이라거나 (MALE, FEMALE),

혹은 요일이라던가(mon, tue, wed, thu, fri, sat, sun)말이죠.

이걸 문자열의 귀찮음은 지우면서, 숫자의 반환값에 따라 달라지게 하는 방법은 없을까요?

 

바로 그럴 때 열겨형(enum)을 사용합니다.

열거형 enum은 의미없는 숫자 대신 의미있는 이름을 붙여 코드를 읽기 쉽게 만들어줍니다.

출력은 문자열로 되면서, 내부적으로는 정수 값으로 처리되기 때문에 성능과 가독성에 높은 이점을 가집니다.

 

예를 들어 요일을 열거형으로 표현하면..

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }; 이 됩니다.

이때, 코드 내부에서는 MON부터 0, 1, 2..순으로 시작됩니다.

원한다면 MON = 1을 선언해서 index를 1부터 증가시킬 수도 있습니다.

 

한번 예제로 알아보죠.

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>

// 요일 열거형 정의: 0 = MON, 1 = TUE, ..., 6 = SUN
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN };

int main(void)
{
    int input;
    enum Day MONTH;

    // 사용자에게 요일을 입력받기 (0 = 월요일)
    printf("날짜를 입력하세요 0(월요일) : ");
    scanf("%d", &input);

    // 입력받은 숫자를 0~6 범위로 정규화하여 열거형 값으로 변환
    MONTH = (enum Day)(input % 7);

    // 요일에 따라 메시지 출력
    switch (MONTH)
    {
    case MON:   //열거형 값 0
        printf("월요일입니다.\n");
        break;
    case TUE:   //열거형 값 1
        printf("화요일입니다.\n");
        break;
    case WED:   //열거형 값 2
        printf("수요일입니다.\n");
        break;
    case THU:   //열거형 값 3
        printf("목요일입니다.\n");
        break;
    case FRI:   //열거형 값 4
        printf("금요일입니다.\n");
        break;
    case SAT:   //열거형 값 5
        printf("토요일입니다.\n");
        break;
    case SUN:   //열거형 값 6
        printf("일요일입니다.\n");
        break;
    default:
        printf("잘못된 입력입니다.\n");
        break;
    }

    return 0;
}

switch ~ case문을 사용한 것을 보면 알 수 있지만, 열거형 대신 정수 상수를 사용해도 됩니다.

다만, 열거형 멤버를 정의하면 이름을 직접 사용할 수 있으므로 훨씬 읽기 쉬운 코드를 만들 수 있습니다.

 

출력은 다음과 같습니다.

날짜를 입력하세요 0(월요일) : 22
화요일입니다.

 

3. typedef를 이용한 형 재정의

기존 자료형 말고 새 자료형을 만드는 방법이 typedef입니다.

구조체, 공용체, 열거형 등은 항상 struct, union, enum 등의 예약어와 함께 사용해야 해서 불편합니다.

특히 함수의 매개변수에 사용하면 struct student std1[]... stu.std1.p1->name같이 복잡하게 사용해야 합니다.

 

이때 typedef를 이용하면 이런 귀찮은 예약어를 생략할 수 있습니다. 

한번 해보죠. 사용법은 다음과 같습니다.

typedef unsigned char BYTE;

이때 typedef는 기존의 자료형인 unsigned char을 새로운 자료형 BYTE로 정의합니다.

 

근데 저런 경우는 잘 안 쓰입니다. 그냥 int나 char을 사용하면 되니까요.

보통 위에 말했던 거처럼 구조체나 공용체 등의 예약어를 생략하는 데 사용됩니다.

 

다음과 같은 예제를 만들어봅시다.

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>

// ST라는 타입의 변수를 인자로 받아 학생 정보를 출력하는 함수를 선언합니다.
void print(ST);

// 학생 정보를 저장할 구조체를 선언합니다.
struct student
{
    int num;            // 학생의 학번을 저장하는 정수형 변수
    char name[20];      // 학생의 이름을 저장하는 문자 배열 (최대 19자 + null 종료 문자)
};

// typedef를 사용하여 struct student에 별칭 ST를 부여합니다.
// 이제 코드 내에서 struct student 대신 ST를 사용할 수 있습니다.
// 이때 typedef는 전역선언을 해야 합니다.
// main 함수 내부에서 지역선언 시 main 함수 밖에서는 ST를 사용할 수 없습니다.
typedef struct student ST;

int main(void)
{
    // ST 타입의 변수 s1을 선언 및 초기화합니다.
    ST s1 = { 20250001, "홍길동" };

    // print() 함수를 호출하여 s1의 학생 정보를 출력합니다.
    print(s1);

    return 0;
}

// print() 함수 정의: 전달받은 s1 변수의 학번과 이름을 출력합니다.
void print(ST s1)
{
    printf("학번 : %d\n", s1.num);
    printf("이름 : %s\n", s1.name);
}

출력은 다음과 같습니다.

 

학번 : 20250001
이름 : 홍길동

 

그래서, #define와의 차이가 뭔가요?

어.. 별로 크게 다르지는 않습니다.

하지만 몇 가지 차이점은 존재합니다. 

 

#define의 경우(단순 텍스트 치환)

- 컴파일 전에 텍스트 치환 

- 타입 오류 감지 불가

- 재정의 어려움, 무조건 전역적 치환

- 읽는 사람이 직관적으로 파악하기 어려움

 

typedef의 경우(타입 재정의)

- 컴파일러가 새 타입 형성, 실제 타입 시스템에 포함됨

- 원래 타입과 연결되어 타입 오류 감지 가능

- 전역 또는 지역적으로 선언 가능(스코프 적용)

- 주석을 입력하는 것과 비슷한 역할

 

필요에 따라 유용하게 사용하도록 합시다.

 

728x90