1. 배열 포인터
예시:
#include<stdio.h>
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int *ptr = arr;
printf("%p\n", ptr);
return 0;
}
위 프로그램에서 배열의 0번째 요소를 가리키는 포인터 ptr이 있다. 마찬가지로 배열의 한 요소 대신 전체 배열을 가리키는 포인터를 선언할 수도 있다. 이 포인터는 다차원 배열에 대해 논할 때 특히나 유용하다.
구문:
data_type (*var_name)[size_of_array];
예시:
int (*ptr)[10];
위에서 ptr은 10개 정수의 배열을 가리킬 수 있는 포인터다. 첨자는 간접보다 우선 순위가 높기 때문에 괄호 안에 간접 연산자와 포인터 이름을 넣어야 한다. 여기서 ptr의 유형은 '10개 정수의 배열을 가리키는 포인터'가 된다.
참고로, 배열의 0번째 요소를 가리키는 포인터와 전체 배열을 가리키는 포인터는 완전히 다르다.
예시:
// C program to understand difference between
// pointer to an integer and pointer to an
// array of integers.
#include<stdio.h>
int main()
{
// Pointer to an integer
int *p;
// Pointer to an array of 5 integers
int (*ptr)[5];
int arr[5];
// Points to 0th element of the arr.
p = arr;
// Points to the whole array arr.
ptr = &arr;
printf("p = %p, ptr = %p\n", p, ptr);
p++;
ptr++;
printf("p = %p, ptr = %p\n", p, ptr);
return 0;
}
출력:
p = 0x7fff6463e890, ptr = 0x7fff6463e890
p = 0x7fff6463e894, ptr = 0x7fff6463e8a4
위에서 p는 배열 arr의 0번째 요소를 가리키는 포인터고, ptr은 전체 배열 arr을 가리키는 포인터다. p의 기본 유형은 int이고, ptr의 기본 유형은 '5개의 정수로 구성된 배열'이다. 우리는 포인터 산술이 기본 크기에 비해 수행된다는 것을 알고 있으므로, ptr++를 쓰면 포인터 ptr이 20바이트 앞으로 이동하게 된다.
다음 그림은 포인터 p와 ptr을 보여준다. 화살표는 배열에 대한 포인터를 나타낸다.
포인터 식을 역참조할 때 우리는 그 포인터 식으로 가리키는 값을 얻는다. 배열에 대한 포인터는 배열을 가리키므로, 역참조할 때 우리는 배열을 가져와야 하고 배열의 이름은 기본 주소를 나타낸다. 따라서 배열에 대한 포인터가 역참조될 때마다 우리는 배열이 가리키는 기본 주소를 얻는다.
예시:
// C program to illustrate sizes of
// pointer of array
#include<stdio.h>
int main()
{
int arr[] = { 3, 5, 6, 7, 9 };
int *p = arr;
int (*ptr)[5] = &arr;
printf("p = %p, ptr = %p\n", p, ptr);
printf("*p = %d, *ptr = %p\n", *p, *ptr);
printf("sizeof(p) = %lu, sizeof(*p) = %lu\n",
sizeof(p), sizeof(*p));
printf("sizeof(ptr) = %lu, sizeof(*ptr) = %lu\n",
sizeof(ptr), sizeof(*ptr));
return 0;
}
출력:
p = 0x7fff55adbff0, ptr = 0x7fff55adbff0
*p = 3, *ptr = 0x7fff55adbff0
sizeof(p) = 8, sizeof(*p) = 4
sizeof(ptr) = 8, sizeof(*ptr) = 20
2. 다차원 배열에 대한 포인터
2차원 배열에서 우리는 두 개의 첨자를 사용하여 각 요소에 접근할 수 있는데, 여기서 첫 번째 첨자는 행 번호를 나타내고, 두 번째 첨자는 열 번호를 나타낸다. 2차원 배열의 요소들은 포인터 표기법의 도움으로 접근할 수도 있다. arr가 2차원 배열이라고 가정하고, 우리는 포인터 식 *(*(arr + i) + j)를 사용하여 배열의 모든 요소 arr[i][j]에 접근할 수 있는 것이다. 이것의 유도 방식에 대해 살펴보도록 한다.
2차원 배열 arr[3][4]이 있다고 하면,
int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
컴퓨터의 메모리는 선형으로 구성되어 있기 때문에 2차원 배열을 행과 열에 저장할 수 없다. 행과 열의 개념은 이론적일 뿐, 실제로 2차원 배열은 행 우선 순서로 저장된다. 즉, 행들이 서로 옆에 배치되는 것이다. 다음의 그림은 위의 2차원 배열이 메모리에 어떻게 저장되는지 보여준다.
각 행은 1차원 배열로 간주될 수 있으므로, 2차원 배열은 차례로 배치되는 1차원 배열의 집합으로 간주될 수 있다. 즉, 우리는 2차원 배열을 '차례로 배치되는 것'이라고 말할 수도 있을 것이다. 여기 arr은 각 원소가 4개의 정수로 구성된 1차원 배열인 3개의 원소로 구성된 배열이다.
우리는 배열의 이름이 0번째 1D 배열을 가리키는 상수 포인터이며 주소 5000을 포함한다는 것을 알고 있다. arr은 '4개의 정수로 이루어진 배열을 가리키는 포인터'이므로, 포인터 산술에 따라 arr + 1은 주소 5016을 나타내고 arr + 2는 주소 5032를 나타낸다.
따라서 arr은 0번째 1D 배열, arr + 1 포인트는 1번째 1D 배열, arr + 2 포인트는 2번째 1D 배열을 가리킨다고 할 수 있다.
그래서 일반적으로, 우리는 이렇게 쓸 수 있다.
① arr + i는 arr의 i번째 요소를 가리키므로, 재역참조 시 당연히 1D 배열인 arr의 i번째 요소를 얻을 것이다. 따라서 *(arr + i)라는 식을 통해 1D 배열의 기본 주소를 알 수 있다.
② 포인터 식 *(arr + i)는 첨자 식 arr[i]와 같다. 따라서 arr[i]와 같은 *(arr + i)는 1D 배열의 기본 주소를 알려준다.
③ 2D 배열의 개별 요소에 액세스하려면 1D 배열의 j번째 요소에 액세스할 수 있어야 한다.
④ *(arr + i)의 기본 유형은 int이고, i번째 1D 배열의 0번째 요소의 주소를 포함하므로, *(arr + i)에 정수 값을 추가하면 i번째 1D 배열의 뒤의 요소의 주소를 얻을 수 있다.
⑤ 예를 들어 *(arr + i) + 1은 1D 배열의 i번째 요소 중 첫 번째 요소의 주소를 나타내고 *(arr + i) + 2는 1D 배열의 i번째 요소의 2번째 주소를 나타낸다.
⑥ 마찬가지로, *(arr + i) + j는 i번째 1D 배열의 j번째 원소의 주소를 나타낸다. 이 식을 역참조하면, i번째 1D 배열의 j번째 요소를 얻을 수 있다.
포인터와 3차원 배열:
int arr[2][3][2] = { {{5, 10}, {6, 11}, {7, 12}}, {{20, 30}, {21, 31}, {22, 32}} };
3차원 배열에서 우리는 세 개의 첨자를 사용하여 각 요소에 접근할 수 있다. 우리는 3차원 배열을 마치 2차원 배열이라고 생각할 수도 있다. 즉, 3차원 배열의 각 요소가 2차원 배열이라고 생각하는 것이다. 3차원 배열 arr은 각 요소가 2차원 배열인 두 요소로 구성된 배열이라고 생각할 수 있다. 배열 arr의 이름은 0번째 2차원 배열을 가리키는 포인터인 것이다.
따라서 포인터 식 *(*(*(arr + i ) + j ) + k)는 첨자 식 ar[i][j][k]와 같다.
우리는 식 *(arr + i)가 arr[i]와 동등하고 식 *(*(arr + i) + j)가 arr[i][j]와 동등하다는 것을 알고 있다. 따라서 우리는 arr[i]가 i번째 2D 배열의 기본 주소를 나타내고, arr[i][j]가 j번째 1D 배열의 기본 주소를 나타낸다고 말할 수 있다.
포인터를 사용한 3D 배열의 예시:
// C program to print the elements of 3D
// array using pointer notation
#include<stdio.h>
int main()
{
int arr[2][3][2] = {
{
{5, 10},
{6, 11},
{7, 12},
},
{
{20, 30},
{21, 31},
{22, 32},
}
};
int i, j, k;
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
for (k = 0; k < 2; k++)
printf("%d\t", *(*(*(arr + i) + j) +k));
printf("\n");
}
}
return 0;
}
출력:
5 10
6 11
7 12
20 30
21 31
22 32
아래 그림은 위 프로그램에 사용된 3D 배열이 메모리에 저장되는 방식을 보여준다.
3. 포인터 배열 첨자하기
arr이 3개의 행과 4개의 열로 구성된 2D 배열이고, ptr이 4개의 정수로 구성된 배열에 대한 포인터이고, ptr이 배열 arr의 기본 주소를 포함한다고 가정하자.
int arr[3][4] = {{10, 11, 12, 13}, {20, 21, 22, 23}, {30, 31, 32, 33}};
int (*ptr)[4];
ptr = arr;
ptr은 첫 번째 행 2D 배열, 즉 4개의 정수로 이루어진 배열에 대한 포인터이므로, ptr + i는 i번째 행을 가리킬 것이다. ptr + i를 역참조하면 i번째 행의 기본 주소를 얻을 수 있다. i번째 행의 j번째 요소의 주소에 접근하려면 포인터 식 *(ptr + i)에 j를 추가할 수 있다. 따라서 포인터 식 *(ptr + i) + j는 i번째 행의 j번째 요소의 주소를 제공하고, 포인터 식 *(*(ptr + i) + j는 i번째 행의 j번째 요소의 값을 제공한다.
우리는 포인터 식 *(*(ptr + i) + j)가 첨자 식 ptr[i][j]와 동일하다는 것을 알고 있다. 따라서 2D 배열의 기본 주소를 포함하는 포인터 변수가 있으면, 해당 포인터 변수를 이중으로 첨자하여 배열의 요소에 접근할 수 있다.
예시:
// C program to print elements of a 2D array
// by scripting a pointer to an array
#include<stdio.h>
int main()
{
int arr[3][4] = {
{10, 11, 12, 13},
{20, 21, 22, 23},
{30, 31, 32, 33}
};
int (*ptr)[4];
ptr = arr;
printf("%p %p %p\n", ptr, ptr + 1, ptr + 2);
printf("%p %p %p\n", *ptr, *(ptr + 1), *(ptr + 2));
printf("%d %d %d\n", **ptr, *(*(ptr + 1) + 2), *(*(ptr + 2) + 3));
printf("%d %d %d\n", ptr[0][0], ptr[1][2], ptr[2][3]);
return 0;
}
출력:
0x7ffc9556b790 0x7ffc9556b7a0 0x7ffc9556b7b0
0x7ffc9556b790 0x7ffc9556b7a0 0x7ffc9556b7b0
10 22 33
10 22 33
'C++' 카테고리의 다른 글
C++ 함수에 배열 전달 총정리 (0) | 2024.05.29 |
---|---|
C++ 배열 파라미터의 크기를 프린트하는 방법 (0) | 2024.05.29 |
C++ 새로운 연산자를 사용해 2D 배열을 동적 선언하는 방법 (0) | 2024.05.28 |
C++ 다차원 배열 총정리 (0) | 2024.05.26 |
C++ 배열(Arrays) 총정리 (0) | 2024.05.22 |