본문 바로가기
언어/C언어

[C 언어] 13. 구조체

by 천무지 2024. 4. 14.
반응형

구조체는 서로 다른 데이터 타입들을 함께 모아 놓은 것입니다.

각각의 원소들을 member라고 부릅니다.

 

구조체의 선언은 다음과 같이 합니다.

struct student {	 //struct student 타입 정의
    int id;
    char name[10];
};

struct는 키워드 이고 student는 구조체의 이름입니다.

구조체의 타입은 여기서 struct student 타입으로 정의한 것입니다.

 

구조체를 선언하고, 구조체 변수를 한번에 선언할 수 있습니다.

struct student {	 //sruct student 타입 정의 & 변수 선언 
    int id;	
    char name[10];
} a1, a2, a3;

구조체를 선언함과 동시에 struct student 타입의 a1, a2, a3라는 구조체 변수는 선언했습니다.

 

구조체 타입을 정의하지 않고 구조체 변수만 선언할 수도 있습니다.

struct {		 //struct 변수 선언
    int id;
    char name[10];
} a1, a2, a3;

구조체의 타입은 정의하지 않고 구조체 변수만 선언했습니다.

일회성으로 처음에만 선언이 가능하고, 나중에는 이 타입으로 구조체 변수를 선언할 수 없습니다.

 

구조체 선언의 사용 예시를 보겠습니다.

#include <stdio.h>
#include <string.h>
struct student {
	int id;
	char name[10];
};
void main(void) {
	struct student s1, s2, s3;
	s1.id = 123456;
	strcpy(s1.name, "trump");
	printf("id: %d, name: %s\n", s1.id, s1.name);
}

구조체는 자신의 멤버에 접근할 때 . 을 사용합니다.

 

구조체는 구조체 이름이 없는 구조체 변수 선언시 같은 멤버로 구성된 경우라도 서로 다른 타입으로 간주합니다.

따라서 다음과 같은 코드에서 a1과 b1은 타입이 다릅니다. 호환될 수도 없습니다.

#include <stdio.h>
#include <string.h>
struct {
	int id;
	char name[10];
} a1;
struct {
	int id;
	char name[10];
} b1;
void main(void) {
	a1.id = 123456;
	b1 = a1;		//incompatible types
	strcpy(a1.name, "trump");
	printf("id: %d, name: %s\n", a1.id, a1.name);
}

 

구조체 변수를 초기화하는 방법은 배열을 초기화 하는 방법과 유사합니다. brace({ })를 사용합니다.

단지 값만 순서와 크기에 맞게 주면 됩니다.

예시를 보겠습니다.

#include <stdio.h>
#include <string.h>
struct student {
	int id;
	char name[10];
};
void main(void) {
	struct student s1 = { 123455, "trump" };
	printf("id: %d, name: %s\n", s1.id, s1.name);
}

brace를 통해서 구조체 변수 s1를 초기화 해주었습니다. 실행결과 초기화가 잘 되었음을 알 수 있습니다.

 

만약 구조체의 타입이 같다면 구조체는 할당이 가능합니다.

코드를 보면서 알아보겠습니다.

#include <stdio.h>
struct student {
	int id;
	char name[10];
};
void main(void) {
	int i;
	struct student s1 = { 123456, "trump" };
	struct student s2 = s1;
	s1.id = 3333;
	printf("s1.x = %d, s1.y = %s\n", s1.id, s1.name);
	printf("s2.x = %d, s2.y = %s\n", s2.id, s2.name);
}

코드를 보면 구조체 변수 s1을 초기화해주었고 구조체 변수 s2에 s1을 할당했습니다.

또한 출력을 통해 할당이 제대로 되었는지 확인해봤습니다.

 

구조체는 구조체를 타입으로 하는 배열도 사용할 수 있습니다.

기존과 크게 달라진 점은 없습니다. 단지 타입이 구조체일 뿐입니다.

코드를 살펴보겠습니다.

#include <stdio.h>
struct point {
	int x;
	int y;
};
void main(void) {
	struct point a[3];
	int i;
	for (i = 0; i < 3; i++) {
		a[i].x = i * 2;
		a[i].y = i * 3;
	}
	for (i = 0; i < 3; i++) {
		printf("a[%d].x: %d, a[%d].y: %d\n", i, a[i].x, i, a[i].y);
	}
}

 

구조체를 타입으로 하는 배열을 선언하고 초기화하는 방법은 brace 안에 brace 로서 초기화 할 수 있습니다.

예시를 보겠습니다.

#include <stdio.h>

struct point {
	int x;
	int y;
};

void main(void) {
	struct point a[3] = { {0, 0}, {2, 3}, {4, 6} };
	int i;
	for (i = 0; i < 3; i++) {
		printf("a[%d].x: %d, a[%d].y: %d\n", i, a[i].x, i, a[i].y);
	}
}

각각의 brace 안의 brace가 각각의 구조체 배열 인덱스에 있는 멤버의 값을 순서대로 의미하게 됩니다.

초기화하는 방법이 과거에 살펴보았던 배열과 유사하다는 것을 알 수 있습니다.

 

구조체는 포인터로도 사용할 수 있습니다.

#include <stdio.h>
struct point {
	int x;
	int y;
};
void main(void) {
	struct point s1 = { 2, 3 };
	struct point s2 = { 4, 6 };
	struct point s3;
	struct point* ptr_s1 = &s1;
	struct point* ptr_s2 = &s2;
	s3.x = ptr_s1->x + ptr_s2->x;
	s3.y = ptr_s1->y + ptr_s2->y;
	printf("s1.x = %d, s1.y = %d\n", s1.x, s1.y);
	printf("s2.x = %d, s2.y = %d\n", s2.x, s2.y);
	printf("s3.x = %d, s3.y = %d\n", s3.x, s3.y);
}

기본 베이스 타입이 struct point로 하는 포인터 변수 ptr_s1, ptr_s2를 선언하였고 각각 s1의 주소와 s2의 주소를 할당했습니다.

이후 포인터 변수 ptr_s1과 prt_s2로 s1과 s2 의 멤버에 접근하여 s3의 멤버를 초기화하고 있습니다.

구조체가 포인터로 선언되었을 때 포인터가 가리키는 주소의 값에 접근하는 방법은 두 가지가 있습니다.

  • (*ptr_s1).x
  • ptr_s1->x

두 표현 모두 s1의 멤버 x의 값을 지칭합니다.

 

구조체는 자기 자신을 베이스 타입으로 하는 포인터 변수를 자기 자신의 멤버로 가질 수 있습니다.

#include <stdio.h>
struct point {
	int id;
	char name[10];
	struct point *ptr;
};
void main(void) {
	int i;
	struct point s1 = { 123456, "trump"};
	struct point s2 = { 222222, "obama" };
	s1.ptr = &s2;
	s2.ptr = &s1;
	printf("s1.x = %d, s1.y = %s\n", s1.id, s1.name);
	printf("s2.x = %d, s2.y = %s\n", s2.id, s2.name);
	printf("s2.x = %d, s2.y = %s\n", s1.ptr->id, s1.ptr->name);
}

구조체의 선언부를 보면 베이스 타입을 자기 자신으로 하는 포인터 변수가 선언되어있습니다.

구조체 변수 s1의 멤버중 포인터 변수에 구조체 변수 s2의 주소를 저장하고, 구조체 변수 s2의 멤버 중 포인터 변수에 구조체 s1의 주소를 저장했습니다.

따라서 s1은 자신의 포인터 변수 멤버를 통해서 s2의 멤버에 접근할 수 있고, 반대로 s2도 자신의 포인터 변수 멤버를 통해서 s1의 멤버에 접근할 수 있게 되었습니다.

코드의 마지막 줄을 보면 s1.ptr->id 와 s1.ptr->name을 통해서 s2의 멤버에 접근한 것을 알 수 있습니다.


Typedef

새로운 data type을 정의할 때 사용합니다.

typedef unsigned int UINT;
void main (void) {
    UINT a = 123;
    printf ("a: %d", a);
}

현재 unsinged int를 UNIT 이라는 타입으로 새롭게 정의했습니다. 길게 unsigned int 를 쓰는 대신  UNIT이라고 사용할 수 있습니다.

 

구조체에서도 이 typedef를 사용할 수 있습니다.

#include <stdio.h>
struct student {
    int id;
    char name[10];
};
typedef struct student std;
void main (void) {
	std s1 = { 123456, "trump" };
   	printf ("id: %d, name: %s\n", s1.id, s1.name);
}

typedef를 통해서 구조체 변수를 선언할 때 구조체의 타입인 struct student를 다 쓰는 대신 std만 써서 구조체 변수를 선언 할 수 있습니다.

쓰는 이유는 단순이 간단하게 쓰려고 하는 것입니다.

 

일반적이로 다음과 같이 정의해서 사용합니다.

typedef struct student {
    int id;
    char name[10];
} std;

void main (void) {
    std s1, s2, s3;
    s1.id = 123456;
    strcpy (s1.name, "trump");
    printf ("id: %d, name: %s", s1.id, s1.name);
}

구조체를 선언할 때 한 번에 typedef로 선언하고 마지막에 별칭을 써줍니다.

 

또한 여기서 구조체의 이름을 생략하고 별칭만 남겨놓을 수 있습니다.

typedef struct {	//no struct name
    int id;
    char name[10];
} std;
void main (void) {
    std s1, s2, s3;
    s1.id = 123456;
    strcpy (s1.name, "trump");
    printf ("id: %d, name: %s", s1.id, s1.name);
}

구조체의 이름은 모르지만 typedef로 별칭을 설정한다면 구조체 변수를 선언할 때 별칭으로 선언할 수 있다.

 

구조체는 인자로 전달할 수도 함수의 반환형으로도 설정할 수 있습니다.

#include <stdio.h>
typedef struct {
    int id;
    char name[10];
} STD;
STD inputSTD(void);
void showSTD(STD a1);
void main (void) {
	STD s1 = inputSTD();
	showSTD(s1);
}
STD inputSTD(void) {
	STD temp;
	scanf("%d %s", &temp.id, temp.name);
	return temp;
}
void showSTD(STD a1) {
	printf("id: %d, name: %s\n", a1.id, a1.name);
}

구조체 타입의 함수안에서 구조체의 정보를 받아서 구조체를 리턴하는 함수입니다.

또한 구조체를 매개변수로 받아 출력을 하는 함수도 있습니다.


Union

공용체는 기본적인 구조와 사용법은 구조체와 동일합니다.

공용체의 선언은 구조체의 struct 대신에 union을 사용합니다.

구조체 뱐수는 멤버들에게 개별적인 메모리를 할당합니다.

공용체는 크기가 가장 큰 멤버에게만 메모리 공간을 할당하고 이 공간을 공요체 내의 모든 멤버들이 공유합니다.

공용체는 여러 개의 멤버 중 단 하나만 사용 가능합니다.

 

공용체의 예시를 보겠습니다.

typedef struct{
	char name[20];
	char mail[20];
	int mobile;
} PROFESSOR;
typedef struct{
	char name[20];
	char major[10];
	int ID;
	float cgpa;
} STUDENT;
typedef struct {
	char type;
	union {
	       PROFESSOR prof;
	       STUDENT stu;
	} status;
} PERSON;
PERSON person;

구조체 PERSON은 학교에 등록된 사람을 저장하기 위한 데이터 타입으로써, 학생이나 교수를 저장할 수 있습니다.

status의 멤버 prof와 stu는 같은 메모리를 공유하므로 PERSON이 가리키는 사람은 교수거나 학생이어야 합니다.

PERSON은 저장된 데이터가 교수의 것인지 학생의 것인지 구분하기 위해서 type을 둡니다. 

PROFESSOR 구조체는 크기가 44바이트, STUDENT는 38바이트입니다.

 

이를 활용한 코드를 보겠습니다.

#include <stdio.h>
#include <string.h>
typedef struct {
	char name[20];
	char mail[20];
	int mobile;
} PROFESSOR;
typedef struct {
	char name[20];
	char major[10];
	int ID;
	float cgpa;
} STUDENT;
typedef struct {
	char type;
	union {
		PROFESSOR prof;
		STUDENT stu;
	}u;
}PERSON;

void printPerson(PERSON p);

void main() {
	PERSON person1, person2;
	person1.type = 1;
	strcpy(person1.u.prof.name, "james");
	strcpy(person1.u.prof.mail, "james@hanmail.net");
	person1.u.prof.mobile = 6055726;

	person2.type = 2;
	strcpy(person2.u.stu.name, "david");
	strcpy(person2.u.stu.major, "cs");
	person2.u.stu.ID = 20010123;
	person2.u.stu.cgpa = 0.55f;

	printPerson(person1);
	printPerson(person2);
}

void printPerson(PERSON p) {

	if (p.type == 1) {
		printf("-------------------------\n");
		printf("name: %s\n", p.u.prof.name);
		printf("mail: %s\n", p.u.prof.mail);
		printf("mobile: %d\n", p.u.prof.mobile);
	}
	else {
		printf("-------------------------\n");
		printf("name: %s\n", p.u.stu.name);
		printf("major: %s\n", p.u.stu.major);
		printf("id: %d\n", p.u.stu.ID);
		printf("cgpa: %f\n", p.u.stu.cgpa);
	}
}


Enum

열거형(Enumeration)의 정의 방법은 구조체, 공용체와 유사합니다.

열거형의 목적은 코드의 가독성 향상이다.

코드를 보면서 이해해보겠습니다.

#include <stdio.h>
//enum week { SUN, MON, TUE, WED, THU, FRI, SAT };
//enum week { SUN, MON, TUE=4, WED, THU, FRI, SAT };
//enum week { SUN, MON=4, TUE, WED=2, THU, FRI, SAT };
void main(void) {
	enum week temp[7];
	int i;
	temp[0] = SUN;
	temp[1] = MON;
	temp[2] = TUE;
	temp[3] = WED;
	temp[4] = THU;
	temp[5] = FRI;
	temp[6] = SAT;
	for (i = 0; i < 7; i++) {
		printf("%d\n", temp[i]);
	}
}

첫 번째 주석으로 입력이 되었다면 enum week에게 허용되는 값은 brace 안에 있는 값들만 허용되고 이외의 값은 가질 수 없습니다. 

각각의 값들은 모두 정수를 의미합니다. 앞에 있는 요소부터 차례대로 0, 1, 2...를 의미합니다.

두 번째 주석과 첫 번째 주석의 차이점이 있다면 중간에 TUE = 4라고 되어있는 것입니다.

값이 앞에서부터 순서대로 0부터 증가한다고 했는데 중간에 갑자기 4라고 한다면, 4이후부터는 5, 6, 7...이렇게 증가합니다.

세  번째 줄도 마찬가지입니다.

각각의 요소 자체가 값은 맞지만 실행하면 정수로 표현됩니다.

또한 enum은 베이스 타입으로 int형을 사용합니다.

어렵게 생각할 것도 없습니다.

실행기지에서 enum을 그냥 정수 자체로 봅니다.

범위 밖이여도 검사를 안하고 다른 enum변수 안에 있는 요소를 넣어도 문제를 검출하지 않습니다.

정말 숫자 그 자체로 봅니다.

enum은 그냥 type 안에 있는 값들이 그냥 integer에 매치되는 정도로 생각하면 됩니다.

 

다시 정리하면 다음과 같습니다.

  • 가독성을 위해서 정수형 상수를 할당하기 위해서 사용합니다.
  • 두 개의 요소가 같은 int 값을 가질 수 있습니다.
  • 요소를 초기화하지 않으면 0부터 1씩 증가하며 초기화됩니다.
  • 순서에 관계없이 임의의 값을 할당할 수 있습니다. 초기화 되지 않은 다음 요소는 +1이 됩니다.
  • 동일한 범위 scope 안에서 이름이 같은 요소의 존재할 수 없습니다.

 

반응형

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

[C 언어] 12. File  (0) 2024.04.14
[C 언어] 11. Input/Output Stream, String  (0) 2024.04.14
[C 언어] 10.포인터  (0) 2024.04.14
[C 언어] 9. 함수  (0) 2024.04.14
[C 언어] 8. 배열  (0) 2024.04.14