본문 바로가기
휴지통/C 프로그래밍

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

by 신재권 2021. 1. 22.

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

구조체 변수를 선언할 때에는 무조건 struct 선언을 추가해야 한다. 하지만 이것이 여간 귀찬흔게 아니다. int형 변수 num을 선언할 때 그저 int num이라 선언하듯이, person형 구조체 변수 man을 선언할 때에도 struct 선언 없이 그저 person man이라 하고 싶다. 그렇다면 구조체를 정의한 후에 typedef 선언을 추가해야 한다.

- typedef 선언

typedef 선언은 기존에 존재하는 자료형의 이름에 새 이름을 부여하는 것을 목적으로 하는 선언이다. 예를 들어서 다음과 같이 typdef선언을 하게 되면

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

이로 인해 다음의 뜻을 컴파일러에 전달하게 된다.

자료형 이름 int에 INT라는 이름을 추가로 붙여줍니다.

따라서 위의 선언 이후 다음의 형태로 int형 변수를 선언할 수 있다 .물론 키워드 int를 사용하는 것과 아무런 차이도 없다.

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

그리고 다음과 같이 INT라는 이름으로 int형 포인터 변수도 선언할 수 있다.

INT *ptr; // int *ptr 과 동일한 선언

typedef 선언에 있어서 새로운 이름의 부여는 가장 마지막에 등장하는 단어를 중심으로 이뤄진다. 즉 다음의 형태로 typedef가 선언되면

typedef name1 name2 name3;

가장 마지막에 등장한 name3가 'name1 name2'에 부여된 새로운 이름이 되는 것이다.

typedef선언을 통해서 복잡한 유형의 자료형 선언을 매우 간결히 처리할 수 잇따. 그래서 대부분의 프로그램 개발에 있어서 적지 않은 typedef선언이 항상 포함된다. 그리고 typedef로 정의되는 자료형의 이름은 대문자로 시작하는 것이 관례이다. 그래야 기본 자료형의 이름과 typedef로 새로이 정의된 자료형의 이름을 구분할 수 있기 때문이다.

-구조체의 정의와 typedef 선언

구조체 변수의 선언에 있어서 struct 선언을 생략할수 있다. 예를 들어 다음과 같이 구조체가 정의되었다고 가정한다.

struct point {

int xpos;

int ypos;

};

이 상황에서는 다음과 같이 구조체 변수를 써야한다.

struct point pos;

그런데 다음과 같이 typedef 선언이 이뤄지고 나면,

typedef struct point Point; //struct point에 Point라는 이름을 부여

이는 struct point를 대신할 수 있도록 Point라는 이름을 정의한 셈이니, 이 후로는 다음과 같이 struct 선언을 생략한 형태로 구조체 변수를 선언할 수 있다.

Point pos;

이렇듯 struct 선언과 구조체의 이름을 대상으로 typedef선언이 가능하다.

사실 모든 구조체의 이름을 대상으로 struct선언의 생략을 위한 typedef 선언이 등장한다. 때문에 다음과 같이 구조체의 정의와 typedef의 선언을 한데 묶을 수 있고, 또 이렇게 선언하는 것이 보다 일반적이다.

typedef struct point{

int xpos;

int ypos;

}Point;

위의 선언은 다음의 정의와 선언을 한데 묶은 것이다.

struct point{

int xpos;

int ypos;

};

typedef struct point Point;

참고로 typedef선언이 추가되었다고 해서 struct 선언을 통한 구조체 변수의 선언이 불가능 한것은 아니다. 즉 다음과 같이 구조체가 정의되면

typedef struct point{

int xpos;

int ypos;

}Point;

다음 두 가지 방식으로 구조체 변수를 선언할 수 있다.

Point pos1; //typdef선언을 이용한 변수의 선언

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

구조체의 이름: typedef로 정의된 이름을 해당 구조체를 가리키는 것이 일반적이다.

-구조체의 이름 생략

typedef struct person{

char name[20];

char phoneNum[20];

int age;

}person;

이렇게 정의가 되면 , 구조체의 이름 person은 사실상 별 의미를 갖기 않게된다. 구조체 변수를 선언할 때도 typedef에 의해 정의된 이름 Person을 사용하기 때문이다. 따라서 다음과 같이 구조체의 이름을 생략하는 것도 가능하다.

typedef struct{

char name[20];

char phoneNum[20];

int age;

}Person;

단 다음과 같이 구조체의 이름을 생략하면 다음의 형태로는 구조체 변수를 선언할 수 없게 된다. 하지만 typedef선언 까지 한 마당에 struct선언을 추가하면서 변수를 선언할 일은 거의 없 다.

struct person man; //불가능한 선언

프로그래머의 프로그래밍 성향에 따라서 이렇듯 구조체의 이름을 생략하는 경우도 흔하니, 구조체의 이름이 생략 가능함을 잊으면 안된다.

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

구조체 변수를 함수의 인자로 전달하거나 함수 내에서 return 문을 통해서 구조체 변수를 반환하는 경우에 발생하는 모든 현상은 int형 변수를 인자로 전달하거나 int형 변수를 반환하는 경우에 발생하는 현상과 완전히 동일하다.

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

다음 코드를 실행하게 되면 , 인자로 전달되는 변수의 값 은 매개변수에 복사가 된다.

void SimpleFunc(int num){...}
int main(){
int age=24;
SimpleFunc(age);   //age에 저장된 값이 매개 변수 num에 전달(복사)
}

마찬가지로 함수의 인자도 구조체 변수가 전달될 수 있으며 , 이러한 인자를 전달받을수 있도록 구조체 변수가 매개변수의 선언으로도 올 수 있다. 그리고 전달되는 구조체 변수의 값은 매개변수에 통째로 복사가 된다.

#include <stdio.h>

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

void ShowPosition(Point pos){
printf("[%d, %d] \n", pos.xpos, pos.ypos);
}

Point GetCurrenPosition(void){
Point cen;
printf("Input current pos: "_");
scanf("%d %d", &cen.xpos, &cen.ypos);
return cen;
}

int main(){
Point curPos = GetCurrentPosition();   // return cen이 curPos에 저장(복사)된다.
ShowPosition(curPos);   //curpos값이 함수 Point pos매개변수로 저장(복사)된다.
return 0;
}
=========================실행결과=====================================
Input current pos : 2 4
[2, 4]

그리고 구조체의 멤버로 배열이 선언되어도 위 예제에서 보인 것과 동일한 형태의 복사가 진행된다. 즉 인자의 전달과정에서, 그리고 값의 반환과정에서 구조체의 멤버로 선언된 배열도 통째로 복사가 된다.

아래 예제는 구조체 변수를 대상으로하는 Call- by- reference의 예다.

#include <stdio.h>

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

void OrgSymTrans(Point *ptr)   //원점대칭
{
ptr->xpos =(ptr->xpos)*-1;
ptr->xpos=(ptr->ypos)*-1;
}

void ShowPosition(Point pos){
printf("[%d, %d] \n", pos.xpos, pos.ypos);
}

int main(){
Point pos = {7, -5};
OrgSymTrans(&pos);   //pos값 원점 대칭
ShowPosition(pos);
OrgSymTrans(&pos);   //pos값 원점 대칭
ShowPosition(pos);
return 0;
}
=======실행결과=========
[-7 , 5]
[7, -5]

위 예제의 정의된 OrgSymTrras함수에서 보이는 바와 같이 구조체의 포인터 변수도 매개변수로 선언이 되어 Call-by-reference 형태의 함수 호출을 구성할 수 있다.

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

기본 자료형 변수를 대상으로 하는 사칙연산을 비롯해서 비교연산 등 다양한 종류의 연산이 가능하다 .그 러나 구조체 변수를 대상으로 하는 매우 제한된 형태의 연산만 허용이 된다. 허용되는 가장 대표적인 연산은 대입연산이며, 그 이외로 주소 값 반환을 목적으로 하는 &연산이나 구조체 변수의 크기를 반환하는 sizeof정도의 연산만 허용이 된다.

구조체 변수간 대입연산의 결과로 멤버 대 멤버 복사가 이뤄진다.

구조체 안에는 배열도 존재할 수 있고, 포인터 변수도 존재할 수 있다. 뿐만 아니라 다른 구조체의 변수도 존재할 수 있따. 따라서 구조체 변수 대상의 덧셈과 뺄셈연산의 결과를 정형화하는데는 무리가 있다.

구조체 변수를 대상으로 덧 셈이나 뺄셈을 하려면 함수를 프로그래머가 직접 정의 해야한다.

3. 구조체의 유용함에 대한 논의와 중첩 구조체

C언어로 구현된 프로그램 중에서 구초제가 정의되지 않은 프로그램을 찾기가 쉽지 않다. 그 만큼 구조체는 프로그램 구현에 있어서 중요한 위치를 차지하고 있다. 그렇다면 어떠한 장점때문에 구조체가 이렇듯 중요한 위치를 차지하는가?

-구조체를 정의하는 이유

구조체를 정의하는 이유는

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

일단 구조체를 정의하지 않는다면, 하나의 배열에 모든 데이터를 저장할 수 없게된다. 때문에 다수의 배열이 필요하게 된다. 그리고 함수의 매개변수 선언도 이렇듯 출력할 데이터만큼의 매개변수가 선언되어야 하기 때문에 불편하다.

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

배열이나 포인터 변수가 구조체의 멤버로 선언될 수 있듯이, 구조체 변수도 구조체의 멤버로 선언될수 있다. 그리고 이렇듯 구조체 안에 구조체 변수가 멤버로 존재하는 경우를 가리켜 '구조체의 중첩'이라 한다.

참고로 구조체 변수를 초기화하는 경우에도 배열의 초기화와 마찬가지로 초기화 하지 않은 일부 멤버에 대해서는 0으로 초기화가 진행된다.

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

구조체는 struct라는 키워드를 사용해서 정의하는 반면, 공용체는 union이라는 키워드를 사용해서 정의한다. 그리고 union이라는 단어의 의미에 공용체의 특성이 고스란히 담겨있다.

구조체 vs 공용체

공용체는 구조체와 비교하면 쉽게 이해 할 수 있다. 그래서 동일한 구성의 멤버를 이용해서 구조체와 공용체를 각각 정의해보자

typedef struct sbos{

int mem1;

int mem2;

double mem3;

}SBox;

typedef union ubox{

int mem1;

int mem2;

double mem3;

}UBox;

위의 코드에서 보이듯이 정의방식에서의 유일한 차이점은 struct 선언을 하느냐, union선언을 하느냐에 있다. 하지만 각각의 변수가 메모리 공간에 할당되는 방식과 접근의 결과에는 많은 차이가 있다. 위의 구조체와 공용체를 대상으로 각각 다음의 연산을 하면 ,

printf("%d \n", sizeof(SBox)); //16출력

printf("%d \n", sizeof(UBox)); // 8출력

16과 8이 출력된다. 여기서 16은 모든 멤버의 크기를 합한 결과이고, 8은 멤버 중에서 가장 크기가 큰 double의 크기만 계산된 결과이다.

위의 코드를 이용해 작성하고 각각의 멤버의 주소를 출력해보면

구조체 멤버의 주소는 각 크기마다 할당이 되어 출력이 되지만

공용체 멤버의 주소는 모두 같게 출력이 된다.

여기서 주목해야 할 점은 UBox를 구성하는 멤버의 주소값이 모두 동일하다는 사실이다.

구조체 변수가 선언이 되면, 구조체를 구성하는 멤버는 각각 할당이 된다. 반면 공용체 변수가 선언되면, 공용체를 구성하는 멤버는 각각 할당되지 않고, 그 중 크기가 가장 큰 멤버의 변수만 하나 할당되어 이를 공유하게 된다.

위의 공용체가 선언되었다고 가정

int main(){
UBox ubx;  //8바이트 메모리 할당
ubx.mem1=20;  //상위 4바이트 메모리 공간에 20을 저장한다
printf("%d \n", ubx.mem2);  //mem2는 int형 변수이므로 이 이름으로 접근할 경우 상위 4바이트의 메모리 공간을 참조하게 된다.
//그런데 앞서 위의 행에서 이 공간에 20을 저장했으므로 20이 출력된다.

ubx.mem3= 7.15; //mem3에 실수를 저장한다. 결과적으로 13행을 통해 저장된 값을 덮어써버리게 된다.
printf("%d \n", ubx.mem1);//16행에서 실수를 저장하면서 덮어써버렸기 때문에 상위 4바이트를 읽어서 출력하면 알수없는 값이
printf("%d \n", ubx.mem2);//출력된다.
printf("%d \n", ubx.mem3);
return 0;
}

====출력결과====
20
-1717986918
-1717986918
7.15

-공용체의 유용함은 다양한 접근방식을 제공함에 있다

공용체의 유용함은 간단히 설명되지 않는다. 결과적으로 '하나의 메모리 공간을 둘 이상의 방식으로 접근할 수 있다.'는 것으로 정리가 되지만 , 유용하게 사용되는 상황은 분야별로 약간씩 차이가 있기 때문이다.

5. 열거형(Enumerated Type)의 정의와 의미

열거형이라는 것도 구조체와 공용체와 마찬가지로 자료형을 정의하는 방법으로 사용이 된다. 열거형 기반의 자료형 정의 방법은 구조체 및 공용체와 유사하고 정의된 열겨형 기반의 변수 선언 방버은 구조체 및 공용체와 완전히 동일하다.

-열거형의 정의와 변수의 선언

열거형으로 syllable이라는 이름의 자료형을 정의한다는 것은 다음을 의미를 지닌다.

"syllable 형 변수에 저자잉 가능한 정수값들을 결정하겠다"

앞서 보인 구조체와 공용체의 경우에는 멤버에 저장할 값의 유형을 결정하였다(자료형의 선언을 통해서), 하지만 열거형의 경우에는 저장이 가능한 값 자체를 정수의 형태로 결정한다. 즉 다음과 같은 선언이 열거형의 정의에 해당한다.

"syllable형 변수에는 1,2,3,4,5,6,7 이 저장 가능하다."

이렇게 변수에 저장이 가능한 값을 열거하여 정의한다고 해서 '열거형'이라 한다.

enum syllable {

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

};

언뜻 보면 정의 방식이 구조체와 차이가 있어 보이나 그 기본적인 구성은 동일하다. struct 대신에 enum이 왔고, 구조체의 정의방식과 마찬가지로 enum에 이어서 자료형의 이름 syllable이 등장하였ㄸ. 그리고 syllable에 관련된 내용은 중괄호 안에 선언되었다. 그럼 이어서 중괄호 안에 있는 내용을 보자 .

Do =1

이는 다음의 의미를 지닌다.

"Do를 정수 1을 의미하는 상수로 정의한다. 그리고 이 값은 syllable 형 변수에 저장이 가능하다"

즉 위의 열거형 정의에서 Do,Re,Mi,Fa,So,La,Ti 라는 이름의 상수를 각각 1,2,3,4,5,6,7로 정의하고 , 이 값들을 syllable형 변수가 저장할 수 있는 값들로 제한한 것이다. 단 위와 같이 열거형을 정의하면 다음과 가팅 enum선언을 추가하여 열거형 변수를 선언해야 하므로(구조체 변수를 선언할 때 struct 선언을 추가하듯이),

enum syllable tone; //열거형 syllable 형 변수 tone의 선언

위와 같은 선언을 했을시

Syllable tone;

for(tone =Do; tone<=Ti; tone=(Syllable)((int)tone+1) 으로 선언이 가능하다.

Do, Re, Mi와 같은 ;열거형 상수'들은 int형으로 표현되는 상수이기 때문이다. 따라서 int형 데이터가 올수 있는 위치에는 이들 '열거형 상수'가 올 수 있다.

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

열거형을 정의하는데 있어서 상수의 값을 명시하지 않으면, 열거형 상수의 값은 어떻게 결정이 될까? 이와 관련해서 다음 열거형 정의를 보자

enum color{RED ,BLUE, WHITE, BLACK};

위 정의에는 열거형 상수의 이름만 선언되었을 뿐 상수의 값은 선언되어 있지 않다. 이러한 경우 열겨형 상수의 값은 0에서부터 시작해서 1씩 증가하는 형태로 결정이 된다. 즉 위의 정의는 다음의 정의와 완전히 동일하다.

enum color {RED=0, BLUE =1, WHITE=2. BLACK=3};

이번에는 정의형태가 조금 다른 예를 들면

enum color{RED=3, BLUE, WHITE=6, BLACK};

이 정의에서는 열거형 상수 BLUE와 BLACK의 값이 선언되어 있찌 않다. 그러나 값이 선언되지 않으면 앞서 선언된 상수보다 1이 증가된 값이 할당된다. 즉 위의 정의는 다음의 정의와 완전히 동일하다.

enum color {RED=3, BLUE=4, WHITE=6, BLACK=7};

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

열거형은 구조체 및 공용체와 정의하는 방식이 유사함에도 불구하고 정의하는 목적에 큰 차이가 있다.

구조체와 공용체는 자료형의 정의에 의미가 있다. 즉 변수를 선언하기 위해서 자료형을 정의하는 것이다. 그렇다면 열거형은 어떨까?

열거형도 마찬가지로, 정의하고 나면 해당 열거형의 변수 선언이 가능하다. 하지만 일반저긍로 열겨형을 정의하는 이유는 다음과 가탇.

DO, Re, Mi와 같이 연관이 있는 이름을 동시에 상수로 선언할 수 있다.

열거형 상수를 정의함으로써 프로그램을 이해하기가 한결 좋아졌다.

열겨헝의 유용함은 둘 이상의 연관있는 이름을 상수로 선언함으로써 프로그램의 가독성을 높이는데 있다.

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

실제로 변수의 선언의 목적이 아닌 상황에서는 위와 같이 정의하기도 한다. 그리고 위와같이 정의가 되어도 열거형 상수 Do, Re, Mi, Fa, So, La, Ti는 상수로써 의미를 지닌다.

 

'휴지통 > C 프로그래밍' 카테고리의 다른 글

문자와 문자열 관련 함수  (0) 2021.01.26
파일 입출력  (0) 2021.01.23
구조체와 사용자 정의 자료형1  (0) 2021.01.21
문자와 문자열 관련 함수  (0) 2021.01.21
함수 포인터와 void 포인터  (0) 2021.01.21