C & C++/디딤돌 C언어

Part 21. 사용자 정의 형식(구조체, 공용체, 열거형)

언휴 2024. 1. 17. 09:03

 

C언어 사용자 정의 형식(구조체, 공용체, 열거형)

74. 구조체

프로그래밍 언어에서는 공통적으로 사용할 기본적인 형식들을 제공하죠.
그리고 프로그램 목적에 맞게 개발자가 형식을 정의하여 사용할 수 있는 문법을 제공하고 있어요.
이러한 문법을 사용자 정의 형식이라 불러요.

C언어에서 개발자가 형식을 정의하여 사용하는 문법에는 구조체, 공용체, 열거형이 있어요.
구조체와 공용체는 여러 개의 멤버를 하나의 형식으로 묶어 사용할 수 있게 정의하는 문법이예요.
그리고 열거형은 사용할 수 있는 값들을 열거하는 형식이죠.

구조체는 C언어에서 가장 많이 사용하는 사용자 정의 형식이예요.
여러 개의 데이터를 하나의 형식으로 묶어서 정의할 수 있어요.
학생의 번호, 이름을 구조체로 정의하면 학생 구조체 변수 하나만 선언해서 번호와 이름을 사용할 수 있어요.
구조체에 정의한 모든 멤버를 위한 메모리를 할당해 주어 사용하기 편해요.
이에 반해 공용체는 멤버 중에 가장 메모리 사이즈가 큰 멤버만큼의 메모리만 할당해요.
따라서 공용체를 사용하면 한 순간에 하나의 의미로만 사용할 수 있고 다른 멤버의 의미는 사용하지 못해요.

먼저 구조체를 살펴볼게요.

구조체는 C언어에서 가장 많이 사용하는 사용자 정의 형식이예요.

예를 들어 우리가 회원 관리 프로그램을 만든다고 가정할게요.
회원마다 아이디, 이름, 주소, 나이, 성별을 관리하고 최대 30명의 회원 데이터를 관리한다고 할게요.

한 명의 아이디를 관리하기 위해 char 형식을 원소로 하는 배열이 필요하겠죠.
따라서 여러 명의 회원 이름을 관리하기 위해서는 char 형식을 원소로 하는 배열이 원소인 배열을 선언해야겠죠.
이름, 주소도 마찬가지로 배열을 원소로 하는 배열이 필요하겠죠.

char ids[MAX_MEMBER][MAX_ID_LEN+1];
char names[MAX_MEMBER][MAX_NAME_LEN+1];
char address[MAX_MEMBER][MAX_ADDR_LEN+1];

여러 회원의 나이와 성별을 관리하기 위해 정수형을 원소로 하는 배열을 선언해야죠.

int ages[MAX_MEMBER];
int genders[MAX_MEMBER];

이처럼 변수를 선언하면 한 명의 회원 데이터에 접근하기 위해 여러 개의 변수를 사용해야겠죠.
구조체를 정의해서 사용하면 배열 하나만 선언해서 관리할 수 있어요.
struct [태그명]
{
   [멤버 형식] [멤버 이름];
   … 중략 
};

구조체를 정의할 때는 struct 키워드를 사용해요.
프로그램 내에 여러 개의 구조체를 정의할 수 있기 때문에 struct 키워드 뒤에 구분하기 위한 태그명을 사용하죠.
그리고 { }을 지정하여 내부에 구조체의 멤버 형식과 멤버 이름을 정의해요.

struct _Member
{
    char id[MAX_ID_LEN+1];
    char name[MAX_NAME_LEN+1];
    char addr[MAX_ADDR_LEN+1];
    int age;
    int gender;
};

이처럼 구조체를 정의하면 struct 키워드와 태크명을 사용하여 변수를 선언할 수 있어요.
그리고 구조체도 배열처럼 여러 개의 멤버로 구성해서 초기화할 때 블럭에 초기값을 나열해요.

struct _Member member1;
struct _Member member2={"jejutour", "장언휴", "제주도 제주시 애월읍 고내리", 20, 1};

이처럼 구조체 형식 변수를 선언할 수 있지만 typedef 문법으로 새로운 형식 이름을 정의하여 사용할 수 있어요. 그리고 구조체 형식 변수로 멤버에 접근할 때는 구조체 변수명과 멤버명 사이에 .를 표시하여 구분한답니다.

typedef struct _Member   Member;
Member member;
member.age = 20;

◈ 구조체로 Member 형식 정의

#include <stdio.h>
#include <string.h>
#define MAX_ID_LEN        20
#define MAX_NAME_LEN   30
#define MAX_ADDR_LEN   50
typedef struct _Member   Member;
struct _Member
{
    char id[MAX_ID_LEN+1];
    char name[MAX_NAME_LEN+1];
    char addr[MAX_ADDR_LEN+1];
    int age;
    int gender;
};
int main()
{
    Member member={""};
    strcpy(member.id,"jejutour");
    strcpy(member.name,"장언휴");
    strcpy(member.addr,"제주도 제주시 애월읍 고내리");
    member.age = 20;
    member.gender = 1;
    printf("아이디:%s 이름:%s \n",member.id,member.name);
    printf("주소:%s 나이:%d 성별:%d\n",member.addr,member.age, member.gender);
    return 0;
}

◈ 실행 결과

아이디:jejutour 이름:장언휴
주소:제주도 제주시 애월읍 고내리 나이:20 성별:1 

구조체 형식을 입력 인자로 전달하면 값을 복사하여 버그가 발생할 수 있어요.

예를 들어 학생 구조체에 이름과 아이큐가 있다고 가정할게요.
학생이 공부하면 아이큐가 올라가게 할거예요.
학생 구조체 형식 변수를 입력 인자로 전달하여 학생의 아이큐를 변경하면 어떻게 될까요?

◈ 구조체 형식 변수로 멤버 사용할 때 버그

#include <stdio.h>
#define MAX_NAME_LEN 20
typedef struct _Student Student;
struct _Student
{
    char name[MAX_NAME_LEN+1]; 
    int iq;
};
void Study(Student stu);
int main()
{
    Student stu = {"홍길동", 100};
    Study(stu);
    printf("main 이름:%s 아이큐:%d\n",stu.name,stu.iq);
}
void Study(Student stu)
{
    printf("%s 공부하다.\n",stu.name);
    stu.iq++;
    printf("IQ:%d\n",stu.iq);
}

◈ 실행 결과

홍길동 공부하다.
IQ:101
main 이름:홍길동 IQ:100

인자를 전달할 때 값을 복사하여 전달해서 입력 매개 변수와 호출한 곳의 변수는 다른 메모리예요.
따라서 값을 복사한 입력 매개 변수를 변경할 뿐 원본 학생 구조체 변수의 값은 아무런 영향을 받지 않아요.

따라서 구조체로 정의한 형식의 데이터를 인자로 전달할 필요가 있을 때는 구조체 포인터로 전달하세요.
구조체 포인터 변수일 때 변수명과 멤버명 사이에 오른쪽 화살표 (->)를 표시하여 멤버에 접근하세요.
◈ 구조체 포인터로 멤버 사용

#include <stdio.h>
typedef struct _Point Point;
struct _Point
{
    double x;
    double y;
};
void ViewPoint(Point *point);
int main()
{
    Point pt = {2,3};
    ViewPoint(&pt);
    return 0;
}
void ViewPoint(Point *point)
{
    printf("x:%0.2lf y:%0.2lf\n",point->x, point->y);
}

◈ 실행 결과

x:2.00 y:3.00

구조체를 사용하여 회원 관리 프로그램을 만들면 구조체 형식을 원소로 하는 배열을 선언하여 관리가 쉬워져요.

◈ 구조체 Member 형식을 원소로 하는 배열

#include <stdio.h>
#include <string.h>
 
#define MAX_MEMBER        30
#define MAX_ID_LEN           20
#define MAX_NAME_LEN     30
#define MAX_ADDR_LEN      50
 
typedef struct _Member    Member;
struct _Member
{
    char id[MAX_ID_LEN+1];
    char name[MAX_NAME_LEN+1];
    char addr[MAX_ADDR_LEN+1];
    int age;
    int gender;
};
 
Member members[MAX_MEMBER];
int main()
{
    return 0;
}

그리고 C언어에서는 구조체 비트 필드 문법을 제공하고 있어요.
구조체의 멤버 선언문 뒤에 콜론을 명시한 후 정수를 사용하면 메모리를 비트 단위로 배정할 수 있어요.
결혼 유무(0,1)처럼 값의 종류가 적은 멤버를 여러 개 포함할 때 구조체 비트 필드를 이용하면 메모리를 줄일 수 있어요.

예를 들어 볼게요.

typedef struct _Data Data;
struct _Data
{
    unsigned char married:1; //1비트 배정
    unsigned char hascar:1; //1비트 배정
    unsigned char hashouse:1; //1비트 배정
    unsigned char age:5; //5비트 배정
};

위처럼 Data 형식을 정의하면 Data의 크기는 1바이트랍니다.
married와 hsacar, hashouse는 1비트에 배정해요.
그리고 age는 5비트를 배정하여 전체를 합하면 1바이를 배정하는 것이죠.
◈ 구조체 비트 필드 사용

//구조체 비트 필드
#include <stdio.h>
 
typedef struct _Data Data;
struct _Data
{
    unsigned char married:1; //1비트 배정
    unsigned char hascar:1; //1비트 배정
    unsigned char hashouse:1; //1비트 배정
    unsigned char age:5; //5비트 배정
};
 
int main(void)
{
    Data data={0};
    printf("size of Data : %d\n",sizeof(Data));
 
    data.married = 1;
    data.hascar = 1;
    data.age = 22;
 
    printf("married:%d, hascar:%d, hashouse:%d, age:%d\n",
        data.married,  data.hascar, data.hashouse, data.age);
    
    return 0;
}

◈ 실행 결과

size of Data:1
married:1, hascar:1, hashouse:0, age:22

75. 공용체

공용체는 여러 개의 멤버 중에서 하나의 멤버의 값만을 사용하는 사용자 정의 형식이예요.
공용체를 정의하는 것은 구조체와 비슷해요.
공용체를 정의할 때는 struct 키워드 대신 union을 사용하세요.
공용체는 구조체와 다르게 모든 멤버마다 별도의 메모리를 부여하지 않고 메모리가 제일 큰 멤버 크기의 메모리만 할당해요.  
unio [태그명]
{
   [멤버 형식] [멤버 이름];
   … 중략 …
};
 
◈ 공용체와 구조체의 메모리 크기 비교
#include <stdio.h>
struct _SDemo
{
    int a;    int b;
};
union _UDemo
{
    int a;    int b;
};
int main()
{
    printf(“struct _SDemo 크기: %d\n”, sizeof(struct _SDemo));
    printf(“union _UDemo 크기: %d\n”, sizeof(union _UDemo));
    return 0;
}
◈ 실행 결과
struct _SDemo 크기: 8
union _UDemo 크기: 4

공용체는 멤버마다 별도의 메모리를 부여하지 않아서 하나의 멤버의 값을 변경하면 다른 멤버의 값은 의미가 없어요.
따라서 공용체는 한 순간에 하나의 멤버의 값만 유효하죠.
 
◈ 공용체의 하나의 멤버를 변경하면 다른 멤버의 값에 영향
#include <stdio.h>
typedef union _Demo Demo;
union _Demo
{
    int i;
    float f;
};
int main()
{
    Demo d;
 
    d.i = 90;
    printf(“d.i: %10d d.f:%0.2f\n”, d.i, d.f);
    d.f = 1.0;
    printf(“d.i: %10d d.f:%0.2f\n”, d.i, d.f);
    d.i = 2;
    printf(“d.i: %10d d.f:%0.2f\n”, d.i, d.f);
    return 0;
}
◈ 실행 결과
d.i:          90  d.f:0.00
d.i: 10653532 d.f:2.00
d.i:            2 d.f:0.00

76. 열거형

열거형은 표현할 수 있는 값의 종류를 열거하는 형식이예요.
가령 성별을 관리할 때 int 형식을 사용하여 0은 여성, 1은 남성을 표현하기로 약속할 수 있겠죠.
하지만 다른 개발자가 코드를 이해하기 어려울 수 있어요.
이 때 열거형을 사용하면 가독성(쉽게 읽을 수 있는 성질) 높은 프로그램을 작성할 수 있어요.

◈ 성별을 표현할 수 있는 Gender 열거형 정의

#include <stdio.h>
typedef enum _Gender Gender;
enum _Gender{    FEMALE, MALE};
int main()
{
    Gender g = FEMALE;
    if(g == FEMALE)
    {
         printf("여성\n");
    }
    else
    {
        printf("남성\n");
    }
    return 0;
}

◈ 실행 결과

여성

열거형을 정의할 때 열거하는 이름과 대응하는 값을 지정할 수도 있어요.
만약 값을 지정하지 않으면 앞에 지정한 값에 1 증가한 값으로 지정하죠.
그리고 맨 처음 열거한 이름에 값을 지정하지 않으면 0으로 지정한답니다.

◈ 열거형의 값 지정

#include <stdio.h>
enum _Test
{
    A, B, C=5, D, E=3, F=3, G
};
int main()
{
    printf("A:%d B:%d C:%d D:%d E:%d F:%d G:%d \n",A, B, C, D, E, F,G);
    return 0;
}

◈ 실행 결과

A:0 B:1 C:5 D:6 E:3 F:3 G:4

그리고 하나의 프로그램에 정의할 상수가 많이 있으면 종류에 따라 열거형으로 묶어 정의하면 가독성을 높일 수 있어요.