10. 2차원 포인터 이것만은 알아두자.
지금까지는 2차원 포인터의 개념에 대해서 설명하였다.
지금부터는 2차원 포인터 변수를 이용하여 어떻게 값을 취하는지 중점적으로 살펴보기로 하자.
2차원 포인터에서는 다음의 문장들만 완벽하게 이해한다면 큰 무리 없이 배열 요소의 값을 취할 수 있게 된다.
int arr[3][2] = { 3, 5 , 12, 54, 534, 923 };
int (*p)[2];
p = arr;
p -----> 1
*p = *(p +0) -----> 2
p + i -----> 3
* ( p + i ) + j -----> 4
* ( * ( p + i ) + j ) -----> 5
**p -----> 6
1) p
가장 중요한 것은 5번째 이다. 이것을 이애하기 위해서 다른 것이 필요하다고 해도 과언이 아니다.
다음과 같이 배열이 정의되어 있을 때 배열 포인터를 이용하여 하나씩 접근해보자.
int arr[3][2] = { 3, 5 , 12, 54, 534, 923 };
int (*p)[2];
p = arr;
다음 배열 포인터 변수를 이용하면 배열 요소 모두를 출력할 수 있다.
printf("%d\n", * ( * ( p + 0 ) + 0 ) ); // 3
printf("%d\n", * ( * ( p + 0 ) + 1 ) ); // 5
printf("%d\n", * ( * ( p + 1 ) + 0 ) ); // 12
printf("%d\n", * ( * ( p + 1 ) + 1 ) ); // 54
printf("%d\n", * ( * ( p + 2 ) + 0 ) ); // 534
printf("%d\n", * ( * ( p + 2 ) + 1 ) ); // 923
가장 안쪽의 괄호와 수치는 행과 관련이 있고 바깥쪽의 괄호와 수치는 행의 배열 요소와 관련이 있다는 것을 알 수 있다.
이것을 다음과 같이 출력 할 수도 있다.
printf("%d\n", * ( p [ 0 ] + 0 ) );
printf("%d\n", * ( p [ 0 ] + 1 ) );
printf("%d\n", * ( p [ 1 ] + 0 ) );
printf("%d\n", * ( p [ 1 ] + 1 ) );
printf("%d\n", * ( p [ 2 ] + 0 ) );
printf("%d\n", * ( p [ 2 ] + 1 ) );
for()문을 이용하면 좀더 간결해질 수 있다. ( 직접 해보기 바란다. )
다시 본문으로 돌아가서 p에 대해서 좀더 알아보자. p는 2차원 배열 포인터 변수를 지칭한다.
2차원 배열 포인터 변수 p를 이용하여 2차원 벼열 모두를 접근할 수 있다. 정의 형식은 다음과 같다.
int arr[3][2] = { 3, 5 , 12, 54, 534, 923 };
int (*p)[2];
주의할 것은 (*p)[2]에서 2라는 수치이다. 이 값은 행을 뜻하는 것이 아니라 열의 갯수를 뜻한다. 왜 열의 개수를 지정할까?
행의 개수를 지정할 수는 없는 것인가? 결론부터 말하면 행의 개수는 지정할 필요가 없다.
또한 위에서 열의 개수 대신 행의 개수를 지정하면 이것은 다른 의미가 되므로( 열의 개수가 3으로 인식함) 역시 사용될수 없다.
왜 그런지 한번 분석해보자.
int arr[][] = { { 10 }, { 32, 64 }, { 53, 62 } } ;
arr배열에서의 행의 개수는 3일 것이다. 그렇다면 열의 개수는?
쉽게 말해서 행의 개수를 컴파일러에게 알려주어도 열의 개수는 추정할 수 없다.
하지만 열의 개수를 알려주면 행의 개수를 얼마든지 추정할 수 있게 된다. 열의 개수는 생략할 수 없다는 것을 명심하자.
이를 다른 말로 하면 " 제 1첨자는 생략 가능해도 다른 첨자는 생략할 수 없다."가 된다.
그러므로 위의 arr정의는 다음과 같이 사용할 수 있다.
int arr[][3] = { { 10 }, { 32, 64 }, { 53, 62 } } ; --->1
int arr[][4] = { { 10 }, { 32, 64 }, { 53, 62 } } ; --->2
1 에서 첫번째 행은 배열 요소 하나만 지정되어 있으므로 두 개의 배열 요소가 생략된 것이고
2 에서 첫번째 행은 배열 요소 세 개가 생략된 것이다. 이와 같이 열을 알면 생략된 배열 요소를 정확히 알 수 있게 된다.
int (*p)[2]에서 2라는 수치는 생략 할 수 없다.이것은 행의 개수가 아니라 열의 개수를 뜻하는 수치이기 때문이다.
이곳에 행의 개수를 사용 해서는 안 된다. 행의 개수만을 지정하면 열의 개수를 알 수 없기 때문이다.
하지만 열의 개수를 지정하면 자동적으로 행의 수를 추정할 수 있으므로 위의 정의가 가능한 것이다. 기억하자.
" 2차원 배열 포인터 변수의 배열 첨자는 열의 개수이다."
int arr[3][2] = { 3, 5 , 12, 54, 534, 923 };
int *p;
p = arr;
arr는 arr라는 배열 전체를 가리키고 있다. 즉, 대상체는 배열 전체가 되므로 sizeof(arr)를 이용하면 24라는 수치가 나온다.
arr를 포인터 변수에 넣기 위해서는 arr 배열 요소 중에서 첫번째 행을 가리킬 수 있는 2차원 배열 포인터 변수가 필요하다.
그에 반해서 p는 가리킬 수 있는 대상체가 어떤 특정한 배열 요소에 국한된다.
대상체는 1차원 배열중에서 가장 첫번째 배열 요소가 되어야 하므로 대상체의 크기는 4바이트가 되어야 한다는 것이다.
그러므로 위의 수식은 성립될 수 없다. 그렇다면 다음 수식은 어떨까?
p = &arr[0][0];
p는 주소를 저장할 수 있는 포인터 변수이며 4바이트 대상체를 가리킬 수 있다.
즉 p 라는 곳에 대상체가 4바이트인 주소를 넣을 수있다는 말과 같으므로 위의 수식은 맞을 수 밖에 없다.
&arr[0][0]은 3이 저장되어 있는 메모리 주소를 뜻한다. 대상체는 3이므로 4바이트가 맞다.
arr[0][0]이 행을 가리키는 것이 아니라 바로 3이라는 수치를 말하기 때문에 위의 수식은 맞는것이다.
int arr[3][2] = { 3, 5 , 12, 54, 534, 923 };
int *p;
p = &arr[0][0];
아무 이상없이 컴파일이 된다. 즉, 문법적으로 맞는다는 이야기가 된다.
그런데 왜 복잡하게 (*p)[2] 라고 정의를 하느냐?
다음을 보자.
int arr[3][2] = { 3, 5 , 12, 54, 534, 923 };
int *p;
p = &arr[0][0];
printf("%d\n",**p);
printf("%d\n",*(p[1] + 0 ) );
첫번째 출력문은 3을 출력하기 위한 작업이다.
**p는 *(p[0] + 0 ) 과 같은 것이므로 첫번재 행의 첫번째 배열 요소를 출력하게 되는 것이다.
두번재 출력문은 두번째 행의 첫번째 배열 요소인 5를 출력하기 위하여 사용하였다.
이 자체의 문법은 맞지만 컴파일 해보면 에러를 발생시킨다.
error C2100: illegal indirection
위의 수식에서 p는 arr[0][0]이 저장된 곳의 위치인 주소를 가지고 있다. 그리고 그 대상체는 4바이트였다. 즉, 여기서는 3을 가리킨다.
그 이상도 그 이하도 아니다. 딱 그만큼만 인지하고 있다. p는 배열의 행과 열을 알지 못한다. 단지 배열의 가장 첫주소만을 알 뿐이다.
다른 사항은 전혀 알지 못한다. 이 말은 p가 1차원 배열 포인터 변수이며, 2차원 포인터 변수가 아니라는 말도 된다. 배열의 첫 요소인 3을 가리키는 주소를 p에 넘겼고 이제 p는 1차원적으로 배열들을 다루게 되는 것이다. p에 1을 더한다는 것은 행 단위로 움직이는 것이 아니라 배열의 요소 단위로 움직이므로 p[0] + 1 과 같은 수식은 적법하지 않다. p는 오로지 다음과 같은 수식만 가능하다.
p + i ;
이를 확인해보자.
#include<stdio.h>
void main()
{
int arr[3][2] = { 3, 5 , 12, 54, 534, 923 };
int *p;
p = &arr[0][0];
printf("%d\n",*p);
printf("%d\n",*(p + 1) );
printf("%d\n",*(p + 2) );
printf("%d\n",*(p + 3) );
printf("%d\n",*(p + 4) );
printf("%d\n",*(p + 5) );
}
에러 없이 잘 출력되고 있다. 분명히 2차원 배열을 1차원 포인터 변수에 할당했는데도 아주 잘 수행되고 있다.
왜 2차원 배열 포인터를 사용하는 것일까?
int arr[50][30];
위의 배열에서 27행 19열에 저장된 값을 출력하고자 한다.
p는 1차원 배열 포인터 변수이기 때무에 배열의 요소를 접근하려면 1차원적으로 밖에는 할 수 없다. 27행 19열의 값을 취하기 위해서는
26 * 18 의 계산을 먼저 수행해야한다.
이 값을 2차원 배열 포인터로 출력하고자 한다.
* ( * ( p + 26 ) ) + 18 ) 이라고 하면 된다. 어떤것이 더 쉬운가? 특히 정형화된 어떤 게산을하게 된다면 2차원 배열을 1차원 배열 포인터로 받는 것은 엄청난 뇌의 학대를 초래한다. 2차원 배열은 2차원 배열 포인터 변수로 받아야 한다는것을 명심하자.
2) *arr = *(arr + 0 )
1차원 이라면 이것은 p 가 가리키고 있는 것의 값이 된다. 즉 , 정수 하나가 출력될 것이다.
그런데 여기서의 p는 2차원 배열 포인터 변수이므로 * 연산자를 하나만 붙이면 이것은 행 전체를 뜻하게 된다.
*p를 printf문으로 출력하면 주소 값이 출력되는데 이는 행을 대표하는 행의 첫번째 배열 요소가 저장된 곳의 주소 값이 된다.
3) p + i
p + i는 p의 i 번째 제 1부분 배열을 뜻한다. 이것은 수식이므로 배열의 특정한 위치를 나타낼 뿐 행 전체나 부분 배열 전체를 뜻하는 것이 아니다. 만약 행의 전체를 뜻하려면 * 연산자가 필요하다. sizeof로 확실히 증명할 수 있다.
( 이 부분은 이해가 잘 안된다... -0- 다시 한번 읽어봐야겠다. )
printf("%d\n", sizeof( p + 1) );
printf("%d\n", sizeof( * ( p + 1 ) ) );
첫번째 출력문은 4, 두번째 출력문은 8이 나온다.
4) * ( p + i ) + j
* ( p + i ) + j 를 이용하여 행을 가리키는 주소를 구했다. 이제 이를 이용하여 행 전체 중에서 하나의 배열 요소를 구할 차례인데 위의 수식이 바로 그런 행위를 한다. 행에서 몇번째 위치에 있다는 것을 j 가 가리키고 있는 것이다. 주소 + 정수는 주소 라는것을 아주 오래전에 이야기 했다. *( p + i ) 는 i 번째 행을 뜻하는 주소이다. 이것에 j 를 더했으므로 이 결과 역시 주소가 된다.
5) * ( * ( p + i ) + j )
* ( p + i ) + j 를 이용하여 주소를 구하고 여기서 다시 * 연산자를 이용하여 값을 하나 취하고 있다. 이를 이용하면 2차원 배열에서 정수 값을 하나 취할 수 있다.
2차원 배열에서는 특정한 값을 취하기 위해서는 첨자가 두 개 필요하므로 포인터에서는 * 연산자가 두개 필요하다.
6) **p
*p 의 대상체는 2차원 배열 중에서 가장 첫번째 부분 배열을 지칭한다. **p 는 첫번째 부분 배열 중에서 첫번째 배열 요소를 지칭한다.
**p == * ( * ( p + 0 ) + 0 ) == p [0][0]
이제는 자연스럽게 받아들여야 한다.
'dev, tech > c, c++' 카테고리의 다른 글
단순 연결 리스트(Simple Linked List) (0) | 2006.04.07 |
---|---|
연결리스트 (0) | 2006.04.07 |
포인터 #7(배열을 함수의 인자로 넘기는 방법) (0) | 2006.04.06 |
포인터 #6 (배열의 첨자) (0) | 2006.04.06 |
함수에서 인자를 넘기는 방식 (0) | 2006.04.06 |
댓글