책정리/혼자 연구하는 C,C++ 1

13장 구조체

GONII 2015. 2. 19. 17:51

13.1 구조체

13.1.1 정의

타입이 다른 변수들의 집합 (이종 변수 집합)

정수나 실수 또는 문자열 등의 단순한 형태로 나타낼 수 없는 복잡한 데이터를 표현할 때 구조체를 사용

관련 정보를 하나의 구조체로 묶어서 선언하면 양이 많은 정보도 쉽게 다룰 수 있으며 함수를 통해 복잡한 정보를 전달하거나 리턴 받을 수도 있다.

구조체 선언은 구조체의 모양을 컴파일러에게 알리는 것 뿐 static, register 같은 기억 부류를 지정한다든가 초기값을 줄 수 없다.

   

여러 가지 정보들이 모여서 하나의 완성된 정보를 구성하기 때문에 실생활에서 예를 들 수 있음

  • 도서 정보 : 저자, 출판사, 출판년도, 총 페이지수, 가격, 도서 번호
  • 상품 정보 : 상품명, 제조사, 용량, 입고일, 매입가, 판매가, 할인율
  • 게임의 주인공 : 현재 위치, 현재 모양, 보유한 아이템, 에너지 상태

       

  • 구조체 형식

    struct 태그명

    {

    멤버목록

    ....

    } 변수명 ;

13.1.2 구조체 태그

구조체 변수가 딱 하나만 필요하다면 태그명 없이 구조체 변수를 바로 선언할 수 있지만 태그를 먼저 정의하고 이 태그로 구조체 변수를 선언하는 것이 더 일반적이다.

   

C 컴파일러에서는 태그를 사용할 때 구조체 태그라는 것을 명확하게 알리기 위해 struct라는 키워드를 태그 앞에 붙이고 변수를 선언해야 했으나, C++ 컴파일러에서는 struct 키워드를 태그명 앞에 붙이지 않아도 됨.

struct tag_test a1 ;        // C 컴파일러 스타일

tag_test a2 ;                // C++ 컴파일러 스타일

   

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

struct

{

char name[10] ;

int age ;

double height ;

} test ;

struct tag_test

{

char name[10] ;

int age ;

double height ;

} ;

tag_test test ;        // 변수 선언

typedef struct

{

char name[10] ;

int age ;

double height ;

} tag_test ;

tag_test test ;        // 변수 선언

위 3개 예제는 모두 동일하게 사용 가능

   

  1. 타입이 정의되면 이 타입으로 같은 형의 변수를 여러 번 선언할 수 있다.
  2. 이 타입으로부터 파생되는 유도형 변수를 선언할 수 있다.

    tag_test *pTest ; // 포인터 변수 선언

    teg_test arTest[10] ; // 배열로 선언

  3. 구조체를 함수의 인수나 리턴값으로도 사용 할 수 있다.

    void output ( tag_test test ) { ... }

    void output ( tag_test *pTest ) { ... }

13.2 멤버의 참조

13.2.1 멤버 연산자

구조체명.멤버명 // 구조체의 멤버를 읽을 때

  • 예제 struct

#include <stdio.h>

#include <string.h>

   

struct tag_Friend

{

char        name[10] ;

int                age ;

double        height ;

} ;

   

void main ( void )

{

struct tag_Friend testf ;

strcpy (testf.name, "유동곤") ;

testf.age = 29 ;

testf.height = 170.2 ;

   

printf(" 이름 = %s, 나이 = %d, 키 = %lf\n", testf.name, testf.age, testf.height ) ;

}

컴파일러는 구조체가 선언될 때 각 멤버의 오프셋과 타입을 기억해 둔다.

그리고 멤버를 참조하는 문장을 만나면 구조체의 시작 번지에서 오프셋을 더한만큼 이동한 후 이 위치에서 멤버의 타입 길이만큼 값을 읽도록 코드를 생성할 것이다. 이 동작을 하는 연산자가 바로 . 연산자 이다.

13.2.2 포인터 멤버 연산자

  • 구조체 포인터 선언 및 초기화

    tag_friend friend ;

    tag_friend *pfriend ;

    pfriend = & friend ;

  • 구조체 포인터 멤버 접근 방법

    (*pfriend).age = 30 ;

    pfriend->age = 30 ; // 구조체 포인터에서 사용되는 방법, 위 방법보다 직관적이고 간편함

       

  • 예제 StructPointer

#include <stdio.h>

#include <string.h>

   

struct tag_friend

{

char name[10] ;

int age ;

double height ;

} ;

   

void main ( void )

{

tag_friend        testfriend ;

tag_friend        *ptestfriend ;

   

ptestfriend = &testfriend ;

   

//< . 연산자 대신 -> 연산자 사용하여 접근

strcpy (ptestfriend->name, "유동곤") ;

ptestfriend->age = 30 ;

ptestfriend->height = 170.2 ;

   

printf("이름 = %s, 나이 = %d, 키 = %.1lf\n", ptestfriend->name, ptestfriend->age, ptestfriend->height) ;

}

13.2.3 구조체 배열

  • 선언

    tag_friend arJuso[10] ; // 배열과 똑같다

  • 접근 방법

    arJuso[0].age = 30 ;

       

  • 구조체 포인터 배열 선언

    tag_friend *pJuso[10] ; // 구조체를 가리킬 수 있는 포인터 배열

  • 접근 방법

    pJuso[3]->age = 40 ;

  • 예제 StructArrayPointer

#include <stdio.h>

#include <stdlib.h>

   

void main ( void )

{

int i ;

   

struct tag_friend

{

char name[10] ;

int age ;

double height ;

} ;        

struct tag_friend        arJuso[10] ;

struct tag_friend        Friend ;

struct tag_friend        *pJuso[10] ;

   

//< 구조체 배열 사용 예         //< struct tag_friend        arJuso[10] ;

arJuso[5].age = 30 ;

   

//< 구조체에 속한 배열 사용 예 //< struct tag_friend        Friend ;

Friend.name[0] = 'k' ;

   

//< 배열에 속한 구조체에 속한 배열 사용 예

arJuso[1].name[2] = 'j' ;

   

//< 구조체 포인터 배열 사용 예 //< struct tag_friend        *pJuso[10] ;

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

{

pJuso[i] = (struct tag_friend *)malloc(sizeof(struct tag_friend)) ;

}

   

pJuso[3]->age = 40 ;

   

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

{

free(pJuso[i]) ;

}

}

13.2.4 중첩 구조체

다른 구조체를 멤버로 포함하는 구조체이다.

struct tag_a

{

int i ;

} ;

struct tag_b

{

tag_a a ;

int b ;

}

  

  

자기 자신을 포함하는 구조체는 선언할 수 없다.

상호 중첩도 물론 안 된다.

자신과 같은 타입의 구조체에 대한 포인터를 멤버로 가지는 것은 가능하다. (자기 참조 구조체)

자기참조 구조체는 연결 리스트나 트리 구성에 아주 요긴하게 사용되는 자료구조이다.

  • 예제 StructAndArray

#include <stdio.h>

#include <stdlib.h>

   

void main ( void )

{

//< 회원 한명의 신상

struct tag_Friend

{

char name[10] ;                // 이름

int age ;                // 나이

double height ;                // 신장

} ;

//< 동아리에 대한 정보

struct tag_Circle

{

char name[16] ;                        // 동아리 이름

int memNumber ;                // 회원 수

struct tag_Friend member[50] ;        //회원 목록

} ;

   

   

//< 동아리 목록

struct tag_Circle arCircle[10] ;

//< 동아리 목록을 가리키는 포인터

struct tag_Circle *pCircle ;

pCircle = arCircle ;

   

//< 4번째 동아리의 3번째 회원 나이

arCircle[4].member[3].age = 21;

   

//< pCircle이 가리키는 동아리의 3번째 회원의 나이

pCircle->member[3].age = 22 ;

   

//< pCircle이 가리키는 동아리의 3번째 회원의 이름 중 2번째 문자

pCircle->member[3].name[2]= 'm' ;

}

  

13.3 구조체의 초기화

13.3.1 초기화

  • 초기화 방법

    struct tag_Friend Friend = { "유동곤", 30, 170.2 } ; // 구조체 선언과 동시에 초기화

  • 예제 InitStruct

#include <stdio.h>

   

struct tag_Friend

{

char name[10] ;

int age ;

double height ;

} ;

   

void main ( void )

{

struct tag_Friend Friend[10] =

{

{ "김은철", 30, 183.2 },

{ "이노성", 43, 140.4 },

{ "이상엽", 34, 174.6 },

{ "이상문", 28, 179.5 },

} ;

   

printf("세 번째 사람 정보 : 이름 = %s, 나이 = %d, 키 = %.1f\n", Friend[2].name, Friend[2].age, Friend[2].height ) ;

}

배열 크기보다 더 많은 초기식이 있으면 에러

초기식이 부족하면 나머지 요소는 모두 0 으로 초기화

  • 예제 InitStruct2

#include <stdio.h>

   

struct tag_A

{

short i ;

int j ;

} ;

   

struct tag_B

{

double d ;

int ari[3][2] ;

struct tag_A A ;

char ch ;

} ;

   

struct tag_B arb[] =

{

{ 0.0, 0, 0, 0, 0, 0, 0, 0, 0, '0' },                // 모든 멤버 나열

{ 1.0, {{1, 1}, {1, 1}, {1, 1}}, {1, 1}, '1' },        // 완전한 형식

{ 2.0, {{2, 2}, {2, 2},}, {2, 2}, '2' },                // 배열 행의 일부 생략

{ 3.0, {{3, 3}, {3,}, {3, 3}}, {3, 3}, '3' },        // 배열 열의 일부 생략

{ 4.0, {{4, 4}, {4, 4}, {4, 4}}, {4, }, '4' },        // 포함 구조체의 일부 생략

{ 5.0, },                                        // 배열 열의 일부 생략

} ;

   

void main ( void )

{

printf("%f\n", arb[2].d) ;

printf("%c\n", arb[3].ch) ;

printf("%d\n", arb[4].A.j) ;

}

13.3.2 구조체 대입

구조체가 배열과 다른 가장 큰 차이점은 구조체끼리 대입이 가능하다는 점

초기화된 구조체 Friend1의 멤버들을 Friend2에 그대로 대입 가능 하다.

struct tag_Friend Friend1 = { "장달상", 19, 180.0 } ;

struct tag_Friend Friend2 ;

Friend2 = Friend1 ;

대입 연산자의 좌, 우변이 동일한 타입의 구조체여야 가능 하다.

   

구조체끼리의 대입 연산 동작은 구조체의 길이만큼 메모리 복사로 정의되어 있다.

Friend2 = Friend1 // memcpy(&Friend2, &Friend1, sizeof(Friend1)) ;

   

배열의 경우는 타입과 크기가 같더라도 대입 연산자로 사본을 만들 수 없다.

배열의 이름은 시작 번지를 가리키는 포인터 상수 이기 때문에 좌변값이 아니며 대입식의 좌변에 놓일 수 없다.

   

구조체는 대입 가능하기 때문에 함수의 인수나 리턴값으로 사용 할 수 있다.

  • 예제 StructArg

#include <stdio.h>

   

struct tag_Friend

{

char name[10] ;

int age;

double height ;

} ;

   

void OutFriend(struct tag_Friend f) ;

   

void main ( void )

{

struct tag_Friend Friend = { "김상형", 30, 180.0 } ;

OutFriend(Friend) ;

}

   

void OutFriend(struct tag_Friend f)

{

printf("이름 = %s, 나이 = %d, 키 = %.1f\n", f.name, f.age, f.age) ;

}

구조체 전달 방식이 편리한 기능이지만 구조체가 커지면 인수 전달에 그만큼 많은 시간을 필요로 하고 메모리도 많이 소모하기 때문에 구조체보다는 포인터를 사용하는 방법이 더 효율적이다.

  • 예제 StructArgPointer

#include <stdio.h>

   

struct tag_Friend

{

char name[10] ;

int age;

double height ;

} ;

   

void OutFriend(struct tag_Friend *pf) ;

   

void main ( void )

{

struct tag_Friend Friend = { "김상형", 30, 180.0 } ;

OutFriend( &Friend ) ;

}

   

void OutFriend(struct tag_Friend *pf)

{

printf("이름 = %s, 나이 = %d, 키 = %.1f\n", pf->name, pf->age, pf->height ) ;

}

  • 예제 StructRet

#include <stdio.h>

#include <string.h>

   

struct tag_Friend

{

char name[10] ;

int age ;

double height ;

} ;

   

struct tag_Friend GetFriend() ;                //< 리턴값이 구조체인 함수 선언

   

void main ( void )

{

struct tag_Friend Friend ;

Friend = GetFriend() ;

   

printf("이름 = %s, 나이 = %d, 키 = %.1f\n", Friend.name, Friend.age, Friend.height ) ;

}

   

struct tag_Friend GetFriend()

{

struct tag_Friend t;

strcpy(t.name, "아무개") ;

t.age = 22 ;

t.height = 177.7 ;

   

return t ;

}

13.3.3 깊은 복사

  • 예제 ShallowCopy

#include <stdio.h>

   

struct tag_Friend

{

char *pName ;

int age ;

double height ;

} ;

   

void main ( void )

{

struct tag_Friend Albert = { NULL, 80, 165.0 } ;

struct tag_Friend kim ;

   

Albert.pName = (char *) malloc(32) ;

strcpy(Albert.pName, "알버트 아이슈타인") ;

   

kim = Albert ;

printf("이름 = %s, 나이 = %d, 키 = %.1f\n", kim.pName, kim.age, kim.height ) ;

   

strcpy(Albert.pName, "아이작 뉴튼") ;

printf("이름 = %s, 나이 = %d, 키 = %.1f\n", kim.pName, kim.age, kim.height ) ;

   

free(Albert.pName) ;

free(kim.pName) ;

}

얕은 복사(Shallow Copy)

   

정수나 실수 따위의 단순 타입만 있을 경우는 얕은 복사만으로도 완전한 사본을 만들 수 있지만 포인터가 포함되어 있을 경우는 대입에 의해 똑같은 번지를 가리키는 문제가 있다.

이처럼 대입 연산자로 단순 대입하여 구조체의 사본을 만드는 것을 얕은 복사(Shallow Copy)라고 한다.

포인터에 대해서는 별도의 메모리를 할당한 후 내용을 복사해야 두 변수가 완전한 독립성을 가지게 된다. 이런 식으로 포인터 멤버에 대해서는 번지를 바로 대입하지 않고 필요한 길이만큼 따로 할당한 후 원본의 내용만 취하는 복사를 깊은 복사(Deep Copy)라고 한다.

b 10* 깊은 복사(Deep Copy)

13.3.4 Quiz 게임

  • 예제 Quiz

  

13.4 비트 구조체

13.4.1 정의

비트들을 멤버로 가지는 구조체이며 비트 필드(bit field)라고 부른다.

double이나 포인터, 배열은 비트필드가 될 수 없다.

struct 태그명

{

타입 멤버 1 : 비트 수 ;

타입 멤버 2 : 비트 수 ;

타입 멤버 3 : 비트 수 ;

} ;

  • 예제 BitField

#include <stdio.h>

   

struct tag_bit

{

unsigned short a : 4 ;

unsigned short b : 3 ;

unsigned short c : 1 ;

unsigned short d : 8 ;

} ;

   

void main ( void )

{

struct tag_bit bit ;

bit.a = 0xf ;

bit.b = 0 ;

bit.c = 1 ;

bit.d = 0xff ;

   

printf("크기 = %d, 값 = %x\n", sizeof(bit), bit) ;

}

13.4.2 활용

메모리를 구성하는 최소 단위인 1비트까지도 알뜰하게 사용할 수 있다는 것이 장점

요즘은 비트 구조체를 사용하는 경우가 흔하지 않다. 과거 메모리가 비쌀 때는 1바이트라도 아끼기 위해 이런 복잡한 구조체를 사용했지만 요즘은 굳이 이렇게 할 필요가 없다.

13.5 공용체

13.5.1 정의

공용체(Union)는 모든 면에서 구조체와 같으면서 선언 문법이나 사용하는 방법이 동일히다.

다만 공용체에 속한 멤버들이 기억 장소를 공유한다는 것만 다르다.

struct

{

int a ;

short b[2]

} st;

union

{

int a ;

short b[2]

} un;

  • 예제 Union

#include <stdio.h>

   

union tag_kong

{

int a ;

short b[2] ;

} ;

void main ( void )

{

union tag_kong un ;

un.a = 0x12345678 ;

printf("un.a = %x\n", un.a) ;

printf("un.b[0] = %x\n", un.b[0]) ;

printf("un.b[1] = %x\n", un.b[1]) ;

   

un.b[0] = (short)0x9999 ;

printf("대입 후의 un.a = %x\n", un.a) ;

}

  • 공용체가 공간을 공유하는 이유

    두 개의 멤버가 같은 공간에 배치되어 있으면 원하는 타입을 선택해서 읽고 쓸 수 있기 때문이다. 이 때 공용체가 속한 멤버들은 전혀 상관없는 값이 아니라 논리적으로 유사한 값이어야 한다. 예를 들어 똑같은 값의 다른 표현이라든가 값 자체가 표현하는 목적이 동일해야 공용체가 될 수 있다.

13.5.2 이름없는 공용체

  • 예제 Union2

#include <stdio.h>

#include <string.h>

   

enum partType

{

HDD,

MONITER,

KEYBOARD

} ;

   

struct tag_Part

{

enum partType Type ;

char Maker[32] ;

int price ;

int capacity ;

int size ;

int keyNum ;

} ;

   

void main ( void )

{

struct tag_Part A ;

A.Type = HDD ;

strcpy(A.Maker, "SAMSUNG") ;

A.price = 100000 ;

A.capacity = 80 ;

}

struct와 union을 같이 쓰는 방법

여러 정보 중 하나만 사용하는 경우 union으로 묶어 관리 하면 효과적이다

   

struct tag_Part

{

enum partType Type ;

char Maker[32] ;

int price ;

union

{

int capacity ;

int size ;

int keyNum ;

} ;

} ;

capacity는 용량을 나타내는 부분이다.

키보드나 모니터에서는 용량이 필요 없다. size, keyNum도 다른 제품이서는 사용되지 않는 정보이다.

  

 

반응형

'책정리 > 혼자 연구하는 C,C++ 1' 카테고리의 다른 글

16장 함수 고급  (0) 2015.02.20
15장 포인터 고급  (0) 2015.02.19
12장 문자열 함수  (0) 2015.02.19
11장 배열과 포인터  (0) 2015.02.19
10장 포인터  (0) 2015.02.19