본문 바로가기
C언어

C언어 구조체

by FraisGout 2020. 7. 5.

구조체란?

구조체(structure type)란 사용자가 C언어의 기본 타입을 가지고 새롭게 정의할 수 있는 사용자 정의 타입입니다.

 

구조체는 기본 타입만으로는 나타낼 수 없는 복잡한 데이터를 표현할 수 있습니다.

 

 

 

배열이 같은 타입의 변수 집합이라고 한다면, 구조체는 다양한 타입의 변수 집합을 하나의 타입으로 나타낸 것입니다.

 

이때 구조체를 구성하는 변수를 구조체의 멤버(member) 또는 멤버 변수(member variable)라고 합니다.

 

구조체의 정의와 선언

C언어에서 구조체는 struct 키워드를 사용하여 다음과 같이 정의합니다.

 

문법

struct 구조체이름

 

{

 

멤버변수1의타입 멤버변수1의이름;

 

멤버변수2의타입 멤버변수2의이름;

 

...

};

 

struct라는 키워드를 사용하여 구조체의 시작을 알리고, 구조체 이름인 book으로 구조체를 정의하고 있습니다.

 

중괄호 사이에 char titile[30], char author[30], int price와 같은 변수들은 book의 멤버 변수들 입니다.

 

마지막 세이콜론은 구조체 정의를 종료한다는 의미입니다. 이렇게 정의된 book 구조체는 사용자 정의 자료형이라고 합니다.

 

이렇게 정의된 구조체 타입은 다음과 같이 구조체 변수로 선언하여 사용할 수 있습니다.

 

문법

struct 구조체이름 구조체변수이름;

 

예제

struct book my_book;

 

 

 

또한, 구조체의 정의와 구조체 변수의 선언을 동시에 할 수도 있습니다.

 

문법

struct 구조체이름

 

{

 

멤버변수1의타입 멤버변수1의이름;

 

멤버변수2의타입 멤버변수2의이름;

 

...

 

} 구조체변수이름;

 

예제

struct book

 

{

 

char title[30];

 

char author[30];

 

int price;

 

} my_book;

 

 

typedef 키워드

C언어의 typedef 키워드는 이미 존재하는 타입에 새로운 이름을 붙일 때 사용합니다.

 

구조체 변수를 선언하거나 사용할 때에는 매번 struct 키워드를 사용하여 구조체임을 명시해야 합니다.

 

하지만 typedef 키워드를 사용하여 구조체에 새로운 이름을 선언하면 매번 struct 키워드를 사용하지 않아도 됩니다.

 

 

 

typedef 키워드를 사용하여 새로운 이름을 선언하는 방법은 다음과 같습니다.

 

문법

typedef struct 구조체이름 구조체의새로운이름;

 

예제

typedef struct book TEXTBOOK;

 

 

 

또한, 구조체의 정의와 typedef 선언을 동시에 할 수도 있습니다.

 

문법

typedef struct (구조체이름)

 

{

 

멤버변수1의타입 멤버변수1의이름;

 

멤버변수2의타입 멤버변수2의이름;

 

...

 

} 구조체의새로운이름;

 

예제

typedef struct {

 

char title[30];

 

char author[30];

 

int price;

 

} TEXTBOOK;

 

 

구조체의 정의와 typedef 선언을 동시에 할 때에는 구조체의 이름을 생략할 수 있습니다.

구조체 멤버로의 접근 방법

배열에서는 인덱스를 이용하여 배열 요소에 접근할 수 있습니다.

 

하지만 구조체에서 구조체 멤버로 접근하려고 할 때는 멤버 연산자(.)를 사용해야 합니다.

 

 

 

구조체에서 구조체 멤버로의 접근 방법은 다음과 같습니다.

 

문법

구조체변수이름.멤버변수이름

 

예제

my_book.author

 

 

구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같습니다.

구조체 변수의 초기화

구조체 변수를 초기화할 때에는 멤버 연산자(.)와 중괄호({})를 사용합니다.

 

 

 

구조체 변수의 초기화 방법은 다음과 같습니다.

 

문법

구조체변수이름 = {.멤버변수1이름 = 초깃값, .멤버변수2이름 = 초깃값, ...};

 

예제

my_book = {.title = "HTMLCSS", .author = "홍길동", .price = 28000};

 

 

 

이 방법을 사용하면 원하는 멤버 변수만을 초기화할 수 있습니다.

 

이때 멤버 변수가 정의된 순서와 초기화하는 순서는 아무런 상관이 없으며, 초기화하지 않은 멤버 변수는 0으로 초기화됩니다.

 

 

 

또한, 배열의 초기화와 같은 방법으로 구조체 변수를 초기화할 수도 있습니다.

 

문법

구조체변수이름 = {멤버변수1의초깃값, 멤버변수2의초깃값, ...};

 

예제

my_book = {"HTMLCSS", "홍길동", 28000};

 

 

 

이때 구조체 정의에서 멤버 변수가 정의된 순서에 따라 차례대로 초깃값이 설정되며, 나머지 멤버 변수는 0으로 초기화됩니다.

 

 

 

다음 예제는 앞서 살펴본 두 가지 방법을 사용하여 각각 구조체 변수를 초기화하는 예제입니다.

 

예제

#include <stdio.h>

 

 

 

struct book

 

{

 

char title[30];

 

char author[30];

 

int price;

 

};

 

 

 

int main(void)

 

{

 

struct book my_book = {"HTMLCSS", "홍길동", 28000};

 

struct book java_book = {.title = "Java language", .price = 30000};

 

 

 

printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",

 

my_book.title, my_book.author, my_book.price);

 

printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",

 

java_book.title, java_book.author, java_book.price);

 

return 0;

 

}

 

다음 예제는 앞선 예제의 구조체에 typedef 키워드를 사용하여 새로운 이름을 선언한 후 사용하는 예제입니다.

 

예제

#include <stdio.h>

 

 

 

typedef struct

 

{

 

char title[30];

 

char author[30];

 

int price;

 

} TEXTBOOK;

 

 

 

int main(void)

 

{

 

TEXTBOOK my_book = {"HTMLCSS", "홍길동", 28000};

 

TEXTBOOK java_book = {.title = "Java language", .price = 30000};

 

 

 

printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",

 

my_book.title, my_book.author, my_book.price);

 

printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",

 

java_book.title, java_book.author, java_book.price);

 

return 0;

 

}

 

위 예제의 실행 결과처럼 typedef 키워드를 사용하여 구조체에 새로운 이름을 선언한 후 사용해도 structr 키워드를 그대로 사용한 것과 같은 결과를 얻을 수 있습니다.

 

구조체 배열 선언

C 언어에서 배열의 요소가 될 수 있는 타입에는 제한이 없으므로, 구조체 역시 배열의 한 요소가 될 수 있습니다.

 

이러한 구조체 배열을 선언하는 방법은 다른 타입의 배열을 선언하는 방법과 같습니다..

 

또한, 구조체 배열에서 각 배열 요소로 접근하는 방법도 일반 배열의 접근 방법과 완전히 같습니다.

 

 

 

다음 예제는 구조체 배열의 선언 및 초기화를 보여주는 예제입니다.

 

예제

struct book text_book[3] =

 

{

 

{"국어", "홍길동", 15000},

 

{"영어", "이순신", 18000},

 

{"수학1", "강감찬", 10000}

 

};

 

 

 

puts("각 교과서의 이름은 다음과 같습니다.");

 

printf("%s, %s, %s\n", text_book[0].title, text_book[1].title, text_book[2].title);

 

위의 예제처럼 구조체 배열은 2차원 배열의 초기화 방법과 똑같은 방법으로 초기화할 수 있습니다.

 

또한, 멤버 연산자(.)를 사용하여 각 배열 요소의 멤버에 접근할 수 있습니다.

 

 

구조체를 가리키는 포인터

구조체 변수를 가리키는 구조체 포인터는 다음과 같이 선언합니다.

 

문법

struct 구조체이름* 구조체포인터이름;

 

예제

struct book* ptr_my_book;

 

 

 

배열의 경우와는 달리 구조체의 이름은 구조체를 가리키는 주소가 아닙니다.

 

따라서 포인터에 할당할 때에는 반드시 주소 연산자(&)를 사용해야 합니다.

 

 

 

구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법에는 다음과 같이 두 가지 방법이 있습니다.

 

 

 

1. 참조 연산자(*)를 이용하는 방법

 

2. 화살표 연산자(->)를 이용하는 방법

 

 

 

참조 연산자를 이용하는 방법은 다음과 같습니다.

 

문법

(*구조체포인터).멤버변수이름

 

예제

(*ptr_my_book).author

 

 

 

참조 연산자(*)는 멤버 연산자(.)보다 연산자 우선순위가 낮으므로 반드시 괄호(())를 사용해야 합니다.

 

 

 

구조체의 멤버에 접근하기 위해서 화살표 연산자(->)를 사용할 수도 있습니다.

 

화살표 연산자의 앞쪽에는 구조체 포인터를, 뒤쪽에는 접근하고자 하는 구조체의 멤버 변수 이름을 사용하면 됩니다.

 

문법

구조체포인터 -> 멤버변수이름

 

예제

ptr_my_book -> author

 

 

 

위의 두 가지 방법은 완전히 같은 동작을 하며, 일반적으로 화살표 연산자가 좀 더 많이 사용됩니다.

 

예제

struct book my_book = {"C언어 완전 해부", "홍길동", 35000};

 

struct book* ptr_my_book; // 구조체 포인트 선언

 

 

 

ptr_my_book = &my_book;

 

 

 

strcpy((*ptr_my_book).title, "C언어 마스터"); // 참조 연산자(*)를 이용하는 방법

 

strcpy(ptr_my_book->author, "이순신"); // 화살표 연산자(->)를 이용하는 방법

 

my_book.price = 32000; // 구조체 변수을 이용한 직접 수정

 

 

 

printf("책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",

 

my_book.title, my_book.author, my_book.price);

 

 

 

함수와 구조체

C언어에서는 함수를 호출할 때 전달되는 인수나, 함수가 종료될 때 반환되는 반환값으로 구조체를 사용할 수 있습니다.

 

그 방식은 기본 타입과 완전히 같으며, 구조체를 가리키는 포인터나 구조체의 한 멤버 변수만을 사용할 수도 있습니다.

 

 

 

다음 예제는 구조체의 멤버 변수를 함수의 인수로 전달하는 예제입니다.

 

예제

typedef struct

 

{

 

int savings;

 

int loan;

 

} PROP;

 

 

 

int main(void)

 

{

 

int hong_prop;

 

PROP hong = {10000000, 4000000};

 

 

 

hong_prop = calcProperty(hong.savings, hong.loan); // 구조체의 멤버 변수를 함수의 인수로 전달함

 

 

 

printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n",

 

hong.savings, hong.loan, hong_prop);

 

return 0;

 

}

 

 

위와 같이 구조체를 인수로 전달하는 방식은 함수가 원본 구조체의 복사본을 가지고 작업하므로 안전하다는 장점을 가집니다.

 

 

 

다음 예제는 함수의 인수로 구조체의 주소를 직접 전달하는 예제입니다.

 

예제

int hong_prop;

 

PROP hong = {10000000, 4000000};

 

 

 

hong_prop = calcProperty(&hong); // 구조체의 주소를 함수의 인수로 전달함.

 

printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", hong.savings, hong.loan, hong_prop);

 

 

위와 같이 구조체 포인터를 인수로 전달하는 방식은 구조체의 복사본이 아닌 주소 하나만을 전달하므로 처리가 빠릅니다.

 

하지만 호출된 함수에서 원본 구조체에 직접 접근하므로 원본 데이터의 보호 측면에서는 매우 위험합니다.

 

 

 

따라서 다음 예제의 calcProperty() 함수처럼 const 키워드를 사용하여 함수에 전달된 인수를 함수 내에서는 직접 수정할 수 없도록 하는 것이 좋습니다.

 

예제

PROP prop;

 

int hong_prop;

 

 

 

prop = initProperty();

 

hong_prop = calcProperty(&prop);

 

 

 

printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", prop.savings, prop.loan, hong_prop);

 

 

위의 예제에서 initProperty() 함수는 반환값으로 구조체를 직접 반환합니다.

 

기본적으로 C언어의 함수는 한 번에 하나의 데이터만을 반환할 수 있습니다.

 

하지만 이렇게 구조체를 사용하면 여러 개의 데이터를 한 번에 반환할 수 있습니다.

 

중첩된 구조체

C언어에서는 구조체를 정의할 때 멤버 변수로 또 다른 구조체를 포함할 수 있습니다.

 

예제

struct name

 

{

 

char first[30];

 

char last[30];

 

};

 

struct friends

 

{

 

struct name friend_name;

 

char address[30];

 

char job[30];

 

};

 

 

 

int main(void)

 

{

 

struct friends hong =

 

{

 

{ "길동", "" },

 

"서울시 강남구 역삼동",

 

"학생"

 

};

 

 

 

printf("%s\n\n", hong.address);

 

printf("%s%s에게,\n", hong.friend_name.last, hong.friend_name.first);

 

printf("그동안 잘 지냈니? 아직 %s이지?\n", hong.job);

 

puts("공부 잘 하고, 다음에 꼭 한번 보자.\n잘 지내^^");

 

return 0;

 

}

 

 

위의 예제에서 friends 구조체는 또 다른 구조체인 name 구조체를 멤버 변수로 포함하고 있습니다.

 

구조체의 크기

일반적으로 구조체의 크기는 멤버 변수들의 크기에 따라 결정됩니다.

 

하지만 구조체의 크기가 언제나 멤버 변수들의 크기 총합과 일치하는 것은 아닙니다.

 

예제

typedef struct

 

{

 

char a;

 

int b;

 

double c;

 

} TYPESIZE;

 

 

 

int main(void)

 

{

 

puts("구조체 TypeSize의 각 멤버의 크기는 다음과 같습니다.");

 

printf("%d %d %d\n", sizeof(char), sizeof(int), sizeof(double));

 

 

 

puts("구조체 TypeSize의 크기는 다음과 같습니다.");

 

printf("%d\n", sizeof(TYPESIZE));

 

return 0;

 

}

 

 

위의 예제에서 구조체 멤버 변수의 크기는 각각 1, 4, 8바이트입니다.

 

하지만 구조체의 크기는 멤버 변수들의 크기 총합인 13바이트가 아니라 16바이트가 됩니다.

 

바이트 패딩(byte padding)

구조체를 메모리에 할당할 때 컴파일러는 프로그램의 속도 향상을 위해 바이트 패딩(byte padding)이라는 규칙을 이용합니다.

 

구조체는 다양한 크기의 타입을 멤버 변수로 가질 수 있는 타입입니다.

 

하지만 컴파일러는 메모리의 접근을 쉽게 하기 위해 크기가 가장 큰 멤버 변수를 기준으로 모든 멤버 변수의 메모리 크기를 맞추게 됩니다.

 

이것을 바이트 패딩이라고 하며, 이때 추가되는 바이트를 패딩 바이트(padding byte)라고 합니다.

 

앞선 예제에서는 크기가 가장 큰 double형 타입의 크기인 8바이트가 기준이 됩니다.

 

맨 처음 char형 멤버 변수를 위해 8바이트가 할당되며, 할당되는 1바이트를 제외한 7바이트가 남게 됩니다.

 

그다음 int형 멤버 변수는 남은 7바이트보다 작으므로, 그대로 7바이트 중 4바이트를 할당하고 3바이트가 남게 됩니다.

 

마지막 double형 멤버 변수는 8바이트인데 남은 공간은 3바이트뿐이므로 다시 8바이트를 할당받습니다.

 

따라서 이 구조체의 크기는 총 16바이트가 되며, 그중에서 패딩 바이트(padding byte)3바이트가 됩니다.

 

 

공용체

공용체(union)union 키워드를 사용하여 선언하며, 한 가지를 제외한 모든 면에서 구조체와 같습니다.

 

바로 모든 멤버 변수가 하나의 메모리 공간을 공유한다는 점만이 다릅니다.

 

모든 멤버 변수가 같은 메모리를 공유하므로, 공용체는 한 번에 하나의 멤버 변수밖에 사용할 수 없습니다.

 

공용체는 순서가 규칙적이지 않고, 미리 알 수 없는 다양한 타입의 데이터를 저장할 수 있도록 설계된 타입입니다.

 

이러한 공용체는 크기가 가장 큰 멤버 변수의 크기로 메모리를 할당받습니다.

 

따라서 공용체 배열을 사용하면, 같은 크기로 구성된 배열 요소에 다양한 크기의 데이터를 저장할 수 있습니다.

 

 

 

다음 예제는 공용체의 멤버 변수를 단 하나만 초기화해도, 나머지 멤버 변수들이 모두 같은 데이터를 공유한다는 것을 보여주는 예제입니다.

 

예제

typedef union

 

{

 

unsigned char a;

 

unsigned short b;

 

unsigned int c;

 

} SHAREDATA;

 

 

 

int main(void)

 

{

 

SHAREDATA var;

 

var.c = 0x12345678;

 

 

 

printf("%x\n", var.a);

 

printf("%x\n", var.b);

 

printf("%x\n", var.c);

 

return 0;

 

}

 

 

공용체에 저장된 값의 의미는 값을 저장할 때 공용체의 어떤 멤버 변수를 사용했는지에 따라 전혀 달리 해석됩니다.

 

따라서 공용체의 어떤 멤버 변수를 사용하여 저장했는지를 별도로 저장하여, 접근할 때에도 같은 멤버 변수를 사용해야 합니다.

 

열거체

열거체(enumerated types)는 새로운 타입을 선언하면서, 동시에 해당 타입이 가질 수 있는 정수형 상숫값도 같이 명시할 수 있는 타입입니다.

 

이러한 열거체를 이용하면 프로그램의 가독성이 높아지고, 변수가 지니는 값에 의미를 부여할 수도 있게 됩니다.

 

 

 

예제

enum Weather {SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30};

 

int main(void)

 

{

 

enum Weather wt;

 

wt = SUNNY;

 

 

 

switch (wt)

 

{

 

case SUNNY:

 

puts("오늘은 햇볕이 쨍쨍!");

 

break;

 

case CLOUD:

 

puts("비가 올락말락하네요!");

 

break;

 

case RAIN:

 

puts("비가 내려요.. 우산 챙기세요!");

 

break;

 

case SNOW:

 

puts("오늘은 눈싸움하는 날!");

 

break;

 

default: puts("도대체 무슨 날씨인건가요!!!");

 

}

 

 

 

puts("각각의 열거체에 해당하는 정수값은 다음과 같습니다.");

 

printf("%d %d %d %d\n", SUNNY, CLOUD, RAIN, SNOW);

 

return 0;

 

}

 

C언어에서 열거체는 enum 키워드를 사용하여 선언합니다.

 

위의 예제처럼 사용자가 별도로 각 멤버에 해당하는 상숫값을 명시할 수 있습니다.

 

이때 상숫값을 따로 명시하지 않으면 0부터 시작되며, 그 다음 멤버의 값은 바로 앞 멤버의 값보다 1만큼 증가되며 정의됩니다.

 

 

 

예제

enum Days {MON, TUE, WED, THU, FRI, SAT, SUN};

 

int main(void)

 

{

 

enum Days today;

 

today = SAT;

 

 

 

if (today >= SAT && today <= SUN)

 

{

 

puts("오늘은 주말이네요~ 주말에도 열심히 공부하는 여러분은 최고에요!");

 

}

 

else

 

{

 

printf("주말까지 %d일 남았어요~ 조금만 더 힘내자구요!", 5 - today);

 

}

 

 

 

puts("각각의 열거체에 해당하는 정수값은 다음과 같습니다.");

 

printf("%d %d %d %d %d %d %d\n", MON, TUE, WED, THU, FRI, SAT, SUN);

 

return 0;

 

}

 

 

'C언어' 카테고리의 다른 글

C언어 선행처리  (0) 2020.07.05
C언어 입력 & 출력  (0) 2020.07.05
C언어 문자  (0) 2020.07.05
C언어 메모리  (0) 2020.07.05
C언어 포인터 & 배열  (0) 2020.07.05

댓글