중첩된 구조체의 정의
#include <stdio.h> struct point { int x; int y; }; struct circle { struct point p; double radius; }; int main() { struct circle c1 = { 10, 10, 1.5 }; struct circle c2 = { {30, 30}, 2.4 }; printf("[circle]\n"); printf("x:%d, y:%d\n", c1.p.x, c1.p.y); printf("radius:%f \n", c1.radius); printf("[circle2]\n"); printf("x:%d, y:%d\n", c2.p.x, c2.p.y); printf("radius:%f\n", c2.radius); return 0; } |
구조체란 하나 이상의 변수를 그룹 지어서 새로운 자료형을 정의하는 것이다. 여기서 그룹 지어지는 변수라는 것은 기본 자료형 변수만을 의미하는 것이 아니다. 변수는 무엇이든 된다. 구조체를 정의할 때 멤버로서 구조체 변수를 포함하는 것을 두고 구조체가 구조체를 포함하였다. 라고 표현한다. 구조체를 포함하는 구조체를 간략하게 중첩된 구조체라 표현한다. 정의하는 순서는 예제에서 보여주듯이 point 구조체를 먼저 정의한 다음에 circle 구조체를 정의해야 한다. 만약에 초기값의 개수와 초기화할 멤버의 수가 일치하지 않는다면 내부 중괄호는 큰 의미를 지닌다.
새로운 자료형의 완성
struct라는 키워드는 반드시 넣어줘야 한다. 이렇듯 구조체로 정의한 자료형과 기본 자료형의 변수 선언 방법에 차이가 있다. 만약에 구조체 변수를 선언하는데 있어서 struct라는 키워드를 붙여주지 않아도 된다면 사용하기도 편리할 것이고, 보다 더 자료형다운 맛도 느껴질 것이다. c++ 에서는 구조체를 정의한 다음, 변수 선언하는 경우에 있어서 struct라는 키워드를 넣어주지 않아도 된다. 구조체 안에 함수도 넣을 수 있다.
키워드 typedef 는 이미 존재하는 자료형에 새로운 이름을 붙이기 위한 용도로 사용된다.
#include <stdio.h> typedef int INT; typedef int* P_INT; typedef unsigned int UINT; typedef unsigned int* P_UNIT; typedef unsigned char UCHAR; typedef unsigned char* P_UCHAR; int main(void) { INT a = 10; P_INT pA = &a; UINT b = 100; P_UNIT pB = &b; UCHAR c = 'a'; P_UCHAR pC = &c; printf("%d, %d, %c \n", *pA, *pB, *pC); return 0; } |
기본 자료형에 typedef 키워드를 사용하여 새로 이름을 부여해주었습니다. 티스토리 창에는 나타나지 않지만 VS를 사용하면 typedef, void, return 는 파랑색 색, 새로 부여한 이름은 민트색, 기초 문법인 <stdio.h>, %d, "", '' 는 주황색으로 나타나는 것을 알 수 있습니다. 어떤 색이 어떻게 사용되는지 알게되면, 굳이 선언이나 정의되어 있는 부분을 모두 보지 않아도 그 해당 부분이 의미하는 바에 대해 감을 잡을 수 있습니다. 일반적으로 사용자가 기본 자료형에 부여하는 새로운 이름은 대문자를 사용하는 것이 좋습니다. 이로써 새로 부여된 이름인지 아닌지 구분이 가능해지기 때문입니다. 뿐만 아니라 상수를 정의할 때도 대문자를 사용합니다. 이러한 것은 관례로서 지켜주는 것이 읽는 사람에게 유익합니다.
typedef 와 구조체 변수의 선언
#include <stdio.h> struct Data { int data1; int data2; }; typedef struct Data Data; int main(void) { Data d = { 1,2 }; printf("%d, %d\n", d.data1, d.data2); return 0; } |
구조체 Data 를 정의하고 있습니다. struct Data 에 Data 라는 이름을 부여해 주고 있습니다. 앞으로는 struct Data d를 대신해서 Data d 라는 선언이 가능해졌습니다. typedef 키워드를 통해 정의한 이름 Data 를 이용해서 구조체 변수 d를 선언하고 있습니다.
#include <stdio.h> typedef struct Data { int data1; int data2; }Data; int main(void) { Data d = { 1,2 }; printf("%d, %d\n", d.data1, d.data2); return 0; } |
구조체 정의와 typedef 선언을 동시에 하고 있음을 볼 수 있습니다. 더불어 typedef struct Data 에서 Data 라는 이롬도 생략 가능합니다.
#include <stdio.h> typedef struct Dog { int data1; int data2; } Dog; typedef struct { int data1; int data2; } Cat; int main(void) { Dog d1; struct Dog d2; Cat c1; // struct Cat c2; return 0; } |
Dog 라는 구조체를 정의하고 있으며, struct Dog 에 Dog 라는 이름도 붙여주고 있습니다. 따라서 두 가지 형태로 변수를 선언할 수 있습니다. 구조체를 정의하되 구조체 자체의 이름은 정의하지 않으면서, 다만 typedef 를 이용해서 Cat 이라는 이름만 붙여주고 있습니다. 따라서 Cat c1; 과 같은 선언은 가능하지만, struct cat c2 와 같은 선언은 불가능합니다.
공용체
사용자 정의 자료형에는 구조체만 있는 것이 아닙니다. 공용체라는 것도 있습니다. 공용체도 지금까지 언급한 구조체와 마찬가지로 사용자가 자료형을 정의할 때 사용되기 때문에 공용체는 많은 부분 구조체와 비슷한 특징을 지닙니다. 구조체 변수를 선언하는 방식에 관련된 내용은 동일합니다. 따라서 공용체가 구조체와 다른 특징만을 살펴보면 됩니다. 정의 방식을 보면 구조체와 상당히 유사하다는 것을 알 수 있습니다. 차이점이라고는 struct를 대신하여 키워드 union을 사용한 것 밖에 없습니다. 공용체는 서로 다른 자료형의 변수를 하나의 메모리 공간에서 공유하는 경우에 사용된다. 따라서, 공용체는 저장해야 할 자료형이 일정하지 않은 경우에 일반적으로 사용된다. 공용체 변수를 선언하는 경우, 멤버 중 가장 메모리를 많이 욕하는 멤버에 초점을 둬서 메모리를 할당합니다. 하나의 메모리 공간에 다양한 형태의 데이터를 저장할 수 있기에 메모리도 효율적으로 사용하게 됩니다. 이렇듯 사용도리 데이터의 자료형이 유동적인 경우, 공용체를 사용하게 되면 메모리 공간을 효율적으로 사용할 수 있게 됩니다.
#include <stdio.h> union u_data { int d1; double d2; char d3; }; int main(void) { union u_data data; data.d2 = 3.3; printf("%d, %f, %c\n", data.d1, data.d2, data.d3); data.d1 = 2; printf("%d, %f, %c\n", data.d1, data.d2, data.d3); data.d3 = 'a'; printf("%d, %f, %c \n", data.d1, data.d2, data.d3); return 0; } |
data 라는 이름의 u_data 공용체 변수를 선언하고 있습니다. u_data 공용체의 멤버 중에서 double 형 멤버가 가장 큰 메모리 8바이트를 요구하므로 8바이트의 메모리 공간이 할당됩니다. 공용체 변수의 멤버 d2에 3.3을 대입하고 있습니다. d2는 double형 변수이므로 8바이트가 요구됩니다. 따라서 8바이트에 걸쳐서 3.3 이라는 값을 저장하게 됩니다. 일단 공용체 멤버 d1, d2, d3까지 저장된 값을 출력하고 있습니다. d1은 int형 변수이므로 4바이트만 읽어서 반환하게 됩니다. 공용체 변수에 저장된 double형 데이터 3.3의 앞부분 4바이트만을 int형으로 반환하면 그 값이 얼마일까요? 의미없는 데이터라고 생각하면 됩니다. 실제로 이러한 형식으로 고용체를 사용하지 않습니다. 그렇다면 d3은 어떻게 출력될까요? d3는 char형 변수이므로 1바이트만 반환하게 됩니다. 역시 의미를 지니는 데이터는 아닙니다.
열거형
지금까지 사용자가 자료형을 정의하기 위한 방법으로 구조체와 공용체에 대해서 공부하였습니다. 이제 마지막으로 열거형에 대해서 이야기해보도록 하겠습니다. 열거형도 사용자가 자료형을 정의하는 수단 중에 하나입니다. color라는 이름의 자료형 정의입니다. enum color {RED=1, GREEN=3, BLUE=5}. 이와 같은 열거형의 정의는 두 가지 의미를 지닙니다. 첫 번째로 color 라는 이름의 자료형 정의입니다. 열거형 정의의 두 번째 의미는 상수의 선언이다.
#include <stdio.h> enum color {RED=1, GREEN=3, BLUE=5}; int main() { enum color c1 = RED; enum color c2 = GREEN; enum color c3 = BLUE; printf("enum variable output: %d, %d, %d\n", c1, c2, c3); printf("constant value output: %d, %d, %d\n", RED, GREEN, BLUE); return 0; } |
color 라는 이름의 열거형을 정의하고 있다. 이는 자료형의 정의와 상수의 선언이라는 두 가지 측면을 지니고 있다고 이미 언급하였다. color 형 변수를 선언하고 각각 RED, GREEN, BLUE를 대입해주었습니다. 이는 color형 변수가 지닐 수 있는 값들입니다. 8, 9, 10에서 선언한 변수가 지니고 있는 값들을 출력해 주고 있습니다. 열거형을 정의하는데 있어서 상수의 값을 지정해주지 않으면 선언된 상수의 값은 0부터 시작해서 1씩 증가하며 정의됩니다. 상수의 값이 정의되어 있지 않은 경우 무조건 0부터 시작합니다. 다음부터 나오는 상수는 정의되어 있으면 정의되어 있는 값으로, 그렇지 않으면 이전에 정의된 값보다 하나 증가된 상수 값이 정의됩니다.
열거형을 정의하는 이유는 열거형을 사용함으로써 변수가 지니는 값에 의미를 부여할 수 있게 되고, 이에 따라서 프로그램의 가독성이 더불어 높아지기 때문입니다.
#include <stdio.h> int main(void) { int day; printf("Input a day(0:Monday - 6:Sunday) : "); scanf_s("%d", &day); switch (day) { case 0: printf("swimming. \n"); break; case 1: printf("jogging. \n"); break; } return 0; } |
가독성에 문제가 있다고 느끼지 못합니다. 가독성이란? 전체 코드가 쉽게 분석되느냐에 따라서 좋고 나쁨을 이야기 할 수 있습니다. 열거형을 도입해서 가독성을 좋게 하였습니다. 한 번 비교해보시죠.
#include <stdio.h> enum days {MON, TUE, WED, THU, FRI, SAT, SUN}; int main(void) { enum days day; printf("Input a day(0:MON ~ 6:SUN)"); scanf_s("%d", &day); switch (day) { case MON: printf("swimming. \n"); break; case TUE: printf("jogging. \n"); break; } return 0; } |
실행 결과 위의 예제와 다를 바 없습니다. 다만 열거형을 정의해서 가독성을 좋게하였습니다.
구조체 변수를 함수의 인자로 전달 및 반환하는 경우에 발생하는일들을 설명하였습니다. 기본 자료형 변수와 차이가 없음을 이해하였을 것입니다. 중첩된 구조체에 대해서도 언급하였습니다. 자료 구조나 알고리즘을 공부하면서 유용하게 사용하게 될 것입니다. 키워드 typedef에 대해서 소개하면서, 구조체에서 어떻게 유용하게 사용될 수 있는지 언급하였습니다. 공용체와 열거형에 대해서도 언급하였습니다. 열거형은 프로그램의 가독성을 높이는데 한몫 할 수 이는 문법 요소입니다.