
struct 구조체이름 {
타입 변수이름;
타입 변수이름;
...
};
book이라는 구조체를 정의해보자.
struct book {
char title[30];
char author[30];
int price;
};
위 예시에서 book이라는 구조체를 만들었다면, 이 구조체를 불러 어딘가에서 사용할 때 book만 참조하는 것이 아닌 struct book까지 참조해야 하는데, 이까지가 데이터 타입으로 봐야한다.
정의한 구조체 타입을 구조체 변수로 사용하기 위해서는 다음과 같이 해야한다.
struct 구조체명 변수명;
위와 같이 구조체를 사용하기 위해 변수명을 지정하여 선언하여 사용할 수 있다.
변수 선언 방법은 다양하게 있다.
// 구조체형을 선언한 후에 구조체 변수 선언
struct book {
char title[30];
char author[30];
int price;
};
struct book book1, book2, book3;
// 구조체 이름을 생략하고 구조체 변수 이름만 선언
struct book {
char title[30];
char author[30];
int price;
} book1, book2, book3;
// 구조체 이름을 생략하고 구조체 변수 이름만 선언
struct {
char title[30];
char author[30];
int price;
} book1, book2, book3;
주로 2번째 방법 구조체형과 구조체 변수를 연결하여 선언이 권장된다.
구조체를 정의하고 이것을 변수로 선언하려면 앞에 struct 키워드를 붙여서 구조체라는 것을 컴퓨터에 알려줘야 한다. 이런 선언이 한두 번이면 상관없겠지만, 횟수가 많다면 매번 struct를 붙여주어야 하므로 불편하다. 이것을 해결해주는 것이 typedef 키워드이다.
C언어에서 typedef 키워드는 이미 존재하는 타입에 새로운 이름을 붙일 때 사용한다. 즉, struct 키워드를 구조체명으로 대체할 수 있게 된다. typedef 키워드를 사용하여 구조체에 새로운 이름을 선언하면, 해당 구조체는 선언한 이름으로 사용할 수 있게 된다.
typedef struct 구조체명 구조체변수명;
또한, 구조체 정의와 typedef 선언을 동시에 할 수도 있다.
typedef struct 구조체명
{
변수타입 변수명;
변수타입 변수명;
} 구조체변수명;
이렇게 선언한 구조체는 다음과 같이 사용할 수 있다.
typedef struct _book {
...
} BOOK;
int main(void) {
BOOK my_book1;
}
즉, '정의한 구조체를 이 이름으로 쓰겠다!'라고 생각하면 된다.
typedef로 선언한 구조체의 경우 대문자를 쓰는 것이 바람직하다.
또한, 구조체의 정의와 typedef 선언을 동시에 할 때에는 구조체의 이름을 생략할 수 있다.
배열에서는 인덱스를 이용하여 배열 요소에 접근할 수 있는데, 구조체에서는 구조체 멤버 변수로 접근하려고 할 때에는 멤버 연산자(.)를 사용해야 한다.
my_book.author // 구조체이름.멤버변수이름
이렇게 접근한 멤버 변수는 일반 변수와 똑같이 사용할 수 있다.
구조체 변수를 초기화 할 때에는 멤버 연산자(.) 및 중괄호({})를 사용한다.
my_book.title = "C Language";
my_book.author = "Dennis";
my_book.price = 30000;
// 또는
my_book = {.title = "C Language", .author = "Dennis", .price = 30000};
위의 방법처럼 하나하나 초기화 하는 방법이 있고, 아래처럼 중괄호로 묶어 사용하는 방법이 있는데 중괄호를 사용하게 되면 원하는 멤버 변수만 초기화할 수 있다. 이렇게 초기화 및 접근한 구조체 멤버 변수는 일반 변수와 똑같이 사용할 수 있다. 이때, 멤버 변수가 정의된 순서와 초기화하는 순서는 아무런 상관이 없으며, 초기화하지 않은 멤버 변수는 0으로 초기화된다.
또한, 배열의 초기화와 같은 방법으로 멤버 연산자 없이 구조체 멤버 변수를 초기화할 수도 있다.
my_book = {"C Language", "Dennis", "30000"};
이때 구조체 정의에서 멤버 변수가 정의된 순서에 따라 차례대로 초깃값이 설정되며, 초기화하지 않은 멤버 변수는 0으로 초기화된다.
이렇게 초기화한 구조체는 다음과 같이 사용할 수 있다.
typedef struct book {
char title[30];
char author[30];
int price;
} BOOK;
int main(void) {
BOOK my_book1 = {"C Language", "Dennis", "30000"};
BOOK my_book2 = {.title = "C Language", .price = 30000};
return 0;
}
C 언어에서 배열의 요소가 될 수 있는 타입에는 제한이 없으므로, 구조체 역시 배열의 한 요소가 될 수 있다. 이러한 구조체 배열을 선언하는 방법은 다른 타입의 배열을 선언하는 방법과 같다. 또한, 구조체 배열에서 각 배열 요소로 접근하는 방법도 일반 배열의 접근 방법과 같다.
위의 예시에서 3권의 책을 저장하는 것을 목표로 한다면,
struct book my_books[3]
my_books[0].title = 'C Language';
my_books[0].author = 'Dennis';
my_books[0].price = 30000;
my_books[1].title = 'Java';
my_books[1].author = 'James';
my_books[1].price = 35000;
my_books[2].title = 'Javascript';
my_books[2].author = 'Brendan';
my_books[2].price = 40000;
//또는
struct book my_books[3] = {
{"C Language", "Dennis", 30000},
{"Java", "James", 35000},
{"Javascript", "Brendan", 40000}
};
...
이런 식으로 일반 변수 또는 2차원 배열의 초기화 방법과 똑같은 방법으로 초기화할 수 있다.
또한, 멤버 연산자(.)를 사용하여 각 배열 요소의 멤버에 접근할 수 있다.
다음 그림은 위의 예제에서 사용된 구조체 배열의 메모리 상태를 보여준다.

그림에서 알 수 있듯 구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다.
구조체 변수는 포인터로도 선언이 가능하다.
struct 구조체이름 *구조체포인터이름;
struct book *ptr_my_book;
배열의 경우와 달리 구조체의 이름은 구조체를 가리키는 주소가 아니다.
따라서 포인터를 할당할 때에는 반드시 주소 연산자를 사용해야 한다.
struct book my_book;
struct book *ptr_my_book;
*ptr_my_book = &my_book;
구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법에는 참조 연산자(*)와 화살표 연산자(->) 두 가지가 있다.
일반 포인터 변수를 참조하듯 사용하면 된다.
(*구조체포인터).멤버변수이름
(*ptr_my_book).author
이때, 참조 연산자는 멤버 연산자보다 연산자 우선순위가 낮으므로 반드시 괄호를 사용하여 먼저 처리하도록 해야한다. 괄호를 사용하지 않으면 오류가 발생한다.
화살표 연산자의 앞쪽에는 구조체 포인터를, 뒤쪽에는 접근하고자 하는 구조체의 멤버 변수 이름을 사용하면 된다.
구조체포인터 -> 멤버변수이름
ptr_my_book -> author
위의 두 가지 방법은 완전히 동일하게 동작하며, 일반적으로 화살표 연산자가 좀 더 많이 사용된다.
struct book my_book = {"C Language", "Dennis", 30000};
struct book *ptr_my_book; // 구조체 포인터 선언
ptr_my_book = &my_book;
strcpy((*ptr_my_book).title, "Java"); // 참조 연산자(*)를 이용하는 방법
strcpy(ptr_my_book -> author, "James"); // 화살표 연산자(->)를 이용하는 방법
my_book.price = 35000; // 구조체 변수를 이용한 직접 수정
printf("책 제목: %s / 저자: %s / 가격: %d",
my_book.title, my_book.author, my_book.price);
Output:
책 제목: Java / 저자: James / 가격: 35000
구조체를 함수의 인수로 전달하는 경우, 구조체의 복사본이 함수로 전달되게 된다.
원본이 보존된다는 장점이 있지만, 만약 구조체의 크기가 크다면 그만큼 시간과 메모리가 소요된다.
int equal(struct student s1, struct student s2) {
if (strcmp(s1.name, s2.name) == 0)
return 1;
else
return 0;
}
구조체의 포인터를 함수의 인수로 전달하는 경우, 함수가 구조체의 데이터를 변경하거나 데이터에 접근할 수 있게 된다. 구조체를 복사하여 사용하는 것보다 시간과 공간을 절약할 수 있으나, 원본에 접근하는 방식이므로 원본 훼손의 가능성이 있다.
int equal(struct student *p1, struct student *p2) {
if (strcmp(p1->name, p2->name) == 0)
return 1;
else
return 0;
}
이를 막기 위해 C 언어의 const 키워드를 사용하면 포인터가 가리키는 값을 변경하지 못하도록 할 수 있다.
struct Point {
int x;
int y;
};
struct Point point = {3, 5};
const struct Point *ptr = &point;