Native/C
구조체
aucd29
2013. 10. 2. 18:47
11-1.구조체
가.정의
구조체는 C의 데이터 타입중에 가장 덩치가 크다. 정수나 실수 또는 문자열 등의 단순한 형태로 나타낼 수 없는 복잡한 데이터를 표현할 때 구조체를 사용한다. 표준 함수 중에 구조체를 사용하는 함수가 있고 윈도우즈에서는 구조체가 아주 흔하게 사용된다. 다행히 구조체는 덩치만 컸지 개념이 간단하기 때문에 별로 어렵지는 않다. 물론 구조체를 이해하는 것과 구조체를 제대로 잘 쓰는 것은 별개의 문제지만 말이다. 일단 구조체의 정의부터 내려 보자.
구조체(Structure)를 문장화하여 정의 내리면 "타입이 다른 변수들의 집합"이라고 할 수 있으며 더 간단하게 표현하면 이종 변수 집합이다. 배열이 타입이 같은 변수들의 집합인 것에 비해 구조체는 다른 타입을 가지는 변수들을 하나의 이름으로 묶어둔 것이다. 한 사람의 신상 정보를 표현하고자 한다면 다음과 같은 변수들이 필요할 것이다.
char Name[10];
int Age;
double Height;
Name은 사람의 이름을 가지는 문자열이며 Age는 나이 정보를 가지는 정수형 변수이고 Height는 이 사람의 키가 얼마나 되는지를 기억한다. 좀 더 자세한 정보들을 기억시키고자 한다면 이외에도 주소나 전화 번호, 혈액형, 성별 따위의 많은 정보들이 필요할 것이다. 보다시피 각 변수들의 타입이 서로 다르기 때문에 배열로 이 변수들을 묶을 수는 없다.
이런 관련성있는 정보들을 하나로 묶어서 선언하고자 할 때 사용하는 타입이 바로 구조체이다. 여러 가지 정보들이 모여서 하나의 완성된 정보를 구성하는 예는 실생활에서도 아주 많이 찾을 수 있다.
도서 정보 : 저자, 출판사, 출판 년도, 총 페이지수, 가격, 도서 번호
상품 정보 : 상품명, 제조사, 용량, 입고일, 매입가, 판매가, 할인율
게임의 주인공 : 현재 위치, 현재 모양, 보유한 아이템, 에너지 상태
관련 정보를 하나의 구조체로 묶어서 선언하면 양이 많은 정보도 쉽게 다룰 수 있으며 함수를 통해 복잡한 정보를 전달하거나 리턴받을 수도 있다. 구조체를 선언할 때는 예약어 struct를 사용한다. 신상 정보를 기억하는 세 개의 변수를 Friend라는 이름의 구조체로 묶어서 표현하고 싶다면 다음과 같이 선언한다.
struct {
char Name[10];
int Age;
double Height;
} Friend;
struct { } 블록안에 구조체에 포함되는 변수들의 목록을 순서대로 선언하고 구조체 변수의 이름을 끝에 적어 주면 된다. 구조체 변수를 선언하는 기본 형식은 "struct { 멤버목록 } 변수명;"이다. 구조체 선언문도 하나의 문장이므로 끝의 세미콜론을 빼먹지 않도록 주의하자.
Name, Age, Height처럼 구조체에 속한 변수들을 멤버(Member)라고 하는데 struct 블록안에 일반 변수를 선언할 때와 똑같은 방법으로 선언하면 된다. 구조체의 멤버가 될 수 있는 타입에는 전혀 제한이 없다. 정수형, 실수형 같은 기본형은 물론이고 포인터, 배열 심지어 구조체가 다른 구조체의 멤버로 포함될 수도 있다. 또한 멤버의 개수에도 제한이 없으므로 원하는만큼 멤버를 선언할 수 있다. 단 static, register같은 기억 부류를 지정한다든가 초기값을 줄 수는 없다.
나.구조체 태그
구조체 변수가 딱 하나만 필요하다면 앞에서 예를 든 방법대로 구조체 변수를 바로 선언할 수 있지만 태그를 먼저 정의하고 이 태그로 구조체 변수를 선언하는 것이 더 일반적이다. 구조체 태그는 열거형의 태그와 마찬가지로 타입에 대해 이름을 붙여 주는 것이다. 태그를 사용하여 구조체를 정의하는 형식은 다음과 같다.
struct 태그명 { 멤버 목록 };
키워드 struct 다음에 태그의 이름을 주고 멤버 목록을 나열한다. 태그도 일종의 명칭이므로 명칭 규칙에만 맞다면 자유롭게 이름을 붙일 수 있는데 관습적으로 구조체 태그는 tag_라는 접두어를 붙이는 경우가 많다. 물론 강제는 아니므로 S나 T 접두를 붙일 수도 있고 별 구분을 할 필요가 없다면 접두를 붙이지 않아도 상관은 없다. 다음은 친구의 신상 명세를 기억하는 구조체 타입을 tag_Friend라는 이름으로 선언하는 예이다.
struct tag_Friend {
char Name[10];
int Age;
double Height;
}; // 끝에 세미콜론이 있어야 함
태그 선언문도 일종의 문장이므로 닫는 } 괄호 다음에 세미콜론이 있어야 함을 유의하도록 하자. 초보자들은 흔히 이 위치에 세미콜론을 빼먹는 실수를 종종 한다. 태그 선언문은 컴파일러에게 구조체의 모양이 어떻다는 것을 등록할 뿐이지 태그를 위해 메모리를 할당한다거나 변수를 생성하는 것은 아니며 중복 선언해도 상관없다. 태그 선언에 의해 컴파일러는 tag_Friend라는 태그가 Name, Age, Height를 멤버로 가지는 구조체라는 것을 기억할 것이다. 태그를 한번 등록해 놓으면 이 태그로 구조체 변수를 여러 번 선언할 수 있다.
struct tag_Friend Friend; // C형
tag_Friend Friend; // C++형
구형 C 컴파일러는 태그를 사용할 때 구조체 태그라는 것을 명확하게 알리기 위해 struct라는 키워드를 태그앞에 붙여 주어야 하나 C++에서는 태그가 하나의 타입으로 인정되기 때문에 struct없이 태그명만으로 구조체를 선언할 수 있다. 최근 컴파일러들은 모두 C++ 컴파일러이므로 이제는 귀찮게 struct키워드를 태그명앞에 일일이 붙이지 않아도 된다.
새로운 타입을 정의하는 typedef문을 사용하면 태그를 정의하는 것과 동일한 효과를 낼 수 있다. 다음 선언문은 FriendType이라는 새로운 타입을 정의한다.
typedef struct {
char Name[10];
int Age;
double Height;
} FriendType;
이 선언에 의해 컴파일러는 Name, Age, Height를 멤버로 가지는 구조체 타입을 FriendType이라는 이름으로 새롭게 정의한다. 이 정의에 의해 FriendType은 int, double, char 등과 완전히 동일한 자격을 가지는 사용자 정의 타입으로 인정된다. typedef로 정의한 타입은 태그와는 문법적으로 다른 존재이지만 C++에서는 동일하게 취급되므로 태그로 변수를 선언하듯이 사용자 정의 타입으로도 변수를 정의할 수 있다.
구조체 변수를 바로 선언할 수도 있고 태그나 사용자 타입을 먼저 정의한 후 간접적으로 구조체 변수를 선언할 수도 있다. 그래서 다음 세 선언문에 의해 선언되는 Friend 변수는 동일한 구조체 변수이다.
struct {
char Name[10];
int Age;
double Height;
} Friend;
struct tag_Friend {
char Name[10];
int Age;
double Height;
};
tag_Friend Friend;
typedef struct {
char Name[10];
int Age;
double Height;
} FriendType;
FriendType Friend;
언뜻 보기에는 변수를 바로 선언하는 방식이 훨씬 더 간단하고 편리해 보인다. 태그나 사용자 정의 타입을 통하는 방법은 조금 번거로운 것 같지만 이렇게 타입을 먼저 정의하면 여러 가지 편리한 점이 있다.
타입이 정의되면 이 타입으로 같은형의 변수를 여러 번 선언할 수 있다. tag_Friend라는 태그에 이 구조체의 모양이 이미 저장되어 있으므로 이런 구조체 변수가 필요하면 언제든지 태그로부터 변수를 선언하기만 하면 된다.
tag_Friend A,B,C;
tag_Friend Babo;
만약 태그를 정의할 수 없고 변수를 바로 선언하는 것만 가능하다면 구조체 변수가 필요할 때마다 멤버 목록을 일일이 나열해야 하므로 무척 불편할 것이다.
이 타입으로부터 파생되는 유도형 변수를 선언할 수 있다. 예를 들어 tag_Friend형의 구조체를 가리키는 포인터 변수를 선언하고 싶다거나 이런 구조체 여러 개를 모아 배열을 구성하고 싶다면 다음과 같이 선언하면 된다.
tag_Friend *pFriend;
tag_Friend arFriend[100];
포인터나 배열은 타입으로부터 유도되는 것이지 변수로부터 유도되는 것이 아니므로 이런 변수를 선언하려면 반드시 타입이 먼저 정의되어 있어야 한다. 또한 구조체가 다른 구조체를 포함한다거나 할 때도 타입이 필요하다.
구조체를 함수의 인수나 리턴값으로도 사용할 수 있다. 예를 들어 친구의 신상 명세를 출력하는 OutFriend라는 함수를 만들어야 한다고 해 보자. 이름, 나이, 키 등의 정보를 따로 따로 넘기지 않으려면 구조체를 통채로 넘기거나 아니면 구조체 포인터를 넘겨야 한다. OutFriend 함수는 아마도 다음과 같은 모양을 가지게 될 것이다.
void OutFriend(tag_Friend aFriend) { ... }
void OutFriend(tag_Friend *pFriend) { ... }
함수의 인수 목록에 tag_Friend형의 변수 또는 포인터를 넘기도록 선언했는데 태그가 없다면 "요렇게 요렇게 생긴 구조체를 전달하라"는 선언 자체가 불가능해진다. 컴파일러가 구조체의 모양을 먼저 알아야 함수의 인수나 리턴값으로 구조체를 사용할 수 있다.
이런 여러 가지 이유로 구조체가 필요할 때는 태그나 사용자 정의 타입을 먼저 정의하고 태그로부터 변수를 선언하는 것이 좋다. 참고로 다음과 같은 형식도 가능하다.
struct tag_Friend {
char Name[10];
int Age;
double Height;
} Friend;
태그도 정의하면서 변수도 같이 선언하는 형식인데 두 개의 문장을 하나로 합친 것 외에는 별 차이가 없다. 코드 길이가 한줄 더 짧아지는 효과가 있기는 하지만 자주 애용되는 방법은 아니다.
가.정의
구조체는 C의 데이터 타입중에 가장 덩치가 크다. 정수나 실수 또는 문자열 등의 단순한 형태로 나타낼 수 없는 복잡한 데이터를 표현할 때 구조체를 사용한다. 표준 함수 중에 구조체를 사용하는 함수가 있고 윈도우즈에서는 구조체가 아주 흔하게 사용된다. 다행히 구조체는 덩치만 컸지 개념이 간단하기 때문에 별로 어렵지는 않다. 물론 구조체를 이해하는 것과 구조체를 제대로 잘 쓰는 것은 별개의 문제지만 말이다. 일단 구조체의 정의부터 내려 보자.
구조체(Structure)를 문장화하여 정의 내리면 "타입이 다른 변수들의 집합"이라고 할 수 있으며 더 간단하게 표현하면 이종 변수 집합이다. 배열이 타입이 같은 변수들의 집합인 것에 비해 구조체는 다른 타입을 가지는 변수들을 하나의 이름으로 묶어둔 것이다. 한 사람의 신상 정보를 표현하고자 한다면 다음과 같은 변수들이 필요할 것이다.
char Name[10];
int Age;
double Height;
Name은 사람의 이름을 가지는 문자열이며 Age는 나이 정보를 가지는 정수형 변수이고 Height는 이 사람의 키가 얼마나 되는지를 기억한다. 좀 더 자세한 정보들을 기억시키고자 한다면 이외에도 주소나 전화 번호, 혈액형, 성별 따위의 많은 정보들이 필요할 것이다. 보다시피 각 변수들의 타입이 서로 다르기 때문에 배열로 이 변수들을 묶을 수는 없다.
이런 관련성있는 정보들을 하나로 묶어서 선언하고자 할 때 사용하는 타입이 바로 구조체이다. 여러 가지 정보들이 모여서 하나의 완성된 정보를 구성하는 예는 실생활에서도 아주 많이 찾을 수 있다.
도서 정보 : 저자, 출판사, 출판 년도, 총 페이지수, 가격, 도서 번호
상품 정보 : 상품명, 제조사, 용량, 입고일, 매입가, 판매가, 할인율
게임의 주인공 : 현재 위치, 현재 모양, 보유한 아이템, 에너지 상태
관련 정보를 하나의 구조체로 묶어서 선언하면 양이 많은 정보도 쉽게 다룰 수 있으며 함수를 통해 복잡한 정보를 전달하거나 리턴받을 수도 있다. 구조체를 선언할 때는 예약어 struct를 사용한다. 신상 정보를 기억하는 세 개의 변수를 Friend라는 이름의 구조체로 묶어서 표현하고 싶다면 다음과 같이 선언한다.
struct {
char Name[10];
int Age;
double Height;
} Friend;
struct { } 블록안에 구조체에 포함되는 변수들의 목록을 순서대로 선언하고 구조체 변수의 이름을 끝에 적어 주면 된다. 구조체 변수를 선언하는 기본 형식은 "struct { 멤버목록 } 변수명;"이다. 구조체 선언문도 하나의 문장이므로 끝의 세미콜론을 빼먹지 않도록 주의하자.
Name, Age, Height처럼 구조체에 속한 변수들을 멤버(Member)라고 하는데 struct 블록안에 일반 변수를 선언할 때와 똑같은 방법으로 선언하면 된다. 구조체의 멤버가 될 수 있는 타입에는 전혀 제한이 없다. 정수형, 실수형 같은 기본형은 물론이고 포인터, 배열 심지어 구조체가 다른 구조체의 멤버로 포함될 수도 있다. 또한 멤버의 개수에도 제한이 없으므로 원하는만큼 멤버를 선언할 수 있다. 단 static, register같은 기억 부류를 지정한다든가 초기값을 줄 수는 없다.
나.구조체 태그
구조체 변수가 딱 하나만 필요하다면 앞에서 예를 든 방법대로 구조체 변수를 바로 선언할 수 있지만 태그를 먼저 정의하고 이 태그로 구조체 변수를 선언하는 것이 더 일반적이다. 구조체 태그는 열거형의 태그와 마찬가지로 타입에 대해 이름을 붙여 주는 것이다. 태그를 사용하여 구조체를 정의하는 형식은 다음과 같다.
struct 태그명 { 멤버 목록 };
키워드 struct 다음에 태그의 이름을 주고 멤버 목록을 나열한다. 태그도 일종의 명칭이므로 명칭 규칙에만 맞다면 자유롭게 이름을 붙일 수 있는데 관습적으로 구조체 태그는 tag_라는 접두어를 붙이는 경우가 많다. 물론 강제는 아니므로 S나 T 접두를 붙일 수도 있고 별 구분을 할 필요가 없다면 접두를 붙이지 않아도 상관은 없다. 다음은 친구의 신상 명세를 기억하는 구조체 타입을 tag_Friend라는 이름으로 선언하는 예이다.
struct tag_Friend {
char Name[10];
int Age;
double Height;
}; // 끝에 세미콜론이 있어야 함
태그 선언문도 일종의 문장이므로 닫는 } 괄호 다음에 세미콜론이 있어야 함을 유의하도록 하자. 초보자들은 흔히 이 위치에 세미콜론을 빼먹는 실수를 종종 한다. 태그 선언문은 컴파일러에게 구조체의 모양이 어떻다는 것을 등록할 뿐이지 태그를 위해 메모리를 할당한다거나 변수를 생성하는 것은 아니며 중복 선언해도 상관없다. 태그 선언에 의해 컴파일러는 tag_Friend라는 태그가 Name, Age, Height를 멤버로 가지는 구조체라는 것을 기억할 것이다. 태그를 한번 등록해 놓으면 이 태그로 구조체 변수를 여러 번 선언할 수 있다.
struct tag_Friend Friend; // C형
tag_Friend Friend; // C++형
구형 C 컴파일러는 태그를 사용할 때 구조체 태그라는 것을 명확하게 알리기 위해 struct라는 키워드를 태그앞에 붙여 주어야 하나 C++에서는 태그가 하나의 타입으로 인정되기 때문에 struct없이 태그명만으로 구조체를 선언할 수 있다. 최근 컴파일러들은 모두 C++ 컴파일러이므로 이제는 귀찮게 struct키워드를 태그명앞에 일일이 붙이지 않아도 된다.
새로운 타입을 정의하는 typedef문을 사용하면 태그를 정의하는 것과 동일한 효과를 낼 수 있다. 다음 선언문은 FriendType이라는 새로운 타입을 정의한다.
typedef struct {
char Name[10];
int Age;
double Height;
} FriendType;
이 선언에 의해 컴파일러는 Name, Age, Height를 멤버로 가지는 구조체 타입을 FriendType이라는 이름으로 새롭게 정의한다. 이 정의에 의해 FriendType은 int, double, char 등과 완전히 동일한 자격을 가지는 사용자 정의 타입으로 인정된다. typedef로 정의한 타입은 태그와는 문법적으로 다른 존재이지만 C++에서는 동일하게 취급되므로 태그로 변수를 선언하듯이 사용자 정의 타입으로도 변수를 정의할 수 있다.
구조체 변수를 바로 선언할 수도 있고 태그나 사용자 타입을 먼저 정의한 후 간접적으로 구조체 변수를 선언할 수도 있다. 그래서 다음 세 선언문에 의해 선언되는 Friend 변수는 동일한 구조체 변수이다.
struct {
char Name[10];
int Age;
double Height;
} Friend;
struct tag_Friend {
char Name[10];
int Age;
double Height;
};
tag_Friend Friend;
typedef struct {
char Name[10];
int Age;
double Height;
} FriendType;
FriendType Friend;
언뜻 보기에는 변수를 바로 선언하는 방식이 훨씬 더 간단하고 편리해 보인다. 태그나 사용자 정의 타입을 통하는 방법은 조금 번거로운 것 같지만 이렇게 타입을 먼저 정의하면 여러 가지 편리한 점이 있다.
타입이 정의되면 이 타입으로 같은형의 변수를 여러 번 선언할 수 있다. tag_Friend라는 태그에 이 구조체의 모양이 이미 저장되어 있으므로 이런 구조체 변수가 필요하면 언제든지 태그로부터 변수를 선언하기만 하면 된다.
tag_Friend A,B,C;
tag_Friend Babo;
만약 태그를 정의할 수 없고 변수를 바로 선언하는 것만 가능하다면 구조체 변수가 필요할 때마다 멤버 목록을 일일이 나열해야 하므로 무척 불편할 것이다.
이 타입으로부터 파생되는 유도형 변수를 선언할 수 있다. 예를 들어 tag_Friend형의 구조체를 가리키는 포인터 변수를 선언하고 싶다거나 이런 구조체 여러 개를 모아 배열을 구성하고 싶다면 다음과 같이 선언하면 된다.
tag_Friend *pFriend;
tag_Friend arFriend[100];
포인터나 배열은 타입으로부터 유도되는 것이지 변수로부터 유도되는 것이 아니므로 이런 변수를 선언하려면 반드시 타입이 먼저 정의되어 있어야 한다. 또한 구조체가 다른 구조체를 포함한다거나 할 때도 타입이 필요하다.
구조체를 함수의 인수나 리턴값으로도 사용할 수 있다. 예를 들어 친구의 신상 명세를 출력하는 OutFriend라는 함수를 만들어야 한다고 해 보자. 이름, 나이, 키 등의 정보를 따로 따로 넘기지 않으려면 구조체를 통채로 넘기거나 아니면 구조체 포인터를 넘겨야 한다. OutFriend 함수는 아마도 다음과 같은 모양을 가지게 될 것이다.
void OutFriend(tag_Friend aFriend) { ... }
void OutFriend(tag_Friend *pFriend) { ... }
함수의 인수 목록에 tag_Friend형의 변수 또는 포인터를 넘기도록 선언했는데 태그가 없다면 "요렇게 요렇게 생긴 구조체를 전달하라"는 선언 자체가 불가능해진다. 컴파일러가 구조체의 모양을 먼저 알아야 함수의 인수나 리턴값으로 구조체를 사용할 수 있다.
이런 여러 가지 이유로 구조체가 필요할 때는 태그나 사용자 정의 타입을 먼저 정의하고 태그로부터 변수를 선언하는 것이 좋다. 참고로 다음과 같은 형식도 가능하다.
struct tag_Friend {
char Name[10];
int Age;
double Height;
} Friend;
태그도 정의하면서 변수도 같이 선언하는 형식인데 두 개의 문장을 하나로 합친 것 외에는 별 차이가 없다. 코드 길이가 한줄 더 짧아지는 효과가 있기는 하지만 자주 애용되는 방법은 아니다.