13-1-나.구조체 태그

구조체 변수가 딱 하나만 필요하다면 앞에서 예를 든 방법대로 구조체 변수를 바로 선언할 수 있지만 태그를 먼저 정의하고 이 태그로 구조체 변수를 선언하는 것이 더 일반적이다. 구조체 태그는 열거형의 태그와 마찬가지로 타입에 대해 이름을 붙이는 것이다. 태그를 사용하여 구조체를 정의하는 형식은 다음과 같다.

 

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키워드를 태그명앞에 일일이 붙이지 않아도 된다. 단, 이렇게 하려면 파일의 확장자를 반드시 CPP를 붙여 C++ 문법으로 컴파일해야 한다.

새로운 타입을 정의하는 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;

 

태그도 정의하면서 변수도 같이 선언하는 형식인데 두 개의 문장을 하나로 합친 것 외에는 별 차이가 없다. 코드 길이가 한줄 더 짧아지는 효과가 있기는 하지만 자주 애용되는 방법은 아니다.