Program Languege/C/C++

구조체 (struct)

Frozen0113 2013. 2. 27. 21:50

구조체 (struct)

 

구조체는 변수들의 집합이라고 할 수 있다. 변수를 하나하나 선언하기가 불편함을 느끼다가 배열을 알고서 다소 해결 되었을 것이다. 허나 또 욕심이 생기지 않았는가? 학생정보를 저장한다고 할 때, 학생 이름, 학번, 성적, 주소 등등 이 정보들을 배열에 넣는다고 한다면 int형 따로 char형 따로 선언을 해서 사용을 해야한다. 이 불편함을 해결하기 위해 다른 자료형들을 모아서 한번에 관리할 수 없을까?

 

그것이 바로 구조체이다. 변수들의 집합. 사용자 마음대로 정의할 수 있다.

 

Ex>  struct

{

구조체 안에 포함 시킬 변수들.

} 구조체 이름;

 

학생정보를 구조체로 표현한다고 해보자.

 

- 소스 코드 예시

 

struct

{

char name[10];  // 학생 이름.

int    ID;           // 학번.

int    KOR;        // 국어 점수. 

int    ENG;        // 영어 점수.

float AVG;        // 평균

}StudentInfo;

 

StudentInfo라는 구조체에는 학생의 이름을 저장할 char형 배열, 학번을 저장할 int형 ID, 평균을 저장할 float형 변수들로 구성되어있다. 다양한 자료형의 정보를 저장할 수 있는 구조이다.

 

위에서 보인 선언 방법은 하나의 구조체 변수를 선언하는 방법이었다. 하지만 학생은 한 명일 수도 있지만 여러명일 수도 있다. 그렇다면 학생수만큼 구조체가 필요할 것이다.

 

Ex> struct 태그명 {멤버 목록};

 

- 소스 코드 예시

 

struct StudentInfo

{

char name[10];

int    ID;

int    KOR;

int    ENG;

float AVG;

};

 

struct StudentInfo stdInfo1;  // C형

StudentInfo stdInfo2;           // C++형

 

구형 C 컴파일러는 태그를 사용할 때 구조체 태그라는 것을 명확하게 알리기 위해 struct라는 키워드를 태그 앞에 붙여야하지만 C++에서는 태그가 하나의 타비으로 인정되기 때문에 struct 없이 태그명만으로 구조체를 선언할 수 있다. 최근 컴파일러들은 모두 C++ 컴파일러이므로 이제는 귀찮게 struct 키워드를 태그명 앞에 일일이 붙이지 않아도 된다. 단, 파일의 확장자는 .cpp로 해야한다.

 

새로운 타입을 정의하는 typedef문을 사용하면 태그를 정의하는 것과 동일한 효과를 낼 수 있다.

 

Ex>    typedef struct

   {

   char name[10];

   int    ID;

   int    KOR;

   int    ENG;

   float AVG;

   }StudentInfo;

 

 

이 선언으로 name, ID, KOR, ENG, AVG를 멤버로 가지는 구조체 타입을 StudentInfo라는 이름으로 정의하는 것이다. 이 정의후로는 일반 변수를 선언하듯 할 수 있다.

 

Ex>    struct                                    struct StudentInfo                   typedef struct

   {                                            {                                           {

   char  name[10];                        char   name[10];                    char   name[10];

   int     ID;                                  int     ID;                              int      ID;

   int     KOR;                               int     KOR;                           int     KOR;

         int     ENG;                               int     ENG;                           int      ENG;

   float  AVG;                              float AVG;                           float   AVG;

    }student;                                };                                           }StudentInfo;

 

        StudentInfo student;                StudentInfo student;

 

 

StudentInfo std1, std2, std3; // 이런식으로 한번에 여러개의 구조체 변수를 선언할 수 있다.

StudentInfo *pStd;               // 포인터로도 선언이 가능하다.

StudentInfo arrayStd[10];      // 배열 또한 선언이 가능하다.

 

void PrintStudentInfo(StudentInfo std){...} // 함수에서 매개변수로도 사용이 가능하다.

 

이외에도 구조체안에 구조체가 포함되는 것도 가능하다.

 

 

그렇다면 배열은 각 인덱스마다 [](랜덤엑세스 연산자)를 통해서 접근했다. 그렇다면 구조체의 경우는 어떻게 접근하는가?

. 멤버 연산자를 통해 접근을 한다.

 

Ex> 구조체명.멤버명

 

- 소스 코드 예시

 

typedef struct

{

char  name[10];

int     ID;

int     KOR;

int     ENG;

float  AVG;

}StudentInfo;

 

int main()

{

StudentInfo std;

 

strcpy(std.name,"홍길동"); // std의 name에 "홍길동"을 복사해서 넣어주는 함수.

 

std.ID      = 13;

std.KOR   = 80;

std.ENG   = 90;

std.AVG  = static_cast<float>(std.KOR + std.ENG) / 2.0f; 

 

return 0;

}

 

 

 

 

디버깅시 std안에 값들을 살펴보았다. StudentInfo 구조체 타입의 std 안에는 name, ID, KOR, ENG, AVG가 있으며 이 멤버에는 .을 사용해서 접근하여 값을 넣어주었다.

 

하지만! 포인터형인 경우 -> 포인터 멤버 연산자를  사용한다.

 

위의 소스 코드를 변경해보겠다.

 

- 소스 코드 예시

 

#include <iostream>

 

using namespace std;

 

typedef struct
{
   char  name[10];
   int   ID;
   int   KOR;
   int   ENG;
   float AVG;
}StudentInfo;

 

int main()
{
   StudentInfo std;
   StudentInfo *pStd = &std;

 
   strcpy(pStd->name,"홍길동"); // std의 name에 "홍길동"을 복사해서 넣어주는 함수.

   pStd->ID     = 13;
   pStd->KOR  = 80;
   pStd->ENG  = 90;
   pStd->AVG  = static_cast<float>(pStd->KOR + pStd->ENG) / 2.0f;

 

   return 0;
}

 

 

 

 

포인터를 통한 멤버 접근도 확인했다. 근데 뭔가 다른 것이 보인다. 포인터로 선언해서 접근하는 경우. 일반 구조체와 달리 0x0012f8f0이 하나가 더 있다. 

 

이것이 실제 pStd의 값. 포인터가 가지는 주소 값이다. pStd를 선언하면 역시 주소 값을 갖는 공간이 하나를 할당받는다. 구조체 멤버에 대해 전부 할당하는 것이 아니라는 뜻이다.

 

pStd에는 std의 첫번째 멤버 name의 주소(std의 시작 주소)를 넘겨주고 -> 멤버 연산자를 통해 std의 멤버에 접근하는 것이다.

 

 

pStd 

0x0012f8f0

 

 

 

 

 

 

 

 

 

 

0x0012f8f0

 

 

 

 

 

 

  →

std 

name

ID

KOR

ENG

AVG

 

 

 

 

홍길동

13

80

90

85

 

 

 

 

 위와 같이 표현할 수 있다. (name은 배열이기지만 여기서는 구조체 포인터 설명을 위해 간단하게 표현했다.)

 

 포인터 이외에 배열도 선언이 가능하다고 했다. 배열을 예시로 좀 더 자세하게 설명을 이어서 하겠다.

 

- 소스 코드 예시

 

#include <iostream>

 

using namespace std;

 

typedef struct
{
   char  name[10];
   int   ID;
   int   KOR;
   int   ENG;
   float AVG;
}StudentInfo;

 

int main()

   StudentInfo std[5];

 

   strcpy(std[0].name,"홍길동");
   strcpy(std[1].name,"아무개");

 

   std[0].ID  = 13;
   std[0].KOR    = 80;
   std[0].ENG  = 90;
   std[0].AVG  = static_cast<float>(std[0].KOR + std[0].ENG) / 2.0f;

 

   std[1].ID  =  3;
   std[1].KOR   = 87;
   std[1].ENG  = 94;
   std[1].AVG  = static_cast<float>(std[1].KOR + std[1].ENG) / 2.0f;

 

   cout << "std[0]의 시작 주소 : " << &std[0] << endl;
   cout << "std[0]의 name 주소 : " << &std[0].name << endl;
   cout << "std[0]의 ID   주소 : " << &std[0].ID << endl;
   cout << "std[0]의 KOR  주소 : " << &std[0].KOR << endl;
   cout << "std[0]의 ENG  주소 : " << &std[0].ENG << endl;
   cout << "std[0]의 AVG  주소 : " << &std[0].AVG << endl;

 

   cout << endl;

 

   cout << "std[1]의 시작 주소 : " << &std[1] << endl;
   cout << "std[1]의 name 주소 : " << &std[1].name << endl;

 

   return 0;
}

 

변수에서 배열을 선언하듯 선언하면된다.

 

 

 

 

 구조체의 시작주소와 구조체의 첫번째 멤버의 시작주소와 같다. 그리고 그 뒤에 멤버들도 연달아 할당된다. 또한 배열로 선언했기 때문에 그 뒤에 인덱스도 이어서 할당된다.

 

...

 std[0].name

 Padding Bit

 std[0].ID

 std[0].KOR

 std[0].ENG

 std[0].AVG

std[1].name 

...

...

크기 : 10byte 

 크기 : 2byte

 크기 : 4byte

크기 : 4byte 

크기 : 4byte 

크기 : 4byte 

크기 : 10byte 

 ...

 ...

 001CFA18

 001CFA22

001CFA24

001CFA28

001CFA2C

001FA30

001FA34 

 ...

 

 

※ 패딩비트(Padding Bit)

- 패딩비트란 구조체나 클래스 등에서 내부에 빈 공간을 채우는 것을 말한다. 실질적으로 사용되거나 의미를 가지지는 않는다.

  빈 공간이 생기는 이유는 가장 큰 자료형을 기준 단위로 공간을 할당하는데 위에 구조체의 경우 4byte가 가장 큰 자료형이기 때문에 

  4byte단위로 할당하면 다음처럼 구조가 이루어진다.(name의 경우 10byte지만 자료형은 char로 1byte에 해당된다.)

 

 

 ...

001CFA18

~

001CFA1B

001CFA1C

~

001CFA1F

001CFA20

~

001CFA21 

001CFA22

~

001CFA23

001CFA24

~

001CFA27

 001CFA28

~

001CFA2B

001CFA2C

~

001CFA2F

001CFA30

 ~

001CFA34

 ...

 ...

std[0].name 

 std[0].name std[0].name

Padding Bit

std[0].ID

 std[0].KOR 

 std[0].ENG

std[0].AVG 

 ...

 ...

 4byte

4byte 

4byte 

 4byte

 4byte

4byte 

4byte 

 ...

 

 name이 10byte를 차지하기 때문에 12byte를 할당하고 남은 2byte에는 int형(4byte)자료형이 들어 갈 수 없기 때문에 다시 뒤에 이어서

 4byte를 할당하는 식으로 메모리 공간을 할당한다.

 

 

- 출처 : 열혈강의 C++ 언어본색

           혼자 연구하는 C/C++  http://www.soenlab.com/

 

※ 공부한 내용을 정리하였기때문에 출처와 내용이 다를 수 있습니다.