13-2-나.포인터 멤버 연산자

여러 번 반복하는 내용이지만 T 타입이 있을 때 T형 배열과 T형 포인터를 언제든지 선언할 수 있다. 그러므로 구조체에 대해서도 배열과 포인터를 선언할 수 있다. 다음 코드는 tag_Friend형 구조체 포인터를 선언및 초기화한다.

 

tag_Friend Friend;

tag_Friend *pFriend;

pFriend=&Friend;

 

Friend 구조체를 먼저 생성하고 이 구조체를 가리킬 수 있는 pFriend 포인터 변수를 선언한 후 여기에 Friend의 번지를 대입했다. 이 상태에서 pFriend 포인터가 가리키는 구조체의 멤버를 참조하고 싶을 때는 다음과 같이 한다.

 

(*pFriend).Age=30;

 

pFriend가 Friend를 가리키고 있으므로 *연산자로 이 포인터가 가리키는 곳을 참조하면 곧 Friend가 된다. (*pFriend)는 곧 Friend와 같으며 따라서 위 연산문은 Friend.Age=30과 같다고 할 수 있다. 위 연산문의 동작을 그림으로 그려보면 다음과 같다.

*pFriend연산문은 1000번지에 있는 tag_Friend형의 구조체를 먼저 읽으며 다음으로 멤버 연산자에 의해 이 번지의 오프셋 10에 저장된 Age 멤버를 읽는다. pi가 i를 가리키고 있을 때 *pi가 곧 i와 같은 것처럼 *pFriend는 Friend와 같으므로 *pFriend에 대해 멤버 연산자를 사용하는 것은 지극히 정상적인 문법이다.

이 문장에서 괄호는 절대로 생략할 수 없는데 왜냐하면 포인터 연산자는 2순위이고 멤버 연산자는 최우선 순위인 1순위이기 때문이다. *pFriend.Age라고 쓰면 컴파일러는 이를 *(pFriend.Age)로 해석하여 멤버 연산자가 먼저 실행되어 에러로 처리된다. 멤버 연산자의 좌변은 반드시 구조체여야 하는데 pFriend는 구조체가 아니라 구조체를 가리키는 포인터이기 때문이다. 그래서 * 연산자가 먼저 실행될 수 있도록 괄호를 반드시 써야 한다.

정리하자면 구조체를 가리키는 포인터 p로 이 구조체의 멤버 m을 읽고 싶다면 (*p).m 연산문을 사용한다. 포인터로부터 구조체 멤버를 참조하는 이런 연산문은 굉장히 자주 사용되는데 괄호를 반드시 써야 하기 때문에 보기에 좋지 않으며 별로 직관적이지도 않아서 가독성을 떨어뜨린다. 그래서 C 설계자는 이럴 때 대신 사용할 수 있는 별도의 포인터 멤버 연산자라는 것을 만들어 놓았는데 이 연산자가 바로 -> 연산자이다. 모양이 화살표와 비슷하다고 해서 화살표 연산자(Arrow Operator)라고 부르기도 하는데 이름이 좀 유치해 보이지만 미국애들도 이 연산자를 Arrow라고 부른다.

포인터 멤버 연산자 ->는 좌변에 구조체 포인터, 우변에 멤버 이름을 취하며 포인터가 가리키는 번지에 저장된 구조체의 멤버를 읽는 연산을 한다. 그래서 (*pFriend).Age를 이 연산자로 고쳐 쓰면 pFriend->Age가 된다. 괄호가 없어서 일단 간단해 보이고 훨씬 더 직관적이다. ->연산자를 말로 바꾼다면 "~번지의 구조체 멤버~"라고 하면 될 것이다. 다음 정의문을 외워 두도록 하자.

 

p 구조체를 가리키는 포인터이고 m 멤버일

(*p).m p->m 같다.

 

이것은 일종의 약속이며 C를 만든 사람이 -> 연산자의 동작을 이렇게 정의해 놓은 것이다. 다음에 -> 연산자를 사용하는 코드를 보게 되면 얼른 (*p).m 문장으로 바꿔 보면 쉽게 이해가 될 것이다. 다음 예제는 앞항의 멤버 연산자를 사용한 예제를 포인터 멤버 연산자로 바꿔 본 것이되 동작은 완전히 동일하다.

 

: StructPointer

#include <Turboc.h>

 

struct tag_Friend {

     char Name[10];

     int Age;

     double Height;

};

 

void main()

{

     tag_Friend Friend;

     tag_Friend *pFriend;

     pFriend=&Friend;

 

     strcpy(pFriend->Name,"아무개");

     pFriend->Age=30;

     pFriend->Height=178.2;

    

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

          pFriend->Name,pFriend->Age,pFriend->Height);

}

 

Friend의 멤버를 직접 읽는 대신 pFriend 포인터로 간접적으로 멤버를 참조했으며 멤버 연산자 . 대신 포인터 멤버 연산자 -> 를 사용했다.