[ C 언어 스터디 ]

자료출처 : 핵심 길잡이 C 프로그래밍언어
도서출판 : 성안당


11. 구조체와 공용체


★ 구조체 ( Structure ) ★

구조체 ( Structure )는 데이터 추상화의 중요한 기법 중의 하나로, 하나 이상의 데이
터 타입을 가진 변수들의 모임을 의미한다.
배열은 데이터형의 같고 동일한 성격을 가진 데이터들의 처리에 사용된다. 그러나 ,
일반적으로 데이터는 여러 가지 서로 다른 성격을 지닌 요소들의 집합으로 구성되어
있으며 , 또한 이들 요소들도 각각 다른 데이터형을 가질 수 있다. 예를 들면 , 사원의 인
적 사항은 사원 번호 , 성명 , 성별 , 나이 , 주소 , 전화 번호 등으로 구성될 수 있으며 , 책
( book )인 경우에는 책제목 , 저자 , 출판사 , 출판년도 , 가격 등으로 나타낼 수 있다.

구조체는 이와 같이 성격은 다르지만 서로 관련이 있는 데이터들을 하나로 묶어서
나타낼 수 있는 데이터의 한 가지 형태 ( type )라고 할 수 있다.


< 구조체의 정의 >

구조체를 정의하는 일반 형식은 다음과 같다.

형 식

struct 구조체명
{

구조체 맴버의 선언 ;

} ;

예를 들어 , 신입 사원의 인적 사항을 사원 번호 , 성명 , 성별 , 나이 주소 전화 번호로
나타낸다고 할 때 , 다음과 같이 구조체로 정의할 수 있다.

struct Member
{

int number ; // 사원번호
char name[ 20 ] ; // 성명
char sex ; // 성별
int age ; // 나이
char address[ 30 ] ; // 주소
char tel[ 12 ] ; // 전화 번호

}

여기서, struct는 예약어이며, Member는 구조체명을 나타낸다. 구조체명은 변수가
아니라 구조( structure )의 형태( template )를 나타내는 식별자로서 구조체를 변수를 선언
할 때 사용된다. 그리고 number , name , sex , age 등과 같이 구조체를 구성하는 요소
들을 맴버( member ) 또는 항목( item )이라 한다. 구조체 맴버는 단순 변수나 배열일
수도 있고 , 포인터 변수 , 구조체 변수 및 구조체 포인터 변수일 수도 있다.

구조체의 정의는 실제 기억 장소를 확보하는 것이 아니라 데이터의 구성 형태를 정
의하는 것으로서, 정의된 구조체로 변수를 선언하면 실제 기억 장소가 확보된다. [ 그림
11 -1 ]은 사원의 인적 사항에 대한 구조체형의 기억 형태를 그림으로 나타낸 것이다.


[ 그림 11-1 ] Member 구조체의 기억 형태


< 구조체 변수의 선언 >

구조체 변수는 미리 정의된 구조체를 데이터형으로 갖는 변수를 의미한다. 구조체
변수를 선언하는 형식은 다음과 같다.

형 식
[ 기억 클래스 ] struct 구조체명 구조체변수1 [ , 구조체변수2 , .... , 구조체변수n ] ;

형 식1 ( 구조체와 구조체 변수를 동시에 선언할 경우 )

struct 구조체명
{

구조체 맴버의 선언 ;

}구조체변수1 , 구조체변수2 , ..... , 구조체변수n ;


형 식2 ( 구조체명을 생략한 경우 )

struct
{

구조체 맴버의 선언 ;

}구조체변수1 , 구조체변수2 , ..... , 구조체변수n ;


예를 들어 , 앞에서 정의한 구조체 Member를 사용하여 다음과 같이 구조체 변수를
선언할 수 있다.

struct Member insu , younghi ;

여기서 insu와 younghi는 Member라는 구조체형을 갖는 구조체 변수이다. 또한 구
조체 정의와 구조체 변수의 선언을 동시에 할 수도 있는데 , 예를 들면 다음과 같다.

struct Member // 구조체 Member 정의
{

int number ;
char name[ 20 ] ;
char sex ;
int age ;
char address[ 30 ] ;
char tel[ 12 ] ;

} insu , younghi ; // 구조체 변수 insu , yunghi 선언

또는

struct // 구조체명을 생략한 경우
{

int number ;
char name[ 20 ] ;
char sex ;
int age ;
char address[ 30 ] ;
char tel[ 12 ] ;

} insu , younghi ; // 구조체 변수 insu , younghi 선언


< 구조체 변수의 멤버 참조 >

구조체 변수의 멤버를 참조하고자 할 경우에는 구조체 멤버 연산자( structure mem-
ber operator )인 도트 연산자( dot operator : . )로 나타내야 한다. 도트 연산자는 구조
체의 구성 요소인 멤버를 참조하기 위한 연산자이며 , 일반 형식은 다음과 같다.

형 식
구조체변수 , 구조체멤버명 ;

앞에서 선언된 구조체 변수 insu와 younghi의 각 멤버는 다음과 같이 사용할 수 있다.

insu . number=2012 ; younghi . number=2015 ;
insu . number="인수" ; younghi . number="영희" ;
insu . number='m' ; younghi . number='f' ;
insu . number=35 ; younghi . number=22 ;
insu . number="서울" ; younghi . number="인천" ;
insu . number="02-300-1234" ; younghi . number="032-123-4567" ;


여기서, 구조체 멤버 연산자는 구조체명과 멤버명을 서로 연결시켜 주는 의미를 가
진다. 이때 도트( . )는 연산자이므로 연산 우선 순위가 존재한다. 즉 증감 연산자(++ ,
-- )나 간접 연산자( * )보다 우선 순위가 높고 , 괄호 또는 대괄호( [ ] )와 같은 우선 순
위를 가진다. 그러므로 연산 우선 순위가 도트 연산자보다 낮은 연산자를 먼저 수행하
려면 반드시 괄호로 묶어야 한다.

< 구조체 변수의 초기화 >

구조체 변수도 단순 변수나 배열과 같이 기억 클래스( storage class )를 가진다. 구조
체 변수의 초기화는 배열과 같이 기억 클래스가 static 또는 extern인 경우에만 가능하
다. 구조체 변수의 초기값들은 다음과 같이 중괄호로 묶어서 나타낸다. 초기화를 하지
않은 경우에는 0(zero)으로 초기화된다.

형 식
struct 구조체명 구조체변수명 = { 초기값1 , 초기값2 , ....., 초기값n } ;


예를 들면 ,

struct score // 학생 성적을 정의하기 위한 구조체 score 정의
{

int hakbun ; // 학번
char name[ 20 ] ; // 이름
int internet ; // 인터넷 활용
int os ; // 운영 체제
int access ; // 액세스
int databass ; // 데이터베이스
int se ; // 소프트웨어 공학

} ;
static struct score student = { 201 , "홍길동" , 100 , 98 , 85 , 90 , 89 } ;

또는 다음과 같이 구조체를 정의하면서 구조체 변수의 선언과 동시에 구조체 변수를
초기화할 수 있다.

struct score
{

int hakbun ;
char name[ 20 ] ;
int internet ;
int os ;
int access ;
int databass ;
int se ;

} student = { 201 , "홍길동" , 100 , 98 , 85 , 90 , 89 } ;


다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
struct score // 구조체 정의
{

int hakbun ;
char name[ 20 ] ;
int internet ;
int os ;
int access ;
int databass ;
int se ;

}
void main ( void )
{

static struct score student = { 201 , "홍길동" , 100 , 98 , 85 , 90 , 89 } ;

printf ( "           성 적 일 람 표 \n " ) ;
printf ( "-------------------------------------------------\n" ) ;
printf (" 학번  성명  internet  os  access  databass se \n" ) ;
printf ( "-------------------------------------------------\n" ) ;
printf ( " %4d %9s %8d %4d %8d %10d %4d \n ", student . hakbun , student . name , student . internet , student . os , student . access , student . databass , student . se ) ;

}
▶ 실행 결과

성 적 일 람 표

-------------------------------------------------
학번 성명 internet os access database se
-------------------------------------------------
201 홍길동 100 98 85 90 89
-------------------------------------------------

 

 

< 구조체 배열 >

구조체 배열( array of structure )은 일반 배열과 선언 형식이나 사용 방법이 같다. 단
지 배열 요소가 구조체로 되어 있다. 구조체 배열의 선언 형식은 다음과 같다.

형 식
[ 기억클래스 ] struct 구조체명 배열명 [ 요소의 개수 ] ;


예를 들어 ,

struct person
{

char name[ 20 ] ;
char sex ;
int age ;

} ;

와 같이 구조체가 정의되어 있다면 , 구조체 배열을 다음과 같이 선언할 수 있다.

struct person member[ 5 ] ;

또한 초기값을 고려하면 ,

static struct person member[ 5 ] = { { " Kwon " , 'M' , 22 } , { " Park " , 'F' , 20 } ,
{ " Choi " , 'F' , 22 } , { " Kim " , 'M' , 21 } , { " Jung " , 'M' , 20 } } ;

와 같이 선언할 수 있다. 여기서 배열 member는 5개의 배열 요소들로 이루어져 있으
며 , 각 배열 요소들은 person과 같은 구조체형을 갖는다. 이때 구조체 배열의 항목을
나타낼 경우에는 첨자가 배열의 요소에 붙는다. 즉 ,

member[ 0 ] . name = " Kwon " ; member[ 1 ] . name = " Park " ;
member[ 0 ] . sex = ' M '; member[ 1 ] . sex = ' F ' ;
member[ 0 ] . age = 22 ; member[ 1 ] . age = 20 ;
 
member[ 2 ] . name = " Choi " ; member[ 3 ] . name = " Kim " ;
member[ 2 ] . sex = ' F ' ; member[ 3 ] . sex = ' M ' ;
member[ 2 ] . age = 22 ; member[ 3 ] . age = 21 ;
 
member[ 4 ] . name = " Jung " ;  
member[ 4 ] . sex = ' M ' ;  
member[ 4 ] . age =N20  

그러나 항목 자체가 배열인 경우에는 그 항목에도 첨자를 사용할 수 있다. 예를 들면
다음과 같다.

member[ 0 ] . name[ 0 ]= ' K ' ;
member[ 0 ] . name[ 1 ]= ' w ' ;
member[ 0 ] . name[ 2 ]= ' o ' ;
member[ 0 ] . name[ 3 ]= ' n ' ;
member[ 0 ] . name[ 4 ]= ' \0 ' ;

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
struct person // 구조체 person 정의
{

char name[ 20 ] ;
char sex ;
int age ;

}
void main ( void )
{

int i ;
static struct person member[ 5 ] = { { " Kwon " , 'M' , 22 } , { " Park " , 'F' , 20 } , { " Choi " , 'F' , 22 } , { " Kim " , 'M' , 21 } , { " Jung " , 'M' , 20 } } ;

printf ( " 성명  성별  나이 \n " ) ;
printf ( " ----------------- \n " ) ;
for ( i=0 ; i<5 ; i++ )
printf ( " %-9s   %c  %7d \n " , member[ i ] . name , member[ i ] . sex ,
member[ i ] . age ) ;

}
▶ 실행 결과

성명 성별 나이
-----------------
Kwon m 22
Park F 20
Choi F 22
Kim M 21
Jung M 20



< 중첩된 구조체 >

중첩된 구조체란 하나의 구조체 안에 구조체가 들어 있는 것을 의미한다. 예
를 들면 ,

struct Date // 구조체 Date 정의
{

int year ;
int month ;
int day ;

}

struct person // 구조체 person 정의
{

char name[ 20 ] ;
struct Date birthday ; // 구조체 변수 선언
char sex ;
int age ;

}customer ;

와 같이 선언할 수 있다. 여기서 구조체 person에는 다른 구조체 Date가 포함되어 있
다. 이와 같이 하나의 구조체가 다른 구조체를 포함하는 경우에 , 멤버( 항목 )의 참조는
도트 연산자( . )가 왼쪽에서 오른쪽으로 결합하여 차례로 멤버( 항목 )를 나열해 주면
된다. 예를 들면

customer . name ;
customer . birthday . year ;
customer . birthday . month ;
customer . birthday . day ;

와 같다.

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
struct Date // 구조체 Date 정의
{

int year ;
int month ;
int day ;

} ;

struct person // 구조체 person 정의
{

char name[ 20 ] ;
struct Date birthday ; // 구조체 Date의 변수 선언
char sex ;
int age ;

}customer ; // 구조체 변수 customer 선언

void main ( void )
{

struct person customer = { " 홍길동 " , { 75 , 5 , 25 } , 'M' , 20 } ;
printf ( " 성명  생년월일  성별  나이 \n " ) ;
printf ( " %-9s  %2d / %2d / %2d  %5d  %5d \n " , customer . name ,
customer . birthday . year , customer . birthday . month ,
customer . birthday . day , customer . sex , customer . age ) ;

}
▶ 실행 결과

성명 생년월일 성별 나이
홍길동 75/ 5/25 M 20



< 구조체 포인터 변수 >

구조체 변수를 선언할 때 포인터 형태로 선언할 수 있다. 실제 C 프로그램 작성 시
프로그램 내에서는 구조체 변수가 포인터 형태로 많이 사용된다. 그 이유는 다음과 같다.

① 배열 자체보다 배열에 대한 포인터로 처리하는 것이 더 쉬운 것과 같다 , 구조체
자체보다 구조체에 대한 포인터로 처리하는 것이 더 쉽다.

② 어떤 컴파일러에서는 구조체를 함수의 인수로 전달할 수 없도록 하고 있지만 , 구
조체에 대한 포인터는 함수의 인수로 사용할 수 있다.

③ 많은 데이터을이 구조체로 되어 있으며 , 이들 구조체의 구조체 멤버가 다른 구
조체를 가리키는 포인터 형태로 되어 있다.

구조체 포인터 변수 ( pointers to structures variables )의 선언 형식은 다음과 같다.

형 식 1

strcut [ 구조체명 ]
{

구조체 멤버의 선언 ;

} * 구조체포인터변수명 ;


형 식 2
struct 구조체명 * 구조체포인터변수명 ;


예를 들면 ,

struct Date
{

int year ;
int month ;
int day ;

} ;

struct Date today ; // 구조체 변수 today 선언
struct Date *date_ptr ; // 구조체 포인터 변수 date_ptr 선언
date_ptr = &today ;

여기서 , today는 구조체 변수이고 , date_ptr은 구조체 포인터 변수로 선언되어 있
다. 즉 date_ptr은 Date형의 구조체 변수인 today를 가리키는 포인터 변수로 선언된
것이다. 따라서 , date_ptr은 Date와 같은 구조체를 가리킨다. 이때 *date_ptr은 첫
번째 구조체가 되며 *( date_ptr + 1 )은 두 번째 구조체가 된다.

구조체 포인터 변수를 사용할 때 구조체 멤버를 참조하는 방법은 직접 참조와 간접
참조 두 가지 방법이 있다 즉 , 구조체 멤버를 참조하기 위해 도트 연산자를 이용하여
멤버를 직접 참조하는 방법과 , 포인터 변수를 이용하여 간접적으로 구조체 멤버를 참조
하는 방법이 있다.

구조체 포인터 연산자 ( -> : structure pointer operator )를 사용하여 멤버를 참조하
는 경우의 일반 형식은 다음과 같다.

형 식 1
구조체포인터변수 -> 멤버명


구조체 포인터 연산자는 구조체 변수가 포인터에 의해 지시되는 경우에 사용된다.
일반적인 변수와 동일하게 포인터 조작이 가능하다.

예를 들면 , 앞에서 정의한 구조체 Date에 있는 멤버를 참조하기 위해 구조체 포인터
변수 date_ptr을 이용하면 다음과 같이 나타낼 수 있다.

date_ptr -> year ;
date_ptr -> month ;
date_ptr -> day ;

형 식 2
( * 구조체포인터변수 ) . 멤버명


이때 도트 연산자( . )는 간접 연산자( * )보다 우선 순위가 높기 때문에 반드시 괄호( )
로 묶어 주어야 한다. 이것은 -> 연산자를 사용할 때와 같은 결과를 가진다. 예를 들면 ,

( *date_ptr ) . year ;
( *date_ptr ) . month ;
( *date_ptr ) . day ;

따라서 ,

date_ptr -> year == ( *date_ptr ) . year == today . year ;
date_ptr -> month == ( *date_ptr ) . month == today . month ;
date_ptr -> day == ( *date_ptr ) . day == today . day ;

가 성립된다.

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
void main ( void )
{

struct time // 구조체 time 정의
{

int hour ;
int minute ;
int second ;

}today ;

static struct time today = { 17 , 48 , 25 } ;
struct time * time_ptr ;
time_ptr = &today ;

printf ( " %d시 %d분 %d초입니다. \n " , time_ptr->hour , time_ptr->minute , time_ptr->second ) ;

}
▶ 실행 결과
17시 48분 25초입니다.

 

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
struct address_book // 구조체 address_book 정의
{

int num ;
char *name ;
char *addr ;
char tel[ 15 ] ;

} ;
void main ( void )
{

struct  address_book  *ptr ; // 구조체 포인터 변수 ptr 선언

// 구조체 배열의 선언과 초기화

struct  address_book  person[ ] = { { 1001, "강감찬", "경기도 안산시 물레동 134번지" , "123-4567" } , { 1002, "홍길동", "서울시 강민구 개포동 53번지" , "621-5632" } , { 1003, "이순신", "부산시 동래구 안락동 100번지" , "920-1012" } , { 1004, "정몽주", "경남 마산시 매천동 17번지 " , "123-4567" } , { 1005, "권 율", "인천시 옥야동 324번지" , "100-8734" } } ;

printf ( "                          주 소 록 \n " ) ;
printf ( "-------------------------------------------------------------\n " ) ;
printf ( "번호    이 름                주소                           전 화 \n " ) ;
printf ( "-------------------------------------------------------------\n " ) ;
for ( ptr = person ; ptr<=person+4 ; ptr++ )
printf ( " %-5d %-8s %-30s &-15s \n " , ( *ptr ) . num , ( *ptr ) . name , ( *ptr ) . addr , ( *ptr ) . tel ) ;
printf ( "-------------------------------------------------------------\n " ) ;

}
▶ 실행 결과

주 소 록

-------------------------------------------------------------
  번호 이 름

주소

전화
-------------------------------------------------------------
  1001 강감찬 경기도 안산시 물레동 134번지 123-4567
  1002 홍길동 서울시 강민구 개포동 53번지 621-5632
  1003 이순신 부산시 동래구 안락동 100번지 920-1012
  1004 정몽주 경남 마산시 매천동 17번지 123-4567
  1005 권 율 인천시 옥야동 324번지 100-8734
-------------------------------------------------------------

 


< 구조체와 함수 >

C 언어에서 구조체는 많은 제약이 있다. 즉 구조체 자체를 함수에 전달하거나 함수
로부터 전달받을 수 없다. 또한 오래된 컴파일러에서는 구조체의 전체 데이터를 한꺼번
에 다른 구조체의 값으로 대입할 수 없다.

구조체에 대하여 실행 가능한 연산자는 주소 연산자 ( & ) 뿐이다. 구조체 멤버는 하나
씩만 접근( access ) 할 수 있다. 그러므로 같은 형태의 구조체 변수들일지라도 구조체 변
수 자체를 다른 구조체 변수에 대입할 수 없으며 , 구조체 변수를 함수로 전달 ( 즉 함수
의 실인수로 사용 ) 하거나 함수의 결과값으로 리턴( return ) 할 수도 없다. 그러나 함수
에 구조체에 관한 정보를 전달할 수는 있다. 예를 들면 ,

struct  Body
{

char  name[ 20 ] ;
int  height ;
float  weight ;

} ;

static struct Body man = { "Hong Gil Dong " , 180 , 65.5 } ;
struct Body woman ;

에서 구조체 변수 man을 woman으로 전체를 복사하려면

woman . name = man . name ;
woman . height = man . height ;
woman . weight = man . weight ;

와 같이 항목별로 각각 처리해야 한다. 그러나 최근의 컴파일러에서는

woman = man ;

이 허용된다. 또한 어떤 컴파일러에서는 구조체 변수 자체를 함수의 실인수로 사용하는
것을 제한하고 있으나 , 항목이 하나의 값만을 가지는 변수라면 항목은 실인수로 사용이
가능하다.

일반적으로 구조체 전체를 실인수로 사용해야 하거나 , 함수의 결과값으로 사용해야
할 경우에는 포인터를 사용하면 된다. 그리고 프로그램에서 구조체 변수를 함수의 외부
에 정의한다면 , 모든 함수에서 그 구조체 항목을 사용할 수 있으므로 함수의 실인수를
사용하지 않고도 같은 효과를 얻을 수 있다.

또한 함수의 실인수로 구조체 배열을 사용할 수도 있으며 , 배열명은 배열의 시작 주
소를 나타내는 포인터 상수이므로 함수의 인수로 전달될 수 있다.

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
struct student
{

int num ;
char *name ;
int kor ;
int eng ;
int mat ;

} ;
void main ( void )
{

int i , sum=0 ;
float average ( struct student *ptr ) ;
static struct student exam[ 5 ] = { { 100 , "kim" , 90 , 88 , 75 } , { 101 , "lee" , 100 , 98 , 89 } , { 102 , "hong" , 98 , 75 , 88 } , { 103 , "jong" , 67 , 87 , 75 } , { 104 , "bae" , 88 , 75 , 80 } } ;

printf ( " 학번   성명       국어  영어  수학  합계  평균  \n " ) ;
printf ( "--------------------------------------------------- \n " ) ;
for ( i=0 ; i<5 ; i++ )
{

sum = exam[ i ] . kor + exam[ i ] . eng + exam[ i ] . mat ;
printf ( " %5d   %-9s   %5d  %5d  %5d  %5d  %5.2f \n " , exam[ i ] . num , exam[ i ] . name , exam[ i ] .kor , exam[ i ] . eng , exam[ i ] .mat , sum , average( exam+i ));

}

}
float average( struct student *ptr )
{

float ave ;
ave = ( ptr->kor + ptr->eng + ptr->mat  /  3.0 f ;
return( ave ) ;

}
▶ 실행 결과

학번 성명 국어 영어 수학 합계 평균
---------------------------------------------------
100 kim 90 88 75 253 84.33
101 lee 100 98 89 287 95.67
102 hong 98 75 88 261 87.00
103 jong 67 87 75 229 76.33
104 bae 88 75 80 243 81.00

 


위 예제에서 함수 average( )의 실인수인 배열명 exam은 배열의 시작 주소를
가지는 포인터로서 i 의 값에 따라 다음과 같은 의미를 가진다.

exam = &exam[ 0 ]
exam + 1 == &exam[ 1 ]
exam + 2 == &exam[ 2 ]
exam + 3 == &exam[ 3 ]
exam + 4 == &exam[ 4 ]

< 비트 필드 구조체 >

프로그램을 작성하다 보면 어떤 변수가 i 와 0( zero )의 값 비트만을 필요로 하는 경
우가 있다. 이때 1바이트 ( byte ) 이상의 길이를 차지하는 변수로 선언하여 사용한다면
기억 장소를 낭비하는 것이다. 실제로는 1비트 ( bit )만 있으면 된다.

이와 같이 기억 장소를 효율적으로 사용하기 위하여 C 언어에서는 구조체 항목으로
비트 필드( bit field )를 사용할 수 있다. 비트 필드는 한 워드( word ) 안에 인접한 비트
들의 집합으로 이루어져 있으며 , 데이터형은 unsigned int 형으로만 선언할 수 있다. 그
러나 어떤 시스템에서는 int형도 가능하다 비트 필드로 이루어진 구조체 변수의 선언
형식은 다음과 같다.

형 식

struct [ 구조체명 ]
{

unsigned [ 비트필드명 1 ] : 비트의 크기 ;
unsigned [ 비트필드명 2 ] : 비트의 크기 ;

unsigned [ 비트필드명 n ] : 비트의 크기 ;

}구조체변수명 1 , 구조체변수명 2 , ..... , 구조체변수명n ;


예를 들면 ,

struct
{

unsigned  x : 1 ;
unsigned  y : 1 ;
unsigned  z : 1 ;

} flag ;

에서 flag는 1비트 크기의 x , y, z를 비트 필드로 가지고 있는 구조체 변수이다. 비트
필드를 참조하는 방법은 일반 구조체 멤버를 참조하는 방법과 동일하다. 즉 다음과 같
이 구조체 변수명 . 멤버명 형식을 사용한다.

flag . x = 0 ;
flag . y = flag . z = 1 ;
if ( flag . x == 0 && flag . y == 0 )

비트 필드명을 생략하면 그 비트의 길이만큼 건너 뛴다. 그리고 , 이 부분은 필드명이
없으므로 참조할 수도 없다. 또한 비트 크기를 0( zero )으로 지정하면 , int형으로 경계선
정렬이 이루어져 다음의 비트 필드는 현재의 워드( word )에 남아 있는 비트들을 건너
뛰어 다음 워드( word )의 첫 번째 비트부터 시작하게 된다. 예를 들면

struct
{

unsigned  a : 2 ;
unsigned   b : 4 ;
unsigned     : 0 ;
unsigned   c : 1 ;

} flag ;

에서 flag는 다음과 같은 형태로 기억된다.



비트 필드는 다음과 같이 몇 가지 제약 사항이 있다.

① 비트 필드는 주소를 가지지 못하기 때문에 포인터를 사용할 수 없으며 , 주소 연
산자( & )도 사용할 수없다.

② 비트 필드는 배열로 선언할 수 없다.

③ 한 개의 비트 필드는 int형의 크기를 초과할 수 없으며 , 전체 비트 필드의 합도
int형의 크기를 초과할 수 없다. 또한 , 비트 필드는 int형의 경계에 맞아야 한다.

④ 비트 필드가 기계의 내부에 기억될 때 int형의 왼쪽 비트부터 오른쪽으로 기억될지
또는 오른쪽 비트에서 왼쪽으로 기억될지는 사용하는 시스템에 따라 달라진다.

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
struct
{

unsigned bit0 : 1 ;
unsigned bit1 : 1 ;
unsigned bit2 : 1 ;
unsigned bit3 : 1 ;
unsigned bit4 : 1 ;
unsigned bit5 : 1 ;
unsigned bit6 : 1 ;
unsigned bit7 : 1 ;

} flags , *ptr ;

void main( void )
{

ptr = &flags ;
flags . bit0 = 1 ;
flags . bit1 = 0 ;
flags . bit2 = 0 ;
flags . bit3 = 0 ;
flags . bit4 = 0 ;
flags . bit5 = 0 ;
flags . bit6 = 1 ;
flags . bit7 = 0 ;

printf ( " %x --> %c \n " , flags , *ptr ) ;

}
▶ 실행 결과
41-->A


실행 결과에서 알 수 있듯이 이 시스템은 비트 필드가 오른쪽 비트에서 왼쪽으로 기
억되었다. 즉 , flags의 기억 형태는 다음과 같다.

  비트7 비트0
0 1 0 0 0 0 0 1


★ 공용체 ( Union ) ★


< 공용체의 선언 >

공용체 ( Union )란 서로 다른 자료형을 가진 데이터들이 동일한 기억 장소를 공유하
며 기억되는 형태를 말한다. 그러나 , 어느 한 시점에서 오직 하나의 데이터형만이 그
기억 장소에 기억된다.

공용체의 정의 , 공용체 변수의 선언 및 공용체 변수의 멤버 ( 항목 )를 참조하는 방법
은 예약어 union을 사용한다는 것을 제외하고는 구조체와 동일하다. 공용체는 메모리
를 절약하기 위해 여러 가지 자료형의 데이터를 하나의 기억 장소에 기억시켜 놓고 필
요한 작업을 하고자 할 때 주로 사용된다.

공용체를 정의하는 일반 형식은 다음과 같다.

형 식

union [ 공용체명 ]
{

공용체 멤버 선언 리스트 ;

} ;


예를 들며 ,

union tag
{

int a ;
float b ;
char c ;

} ;

여기서 , 공용체 tag는 int , float , char의 3가지 데이터형 중에서 기억 장소를 가장 많
이 차지하는 float형의 길이 ( 4 byte )만큼의 기억 장소를 확보한 다음 , int , float , char
형의 데이터가 그 기억 장소를 공유하며 기억하게 된다. 그러므로 어느 한 시점에서 그
기억 장소에 기억되어 있는 데이터의 형과 값이 무엇인가를 판단하는 것은 전적으로
프로그래머의 책임이다.

공용체 변수를 선언하는 형식은 다음과 같다.

형 식 1
union 공용체명 공용체변수1 [ , 공용체변수2 , ....... , 공용체변수n ] ;

형 식 2

union [ 공용체명 ]
{

공용체 멤버 선언 리스트 ;

} 공용체변수1 [ , 공용체변수2 , ....... , 공용체변수n ] ;


예를 들면 , 다음과 같이 공용체 변수를 선언할 수 있다.

union tag
{

int a ;
float b ;
char c ;

} ;
union tag value ;

공용체와 공용체 변수를 각각 선언한 경우

또는

union tag
{

int a ;
float b ;
char c ;

} value ;

공용체와 공용체 변수를 동식에 선언한 경우


공용체의 초기화 방법은 구조체의 초기와 방법과 같다. 즉 , 중괄호 ( { , } )를 사용할
수 있다. 그러나 주의해야 할 점은 공요체의 초기화는 최초의 공용체 멤버( member )
값만 초기값을 배정할 수 있다는 것이다.

union u_test
{

int i ;
double d ;

} ;
union  u_test  data_value = { 12 } ;

여기서 , 공용체 , u_test는 int , double형의 2가지 데이터형 중에서 기억 장소를 가장
많이 차지하는 double형의 길이 ( 8 byte )만큼의 기억 장소를 확보한 다음 int , double
형의 데이터가 그 기억 장소를 서로 공유한다. 이때 초기값 12는 int형으로 i 의 값이
된다.

다음 프로그램은 구조체와 공요체의 크기를 출력하는 프로그램이다. 실행 결과를 나타내시오.

# include < stdio.h >
struct  struct_tag  // 구조체 struct_tag 정의
{

char c1 ;
int i1 ;
float f1 ;

} x ;
union  union_tag  // 공용체 union_tag 선언
{

char c2 ;
int i2 ;
float f2 ;

} y ;

void main ( void )
{

int a , b ;
a = sizeof ( struct struct_tag ) ;  // 구조체의 크기
b = sizeof ( union union_tag ) ; // 공용체의 크기

printf ( " struct의 크기 = %d \n " , a ) ;
printf ( " union의 크기 = %d \n " , b ) ;

}
▶ 실행 결과
struct의 크기 = 12
union의 크기 = 4


< 공용체 변수의 멤버 참조 >

공용체 변수의 멤버를 참조하는 것은 구조체의 멤버를 참조하는 것과 같다. 그 형식
은 다음과 같다.

형 식

공용체변수명 . 멤버명
또는
공용체포인터변수명 -> 멤버명


예를 들어 , 공용체 Data를 다음과 같이 선언한다고 하자 .

union Date
{

int num ;
double feet ;
char letter ;

} ;
union Data sample ;

위와 같이 공용체 변수 sample이 선언되었다면 8바이트로 구성된 기억 장소
sample에 대해 각 멤버를 다음과 같이 참조할 수 있다.

sample . num = 7 ;
sample . feet = 20.57 ;
sample . letter = 'a' ;

또한 공용체 변수는 배열이나 포인터 형태로도 선언될 수 있다. 예를 들면,

union Data number[ 5 ] ; // 공용체 배열을 선언하는 경우

union Data *ptr ; // 공용체 포인터 변수를 선언하는 경우
ptr = &number ;

위의 공용체 변수들은 다음과 같은 형태로 사용된다.

number[ 1 ] . num = 20 ;   ptr -> num = 70 ;
number[ 1 ] . feet = 23.78 ;  ptr -> feet = 0.12 ;
number[ 1 ] . letter = 'A' ;  ptr -> letter = 'B' ;

공용체는 구조체나 배열 속에 포함되어 나타날 수 있으며 , 반대로 공용체 내에 구조
체나 배열을 포함할 수도 있다. 이때 멤버를 참조하는 방법은 중첩된 구조체에서 멤버
를 참조하는 방법과 같다. 예를 들면 ,

struct
{

int number ;
char name[ 20 ] ;
union
{

int age ;
float weight ;
char sex ;

} data ;

} table[ 5 ] ;
에서 각 멤버는 다음과 같이 나타낼 수 있다.

table[ 0 ] . data . age = 21 ;
table[ 0 ] . data . weight = 12.345 ;
table[ 0 ] . data . sex = 'M' ;

table[ 0 ] . number = 1001 ;
table[ 0 ] . name = "Hong" ;

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
void main ( void )
{

union person
{

char *name ;
int age ;
char *job ;

} ;
union person list ;

list . name = "kim" ;
printf ( " %8s " , list . name ) ;

list . age = 27 ;
printf ( " %6d세 " , list . age ) ;

list . job = " office worker " ;
printf ( " %10s \n " , list . job ) ;

}
▶ 실행 결과
kim 27세 office worker


★ 형의 정의 ( Type define ) ★

C 언어에서는 프로그래머가 기존의 데이터형 이름을 새로운 데이터형 이름으로 정의
할 수 있는 typedef이라는 명령이 있다. 새로운 형을 정의하는 typedef의 일반 형식
은 다음과 같다.

형 식
typedef   기존의  데이터형  새로운  데이터형 ;

여기서 , 기존의 데이터형은 int , float , char 등과 같은 시스템 데이터형이거나 프로그
매머가 typedef문으로 미리 선언한 새로운 데이터형일 수 있다. 예를 들어 , int형을
integer라는 이름으로 사용하고자 한다면 다음과 같이 선언하면 된다.

typedef  int  integer ;

이와 같이 선언하면 , integer를 int와 같이 정수형을 나타내는 새로운 데이터형으로
간주한다. 즉 , integer로 선언한 변수들은 int형으로 선언한 것과 같은 결과를 얻게
된다.

예를 들면 ,

integer a , b , c ;


int a , b , c ;
는 동일하다. 즉 변수 a , b , c는 모두 정수형 ( int )으로 취급된다.

일반적으로 기존의 데이터형과 구분하기 위해 새로운 데이터형은 대문자를 많이 사
용한다. 예를 들면

typedef int INTEGER
typedef float REAL
typedef REAL SILSU

INTEGER i , j , k ; ==> int i , j , k 와 같다.
REAL  x , y[ 10 ] , *ptr ; ==> float x , y[ 10 ] , *ptr과 같다.
SILSU z ; ==> float z 와 같다.

또한,

typedef  char  *string ;

에서 새로운 데이터형 string은 char * , 즉 문자형 포인터 변수를 나타낸다. 따라서 ,

string  name , address ;


char *name , *address ;
와 같다. 또한

typedef  char  string[ 10 ] ;
string letters , text ;


char letters[ 10 ] , text[ 10 ] ;
와 동일하다. typedef문은 구조체에서도 사용할 수 있다, 예를 들면 ,

typedef  struct
{

float a ;
float b ;

} complex ;
complex value ;

와 같이 complex는 다른 구조체 변수를 선언하는 데 사용할 수 있다. 이것은

struct
{

float a ;
float b ;

} value ;

를 의미한다.

C 언어에서 typedef문과 #define문은 데이터형을 정의하는 관점에서 볼 때 비슷한
점들을 많이 가지고 있지만, 다음과 같은 차이가 있다.

① typedef는 #define과 달리 데이터형을 정의하는 데에만 사용된다.
② typedef는 컴파일러에 의해 실행되고 #define은 선행처리기 ( perprocessor )에
의해 실행된다.
③ 데이터형의 정의는 #define도 가능하지만 typedef 보다 융통성이 많다.

C 언어에서 기존의 데이터형을 typedef로 다시 선언하여 사용하는 이유는 다음과
같다. typedef문은 데이터형을 보다 편리하고, 쉽게 인식할 수 있도록 해 준다. 그리
고 C 언어의 데이터형은 컴퓨터에 따라 기억되는 크기가 다를 수 있다. 예를 들어 ,
프로그램이 16비트( 2byte ) 길이의 정수형 변수를 필요로 한다면 어떤 시스템에서는
int로 선언하면 된다. 그러나 다른 시스템에서는 int가 32비트의 길이를 가지므로 이
때는 short형으로 선언해 주어야 한다. 그러므로 변수를 선언할 때 int로 선언하지
않고

typedef  int  TWOBYTE ;

와 같이 선언한 다음 프로그램에서 정수형 변수를 TWOBYTE로 선언하여 사용하면
된다.

이러한 경우 int가 32비트인 컴퓨터에서 프로그램을 실행하려면 ,

typedef  int  TWOBYTE ;



typedef  short  TWOBYTE ;

로 프로그램을 수정해 주면 된다. 따라서 typedef문으로 나타내면 프로그램의 이식성
을 높일 수 있다.

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
typedef  struct
{

int num ;
char name[ 20 ] ;
char *addr ;
char *tel ;

}PERSON ;

void main ( void )
{

int i ;
PERSON man[ 3 ] = { { 100 , "Kwon" , "seoul" , " 123-4567 " } , { 101 , "Choi" , "daegu" , " 532-1123 " } , { 102 , "Lee" , "busan" , " 712-9812 " } } ;

PERSON *ptr ;
ptr = man ;

for ( i=0 ; i<3 ; i++ )
printf ( " %5d  %-9s  %-10s  %-10s \n " , ( ptr + i )->num ,
( ptr + i )->name , ( ptr + i )->addr , ( ptr + i )->tel ) ;

}
▶ 실행 결과

100 Kwon seoul 123-4567
101 Choi daegu 532-1123
102 Lee busan 712-9812


★ 열거형 ( Enumeration type ) ★

열거형 데이터는 데이터가 가질 수 있는 모든 값들을 나열함으로써 정의되는 데이터
들의 집합 구조를 의미한다. 이것은 예약어 enum을 이용하여 새로운 데이터형과 그
데이터형이 사용할 수 있는 상수들을 정의한다 . 열거형 데이터의 정의와 열거형 변수의
선언 형식은 다음과 같다.

형 식 1 ( 열거형 정의 )
enum 열거형이름 { 열거형 상수 리스트 } ;

형 식 2 ( 열거형 변수의 선언 )
enum 열거형이름 열거형변수1 [ , 열거형변수2 , ..... , 열거형변수n ] ;
또는
enum 열거형이름 { 열거형 상수 리스트 } 열거형변수1 [ , 열거형변수2 , ..... , 열거형변수n ] ;

예를 들면 ,
enum  week  { sun , mon , tue , wed , thu , fri , sat } ; // 열거형의 정의
enum  week  x , y ;   //열거형 변수 x , y의 선언

에서 week는 열거형 이름이고 , { sun , mon , tue , wed , thu , fri , sat }는 열거형 상수 리
스트이다. 집합 내의 원소 sun , mon , tue , wed , thu , fri , sat는 열거형 상수
( enumerator )라 한다. 열거형 변수 x와 y는 enum week형에 관련된 열거형 상수 리
스트 중 한 개의 값을 가질 수 있다. 즉 다음과 같이 사용할 수 있다.

x = mon ;
y = thu ;

그러나 , enum으로 정의된 새로운 데이터형은 실제로 정수형 상수값을 가진다. 컴파
일러는 중괄호 { }속에 나열된 열거형 상수 리스트에 대하여 임의의 순서를 지정하지
않는한 순서대로 0 , 1 , 2 , .....와 같이 1씩 증가하며 값을 할당한다. 그러므로 위의 예에
서는 enum week { 0 , 1 , 2 , 3 , 4 , 5 , 6 }로 정의된다. 따라서 x = mon이므로 1이 기억되
며 , y = thu이므로 4가 기억된다.

열거형 상수에 정수값을 대입할 수도 있다. 예를 들면 ,

enum week { sun , mon=10 , tue , wed , thu=30 , fri , sat } ;

와 같이 정의하였다면 mon과 thu는 1과 4가 아니라 10과 30이 된다. 그리고 열거형
상수는 바로 앞의 값에서 1씩 증가되면서 값이 부여되므로 sun=0 , thu=11 , wed=12 ,
fri=31 , sat-32가 된다.

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램
# include < stdio.h >
void main ( void )
{

enum week { sun , mon , tue , wed , thu , fri , sat  } ;
enum week x , y ;
x=mon ;
y=thu ;
printf ( " x=%d   y=%d \n " , x , y ) ;

}
▶ 실행 결과
x=1   y=4