13-2.멤버의 참조

13-2-가.멤버 연산자

같은 타입의 변수 집합인 배열을 참조할 때는 [ ] 연산자와 첨자를 사용한다. [ ] 연산자안에 참조하고자 하는 배열 요소의 번호인 첨자만 적으면 배열 요소를 읽거나 쓸 수 있다. 다음이 그 예이다.

 

int ar[5];

ar[3]=1;

 

배열 요소를 참조하는 방식이 이렇게 단순한 이유는 배열을 구성하는 모든 요소의 크기가 일정하고 서로 인접해 있기 때문이다. 그래서 단순히 "몇 번째 것"이라는 순서만 지정하면 첨자 연산(포인터와 정수의 덧셈, 그리고 *연산자의 합작 연산)에 의해 원하는 배열 요소를 신속하고 정확하게 찾을 수 있다. 그러나 구조체의 멤버는 타입이 제각각 다르며 크기도 다르다. 따라서 구조체에 속한 멤버를 읽을 때는 "첫 번째 멤버"나 "두 번째 멤버" 따위의 순서값을 사용할 수 없으며 별도의 연산자와 멤버의 이름을 사용해야 한다.

구조체의 멤버를 읽을 때는 멤버 연산자를 사용하는데 모양은 마침표와 같다. 즉 점(.) 하나로 되어 있으며 "구조체명.멤버명" 형식으로 사용한다. 예를 들어 Friend구조체의 Age 멤버값을 읽고자 한다면 Friend.Age라고 쓰면 된다. 다음 예제는 구조체에 정보를 대입해 보고 그 값을 출력한다.

 

: Struct

#include <Turboc.h>

 

struct tag_Friend {

     char Name[10];

     int Age;

     double Height;

};

 

void main()

{

     tag_Friend Friend;

 

     strcpy(Friend.Name,"아무개");

     Friend.Age=30;

     Friend.Height=178.2;

    

     printf("이름=%s, 나이=%d, 키=%.1f\n",Friend.Name,Friend.Age,Friend.Height);

}

 

main 함수 이전에 tag_Friend라는 이름으로 구조체 태그를 먼저 선언했는데 열거형이나 구조체같은 사용자 정의 타입은 가급적이면 main 함수 이전에 선언해야 모든 함수에서 이 타입을 사용할 수 있다. main 함수 안에 구조체 태그를 선언할 수도 있지만 이렇게 되면 이 구조체는 main 함수 안에서만 사용할 수 있는 지역 타입이 된다. 태그 정의는 실제로 변수를 생성하는 것도 아니므로 가급적이면 앞쪽에 선언하여 컴파일러가 먼저 알 수 있도록 하는 것이 좋다.

main 함수에서는 태그로부터 Friend라는 이름의 구조체 변수를 선언했다. Friend는 Name, Age, Height 등의 멤버를 가지는데 이 멤버들을 읽고 쓸 때는 멤버 연산자를 사용한다. Age 멤버에 30을 대입하려면 Friend.Age=30; 이라는 대입문을 사용하며 Height 멤버의 값을 읽고 싶을 때는 Friend.Height라고 쓴다.

구조체 멤버들은 크기가 제각각이기 때문에 배열처럼 단순한 곱셈으로 멤버의 위치를 찾을 수 없으며 구조체 시작 번지로부터 멤버까지의 거리인 오프셋(offset)을 더해 멤버를 읽는다. Friend.Height 연산문에 의해 Height멤버가 읽혀지는 과정을 보자. 구조체 변수는 멤버들의 집합이며 메모리에 생성될 때 선언된 멤버가 순서대로 할당된다. Friend 구조체는 다음과 같은 모양을 가지며 메모리상의 위치는 실행할 때마다 달라지지만 설명의 편의상 1000번지에 생성되었다고 하자.

오프셋은 구조체의 시작 번지에서 멤버까지의 거리인데 이 값은 자기보다 앞에 있는 멤버들의 크기의 총합과 같다. 첫 번째 멤버인 Name의 오프셋은 0이며 Age는 Name의 크기만큼 더한 오프셋 10을 가지고 Height는 자기보다 앞에 선언되어 있는 Name과 Age의 크기의 합 14만큼의 오프셋을 가진다. 이 상태에서 멤버 연산자로 Height를 읽으면 이 연산자는 구조체의 시작 번지에서 오프셋을 더해 멤버의 시작 번지를 찾고 이 번지에서 멤버의 타입만큼 값을 읽는다. Height의 오프셋은 14이므로 1000+14=1014번지에서 시작되며 이 멤버가 double형으로 정의되어 있으므로 이 번지에서부터 8바이트를 읽으면 된다.

컴파일러는 구조체가 선언될 때 각 멤버의 오프셋과 타입을 기억해 둔다. 그리고 멤버를 참조하는 문장을 만나면 구조체의 시작 번지에서 오프셋을 더한만큼 이동한 후 이 위치에서 멤버의 타입 길이만큼 값을 읽도록 코드를 생성할 것이다. 이런 동작을 하는 연산자가 바로 . 연산자이다.

배열 요소와 마찬가지로 멤버는 구조체에 소속되어 있을 뿐이지 일반 변수와 완전히 같은 자격을 가진다. Friend의 멤버 Age는 정수형으로 선언되어 있으므로 정수값을 기억하며 정수형 변수가 사용될 수 있는 곳이면 어느 곳에든지 올 수 있다. 다만 구조체에 속해 있기 때문에 이 값을 읽거나 쓸 때 반드시 멤버 연산자로 어느 구조체에 속해 있는지를 밝혀야 한다는 것만 다르다.

구조체 멤버의 통용 범위는 구조체 내부로 국한되며 구조체 없이 멤버 홀로 사용될 수는 없다. 그래서 멤버를 참조할 때는 반드시 앞에 소속을 밝혀야 하는데 이는 소속이 다르면 멤버의 이름이 같아도 상관없다는 얘기도 된다. 다음과 같이 다른 구조체 내에서 같은 이름으로 멤버를 각각 선언할 수 있다.

 

struct {

     int Value;

     double Ratio;

     char Name[10];

} Score;

struct {

     double Value;

     char Product[32];

     int Num;

} Sales;

 

Score 구조체와 Sales 구조체에 Value라는 같은 이름의 멤버가 각각 선언되어 있으며 타입도 서로 다르다. 하지만 두 멤버의 소속이 다르기 때문에 이렇게 명칭이 중복되어도 모호하지 않으며 아무런 문제가 없다. 두 멤버를 사용할 때는 항상 Score.Value와 Sales.Value 식으로 소속된 구조체를 밝혀야 하며 컴파일러는 구조체 선언을 알고 있으므로 각 멤버의 타입을 정확하게 구분할 수 있다.