48. 구조체와 공용체
1. 키워드
- 전방 선언(Forward Declaration)
2. 구조체와 공용체 활용하기
- 지금까지 구조체와 공용체를 단독으로 사용했다.
- 하지만 구조체 안에 구조체와 공용체가 들어갈 수 있고, 반대로 공용체 안에 구조체와 공용체가 들어갈 수도 있다.
3. 구조체 안에서 구조체 멤버 사용하기
- 다음과 같이 구조체는 구조체를 멤버로 가질 수 있다.
#include <stdio.h>
struct Phone // 휴대전화 구조체
{
int areacode; // 국가번호
unsigned long long number; // 휴대전화 번호
};
struct Person // 사람 구조체
{
char name[20]; // 이름
int age; // 나이
struct Phone phone; // 휴대전화 구조체를 멤버로 가짐
};
int main()
{
struct Person p1;
p1.phone.areacode = 82; // 변수.멤버.멤버 순으로 접근하여 값 할당
p1.phone.number = 3045671234; // 변수.멤버.멤버 순으로 접근하여 값 할당
printf("%d %llu\n", p1.phone.areacode, p1.phone.number);
return 0;
}
// 82 3045671234
- 구조체를 멤버로 가지려면 다음과 같이 구조체 안에서 구조체 변수를 선언하면 된다.
- 여기서는 사람 구조체가 휴대전화 구조체를 멤버로 가지고 있다.
struct Person // 사람 구조체
{
char name[20]; // 이름
int age; // 나이
struct Phone phone; // 휴대전화 구조체를 멤버로 가짐
};
- 멤버에 접근할 때는
.
(점)을 사용하며 가지고 있는 구조체에 계층적으로 접근하면 된다. - 다음은
.
을 두 번 사용하여 휴대전화 구조체의areacode
와number
멤버에 접근하여 값을 할당하고, 값을 가져온다.
p1.phone.areacode = 82; // 변수.멤버.멤버 순으로 접근하여 값 할당
p1.phone.number = 3045671234; // 변수.멤버.멤버 순으로 접근하여 값 할당
printf("%d %llu\n", p1.phone.areacode, p1.phone.number);
구조체 정의 순서
- 구조체를
Phone
→Person
순서가 아니라Person
→Phone
순서로 바꿔서 컴파일해 보면 정의되지 않는 구조체를 사용한다는 식으로 에러 메시지가 나온다. - 구조체 안에 구조체가 변수로 들어갈 때는 안에 들어가는 구조체를 먼저 선언해 준다.
- 만약
Phone
구조체를 다른 곳에서는 쓰지 않고, 특정 구조체 안에서만 쓴다면 구조체 안에 구조체를 정의하는 게 더 편리하다. - 단, 이때는 구조체를 정의한 뒤 반드시 변수를 선언해야 한다.
struct Person // 사람 구조체
{
char name[20]; // 이름
int age; // 나이
struct Phone // 휴대전화 구조체를 사람 구조체 안에 정의
{
int areacode; // 국가번호
unsigned long long number; // 휴대전화 번호
} phone; // 구조체를 정의하는 동시에 변수 선언
};
구조체 변수를 선언하는 동시에 초기화하기
- 구조체 변수를 선언하면서 안에 들어있는 구조체까지 초기화하려면
{}
(중괄호) 안에{}
를 사용한다.
- 다음과 같이 구조체 안에 들어있는 구조체 멤버 위치에
{}
를 사용하여 값을 묶어주면 된다.
#include <stdio.h>
struct Phone
{
int areacode;
unsigned long long number;
};
struct Person
{
char name[20];
int age;
struct Phone phone;
};
int main()
{
// phone의 멤버 areacode에는 82, number에는 3045671234
struct Person p1 = {.name = "Andrew", .age = 25, {.areacode = 82, .number = 3045671234}};
printf("%d %llu\n", p1.phone.areacode, p1.phone.number);
// phone의 멤버 areacode에는 82, number에는 3087654321
struct Person p2 = {"Maria", 19, {82, 3087654321}};
printf("%d %llu\n", p2.phone.areacode, p2.phone.number);
return 0;
}
// 82 3045671234
// 82 3087654321
4. 구조체 안의 구조체 멤버에 메모리 할당하기
- 이번에는 구조체 안의 구조체 멤버에 메모리를 할당해 보자.
- 먼저 다음은 구조체 안에 구조체 멤버가 변수로 있는 상태에서 메모리를 할당하여 사용하는 방법이다.
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
struct Phone // 휴대전화 구조체
{
int areacode; // 국가번호
unsigned long long number; // 휴대전화 번호
};
struct Person // 사람 구조체
{
char name[20]; // 이름
int age; // 나이
struct Phone phone; // 휴대전화 구조체를 멤버로 가짐
};
int main()
{
struct Person *p1 = malloc(sizeof(struct Person)); // 사람 구조체 포인터에 메모리 할당
p1->phone.areacode = 82; // 포인터->멤버.멤버 순으로 접근하여 값 할당
p1->phone.number = 3045671234; // 포인터->멤버.멤버 순으로 접근하여 값 할당
printf("%d %llu\n", p1->phone.areacode, p1->phone.number);
free(p1); // 동적 메모리 해제
return 0;
}
// 82 3045671234
struct Person *p1 = malloc(sizeof(struct Person));
과 같이 사람 구조체 포인터에 메모리를 할당한다.p1
은 포인터이므로->
(화살표 연산자)를 사용하여 멤버에 접근한다.- 하지만
phone
은 포인터가 아닌 일반 변수이므로.
을 사용하여 멤버에 접근한다. - 즉,
포인터->멤버.멤버
모양이다.
p1->phone.areacode = 82; // 포인터->멤버.멤버 순으로 접근하여 값 할당
p1->phone.number = 3045671234; // 포인터->멤버.멤버 순으로 접근하여 값 할당
printf("%d %llu\n", p1->phone.areacode, p1->phone.number);
.
이냐->
냐 구분할 때는 현재 구조체가 선언된 상태를 확인하면 된다.- 변수면
.
, 포인터면->
이다. - 구조체 포인터의 사용이 끝났다면 반드시
free
함수로 동적 메모리를 해제한다.
- 이번에는 구조체의 포인터를 멤버로 가지는 상황을 알아보자.
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
struct Phone // 휴대전화 구조체
{
int areacode; // 국가번호
unsigned long long number; // 휴대전화 번호
};
struct Person // 사람 구조체
{
char name[20]; // 이름
int age; // 나이
struct Phone *phone; // 휴대전화 구조체 포인터 선언
};
int main()
{
struct Person *p1 = malloc(sizeof(struct Person)); // 바깥 구조체의 포인터에 메모리 할당
p1->phone = malloc(sizeof(struct Phone)); // 멤버 포인터에 메모리 할당
p1->phone->areacode = 82; // 포인터->포인터->멤버 순으로 접근하여 값 할당
p1->phone->number = 3045671234; // 포인터->포인터->멤버 순으로 접근하여 값 할당
printf("%d %llu\n", p1->phone->areacode, p1->phone->number);
free(p1->phone); // 구조체 멤버의 메모리를 먼저 해제
free(p1); // 구조체 메모리 해제
return 0;
}
// 82 3045671234
- 사람 구조체를 살펴보면
struct Phone *phone;
과 같이 구조체 포인터를 멤버로 가지고 있다.
struct Person // 사람 구조체
{
char name[20]; // 이름
int age; // 나이
struct Phone *phone; // 휴대전화 구조체 포인터 선언
};
- 구조체 포인터는 선언만 해서는 사용을 할 수가 없다.
- 일단 바깥 구조체의 포인터에 메모리를 할당한 뒤 멤버로 있는 구조체 포인터에 메모리를 할당한다.
struct Person *p1 = malloc(sizeof(struct Person)); // 바깥 구조체의 포인터에 메모리 할당
p1->phone = malloc(sizeof(struct Phone)); // 멤버 포인터에 메모리 할당
p1
은 포인터이므로->
를 사용하면 되고, 구조체 멤버phone
도 메모리를 할당한 포인터이므로 똑같이->
를 사용하면 된다.- 즉,
포인터->포인터->멤버
모양이다.
p1->phone->areacode = 82; // 포인터->포인터->멤버 순으로 접근하여 값 할당
p1->phone->number = 3045671234; // 포인터->포인터->멤버 순으로 접근하여 값 할당
printf("%d %llu\n", p1->phone->areacode, p1->phone->number);
- 구조체 포인터 사용이 끝났으면 메모리를 해제해야 하는데 순서가 중요하다.
- 반드시 안쪽에 있는 멤버부터 메모리를 해제한다.
- 바깥에 있는 구조체 포인터를 먼저 해제하면 데이터가 사라지므로 안에 있는 멤버에 더이상 접근할 수 없게 된다.
- 그래서 멤버 포인터에 저장된 주소도 알 수 없으므로 해제도 할 수 없게 된다.
- 다음과 같이 멤버 포인터의 주소를 미리 다른 곳에 저장해 두었다면 바깥에 있는 구조체 포인터를 먼저 해제해도 된다.
struct Phone *phone = p1->phone;
free(p1); // 바깥의 구조체 메모리를 먼저 해제
free(phone); // 미리 저장해 둔 포인터를 이용해서 멤버 포인터 해제
구조체 정의 순서
- 구조체를
Phone
→Person
순서가 아니라Person
→Phone
순서로 바꿔서 컴파일해 보면 정의되지 않은 구조체를 사용한다는 식으로 에러 메시지가 나온다. - 구조체 안에 구조체가 포인터로 들어갈 때는 전방 선언을 사용하면 정의되지 않은 구조체를 먼저 사용할 수 있다.
struct Phone; // Phone 구조체 전방 선언
struct Person
{
char name[20];
int age;
struct Phone *phone; // 아직 정의되지 않은 구조체를 포인터로 선언
};
struct Phone // Phone 구조체 정의
{
int areacode;
unsigned long long number;
};
- 전방 선언은
struct Phone;
과 같이struct
키워드에 구조체 이름을 지정해 주면 된다. - 단, 전방 선언을 했다면 반드시 구조체를 따로 정의해 주어야 한다.
5. 익명 구조체와 익명 공용체 활용하기
- 이번에는 익명 구조체와 익명 공용체를 활용하는 방법을 알아보자.
- 다음은 3차원 벡터 좌표를 구조체와 공용체로 나타낸 예제이다.
#include <stdio.h>
struct Vector3 // 3차원 벡터 좌표
{
union // 익명 공용체
{
struct // 익명 구조체
{
float x; // x 좌표
float y; // y 좌표
float z; // z 좌표
};
float v[3]; // 좌표를 배열로 저장
};
};
int main()
{
struct Vector3 pos;
for (int i = 0; i < 3; i++) // 3번 반복
{
pos.v[i] = 1.0f; // v는 배열이므로 인덱스로 접근할 수 있음
}
printf("%f %f %f\n", pos.x, pos.y, pos.z); // x, y, z로 접근
return 0;
}
// 1.000000 1.000000 1.000000
x
,y
,z
좌표를 저장하는Vector3
구조체를 정의했다.- 먼저 맨 안쪽
x
,y
,z
는float
타입으로 선언되어 있으며 익명 구조체이다. - 이때
x
,y
,z
는 각각의 값을 독립적인 공간에 저장하기 위해 구조체로 정의한다. - 또한,
pos.x
처럼 멤버에 바로 접근하기 위해 익명 구조체로 정의한다. - 그리고 익명 공용체가
x
,y
,z
익명 구조체와 배열v
를 감싸고 있다. pos.v
처럼 멤버에 바로 접근하기 위해 익명 공용체로 정의한다.
struct Vector3 // 3차원 벡터 좌표
{
union // 익명 공용체
{
struct // 익명 구조체
{
float x; // x 좌표
float y; // y 좌표
float z; // z 좌표
};
float v[3]; // 좌표를 배열로 저장
};
};
float
타입x
,y
,z
는 변수3
개이고,float v[3]
은 배열의 요소가3
개이다.- 자료형도 같고 개수도 같으므로 결국 같은 공간을 차지한다.
- 따라서 공용체로 묶어주면
x
,y
,z
와v[3]
은 같은 공간을 공유하게 된다.
- 다음과 같이
v
는 배열이므로 인덱스로 접근하여 값을 할당할 수 있다. - 여기서는
3
번 반복하면서v[0]
,v[1]
,v[2]
에1.0
을 할당했다.
- 공용체에 값을 넣었으므로 값은 같은 공간에 있다.
- 따라서
x
,y
,z
멤버로도 접근할 수도 있다.
- 이처럼 익명 구조체와 익명 공용체를 사용하면 같은 값이지만 이름과 형태를 달리하여 접근할 수 있다.