사용자 정의형이란, 기본 자료형 이외에 사용자가 직접 정의하는 데이터 형을 말해요.
등이 사용자 정의형에 해당해요.
구조체는 서로 다른 형의 변수들을 하나의 단위로 묶을 수 있는 방법이에요. 예를들어, 학생의 정보를 저장할 때 학생의 학번, 이름, 학과 등의 정보를 묶어 학생이라는 한개의 단위로 취급할 수 있어요.
구조체는 아래와 같이 선언해요.
struct student
{
int id;
char name[10];
char major[20];
}
struct
키워드를 사용해, 구조체를 만들 수 있어요.
위 예시에서, student는 구조체의 이름이에요.
id, name, major는 이 구조체의 멤버에요.
이렇게 선언한 구조체는 변수를 선언한 것이 아니라, struct student
라는 자료형을 선언한것이에요. 자료형이 오는 자리에 사용할 수 있어요.
struct student stu1, stu2;
이 구조체의 크기는 몇 바이트일까요?
구조체의 크기도 sizeof를 사용해 계산할 수 있지만, 각 멤버의 크기를 합해서도 계산할 수 있어요.
student 구조체의 경우, 메모리의 크기는 아래와 같아요.
멤버명 | 맴버 크기 |
---|---|
id | 4 |
name | 10 |
major | 20 |
--- | ----- |
총합 | 34 |
구조체 변수의 경우, 같은 구조체 형의 변수끼리만 값을 재정할 수 있어요.
struct student stu1, stu2;
stu1 = 10; // 오류
stu1 = stu2; // 가능
구조체 멤버의 경우, .
을 사용해 접근할 수 있어요.
stu1.id = 201030;
strcpy(stu2.name, "홍길동");
구조체 변수는 선언과 동시에도 변수를 선언할 수 있어요.
struct student
{
int id;
char name[10];
char major[20];
} stu3;
struct student stu4;
이외에도, 구조체의 이름을 정하지 않은 채 선언할 수 있어요.
struct
{
int id;
char name[10];
char major[20];
} stu5;
struct
{
int id;
char name[10];
char major[20];
} stu6;
이 경우, stu5와 stu6은 같은 멤버로 구성되지만 다른 구조체 형으로 취급돼요. 따라서, stu5 = stu6
은 오류가 발생해요.
구조체는 배열과 유사한 방식으로 초기화해요.
// 멤버 순서대로 초기화돼요.
struct student stu = {201030, "홍길동", "컴퓨터학부"};
// stu.id = 201030, stu.name = "홍길동", stu.major = "컴퓨터학부"
C99 이상부터, .
연산자를 사용해 멤버를 지정하여 초기화 할 수 있어요.
struct student stu = {.name = "홍길동", .id = 201030, .major = "컴퓨터학부"};
복합 리터럴을 사용해서 구조체 멤버의 값을 배정할 수도 있어요.
복헙 리터럴은 캐스트를 사용해 형을 지정해요.
stu = (struct student) {201030, "홍길동", "컴퓨터학부"};
// 복합 리터럴 내부에서도 멤버를 지정해 초기화할 수 있어요.
stu = (struct student) {.name = "홍길동"};
구조체의 멤버로 다른 구조체가 올 수 있어요.
struct student
{
char name[20];
char major[20];
}
struct professor
{
char name[20];
}
struct subject
{
char name[20];
struct professor prof;
struct student student;
}
struct subject math;
math.professor.name = "홍길동";
구조체도 포인터로 가리킬 수 있어요.
struct student stu, *stu_p;
stu_p = &stu;
구조체의 포인터는 포인터와 동일하게 사용해요. 멤버에 바로 접근하지 못하고, 역참조 연산자를 통해 구조체를 가져와 멤버에 접근해야 해요.
stu_p.grade = 88; // 오류 : stu_p는 포인터.
*stu_p.grade = 88; // 오류 : stu_p.grade에 역참조 연산자 사용.
(*stu_p).grade = 88; // 올바른 사용.
하지만, ->
연산자를 사용하면 구조체 포인터에서 바로 멤버에 접근할 수 있어요.
stu_p->grade = 88;
구조체 배열은 일반적인 자료형들의 배열과 동일하게 사용해요.
struct student students[3];
구조체 배열을 초기화 하는 것 또한 동일해요. 단지 요소로 구조체의 초기화 구문을 쓸 뿐이에요.
students[3] = {{111111, "홍길동", "컴퓨터학부"}, {222222, "이몽룡", "국문학과"}, {333333, "이춘향", "철학과"}};
C99부터는 이렇게도 초기화 할 수 있어요.
students[3] = {
[1] = {222222, "이몽룡", "국문학과"},
[2] = {333333, "이춘향", "철학과"}
};
C99의 구조체 초기화 구문이랑 같이 사용할 수 있어요.
students[3] = {
[1].id = 111111, [1].name = "홍길동", [1].major = "컴퓨터학부"
};
함수의 인자로 구조체가 전달될 때, 구조체는 값으로 전달돼요. 하지만, 구조체가 많은 멤버를 가지거나, 큰 배열을 멤버로 가질 경우 함수 인자로 구조체를 전달하는 것은 비효율적이에요.
따라서, 대부분의 프로그램에서는 함수의 인자로 구조체의 주소를 사용해요.
typedef
키워드는 기존의 자료형으로부터 유도한 새로운 자료형을 만들기 위해 사용돼요. 일반적인 사용 방법은 4가지로 나눌 수 있어요.
typedef type_name new_type;
typedef type_name * new_type; // 포인터 타입
typedef type_name new_type[size]; // 배열 타입
typedef type_name (*new_type)(parameter_type_list); // 함수 포인터 타입
type_name은 기존에 알려진 자료형이고, new_type은 새로운 자료형의 이름이에요.
typedef type_name new_type;
위 형태의 자료형 선언에 대해 알아볼게요. 대표적인 예시로 size_t
형이 있어요. C표준에 따르면, size_t
형은 부호가 없는 정수형이라 되어 있어요. 컴파일러는 아래와 같이 선언해 사용해요.
typedef unsigned int size_t
구조체 변수 등을 만들 때, 매번 struct student처럼 길게 쓰는게 불편할 때도 typedef를 사용할 수 있어요.
typedef struct student Student;
Student stu;
아예 구조체를 만들 때 typedef를 사용할 수도 있어요.
typedef struct
{
int id;
char name[10];
char major[20];
} Student;
typedef type_name * new_type;
위와 같이 선언하면, type_name의 포인터 형을 만들 수 있어요.
간단한 예시로, int*형을 표현해보면 아래와 같아요.
typedef int *intptr;
intptr은 int형의 포인터 타입이에요.
typedef type_name new_type[size];
위와 같이 선언하면 type_name형의 길이가 size인 배열 형을 만들 수 있어요.
예를 들어, 길이 10의 int형 배열에 대한 자료형을 만들어보면 아래와 같아요.
typedef int intarr[10];
typedef return_type (*new_type)(parameter_type_list);
함수 포인터에 대한 자료형 선언이에요. 함수 포인터를 선언할 때, 자료형 부분을 아래와 같이 적었었어요.
int sum(int, int);
int (*f)(int, int) = sum;
여기서, int (*f)(int, int)
부분을 typedef로 선언하는거에요.
위 예시의 sum 함수에 대한 포인터 타입을 만들어보자면 아래와 같아요.
typedef int (*int_f)(int, int);
배운 내용들을 정리해보고 있어요. 잘못 기재된 내용이 있다면, 댓글로 지적해주시면 수정할게요.