이전 chapter에 있는 포인터 배열과 포인터 배열의 포인터 형
을 이해하고 이번 chapter를 시작하길 바라며...
int arr[10]; // arr은 int형 포인터
여이서 arr은 int형 포인터이다. 따라서 함수의 인자로 전달되기 위해서는 함수의 매개변수가 int형 포인터로 선언되어야 한다.
그렇다면 2차원 배열의 이름은 어떻게 될까?
(2차원 배열의 이름이 더블 포인터 형은 말도 안되는 소리다.)
2차원 배열에서는 세로와 가로가 있기 때문에 조금 더 복잡해진다.
우선 예제를 살펴보자.
#include <stdio.h>
int main()
{
printf("%d \n", arr2d);
printf("%d \n", arr2d[0]);
printf("%d \n\n", &arr2d[0][0]);
printf("%d \n", arr2d[1]);
printf("%d \n\n", &arr2d[1][0]);
printf("%d \n", arr2d[2]);
printf("%d \n\n", &arr2d[2][0]);
printf("sizeof(arr2d): %d \n", sizeof(arr2d));
printf("sizeof(arr2d[0]): %d \n", sizeof(arr2d[0]));
printf("sizeof(arr2d[1]): %d \n", sizeof(arr2d[1]));
printf("sizeof(arr2d[2]): %d \n\n", sizeof(arr2d[2]));
printf("+) sizeof(arr2d[0][0]): %d \n", sizeof(arr2d[0][0]));
return 0;
}
> 출력
-1338248536
-1338248536
-1338248536
-1338248524
-1338248524
-1338248512
-1338248512
sizeof(arr2d): 36 = 3x3x4
sizeof(arr2d[0]): 12 = 3x4
sizeof(arr2d[1]): 12
sizeof(arr2d[2]): 12
+) sizeof(arr2d[0][0]): 4
2차원 배열에서 첫 번째 요소의 주소 값을 출력하기 위해서는 다음과 같은 형태의 문장으로 나타낼 수 있다.
printf("%p", arr2d);
printf("%p", arr2d[0]);
printf("%p", arr2d[0][0]);
언뜻보면 모두 같은 것을 나타내는 것 같지만,
arr2d
는 첫 번째 요소를 가리키면서 배열 전체를 의미한다.
arr2d[0]
은 첫번째 요소를 가리키되 1행만을 의미한다.
그래서 sizeof 연산의 결과가 다른 것이고, arr2d
와 arr2d[0]
은 서로 다른 것이다.
arr2d[0][0]
의 sizeof 연산 결과는 알다시피 int형이므로 4이다.
1차원 배열에서 포인터 연산을 진행했을 때 포인터 대상의 증가 및 감소는 포인터 형의 크기만큼 이루어졌고 다음 배열로 가리키게 되는 연산이었다.
2차원 배열에서 포인터 연산은 어떻게 될까?
#include <stdio.h>
int main()
{
int arr1[3][2];
int arr2[2][3];
printf("arr1: %p \n", arr1);
printf("arr1+1: %p \n", arr1 + 1);
printf("arr1+2: %p \n\n", arr1 + 2);
printf("arr2: %p \n", arr2);
printf("arr2+1: %p \n\n", arr2 + 1);
return 0;
}
> 출력
arr1: 00000079170FF3C8
arr1+1: 00000079170FF3D0
arr1+2: 00000079170FF3D8
arr2: 00000079170FF3F8
arr2+1: 00000079170FF404
arr1을 대상으로 +1할 때마다 8씩 증가하고, (8 = 2 4)
arr2를 대상으로 +1할 때마다 12씩 증가했다. (12 = 3 4)
2차원 배열이름을 대상으로 증가 및 감소연산을 할 경우,
각 행의 첫 번째 요소의 주소 값으로 이동한다.
2차원 배열이름의 포인터 형에는 다음 두 가지 정보가 함께 담겨야한다.
따라서, 다음 배열이름의 포인터 형을 묻는다면,
int arr[3][4];
배열이름 arr이 가리키는 대상은 int형 변수이고, 포인터 연산 시 sizeof(int)X4의 크기만큼 주소 값이 증감하는 포인터 형이다.
이러한 유형의 포인터 변수 선언은 아래와 같이 한다.
int (*ptr)[4];
이것은 포인터 변수의 선언이며 배열 포인터 변수
라 한다.
ptr이라는 이름의 포인터 변수를 선언한 것이며, 가리키는 대상이 int형이고, [4]번 건너뛴다는 것을 의미한다. (대괄호 안에는 가로 길이를 넣어주면 된다.)
다른 자료형의 배열 포인터 변수를 선언해보자.
char (*arr1)[4];
double (*arr2)[7];
arr1은 char형 변수를 가리키면서, 포인터 연산시 sizeof(char)X4 크기 단위로 값이 증가 및 감소하는 포인터 변수,
arr2는 double형 변수를 가리키면서, 포인터 연산시 sizeof(double)X7 크기 단위로 값이 증가 및 감소하는 포인터 변수.
#include <stdio.h>
int main()
{
int Arr1[2][2] = {
{1, 2}, {3, 4}
};
int Arr2[3][2] = {
{1, 2}, {3, 4}, {5, 6}
};
int Arr3[4][2] = {
{1, 2}, {3, 4}, {5, 6}, {7, 8}
};
int(*ptr)[2];
int i;
ptr = Arr1;
printf("** Show 2,2 arr1 **\n");
for (i = 0; i < 2; i++)
printf("%d %d \n", ptr[i][0], ptr[i][1]);
ptr = Arr2;
printf("** Show 3,2 arr2 **\n");
for (i = 0; i < 3; i++)
printf("%d %d \n", ptr[i][0], ptr[i][1]);
ptr = Arr3;
printf("** Show 4,2 arr3 **\n");
for (i = 0; i < 4; i++)
printf("%d %d \n", ptr[i][0], ptr[i][1]);
return 0;
}
> 출력
** Show 2,2 arr1 **
1 2
3 4
** Show 3,2 arr2 **
1 2
3 4
5 6
** Show 4,2 arr3 **
1 2
3 4
5 6
7 8
다음 두 선언의 차이점이 무엇일까?
int * whoA[4]; // 포인터 배열
int (*whoB)[4]; // 배열 포인터
전자는 배열 선언이고 후자는 포인터 변수 선언이다.
whoA는 int형 포인터 변수로 이루어진 int형 포인터 배열이고,
whoB는 가로길이가 4인 int형 2차원 배열을 가리키는 용도의 포인터 변수이다.
#include <stdio.h>
int main()
{
int num1 = 10, num2 = 20, num3 = 30, num4 = 40;
int Arr2d[2][4] = { 1,2,3,4,5,6,7,8 };
int j;
int* whoA[4] = { &num1,&num2, &num3, &num4 }; // 포인터 배열
int(*whoB)[4] = Arr2d; // 배열 포인터
printf("%d %d %d %d \n\n", *whoA[0], *whoA[1], *whoA[2], *whoA[3]);
for (i = 0; i < 2; i++)
{
for (j = 0; j < 4; j++)
printf("%d ", whoB[i][j]);
printf("\n");
}
return 0;
}
> 출력
10 20 30 40
1 2 3 4
5 6 7 8
차이가 보이는가?
whoA를 이용해서 각 변수들의 포인터들을 배열로 만들었다.
whoB는 Arr2d[2][4]라는 2차원 배열을 가리켰다.
예를 들어 배열 2개가 있고 이 배열의 주소값을 받을 인자는 포인터로 받아야한다.
void SimpleFunc(int (*parr1)[7], double (*parr2)[5])
// void SimpleFunc (int parr1[][7], double parr2[][5])도 가능
{
....
}
int main(void)
{
int arr1[2][7];
double arr2[4][5];
simpleFunc(arr1, arr2);
}
위 내용을 정리한 예제를 하나 보자.
#include <stdio.h>
void ShowArr2DStyle(int(*arr)[4], int column) // 배열 요소 전체 출력
{
int i, j;
for (i = 0; i < column; i++)
{
for (j = 0; j < 4; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
printf("\n");
}
int Sum2DArr(int arr[][4], int column) // 배열 요소의 합 반환
{
int i, j, sum = 0;
for (i = 0; i < column; i++)
for (j = 0; j < 4; j++)
sum += arr[i][j];
return sum;
}
int main()
{
int arr_1[2][4] = { 1,2,3,4,5,6,7,8 };
int arr_2[3][4] = { 1,1,1,1,3,3,3,3,5,5,5,5 };
ShowArr2DStyle(arr_1, sizeof(arr_1) / sizeof(arr_1[0]));
ShowArr2DStyle(arr_2, sizeof(arr_2) / sizeof(arr_2[0]));
printf("arr1의 합: %d \n", Sum2DArr(arr_1, sizeof(arr_1) / sizeof(arr_1[0])));
printf("arr2의 합: %d \n\n", Sum2DArr(arr_2, sizeof(arr_2) / sizeof(arr_2[0])));
return 0;
}
> 출력
1 2 3 4
5 6 7 8
1 1 1 1
3 3 3 3
5 5 5 5
arr1의 합: 36
arr2의 합: 36
함수의 매개변수 선언에 집중해보자.
그리고 sizeof(arr) / sizeof(arr[0])
는 2차원 배열의 세로길이 계산시 흔히 사용되는 방법이니 기억하면 좋다.⭐
1차원 배열 배울 때 (Chapter 13) 배열과 포인터의 관계를 공부하면서, arr[i] == *(arr+i)
는 매우 중요하다는 것을 배웠다.
2차원 배열에서도 동일하게 적용된다.
int arr[3][2] = {{1, 2},
{3, 4},
{5, 6}};
arr[2][1] = 4;
(*(arr+2))[1] = 4;
*(arr[2]+1) = 4;
*(*(arr+2)+1) = 4;
// 위 4개의 문장이 모두 같으 말이다...😂
자,,,, 미치고 환장할 부분 나와버렸다...
괄호와 *
연산자에 대한 이해가 되어야 위를 이해할 수 있다.
예제를 통해 추가로 확인해보자.
#include <stdio.h>
int main()
{
int a[3][2] = {
{1, 2},
{3, 4},
{5, 6}
};
printf("a[0]: %p \n", a[0]);
printf("*(a+0): %p \n", *(a+0));
printf("a[1]: %p \n", a[1]);
printf("*(a+1): %p \n", *(a + 1));
printf("a[2]: %p \n", a[2]);
printf("*(a+2): %p \n", *(a + 2));
printf("%d %d \n", a[2][1], (*(a + 2))[1]);
printf("%d %d \n", a[2][1], *(a[2] + 1));
printf("%d %d \n", a[2][1], *(*(a + 2) + 1));
return 0;
}
> 출력
a[0]: 00000079170FF6D8
*(a+0): 00000079170FF6D8
a[1]: 00000079170FF6E0
*(a+1): 00000079170FF6E0
a[2]: 00000079170FF6E8
*(a+2): 00000079170FF6E8
6 6
6 6
6 6
자아... *(x+i)
는 x[i]
와 같다는 것을 알아야한다.
그렇다면 위에를 하나하나 뜯어보면서 변형이 가능하다는 것을 알 수 있다.
대괄호를 하나 풀 때마다 *( + )
가 따라온다고만 이해하면 저것들을 다 이해할 수 있다!!!
<Review>
정말 말이 어려운 2차원 배열의 포인터였다...
하지만 기본만 잘 기억하고 있다면 응용쯤이야! 할 수 있지 않나~~
빠르게 다음 chpater로 고고우!