juuuding

CH23 구조체와 사용자 정의 자료형2 본문

C언어/윤성우의 열혈 C

CH23 구조체와 사용자 정의 자료형2

jiuuu 2023. 3. 28. 10:13

 1. 구조체의 정의와 typedef 선언

 

1. typedef 선언

typedef 선언은 기존에 존재하는 자료혀으이 이름에 새 이름을 부여하는 것을 목적으로 하는 선언이다. 

typedef int INT    // int의 또 다른 이름 INT 부여

즉, 자료형의 이름 int에 INT라는 이름을 추가로 붙여준다는 의미다. 따라서 위의 선언 이후로는 다음 형태로 int형 변수를 선언할 수 있다.

INT num;     //int num; 과 동일한 선언

그리고 typedef로 정의되는 자료형의 이름은 대문자로 시작하는 것이 관례이다.

 

 

2. 구조체의 정의와 typedef 선언

대부분 구조체 이름을 대상으로 struct 선언의 생략을 위한 typedef 선언이 등장한다. 

typedef struct point {
	int xpos;
	int ypos;
} Point;

int main(void) {

	Point pos = { 10,20 };
}

참고로 typedef 선언이 추가되었다고 해서 struct 선언을 통한 구조체 변수 선언이 불가능한 것은 아니다. 즉 위의 코드에서

struct point pos2;      //struct 선언을 추가한 형태의 변수 선언

이러한 선언도 가능하다. 

 

 

3. 구조체 이름의 생략

typedef struct point {
	int xpos;
	int ypos;
} Point;


// 두 코드는 동일하다

typedef struct {
    int xpos;
    int ypos;
} Point

typedef로 인해 새로 정의가 되면 기존 구조체 이름 point는 사실상 별 의미를 갖지 않는다. 따라서 위와 같이 구조체의 이름을 생략하는 것도 가능하다.  

 

 

 

 2. 함수로의 구조체 변수 전달과 반환

 

1. 함수의 인자로 전달되고, return 문에 의해 반환되는 구조체 변수

 다음과 같이 함수의 인자로 구조체 변수가 전달될 수 있고, 매개변수로도 구조체 변수가 올 수 있다. 

#define CRT_SECURE_NO_WAWRNINGS
#include <stdio.h>

typedef struct point {
	int xpos;
	int ypos;
} Point;


void Showposition(Point pos) {
	printf("[%d, %d]", pos.xpos, pos.ypos);
}
Point GetCurrentPosition(void) {
	Point cen;
	printf("central pos 입력:");
	scanf("%d", &cen.xpos);
	scanf("%d", &cen.ypos);

	return cen;

}
int main(void) {

	Point curPos = GetCurrentPosition();
	Showposition(curPos);
	return 0;
}

 

추가로 구조체 변수를 대상으로 하는 Call-by-reference도 가능하다.

#define CRT_SECURE_NO_WAWRNINGS
#include <stdio.h>

typedef struct point {
	int xpos;
	int ypos;
} Point;


void OrgSymTrans(Point *ptr) {
	ptr->xpos = (ptr->xpos) * (-1);
	ptr->ypos = (ptr->ypos) * (-1);
}
void ShowPosition(Point *ptr) {
	printf("[%d, %d]", ptr->xpos, ptr->ypos);
}
int main(void) {

	Point dot = {7,-5};
	OrgSymTrans(&dot);
	ShowPosition(&dot);
	OrgSymTrans(&dot);
	ShowPosition(&dot);
	return 0;
}

 

 

2. 구조체 변수를 대상으로 가능한 연산

 구조체 변수를 대상으로는 매우 제한된 형태의 연산만 허용된다. 허용되는 가장 대표적인 연산은 대입연산이며, 그 외로 &연산이나 sizeof 연산만 허용된다. 대입연산은 "Point pos1 ={1,2}; Point pos2; pos2=pos1;"를 하면 그 결과로 멤버 대 멤버의 복사가 이뤄진다. 구조체 변수를 대상으로 덧셈, 뺄셈도 가능하긴 하지만 구조체 안에 배열, 포인터 변수도 존재할 수 있기 때문에 이를 정형화하기엔 무리가 있다. "pos1.xpos-pos2.xpos"와 같이 함수의 정의를 통해서 덧셈, 뺄셈의 결과를 프로그래머가 직접 정의해야 한다. 

 

 

 

 3. 구조체의 유용함에 대한 논의와 중첩 구조서식 코드 작성

 

1. 구조체를 정의하는 이유

 "구조체를 통해서 연관 있는 데이터를 하나로 묶을 수 있는 자료형을 정의하면, 데이터의 표현 및 관리가 용이해지고, 그만큼 합리적인 코드를 작성할 수 있게 된다."

 

 

2. 중첩된 구조체의 정의와 변수의 선언

#define CRT_SECURE_NO_WAWRNINGS
#include <stdio.h>

typedef struct point {
	int xpos;
	int ypos;
} Point;

typedef struct circle {
	Point cen;
	double rad;
}Circle;

void ShowCircleInfo(Circle* ptr) {
	printf("[%d, %d]\n", (ptr->cen).xpos, (ptr->cen).ypos );
	printf("radius: %g", ptr->rad);
}

int main(void) {
	Circle c1 = { {1,2},3.5 };
	Circle c2 = {2, 4, 3.9};
	ShowCircleInfo(&c1);
	ShowCircleInfo(&c2);

	return 0;
}

참고로 구조체 변수를 초기화할 때에도 초기화하지 않은 일부 멤버에대해서는 0으로 초기화가 진행된다.

 

 

 

 4. 공용체(Union Type)의 정의와 의미

 

1. 구조체 vs 공용체

 구조체 변수에서는 구조체를 구성하는 멤버는 각각 주소 할당이 된다. 공용체 변수에서는 멤버에 주소가 각각 할당되지 않고, 그 중 가장 크기가 큰 변수만 하나 할당되어 이를 공유한다. 

#define CRT_SECURE_NO_WAWRNINGS
#include <stdio.h>

typedef union ubox {
	int mem1;
	int mem2;
	double mem3;
}UBox;

int main(void) {
	UBox ubx;
	ubx.mem1 = 20;
	printf("%d\n", ubx.mem2);

	ubx.mem3 = 7.15;
	printf("%d\n", ubx.mem1);
	printf("%d\n", ubx.mem2);
	printf("%g", ubx.mem3);
}

/*
20
-1717986918
-1717986918
7.15
*/

이 실행결과로 공용체의 멤버들이 메모리 공간을 공유하고 있음을 확인할 수 있다. 

 

 

2. 공용체의 유용함은 다양한 접근방식을 제공하는데 있다.

 공용체의 유용함은 '하나의 메모리 공간을 둘 이상의 방식으로 접근할 수 있다.'는 것으로 정리할 수 있다.

"int형 정수 하나 입력 받기 -> 정수의 상위 2바이트와 하위 2바이트 값을 양의 정수로 출력 -> 상위 1바이트와 하위 1바이트에 저장된 값의 아스키 문자 출력" 과 같은 상황에서 공용체의 유용함을 느낄 수 있다. 

#define CRT_SECURE_NO_WAWRNINGS
#include <stdio.h>

typedef struct dbshort {
	unsigned short upper;
	unsigned short lower;
}DBShort;

typedef union rdbuf {
	int iBuf;
	char bBuf[4];
	DBShort sBuf;
}RDBuf;

int main(void) {
	RDBuf buf;
	printf("정수 입력: ");
	scanf("%d", &buf.iBuf);

	printf("상위 2바이트: %d\n", buf.sBuf.upper);
	printf("하위 2바이트: %d\n", buf.sBuf.lower);
	printf("상위 1바이트 아스키 코드: %c\n", buf.bBuf[0]);
	printf("하위 1바이트 아스키 코드: %c\n", buf.bBuf[3]);

	return 0;
}

위의 코드와 같이 적절한 정의를 통해서 4바이트 메모리 공간을 2바이트씩, 그리고 1바이트씩 접근할 수 있다.

 

 

 

 5. 열거형(Enumerated Type)

 

1. 열거형의 정의와 변수의 선언

 구조체와 공용체의 경우에는 자료형의 선언을 통해서 멤버에 저장할 값의 유형을 결정했다. 이와 달리 열거형은 저장이 가능한 값을 정수의 형태로 결정한다. 열거형은 변수에 저장이 가능한 값들을 열거하여 정의한다고 해서 '열거형'이라고 한다. 

#define CRT_SECURE_NO_WAWRNINGS
#include <stdio.h>

typedef enum syllable {
	Do = 1, Re = 2, Mi = 3, Fa = 4, So = 5, La = 6, Ti = 7
}Syllable;

void Sound(Syllable sy) {
	switch (sy)
	{
	case Do:
		puts("도입니다"); return;
	case Re:
		puts("레입니다"); return;
	case Mi:
		puts("미입니다"); return;
	case Fa:
		puts("파입니다"); return;
	case So:
		puts("솔입니다"); return;
	case La:
		puts("라입니다"); return;
	case Ti:
		puts("시입니다"); return;
	}
}

int main(void) {
	Syllable tone;
	for (tone = Do; tone <= Ti; tone++) {
		Sound(tone);
	}
	return 0;
}

위 코드에서 Do=1은 "Do를 정수1을 의미하는 상수로 정의한다. 그리고 이 값은 syllable형 변수에 저장이 가능하다."는 의미를 가진다. 그리고 for문은 for(tone=1;tone<=7; tone+=1)으로 대신해도 결과는 동일하다. 

 

 

2. 열거형 상수의 값이 결정되는 방식

enum color {RED, BLUE, WHITE, BLACK}; 여기서는 상수의 이름만 정의되었을 뿐 상수의 값은 선언되어 있지 않다. 이러한 경우 열거형 상수의 값은 0부터 시작해서 1씩 증가하는 형태로 결정이 된다.

만약 enum color {RED=0, BLUE, WHITE=6, BLACK};이라 선언되었다면, 이는 enum color {RED=0, BLUE=1, WHITE=6, BLACK=7};과 같다.

 

 

3. 열거형의 유용함은 이름있는 상수의 정의를 통한 의미의 부여에 있다.

 열거형의 유용함은 둘 이상의 연관이 있는 이름을 상수로 선언함으로써 프로그램의 가독성을 높이는데 있다. 위의 코드를Do 대신 1, Re 대신 2를 사용했더라면 이것이 음계와 관련이 있다는 사실을 파악하기 힘들다. 따라서 다음과 같이 자료형의 이름을 생략한 형태로 열거형을 정의할 수도 있다.

enum {Do = 1, Re = 2, Mi = 3, Fa = 4, So = 5, La = 6, Ti = 7};

위와 같이 정의가 되어도 각각 열거형 상수로써 의미를 지닌다.