본문으로 건너뛰기

구조체(Struct), 공용체(Union), 열거형(Enum)

구조체 (Struct)

필요성 및 정의

서로 다른 자료형의 변수들을 논리적으로 묶어 하나의 새로운 자료형을 만들어야 할 때가 있다. 예를 들어 '책' 한 권의 정보는 제목(문자열), 저자(문자열), 페이지 수(정수), 가격(정수) 등 다양한 자료형으로 구성된다.

이렇게 연관된 데이터를 하나의 단위로 통합하기 위해 사용하는 것이 바로 **Struct(구조체)**이다.

Struct를 정의하는 기본 형식은 다음과 같다. 정의 구문 마지막에는 반드시 세미콜론(;)을 붙여야 한다.

struct 구조체_태그_이름 {
자료형1 변수명1;
자료형2 변수명2;
// ...
};

Struct를 구성하는 각 변수를 Member(멤버) 또는 구성요소라고 한다. 한 Struct 내에서 Member의 이름은 서로 유일해야 하며, 정의 단계에서는 Member에 초기값을 대입할 수 없다.

변수 선언

Struct 정의는 새로운 자료형을 만드는 과정일 뿐, 변수를 선언하는 것은 아니다. 정의된 Struct 자료형을 사용하여 변수를 선언하는 방법은 여러 가지가 있다.

  1. 정의와 선언 분리 Struct 자료형을 먼저 정의한 후, 필요할 때 struct 구조체_태그_이름을 사용하여 변수를 선언한다.

    // struct book 자료형 정의
    struct book {
    char title[50];
    char author[50];
    int pages;
    int price;
    };

    // struct book 타입의 변수 선언
    struct book mybook;
  2. 정의와 선언 동시 진행 Struct를 정의하면서 동시에 해당 타입의 변수를 선언할 수 있다.

    struct book {
    char title[50];
    char author[50];
    int pages;
    int price;
    } yourbook; // yourbook 변수를 바로 선언

    이 경우 구조체 태그 이름(book)은 생략할 수 있다.

구조체 자료형의 동일성

Struct의 Member 구성이 완전히 같더라도, 별도로 정의된 두 Struct는 서로 다른 자료형으로 취급된다. 따라서 서로 다른 자료형으로 간주되는 Struct 변수 사이에는 대입 연산(=)을 사용할 수 없다.

// 전역으로 정의된 struct complex
struct complex {
double real;
double img;
};

int main(void) {
// main 함수 내부에 다시 정의된 struct complex
struct complex {
double real;
double img;
} comp;

struct complex comp1 = {3.0, 4.0}; // 전역 struct complex 타입

// comp = comp1; // 에러 발생! 서로 다른 자료형임
}

Typedef를 이용한 형 재정의

**Typedef(형 재정의)**를 사용하면 struct 구조체_태그_이름 형태의 긴 자료형을 더 간단한 이름으로 대체할 수 있다.

  1. 기존 Struct에 Typedef 적용

    // struct book 자료형 정의
    struct book { ... };
    // struct book을 book이라는 새로운 이름으로 재정의
    typedef struct book book;

    // 'book' 키워드만으로 변수 선언 가능
    book mybook;
    book yourbook;
  2. 정의와 Typedef 동시 진행 Struct를 정의하는 동시에 Typedef를 사용하여 새로운 자료형 이름을 지정할 수 있다.

    typedef struct {
    char title[50];
    int price;
    } software; // software라는 새로운 자료형 이름으로 정의

    // 'software' 키워드로 변수 선언
    software visualc;

초기화 및 멤버 접근

Struct 변수는 배열처럼 중괄호 {}를 사용하여 초기화할 수 있다. Struct의 Member에 접근할 때는 멤버 접근 연산자인 마침표(.)를 사용한다.

// typedef struct book book; 이 선언되었다고 가정
book mybook = {"C@PL.com", "강환수", 530, 20000};

// 멤버 접근
printf("저자: %s\n", mybook.author); // "강환수" 출력

구조체 포인터

Struct 변수의 주소를 저장하기 위해 Struct 포인터를 사용한다.

struct univ {
char title[50];
int students;
};

struct univ ku = {"한국대학교", 5000};
struct univ *ptr = &ku; // 포인터 ptr이 ku의 주소를 가리킴

포인터를 통해 Struct의 Member에 접근할 때는 화살표 연산자(->)를 사용한다. 또는 간접 참조 연산자(*)와 멤버 접근 연산자(.)를 조합하여 (*ptr).member 형태로도 사용할 수 있다. 이때 *ptr을 괄호로 묶는 것이 중요하다. 괄호가 없으면 *ptr.title*(ptr.title)로 해석되어 연산자 우선순위 때문에 에러가 발생한다.

  • ptr->title: 포인터 ptr이 가리키는 Struct의 title Member를 참조

  • (*ptr).title: 위와 동일한 의미

  • *ptr->title: *(ptr->title)과 동일. title Member가 가리키는 값(이 경우 title 문자열의 첫 번째 문자)을 참조

구조체 배열

동일한 형태의 Struct 변수를 여러 개 사용하려면 Struct 배열을 선언한다.

struct book {
char author[50];
char title[50];
int pages;
};

struct book clang[3] = {
{"Deitel", "C How To Program", 600},
{"Al Kelly", "A Book On C", 700},
{"Stephen Prata", "C Primer Plus", 800}
};

배열의 각 원소는 첨자(index)를 이용해 접근하며, Member는 . 연산자로 접근한다.

printf("저자: %s\n", clang[0].author); // "Deitel" 출력 

함수 인자로서의 구조체

Struct는 함수의 인자로 전달되거나 반환값으로 사용될 수 있다.

  1. 값에 의한 호출 (Call by Value) Struct 변수 자체를 인자로 전달하는 방식이다. 함수 내부에서 인자로 받은 Struct는 원본의 복사본이므로, 함수 내에서 수정이 원본에 영향을 주지 않는다.

    complex paircomplex1(complex com) {
    com.img = -com.img;
    return com;
    }

    // 호출
    pcomp = paircomplex1(comp);
  2. 주소에 의한 호출 (Call by Address) Struct의 주소(포인터)를 인자로 전달하는 방식이다. 이 경우 함수는 원본 변수에 직접 접근하여 값을 수정할 수 있다. Struct의 크기가 클 경우, 전체를 복사하는 값에 의한 호출보다 주소 값만 전달하는 이 방식이 훨씬 효율적이다.

    void paircomplex2(complex *com) {
    com->img = -com->img;
    }

    // 호출
    paircomplex2(&comp);

공용체 (Union)

**Union(공용체)**은 서로 다른 자료형의 Member들이 하나의 동일한 저장 공간을 공유하는 자료형이다. 정의 방식은 Struct와 유사하지만 struct 키워드 대신 union을 사용한다.

union data {
char ch;
int cnt;
double real;
};

메모리 구조 및 특징

  • 메모리 공유: 모든 Member가 같은 메모리 공간을 사용한다.

  • 크기: Union의 전체 크기는 Member 중 가장 큰 자료형의 크기로 결정된다. 위 예시에서 union data의 크기는 double 형의 크기인 8바이트가 된다.

  • 단일 값 저장: 한 번에 하나의 Member 값만 유효하게 저장할 수 있다. 마지막으로 값을 대입한 Member만이 의미 있는 데이터를 가지며, 다른 Member에 접근하면 의미 없는 값이 출력될 수 있다.

이용 방법

Union의 Member에 접근할 때는 Struct와 동일하게 . 또는 -> 연산자를 사용한다. 초기화는 첫 번째 선언된 Member의 자료형으로만 가능하다.

typedef union data uniondata;

// uniondata data1 = {10}; // 두 번째 멤버(int) 타입으로는 초기화 불가, 에러 발생
uniondata data2 = {'A'}; // 첫 멤버(char) 타입으로 초기화 가능
uniondata data3 = data2; // 다른 동일한 Union 변수 값으로 초기화 가능

data2.cnt = 100; // cnt 멤버에 값을 저장하면 ch 멤버의 값은 의미를 잃음

열거형 (Enum)

**Enum(열거형)**은 관련 있는 정수형 상수들의 집합에 이름을 붙여 정의하는 자료형이다. enum 키워드를 사용하여 정의한다.

enum color {yellow, red, blue, magenta, green};
enum color col; [cite: 79]

위 구문에서 yellow, red, blue 등은 열거형 상수가 된다. 이 상수들은 별도의 값을 지정하지 않으면 0부터 시작하여 1씩 증가하는 정수 값에 자동으로 대응된다 (yellow=0, red=1, blue=2, ...).

필요에 따라 각 상수에 특정 정수 값을 직접 지정할 수도 있다. 값이 지정되지 않은 상수는 바로 앞 상수의 값보다 1 큰 값을 자동으로 갖게 된다.

// 각 상수에 특정 연도 값을 지정
enum pl {c=1972, cpp=1983, java=1995, cs=2000};

// 일부 상수만 값을 지정
// circle=0, tri=3, rect=4, star=7, dia=8 로 정의됨
enum shape {circle, tri=3, rect=4, star=7, dia};

Enum은 코드의 가독성을 높이는 데 유용하다. 예를 들어 switch 문에서 의미 없는 숫자 대신 의미 있는 열거형 상수를 사용하면 코드를 이해하기가 훨씬 쉬워진다.