Skip to content

00_03.StructsAndUnions

jdeokkim edited this page Apr 24, 2022 · 1 revision

배열

C언어에서는 int x = 0;, float y = 0.0f와 같이 단 하나의 값을 저장할 수 있는 변수도 있지만, 여러 개의 값을 저장할 수 있는 변수도 있다. 여기에 해당하는 변수가 바로 배열 (array)과 구조체 (structure) 변수이다. 배열은 같은 종류의 자료형을 가진 여러 값의 집합을 의미하며, 가장 많이 사용되는 대표적인 자료 구조 (data structure)이다.

이제 int형의 값 10개를 저장할 수 있는 배열을 만들어보자.

#include <stdio.h>

/*
    배열의 길이는 반드시 `#define`를 사용하여 정의해야
    나중에 프로그램을 수정해야 할 때 편하다.

    `int values[10];` 멈춰!!!
*/
#define N 10

int main(void) {
    // 배열의 길이가 `N`인 배열을 생성한다.
    int values[N];
    
    // 배열의 모든 원소를 `-1`로 초기화한다.
    for (int i = 0; i < N; i++)
        values[i] = -1;

    // 배열의 모든 원소를 출력한다.
    for (int i = 0; i < N; i++)
        printf("values[%d]: %d\n", i, values[i]);

    return 0;
}

이제 배열을 선언과 동시에 초기화하는 방법에 대해 알아보자.

#include <stdio.h>

// 배열의 길이를 나타내는 매크로 상수.
#define N 5

int main(void) {
    /*
        배열을 선언과 동시에 초기화할 때 초기화 배열의 길이가 
        실제 배열의 길이보다 짧을 경우, 나머지 원소의 값은
        0이 된다.

        즉, `int values[N] = { 1, 2, 3, 0, 0 };`과 같은 뜻!
    */
    int values[N] = { 1, 2, 3 };

    // 배열의 모든 원소를 출력한다.
    for (int i = 0; i < N; i++)
        printf("values[%d]: %d\n", i, values[i]);

    return 0;
}

C99부터는 특정 원소만을 선택해서 값을 지정할 수 있게 되었다.

#include <stdio.h>

// 배열의 길이를 나타내는 매크로 상수.
#define N 5

int main(void) {
    // 첫 번째 원소와 세 번째 원소만을 초기화한다.
    int values[N] = { [0] = 1, [2] = 3 };

    // 배열의 모든 원소를 출력한다.
    for (int i = 0; i < N; i++)
        printf("values[%d]: %d\n", i, values[i]);

    return 0;
}

일반적으로 배열의 크기는 배열의 자료형이 T고 배열의 길이가 N일 때, N * sizeof(T)가 된다. 따라서, int values[10];이라는 배열이 있다면, 이 배열의 크기는 10 * sizeof(int)가 되는 것이다. 이를 통해, 배열의 길이는 sizeof(values) / sizeof(T), 즉 sizeof(values) / sizeof(values[0])이 됨을 알 수가 있다.


구조체

구조체 (structure)는 다양한 종류의 자료형을 가진 여러 값의 집합을 의미하며, 배열만큼 자주 사용되는 자료 구조이다. 자료형의 각 원소는 멤버 (member) 또는 멤버 변수 (member variables)라고 하며, 각 원소는 배열처럼 인덱스로 접근하는 대신 그 원소의 이름으로 접근한다.

#include <stdio.h>

/* 원을 나타내는 구조체. */
struct circle {
    int x, y;    // 원의 중심점.
    int radius;  // 원의 반지름.
}; // 제발!!! 구조체 선언할 때 세미콜론 빼먹지 마!!!

/* 직사각형을 나타내는 구조체. */
struct rectangle {
    int x, y;           // 직사각형의 시작점.
    int width, height;  // 직사각형의 가로 및 세로 길이.
};

int main(void) {
    // 이름이 없는 구조체의 변수를 생성한다.
    struct /* 오잉? 이름이 없네? */ {
        int value;
    } s;

    // `circle` 구조체 변수를 생성한다.
    struct circle c = { 0, 0, 7 };

    /*
        `r.width`를 제외한 나머지 원소는 자동으로 0으로 
        초기화된다. (C99 표준의 'Designated Initializers')
    */
    struct rectangle r = { .width = 10, .height = 8 };

    // `s.value`의 값을 변경한다.
    s.value = -1;

    // `c`의 반지름을 출력한다.
    printf("c.radius: %d\n", c.radius);

    // `r`의 시작점 좌표를 출력한다.
    printf("r.x: %d\n", r.x);
    printf("r.y: %d\n", r.y);

    return 0;
}

typedef라는 키워드를 사용하면 구조체에 또다른 이름을 붙일 수 있다.

#include <stdio.h>

/* 원을 나타내는 구조체. */
typedef struct circle {
    int x, y;    // 원의 중심점.
    int radius;  // 원의 반지름.
} Circle; // 이제 `struct circle`을 `Circle`이라고 쓸 수 있다!

int main(void) {
    // `circle` 구조체 변수를 생성한다.
    Circle c = { 0, 0, 7 };

    // `c`의 반지름을 출력한다.
    printf("c.radius: %d\n", c.radius);

    return 0;
}

유니온

유니온 (union)은 구조체와 비슷하게 다양한 종류의 자료형을 가질 수 있는 자료형이지만, 각 멤버에 공간을 따로 할당해주는 구조체와는 다르게 유니온의 모든 멤버는 하나의 공간을 같이 사용하며, 유니온의 크기는 유니온에서 가장 큰 원소의 크기가 된다. 이게 무슨 뜻이냐면... 아래 코드에서 sizeof(union u)sizeof(int) 또는 sizeof(float) 중에서 더 큰 값이 된다는 뜻이다.

/* `int`형 값 또는 `float`형 값을 저장할 수 있는 유니온. */
union u {
    int i;
    float f;
};

유니온은 주로 어떤 자료형인지 확실히 알 수 없는 단 하나의 값을 저장할 때 사용하거나, 저수준 또는 임베디드 프로그래밍 (low-level/embedded programming)에서 비트와 바이트 관련 연산을 할 때 사용된다.

#include <stdio.h>

/* 실수 (real number)를 나타내는 구조체. */
typedef struct {
    unsigned char type;  // 저장된 값의 종류.
    union {              // 구조체에 저장된 값.
        int i;
        float f;
    };
} Real;

int main(void) {
    /*
        `x.type`가 0이면 정수이고, 1이면 실수를 
        나타낸다고 가정하자.
    */
    Real x = { .type = 0 };

    printf("sizeof(x): %lu\n\n", sizeof(x));

    // `x.i`와 `x.f`의 값을 둘 다 변경한다.
    x.i = 10;

    printf("x.i의 값은 %d이다.\n", x.i);
    printf("x.f의 값은 %f이다.\n\n", x.f);

    x.type = 1;

    // `x.i`와 `x.f`의 값을 둘 다 변경한다.
    x.f = 7.77;

    printf("x.i의 값은 %d이다.\n", x.i);
    printf("x.f의 값은 %f이다.\n", x.f);

    return 0;
}

열거형

열거형 (enumeration)은 서로 관련이 있는 여러 상수 값의 집합이다. 우리가 픽셀 그래픽의 RPG 게임을 만든다고 생각해보자. 우리가 캐릭터를 움직일 때는 땅이나 잔디처럼 지나다닐 수 있는 곳도 있지만, 벽이나 물처럼 지나갈 수 없는 곳도 존재한다.

픽셀 그래픽 RPG 게임의 예시 (출처: https://bakudas.itch.io/generic-rpg-pack)

이것을 열거형으로 나타내면 다음과 같다.

/* 게임 맵의 타일 종류를 나타내는 열거형. */
enum tile_type {
    GROUND,  // 그냥 땅.
    GRASS,   // 그냥 잔디.
    WATER,   // 그냥 물.
    WALL     // 그냥 벽.
};

typedef enum tile_type TileType;

/* ... */

// enum tile_type mool = WATER;
TileType mool = WATER;

열거형의 값은 별도의 값이 정해져 있지 않으면 0부터 시작한다. 즉, GROUND는 0, GRASS는 1, WATER는 2가 되고, WALL은 3이 되는 것이다. 이제 유니온의 마지막 예제 프로그램을 열거형을 사용해 다시 작성해보자.

#include <stdio.h>

/* `Real` 구조체에 저장된 값의 종류를 나타내는 열거형. */
typedef enum {
    INTEGER,        // 정수 값.
    FLOATING_POINT  // 부동 소수점 값.
} RType;

/* 실수 (real number)를 나타내는 구조체. */
typedef struct {
    RType type;  // 저장된 값의 종류.
    union {      // 구조체에 저장된 값.
        int i;
        float f;
    };
} Real;

int main(void) {
    /*
        `x.type`가 0이면 정수이고, 1이면 실수를 
        나타낸다고 가정하자.
    */
    Real x = { .type = INTEGER };

    printf("sizeof(x): %lu\n\n", sizeof(x));

    // `x.i`와 `x.f`의 값을 둘 다 변경한다.
    x.i = 10;

    printf("x.i의 값은 %d이다.\n", x.i);
    printf("x.f의 값은 %f이다.\n\n", x.f);

    x.type = FLOATING_POINT;

    // `x.i`와 `x.f`의 값을 둘 다 변경한다.
    x.f = 7.77;

    printf("x.i의 값은 %d이다.\n", x.i);
    printf("x.f의 값은 %f이다.\n", x.f);

    return 0;
}