제 22강) 다차원배열2 - 다차원배열과 포인터 |
오늘은 다차원배열의 두번째 시간으로 다차원배열과 포인터의 관계에 대해서 알아봅니다.
다시 한번 되짚어 보자면 다차원배열은 2차원 이상의 배열을 다차원이라고 합니다.
1) 다차원 배열 |
다음과 같은 2차원배열이 있다고 합시다.
int arr[2][2];
여기서 "arr"는 2차원배열로 선언된 배열의 이름이자 배열의 첫번째 원소의 주소(번지)를 나타냅니다.
(1차원배열에서와 같습니다.)
#include <stdio.h>
int main()
{
int arr[2][2];
printf("arr = %u\n", arr);
printf("arr[0] = %u\n", arr[0]);
return 0;
}
그래서 번지수가 똑같습니다.
(그러므로 arr은 &arr[0]과 같습니다.)
여기까지는 1차원배열과 같은 내용이지만 2차원배열에서는 하나가 더 추가됩니다.
int arr[2][2];
여기서 "arr[0]"는 두 개의 정수형 원소를 갖는 배열입니다.
(arr[0][0], arr[0][1] 총 2개)
이 중에서 arr[0]는 첫번째 정수형 원소의 주소(번지)인 &arr[0][0]와 같습니다.
#include <stdio.h>
int main()
{
int arr[2][2];
printf("arr[0] = %u\n", arr[0]);
printf("&arr[0][0] = %u\n", &arr[0][0]);
return 0;
}
2) 다차원 배열 연산 |
배열과 포인터 강의에서 배열 연산에 대해서 배웠습니다.
그럼 다차원 배열에서의 연산은 어떻게 될까요?
먼저 다음과 같은 배열이 있다고 합시다.
#include <stdio.h>
int main()
{
int arr[4][2] = { { 1, 2 } , { 3, 4 } , { 5, 6 } , { 7, 8 } };
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("arr[%d][%d] : %p ", i, j, &arr[i][j]);
}
printf("\n");
}
return 0;
}
(배열의 주소를 나열하는 프로그램입니다.)
arr에 1을 더한 값의 주소와 arr[0]에 1을 더한 값의 주소가 같은지 확인하여봅시다.
확실히 하기 위해서 2를 더한 값도 같이 확인하여봅시다.
#include <stdio.h>
int main()
{
int arr[4][2] = { { 1, 2 } , { 3, 4 } , { 5, 6 } , { 7, 8 } };
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("arr[%d][%d] : %p ", i, j, &arr[i][j]);
}
printf("\n");
}
printf("\n");
printf("arr + 1 : %p\n", &arr + 1);
printf("arr[0] + 1 : %p\n", &arr[0] + 1);
printf("arr + 2 : %p\n", &arr + 2);
printf("arr[0] + 2 : %p\n", &arr[0] + 2);
return 0;
}
(7번 ~ 14번줄은 배열 전체의 주소를 보여주는 문장입니다.)
결과는
다음과 같습니다.
arr에 1을 더하면 전체 배열 크기만큼 더해진 주소의 값이, arr[0]에 1을 더하면 arr의 한 줄의 배열 크기만큼 더해진 주소의 값이 출력됩니다.
(배열 안의 값은 주소를 뜻합니다.)
그림으로 보자면 위와 같은 메모리 공간이 있다고 한다면 arr에 1을 더하면 arr전체 배열의 크기만큼 더한 주소의 값이 나오게 됩니다.(빨간 부분 만큼)
arr[0]에 1을 더하면 arr의 한 줄의 배열크기(int형 원소 2개가 들어가는 배열이므로 8) 만큼 더해진 주소의 값이 나오게 됩니다.
3) 이중 포인터, 다중 포인터 |
다차원 배열과 포인터를 엮기 전에, 이중 포인터 즉, 다중 포인터에 대해서 알아봅시다.
int num = 5;
int* ptr = # // 일반적인 포인터(변수를 가리킴)
int** pptr = &ptr; // 이중 포인터(포인터를 가리킴)
위와 같이(3번줄) 포인터 기호(애스터리스크)가 2개 이상인 포인터를 다중 포인터라고 합니다.
(포인터가 3개면 삼중 포인터, 4개면 사중 포인터)
이중 포인터는 포인터와 연결하여 연결된 포인터가 가리키는 메모리를 가리키게 됩니다.
즉, 포인터를 한 번 더 연결하는 셈이죠.
예제를 통해 확인하여봅시다.
#include <stdio.h>
int main()
{
int num = 13;
int* ptr = #
int** pptr = &ptr;
printf("num : %d(%p)\n", num, &num);
printf("*ptr : %d(%p)\n", *ptr, ptr);
printf("**pptr : %d(%p)\n", **pptr, *pptr);
return 0;
}
그럼 값을 조작하여봅시다.
#include <stdio.h>
int main()
{
int num = 13;
int* ptr = #
int** pptr = &ptr;
printf("num : %d(%p)\n", num, &num);
printf("*ptr : %d(%p)\n", *ptr, ptr);
printf("**pptr : %d(%p)\n", **pptr, *pptr);
printf("\n값 조작 → *ptr += 4\n");
*ptr += 4;
printf("num : %d(%p)\n", num, &num);
printf("*ptr : %d(%p)\n", *ptr, ptr);
printf("**pptr : %d(%p)\n", **pptr, *pptr);
printf("\n값 조작 → **pptr -= 10\n");
**pptr -= 10;
printf("num : %d(%p)\n", num, &num);
printf("*ptr : %d(%p)\n", *ptr, ptr);
printf("**pptr : %d(%p)\n", **pptr, *pptr);
return 0;
}
14번 줄에서 ptr을 이용하여 기존의 num의 값에 4를 더해주고
20번 줄에서 pptr을 이용하여 14번줄에서 변경된 num의 값에 10을 빼주는 예제입니다.
4) 다차원 배열과 포인터(이차원배열) |
이제 다차원 배열(이차원배열)에 포인터를 섞어봅시다.
18강에서 우린
#include <stdio.h>
int main()
{
int arr[4] = {1, 2, 3, 4};
int* ptr = arr; // 포인터를 배열의 첫번째 인덱스에 연결(arr[0])
return 0;
}
위와 같이 포인터를 연결하면 arr배열의 첫 요소에 포인터 ptr이 연결된다는 사실을 배웠습니다.
그럼 이 내용이 다차원배열에서도 그대로 가는지 알아봅시다.
위에서 봤던 예제를 가져오겠습니다.
#include <stdio.h>
int main()
{
int arr[2][2];
printf("arr[0] = %u\n", arr[0]);
printf("&arr[0][0] = %u\n", &arr[0][0]);
return 0;
}
이 예제를 주소 비교를 하기 위해서
#include <stdio.h>
int main()
{
int arr[4][2];
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("arr[%d][%d] : %p ", i, j, &arr[i][j]);
}
printf("\n");
}
return 0;
}
이렇게 바꾸고 시작하겠습니다.
먼저 포인터를 연결하고 주소를 출력하여봅시다.
여기서 포인터를 이차원배열에 연결할 때는 일차원배열과는 연결하는 법이 다릅니다.
int arr[4];
int* ptr = arr;
일차원배열은 위와 같이 포인터와 연결을 했습니다만 이차원은 좀 다릅니다.
먼저 문제를 내볼까요?
다음 중 이차원배열을 포인터와 연결하는 방법중 맞은 것은?
배열: int arr[4][2];
1. int* ptr = arr;
2. int (*ptr) = arr;
3. int* ptr = arr[0];
4. int** ptr = arr;
5. int (*ptr)[2] = arr;
6. int* ptr[2] = arr;
정답은 바로 5번입니다.
(5번과 6번은 같은 것 같지만 다릅니다.)
(5번은 int형 배열 포인터, 6번은 포인터 배열입니다.)
배열 포인터란? |
배열을 가리킬 수 있는 포인터를 뜻합니다. |
포인터 배열이란? |
포인터를 가질 수 있는 배열을 뜻합니다. (배열 안의 원소가 포인터) |
이차원배열을 포인터와 연결하기 위해서는 배열 포인터를 이용하여 연결해야합니다.
이때 주의해야할 점은 연결해줄 이차원배열의 열의 크기와 같은 크기의 배열 포인터를 생성해야한다는 점입니다.
#include <stdio.h>
int main()
{
int arr[4][2] = { 1, 2, 3, 4, 5, 6, 7, 8 };
int (*ptr)[2] = arr;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("arr[%d][%d] : %p ", i, j, &arr[i][j]);
}
printf("\n");
}
printf("arr[0][0] : %d(%p) ptr[0][0] : %d(%p)\n", arr[0][0], &arr[0][0], ptr[0][0], &ptr[0][0]);
return 0;
}
위의 예제를 보면 이차원배열에 포인터를 연결하였습니다.
16번줄을 보면 이렇게 연결한 포인터를 배열처럼 사용하는 것을 볼 수 있습니다.
이제 이차원배열에 연결된 포인터를 이용하여 연산을 해봅시다.
#include <stdio.h>
int main()
{
int arr[4][2] = { 1, 2, 3, 4, 5, 6, 7, 8 };
int (*ptr)[2] = arr;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("arr[%d][%d] : %p ", i, j, &arr[i][j]);
}
printf("\n");
}
for (int i = 0; i < 4; i++)
{
printf("*(ptr + %d) : %p\n", i, *(ptr + i));
}
return 0;
}
위의 예제를 실행해보면
이런 결과를 얻게 되는데요.
arr[0][0]와 *ptr, arr[1][0]과 *(ptr + 1), arr[2][0]과 *(ptr + 2), arr[3][0]과 *(ptr + 3)이 같은 것을 볼 수 있습니다.
이와 같이 이차원배열과 연결된 포인터에 덧셈 연산을 하게 되면 한줄 단위(행 단위)로 증가하게 됩니다.
그 이유는 연결된 배열포인터가 2개의 int 공간을 가진 포인터이기 때문이죠.
그럼 열에 접근하는 방법은 없을까요?
printf("*(ptr + %d) + %d : %d\n", 2, 1, *(*(ptr + 2) + 1));
방법은 간단합니다.
*(ptr + 2)는 arr[2]에 해당되므로 그 뒤에 열만큼 더해줍니다.
위의 상황에서는 2행 1열에 접근하기 때문에 *(ptr + 2) + 1이 되는데 이 메모리 주소가 가리키는 값을 출력하려면 *(*(ptr + 2) + 1)을 해주면 됩니다.
즉, *(포인터 + 행) + 열이 해당 위치의 주소이고 *(*(포인터 + 행) + 열)이 해당 위치의 주소가 가리키는 값이 됩니다.
그럼 위의 성질을 이용해서 배열포인터를 사용해서 배열을 출력하는 프로그램을 만들어봅시다.
#include <stdio.h>
int main()
{
int arr[4][2] = { 1, 2, 3, 4, 5, 6, 7, 8 };
int (*ptr)[2] = arr;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("arr[%d][%d] : %d(%p) ", i, j, arr[i][j], &arr[i][j]);
}
printf("\n");
}
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("*(ptr + %d) + %d : %d(%p) ", i, j, *(*(ptr + i)+j), (*(ptr + i)+j));
}
printf("\n");
}
return 0;
}
이제 배열 자체의 포인터를 가지고 접근하여봅시다.
위의 예제의 배열을 그대로 이용하겠습니다.
#include <stdio.h>
int main()
{
int arr[4][2] = { 1, 2, 3, 4, 5, 6, 7, 8 };
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
printf("arr[%d][%d] : %d(%p) ", i, j, arr[i][j], &arr[i][j]);
}
printf("\n");
}
printf("*arr : %p\n", *arr);
printf("*arr + 1 : %p\n", *arr + 1);
printf("*(arr + 1) : %p\n", *(arr + 1));
printf("*arr + 2 : %p\n", *arr + 2);
printf("*(arr + 2) : %p\n", *(arr + 2));
printf("*arr + 3 : %p\n", *arr + 3);
printf("*(arr + 3) : %p\n", *(arr + 3));
return 0;
}
여기서 알 수 있는 것은 다음과 같습니다.
연산(*arr + n)을 하면 배열의 모든 요소에 순서대로 도달한다는 점과
연산(*(arr + n))을 하면 배열의 행 요소에 도달한다는 점입니다.
이렇게 소단원 4를 배움으로써 다음과 같은 결론을 도출 할 수 있습니다.
arr[0] == &arr[0][0] == *(arr + 0) == *arr
arr[1] == &arr[1][0] == *(arr + 1)
arr[2] == &arr[2][0] == *(arr + 2)
arr[n] == &arr[n][0] == *(arr + n)
*arr[0] == &arr[0][0] == *(*(arr + 0)) == **arr
*arr[1] == &arr[1][0] == *(*(arr + 1))
*arr[2] == &arr[2][0] == *(*(arr + 2))
*arr[n] == &arr[n][0] == *(*(arr + n))
arr[0][0] == *(*(arr + 0) + 0) == **arr
arr[0][1] == *(*(arr + 0) + 1) == *(*arr + 1)
arr[1][2] == *(*(arr + 1) + 2)
arr[n][m] == *(*(arr + n) + m)
5) 다차원 배열을 매개변수로 넘겨보자(이차원배열) |
이제 이차원배열을 매개변수로 넘겨봅시다.
일차원배열의 경우에는
void test(int* arr, int length) { ... }
또는
void test(int arr[], int length) { ... }
이렇게 해주었습니다.
이차원배열의 경우에는 배열 포인터를 이용할 때 "열"을 맞추어주듯이 매개변수로 넘길때도 "열"을 맞추어 줘야 합니다.
int arr[5][4]; 일때
void test(int (*arr)[4], int length) { ... }
또는
void test(int arr[][4], int length) { ... }
double arr[3][7]; 일때
void test(double (*arr)[7], int length) { ... }
또는
void test(double arr[][7], int length) { ... }
이렇게 열을 맞추어줘야 합니다.
이때 매개변수로 하나 더 넘겨주는 length는 해당 이차원배열의 행의 개수를 줘야합니다.
#include <stdio.h>
// 각 원소에 2를 곱하는 함수
void doubler(int arr[][4], int length) // 이때 length는 행의 개수
{
for (int i = 0; i < length; i++)
{
for (int j = 0; j < 4; j++)
{
arr[i][j] *= 2;
}
}
}
// 배열을 출력하는 함수
void printArr(int arr[][4], int length)
{
for (int i = 0; i < length; i++)
{
for (int j = 0; j < 4; j++)
{
printf("arr[%d][%d] : %d ", i, j, arr[i][j]);
}
printf("\n");
}
printf("\n");
}
int main()
{
int arr[2][4] = { 1, 2, 3, 4, 5, 6, 7, 8 };
printArr(arr, 2);
printf("Double!!!\n");
doubler(arr, 2);
printArr(arr, 2);
return 0;
}
다음시간에는 |
오늘까지 해서 다차원배열과 포인터를 배웠습니다.
아직 한 가지가 더 남아있습니다.
바로 "메모리 할당"인데요.
메모리는 "동적 할당"과 "정적 할당"으로 구분됩니다.
이 부분에 대해서 다음 시간에 배워보도록 합시다.
'Study > C언어' 카테고리의 다른 글
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 24 [구조체와 형식 정의 지정자(struct, typedef)] (0) | 2018.10.28 |
---|---|
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 23 [메모리 구조와 메모리 할당, 배열 동적할당(malloc, realloc, calloc, free)] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 21 [다차원배열1 - 다차원배열의 기본(2차원, 3차원)] (0) | 2018.10.17 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 17 [포인터] (0) | 2018.10.17 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 18 [배열과 포인터] (0) | 2018.10.17 |