구조체(Struct), 공용체(Union), 열거형(Enum)
구조체 (Struct)
필요성 및 정의
서로 다른 자료형의 변수들을 논리적으로 묶어 하나의 새로운 자료형을 만들어야 할 때가 있다. 예를 들어 '책' 한 권의 정보는 제목(문자열), 저자(문자열), 페이지 수(정수), 가격(정수) 등 다양한 자료형으로 구성된다.
이렇게 연관된 데이터를 하나의 단위로 통합하기 위해 사용하는 것이 바로 **Struct(구조체)**이다.
Struct를 정의하는 기본 형식은 다음과 같다. 정의 구문 마지막에는 반드시 세미콜론(;)을 붙여야 한다.
struct 구조체_태그_이름 {
자료형1 변수명1;
자료형2 변수명2;
// ...
};
Struct를 구성하는 각 변수를 Member(멤버) 또는 구성요소라고 한다. 한 Struct 내에서 Member의 이름은 서로 유일해야 하며, 정의 단계에서는 Member에 초기값을 대입할 수 없다.
변수 선언
Struct 정의는 새로운 자료형을 만드는 과정일 뿐, 변수를 선언하는 것은 아니다. 정의된 Struct 자료형을 사용하여 변수를 선언하는 방법은 여러 가지가 있다.
-
정의와 선언 분리 Struct 자료형을 먼저 정의한 후, 필요할 때
struct 구조체_태그_이름
을 사용하여 변수를 선언한다.// struct book 자료형 정의
struct book {
char title[50];
char author[50];
int pages;
int price;
};
// struct book 타입의 변수 선언
struct book mybook; -
정의와 선언 동시 진행 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 구조체_태그_이름
형태의 긴 자료형을 더 간단한 이름으로 대체할 수 있다.
-
기존 Struct에 Typedef 적용
// struct book 자료형 정의
struct book { ... };
// struct book을 book이라는 새로운 이름으로 재정의
typedef struct book book;
// 'book' 키워드만으로 변수 선언 가능
book mybook;
book yourbook; -
정의와 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는 함수의 인자로 전달되거나 반환값으로 사용될 수 있다.
-
값에 의한 호출 (Call by Value) Struct 변수 자체를 인자로 전달하는 방식이다. 함수 내부에서 인자로 받은 Struct는 원본의 복사본이므로, 함수 내에서 수정이 원본에 영향을 주지 않는다.
complex paircomplex1(complex com) {
com.img = -com.img;
return com;
}
// 호출
pcomp = paircomplex1(comp); -
주소에 의한 호출 (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
문에서 의미 없는 숫자 대신 의미 있는 열거형 상수를 사용하면 코드를 이해하기가 훨씬 쉬워진다.