리눅스 Tips, 리눅스 C/C++ 프로그래밍, 모바일 클라우드 동향 및 테스트 등

2012년 11월 5일 월요일

리눅스 C/C++ : 11장 구조체


1. 원시데이터 타입

 C 언어는 매우 기본적으로 사용하는 5가지 정도의 원시 데이터 타입이라는 것을 가지고 있다는 것을 앞서 배웠다. 이들 기본 타입은 다음과 같은 것들이다.

  • int, float, double, char, long long int, Pointer

 인간이 다루는 매우 복잡한 데이터들도 숫자와 문자, 도형 이라는 걸 생각하면 컴퓨터가 이렇게 단지 몇가지만의 데이터 타입을 가지는 것도, 어찌보면 당연한 결과라고 할 수 있을거 같다. C 언어뿐만 아니라 거의 대부분의 언어가 6-8개정도의 원시데이터 타입만을 가지고 있을 뿐이다. 종류역시 한두개 정도만 제외하고는 C와 거의 차이가 없다.


2. 원시데이터 타입의 구조화

 인간이 다루는 데이터로 보자면, 숫자,문자,도형만 있어도 모든 정보를 다룰 수 있기는 하다. 그렇지만 너무나 비효율적이다. 그래서, 이들 데이터 타입을 구조화해서 새로운 데이터 타입을 만들어서 사용하게 된다. 예를 들자면, 주소 정보를 관리하기 위해서 주소록을 만들고, 개인신상관리를 위해서 신상카드를 만들어서 사용하는 것이다. 이렇게 구조화하게 되면, 정보를 훨씬 깔끔하게 다룰 수 있게 된다.

 만약 유저정보를 관리할 목적이라면, 아래와 같이 데이터를 구조화 할 수 있을 것이다.

            +--- User Info  -----------------------+

            | Name : Text                          |

            | Age  : Number                        |

            | Address : Text                       |

            | Email   : Text                       |

            | Home    : Text                       |

            +--------------------------------------+
          
 TextNumber만으로 유저정보 관리를 위한 User Info라는 새로운 데이터 타입을 만들었다.


3. 구조체

 C언어도 원시데이터 타입을 구조화해서 새로운 데이터 타입을 만들 수 있도록 지원하고 있다. 이것을 우리는 구조체(Structure)라고 한다. 구조체는 다음과 같은 방식으로 만들 수 있다.

- 조작을 쉽게 하기 위한 단일 이름으로 묵어 놓은 하나 이상의 변수들의 집합 = 구조체

          struct 구조체이름 

          {

             데이터타입 변수명;

             데이터타입 변수명;

             데이터타입 변수명; 

          };

 위에서 예로 들었던, 유저정보를 구조체로 만들어 보도록하자. 이름은 문자열이 들어가게 되므로 char의 배열이나 포인터형식으로 선언해야 할 것이다. 포인터는 좀 귀찮으니, 모든 문자열은 char의 배열로 하도록 하겠다. 나이는 int형으로 하면 될것이고, 주소, 이메일, 홈페이지는 모두 char 배열로 하면 문제없을 것이다.

- 구조체에 포함된 각각의 변수 = 구조체 변수

          struct userInfo

          {

              char name[12]; 

              int age;

              char address[80];

              char email[40];

              char home[40];

          };

 구조체는 내부적으로 자신이 사용할 변수들을 유지하게 되는데, 이러한 변수를 멤버변수라고 한다.


4. 구조체의 정의, 선언 그리고 사용

 구조체는 원시데이터 타입을 요소로 가지는 사용자 정의 데이터타입으로 볼 수 있다. 그러므로 다른 원시데이터 타입과 마찬가지로 선언해서 사용하면 된다. 그러나 사용자 정의 데이터 타입이기 때문에, 구조체의 구조를 먼저 정의해줘야 한다. 인사기록 카드를 만들려면, 카드에 어떤 내용이 들어가야 하는지를 먼저 정의해야 하는것과 마찬가지다.

 구조체의 정의는 위에서 이미 설명한바가 있다. 이제 정의를 하는 위치가 문제가 되는데, 구조체는 프로그램 전체에서 선언되고 사용될 수 있으므로, 글로벌영역에서 정의가 된다. 예를 들자면 아래와 같다.

          // userInfo 구조체를 정의한다.

          struct userInfo

          {

              char name[12]; 

              int age;

              char address[80];

              char email[40];

              char home[40];

          };

          

          int main()

          {

              struct userInfo MyUser;

          }         


 선언은 일반데이터타입과 마찬가지다. 구조체의 이름뒤에 변수명을 적어주면 된다.


          struct userInfo Myuser;
          
 이렇게 정의와 선언이 끝났다면, 이제 사용하는 일만 남았다. 구조체는 다른 원시 데이터 타입들과는 달리, 내부에 멤버변수를 가진다. 그러므로 각각의 멤버변수별로 접근할 수 있어야 한다.

 C 언어는 멤버 연산자 "."을 이용해서 멤버변수에 접근할 수 있도록 하고 있다. userInfo 구조체 선언인 MyUser에서 각각의 멤버변수는 다음과 같이 접근할 수 있다.

          strcpy(MyUser.name, "yundream\0");

          MyUser.age = 33;

          strcpy(MyUser.email, "yundream@gmail.com\0");

          strcpy(MyUser.home, "http://www.joinc.co.kr\0");          

- strcpy는 문자열 복사 함수로써 strcpy()를 쓰기 전에는 반드시 복사할 문자열을 검사해 줘야 한다
- strcpy는 헤더파일 #include<string.h>가 필요 하다


5. 구조체와 배열

 어렵게 생각할 필요는 없다. 구조체도 데이터 타입이므로, 다른 원시 데이터처럼 배열을 이용해서 동일하게 구조화할 수 있다. 만약 유저정보를 5개를 저장하는 프로그램을 만든다면, 다음과 같이 배열로 선언하면 된다.


          struct userInfo Myuser[5];
          

 접근 역시 배열첨자를 이용하면된다.


          strcpy(MyUser[0].name, "yundream\0");

          MyUser[0].age = 33;

          strcpy(MyUser[0].email, "yundream@gmail.com\0");

          strcpy(MyUser[0].home, "http://www.joinc.co.kr\0");
          

 아주 간단하다.

- 구조체를 포함하는 구조체
: 멤버 연사자"."을 두 번 적용하면 된다. 즉 구조체를 포함한 변수가 my라고 하고 구조체에 포함된 구조체를 you라고 할 때 my.you.** 과 같은 방식으로 하면 된다.

- 구조체 초기화
: 구조체 초기화는 구조체 변수이름을 정할 때 초기값을 적어 주면 되는데 만약 구조체 변수가 "mysale"이라고 정할 때 }mysale={{"*****"}}; 와 같고 배열을 초기화 할 때는 변수뒤에 배열을 나타내는 "[]"을 넣으면 된다.


6. 구조체와 포인터

 배열과 포인터는 메모리 상에서 근본적으로 동일한 구조를 가진다는 것을 배웠다. 구조체를 배열로 다룰 수 있으니, 마찬가지로 포인터로도 다룰 수 있으며, 사용하는 방법도 10장에서 배웠던것과 동일하다.

 참, 다른 원시데이터 타입과 다른점이 있다. 구조체는 멤버변수를 가지고 있기 때문이다. 앞에서 구조체의 멤버변수에 접근하기 위해서 멤버연산자 .를 사용하면 된다는 것을 배웠다. 그러나 구조체를 포인터로 선언했을 경우에는 멤버연산자를 사용할 수가 없다. 멤버연산자는 을 가져오기 위해서 사용하는 연산자인데, 포인터는 이 아닌 주소를 다루기 때문이다. 그러므로 주소가 가리키는 곳의 을 가져오기 위한 새로운 연산자가 필요하게 된다. C는 구조체 멤버변수의 포인터연산을 위해서 참조연산자라는 것을 제공한다. 참조연산자는 ->를 사용하면 된다.

          strcpy(MyUser->name, "yundream\0");

          MyUser->age = 33;

          strcpy(MyUser->email, "yundream@gmail.com\0");      

 당연하지만, 포인터는 주소만 가리키는 도구이므로, 실제 데이터를 저장하기 위해서는 메모리를 할당해야만 한다. 메모리 할당은 malloc(3) 함수를 이용하면 된다. 아래 코드는 userInfo 구조체를 포인터로 선언한다음, 5개의 userInfo 정보를 저장할 수 있도록 메모리를 할당하는 프로그램이다. 메모리를 할당하기 위해서는 구조체의 크기를 알아야 할것인데, 다른 데이터 타입과 마찬가지로 sizeof명령을 이용해서 알아낼 수 있다.

          #include <unistd.h> - 소켓 프로그래밍 

          #include <stdlib.h>

          

          struct userInfo

          {

              char name[12];

              int age;

              char address[80];

              char email[40];

              char home[40];

          };

          

          int main()

          {

              struct userInfo *MyUser;

          

              printf("structure Size is %d\n", sizeof(struct userInfo));

              MyUser = (struct userInfo *)malloc(sizeof(struct userInfo) * 5);

          }

          

7. 예제 프로그램

 그럼 간단한 예제 프로그램을 만들어 보도록하자. 이 프로그램은 사용자 정보를 입력받아서 출력하는 일을 한다. 입력받는 정보는 다음과 같다.

  • 이름 : 문자열
  • 나이 : 숫자

 다음과 같이 구조체를 정의할 수 있을 것이다.

          struct userinfo

          {

              char name[20];

              int  age;

          };
          
 나이는 100살을 넘기기 힘들 것이다. 그러므로 age 변수의 경우 short int로 정의를 할 수도 있을 것이다. short int는 2byte이므로 4byte의 age에 비해서 2byte의 크기를 절약할 수 있을것이라고 생각할 수 있다. 하지만 다른 여러가지 이유들 때문에, 꼭 메모리 크기를 절약할 수 있는 것은 아니다. 이에 대한 내용은 따로 기회가 되면 다루도록 하겠다. 우선은 그냥 int형으로 하겠다.

 사용자 정보는 5개까지만 입력하도록 하겠다.

          #include <stdio.h>

          #include <string.h>

          

          struct userinfo

          {

            char name[20];

            int age;

          };

          

          int main(int argc, char **argv)

          {

            int age;

            int i;

            char buf[40];

            struct userinfo myfriend[5];

          

            for (i = 0; i < 5; i++)

            {

              printf("Name : ");

              fgets(buf, 19, stdin);

              buf[strlen(buf)-1] = '\0';            // <--- 1

              sprintf(myfriend[i].name, "%s", buf);

          

              printf("Age  : ");

              fgets(buf, 19, stdin);

              age = atoi(buf);

              myfriend[i].age = age;

            }

          

            printf("=======================\n");

            for (i = 0; i < 5; i++)

            {

              printf("%12s : %d\n", myfriend[i].name, myfriend[i].age);

            }

          }
          
 fgets(3)은 키보드로 부터 문자열을 입력받기 위해서 사용하는 함수다. 1은 키보드로 입력된 개행문자를 제거하기 위해서 사용했다.

 atoi(3) 함수는 문자열을 int형 값으로 변경하기 위해서 사용한다. 위의 예제는 나이를 숫자로 받아서 하는일이 없으니, 그냥 문자열 그대로 저장해도 상관은 없을 것이다. 그러나 나이를 가지고 비교한다던지 하는 숫자연산 작업이 있을 수 있으므로, 나중을 위해서 int 형으로 변환하는게 좋을 것이다.

 다음은 테스트 결과다.

          # ./userinfo

          Name : yundream

          Age  : 32

          Name : kopete

          Age  : 28

          Name : dream

          Age  : 31

          Name : minsu

          Age  : 29

          Name : test

          Age  : 32

          =======================

              yundream : 32

                kopete : 28

                 dream : 31

                 minsu : 29

                  test : 32

댓글 1개:

  1. 출처 : http://mnslaboratory.springnote.com/pages/1400094
    (리스트 부분은 생략함)

    답글삭제