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개 예제는 모두 동일하게 사용 가능
- 타입이 정의되면 이 타입으로 같은 형의 변수를 여러 번 선언할 수 있다.
-
이 타입으로부터 파생되는 유도형 변수를 선언할 수 있다.
tag_test *pTest ; // 포인터 변수 선언
teg_test arTest[10] ; // 배열로 선언
-
구조체를 함수의 인수나 리턴값으로도 사용 할 수 있다.
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 |