본문 바로가기
휴지통/C 언어

구조체

by 신재권 2021. 2. 4.

구조체(structure)라는 것은 하나 이상의 변수(포인터 변수와 배열 포함)를 묶어서 새로운 자료형으로 정의하는 도구이다.

 

예를 들어보자.

마우스의 좌표정보를 저장하고 관리한다고 가정하면,

int xpos;  //마우스의 x좌표

int ypos; //마우스의 y좌표

 

이둘은 함께하기 때문에, 즉 독립된 정보를 표현하는 것이 아닌, 마우스의 위치라는 하나의 정보를 표현한다.

결국 마우스의 현재 위치 정보를 출력하기 위해서는 저 위에 두변수에 저장된 값을 동시에 참조해야한다.

또한 동시에 갱신되어야 한다 .

그리고 둘중 하나라도  소멸이 된다면  나머지 하나는 의미없는 변수가 되어버린다.

 

결국 이둘의 변수를 묶기 위해 구조체를 사용한다.

 

struct point

{
int xpos;

int ypos;

};  

 

위의 코드는 point라는 구조체를 정의한 것이다.

즉 point라는  이름이 int, float같은 자료형의 이름이 되는 것이다.

기본자료형은 아니기 때문에, 이를 가리켜 '사용자 정의 자료형 (user defined data type)'이라 한다.

 

구조체는 배열, 포인터등 변수의 성격을 띄는 데이터를 구조체의 멤버로 선언할 수 있다.

 

struct type_name val_name;  //구조체 변수 선언의 기본 형태

struct point pos; //이렇게 선언시 다음의 형태로 존재

int xpos
int ypos

 

즉 구조체 변수 pos에는 int형 변수 xpos와 ypos가 존재하게 된다.

멤버에 접근의 형식은

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

즉 ( . )연산자를 사용해 접근이 가능하다.

pos.xpos = 2;  //구조체 변수 pos의 멤버 xpos에 20을 저장

printf("%d \n", pos.xpos); //구조체 변수 pos의 멤버 xpos를 출력

 

또한 구조체 변수는 선언과 동시에 초기화가 가능하다.

struct point pos = {1, 2};  //xpos, ypos를 각각 1과 2로 초기화

 

struct person{
 char name[20];

 char phoneNum[20];

 int age

};

위와 같이 선언되었다고 가정하면

구조체 변수를 이렇게 선언할 수 있다.

struct person man;

strcpy(man.name, "홍길동");

strcpy(man.phoneNum, "010-xxxx");

man.age = 23;

 

또는

 

scanf("%s", man.name);

scanf("%s", man.phoneNum);

scanf("%d", &man.age);

 

이렇게 입력할 수 있다.

strcpy함수를 사용하는 건 배열이고, 문자열이기 때문에 호출해 사용할 수 있고,

scanf 함수를 이용해서도 입력받을 수 있다.

초기화 과정에서는 문자열 저장을 위해 strcpy 함수를 호출하지 않아도 된다.

 

구조체를 정의함과 동시에 변수 선언도 가능하다.

struct point {

int xpos;

int ypos;

}pos1, pos2, pos3;

 

struct point {

int xpos;

int ypos;

};

struct point pos1, pos2, pos3

 

이둘은 동일한 선언이다.


다수의 구조체 변수를 선언할 때에는 구조체 배열의 선언을 고려할 수 있다. 

구조체 배열의 선언 방식은 일반 배열의 선언과 똑같다.

반환형 배열이름[배열크기]

struct point arr[4];

arr[0] arr[1] arr[2] arr[3]
int xpos int xpos int xpos int xpos
int ypos int ypos int ypos int ypos

이런 구조로 할당이 된다.

각각 배열에 접근할 때에는

arr[1].xpos 

arr[2].ypos 

이런식으로 접근을 할 수 있다.

 

구조체 배열을 선언과 동시에 초기화도 가능한데.

struct point arr[3]={

 {1, 2};  //첫번째 요소의 초기화

 {3, 4};  //두번째 요소의 초기화 

 {5 ,6}  //세번째 요소의 초기화 

};

이런식로 초기화가 가능하다.

 

구조체 배열의 선언과 접근은 일반적인 배열과 동일한데, 구조체 포인터 변수의 선언  및 연산의 방법도 일반적인 포인터 변수의 선언 및 연산과 동일하다.

 

일반적인 포인터 변수 선언

int num =10;

int *ptr= #

*ptr = 20;  

 

struct point pos = {11, 12};

struct point *pptr= &pos;  

(*pptr).xpos = 10;

(*pptr).ypos =20;

위의 접근방식은

pptr->xpos =10;

pptr->ypos = 20; 

이 선언과 완전히 동일하고, 주로 이렇게 포인터 연산을 진행한다.

 

 

즉 포인터 변수를 대상으로 *연산하는 것은 동일하다 . 다만 구조체 변수의 경우 멤버 선택을 위해 .연산만 추가할 뿐이다.

 

또한 포인터 변수도 구조체의 멤버가 될 수 있다.

#include <stdio.h>

struct point{
int xpos;
int ypos;
};

struct circle
{
double radius;
struct point * center;
}

int main(){
struct point cen { 5, 7};
double rad =5.5;

struct circle ring = {rad, &cen};
printf("원의 반지름 : %g\n", ring.radius);
printf("원의 중심 [%d, %d] \n", (ring.center)->xpos, (ring.center)->ypos);
return 0;
}
================실행 결과==================
원의 반지름 : 5.5
원의 중심 [2, 7]

TYPE형 구조체 변수의 멤버로 TYPE형 포인터 변수를 둘 수 있다.

struct point{

int xpos;

int ypos;

struct point *ptr;  

}

이렇게 선언이 가능하다.

위 선언은 자료구조에서 많이 사용된다.

 

구조체 변수의 주소 값은 구조체 변수의 첫 번째 멤버의 주소 값과 동일하다.

 


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

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

자료형의 이름 int에 INT라는 이름을 추가로 붙여주는 것이다.

INT num; 과 int num; 은 동일한 선언이다.

INT *ptr 등 포인터 변수도 선언이 가능하다.

 

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

typedef a b c;  //a b에 부여된 새로운 이름 c

 

typedef 선언을 통해서 복잡한 유형의 자료형 선언을 매우 간결히 처리할 수 있다.

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

 

struct point

{

int xpos;

int ypos;

};

typedef struct point Point; 

 

이렇게 선언된 구조체를

 

typedef struct point {
int xpos;

int ypos;

} Point;  

을 할수있다. 즉 구조체의 정의와 동시에 선언을 할 수 있다.

구조체의 struct 선언의 생략을 위한 typedef선언이다.

 

구조체의 이름 생략도 가능하다.

typedef struct 

{
 char name [20];

char phoneNum[20];

int age;

}Person;

 

단 구조체의 이름을 생략하면 아래선언을 할 수 없게 된다 .

굳이 typedef 선언을 한 마당에 struct선언을 할 필요는 없다.

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


함수의 인자로 구조체 변수가 전달될 수 있으며, 이러한 인자를 전달 받을 수 있도록 구조체 변수가 매개변수의 선언으로 올 수 있다. 

#include <stdio.h>

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

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

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

int main()
{
	Point curPos = GetCurrentPosition();
    ShowPosition(curPos);
    return 0;
}
===============실행 결과=======================
Input current pos : 2 4
[2, 4]

함수가 반환하는 값으로 curPos를 초기화 하고 있다. 이문장에 호출하는 함수는 19행에서 프로그램 사용자로부터 입력 받은 위치 정보로 초기화 된 구조체 변수 cen을 반환하고 있다. 그 결과 변수 cen에 저장된 값은 9행의 매개변수 pos에 나란히 저장(복사)된다.

 

구조체의 멤버로 배열이 선언되어도 위 예제와 동일한 형태의 복사가 진행된다. 

인자의 전달과정에서, 그리고 값의 반환과정에서 구조체의 멤버로 선언된 배열도 통째로 복사가 된다.

 

또한 포인터 변수도 매개변수로 선언이 되어서 Call-by-reference 형태의 함수 호출을 구성할 수 있다.

 


구조체 변수를 대상으로 하는 연산은 매우 제한된 형태의 연산만 허용이 된다.

허용되는 가장 대표적인 연산은 대입연산이며, 그 이외로 주소 값을 반환을 목적으로 하는 &연산이나  구조체 변수의 크기를 반환하는 sizeof연산만 허용이 된다.

#include <stdio.h>

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

int main()
{
	Point pos1 ={1, 2};
    Point pos2;
    pos2= pos1; 	//pos1의 멤버 대 pos2의 멤버 간 복사가 진행됨
    
    printf("크기 : %d \n", sizeof(pos1)); 	//pos1의 전체 크기 반환
    printf("[%d, %d] \n", pos1.xpos, pos1.ypos);
    printf("크기 : %d \n", sizeof(pos2)); 	//pos2의 전체 크기 반환
    printf("[%d, %d] \n", pos2.xpos, pos2.ypos);
    return 0;
}    

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

 

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


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

 

배열이나 포인터 변수가 구조체의 멤버로 선언될 수 있듯이 , 구조체의 변수도 구조체의 멤버로 선언이 가능하다. 

이렇게 구조체 안에 구조체 변수가 멤버로 존재하는 경우를 가리켜 '구조체의 중첩'이라 한다.

 

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

 


typedef struct sbox

{

int mem1;

int mem2;

double mem3;

}SBox;

 

typedef union ubox   //공용체 선언

{

int mem1;

int mem2;

double mem3;

}UBox;

 

공용체와 구조체는 선언방식은 비슷하지만, 각각의 변수가 메모리 공간에 할당되는 방식과 접근의 결과에는 많은 차이가 있다.

 

sizeof(SBox); sizeof(UBox);  이렇게 진행할시 만약 출력을 했다 가정하면

각각 16과 8이 출력된다.

 

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

 

공용체는 멤버의 주소값이 모두 동일하다 .

구조체는 멤버의 주소값이 각각 다르다.

 

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

 

결론은 공용체의 유용함은 다양한 접근방식을 제공한다.

결과적으로 '하나의 메모리 공간을 둘 이상의 방식으로 접근할 수 있다'라는 것으로 정리가 된다.

 


열거형

enum 이름 

{   

   a= 1, b= 2 ....

};

 

정의방식이 구조체와 차이가 있어보이나 기본적인 구성은 동일하다. struct대신에 enum이 왔고, 자료형의 이름이 온다.

그리고 관련된 내용은 중괄호 안에 선언된다. 

 

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

 

열거형을 정의하면 

enum 자료형이름 변수이름; 이렇게 선언하면 된다.

 

또한 a등을 상수선어했기 때문에

switch문의 case레이블로도 사용이 가능하다.

 

열거형을 정의하는데 있어 상수값을 명시하지않아도, 상수 값이 결정이 된다.

0부터 시작되서 1씩 증가한다.

만약 중간에 상수값이 명시되어있지 않아도 바로앞 열거형 멤버에 상수가 정의되어있으면 그 상수에 +1이 된다.

 

열거형은 연관있는 이름을 동시에 상수로 선언할수 있는 유용함을 가지고 있다.

즉 둘 이 상의 연관이 있는 이름을 상수로 전환함으로써 프로그램의 가독성을 높이는 효과를 가진다.

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

파일입출력2  (0) 2021.02.07
파일 입출력1  (0) 2021.02.06
문자열 입력 함수  (0) 2021.02.02
문자와 문자열  (0) 2021.02.01
함수 포인터, void 포인터  (0) 2021.01.31