이번 정리는 포인터 얘기는 아니지만, 배열은 포인터와 거의 비슷하니 뭐 포인터는 맞겠지.
지금까지 다룬 배열은 1차원 배열만을 다뤘지만, 차원이라는 단어가 붙은 이상 그 위의 차원도 당연히 있을 것이다. 그래서 이번 정리는 다차원 배열에 관한 정리를 해보자
다차원 배열
1차원 배열은 논리적으로 선형적인 구조를 띤다. 이를 확장하여 2차원배열은 논리적으로 평면구조, 3차원배열은 직육면체구조의 배열이다. 그러면 4차원 이상의 배열은 논리적으로 어떤 형태일까? 당연히 우리는 알리가 없다. 사실 쓸모도 없고, 논리적인 구조를 이해하기도 힘들다. 하지만 컴파일러는 문법적으로 허용해준다(어떻게 가능한지는 후술하겠다). 따라서 다차원 배열은 보통 2, 3차원 배열을 말한다.
이제 선언방법을 보자.
TYPE dim_1[x]; // 길이x의 TYPE형 1차원 배열
TYPE dim_2[y][x]; // 가로x, 세로y인 TYPE형 2차원 배열
TYPE dim_3[z][y][x]; // 가로x, 세로y, 높이z인 TYPE형 3차원 배열
이렇게 배열을 선언하게 되면 다음과 같이 (논리적인 구조로) 메모리에 할당된다.
ex) int[2][4] | 1열 | 2열 | 3열 | 4열 |
1행 | int[0][0] | int[0][1] | int[0][2] | int[0][3] |
2행 | int[1][0] | int[1][1] | int[1][2] | int[1][3] |
이렇게 할당된 배열에서는 int[i][j]의 방법으로 요소에 접근하면 된다.
sizeof로 연산하면 얼마가 나올까? 다음 예제를 보자
#include <stdio.h>
int main () {
int arr1[3][4];
int arr2[7][2][3];
printf("%d %d", sizeof(arr1), sizeof(arr2));
return 0;
}
실행 결과
48 168
arr1의 경우 4(가로) * 3(세로) * 4(sizeof(int)) = 48이고 arr2의 경우 7 * 2 * 3 * 4 = 168로, 정확히 계산된다.
다차원 배열의 메모리상 물리적 존재 구조
다차원 배열의 논리적인 구조는 그 차원을 대표하는 도형의 구조를 띈다. 그렇다면, 실제로 메모리상에서는 어떻게 존재할까? 다음 예제를 살펴보자.
#include <stdio.h>
int main () {
int arr[3][3];
for (int i = 0; i < 3; i++) {
printf("%d: ", i);
for (int j = 0; j < 3; j++) {
printf("%p ", &arr[i][j]);
}
printf("\n");
}
return 0;
}
실행 결과
0: 0061FEF4 0061FEF8 0061FEFC
1: 0061FF00 0061FF04 0061FF08
2: 0061FF0C 0061FF10 0061FF14
보다시피 2차원 배열의 메모리상 주소는 선형적으로 존재함을 알 수 있다.
아래와 같이 존재할 줄 알았던 2차원 배열은
ex) int[2][4] | 1열 | 2열 | 3열 | 4열 |
1행 | int[0][0] | int[0][1] | int[0][2] | int[0][3] |
2행 | int[1][0] | int[1][1] | int[1][2] | int[1][3] |
사실 아래처럼 존재하는 것이다.
ex) int[2][4] | int[0][0] | int[0][1] | int[0][2] | int[0][3] | int[1][0] | int[1][1] | int[1][2] | int[1][3] |
3차원 배열 역시 2차원 배열의 확장과 같으므로 같은 방식으로 메모리에 존재 할 것이다.
다차원 배열 선언 및 초기화
선언하는 방법은 위에서 언급했으니 이번에는 선언과 초기화를 동시에 하는 방법을 알아보자.
일반적으로 사용하는 방법은
int arr[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
이다. 행을 중괄호로 구분하여 선언하는 것이다.
1차원 배열처럼 일부 요소를 생략할 수 있다. 이때는 1차원 배열처럼 비어있는 요소는 0으로 초기화된다.
int arr[3][3] = {
{1}, // == {1, 0, 0}
{4, 5}, // == {4, 5, 0}
{7, 8, 9}
}
행을 구분하지 않고 선언하는 것도 가능하다.
int arr[3][3] = {1, 2, 3, 4, 5, 6, 7};
이 경우 arr[0][0]부터 가로 방향으로 순차적으로 입력된다. 하지만 8, 9번째 요소는 생략되어있으니 0으로 초기화가 된다.
int arr[3][3] = {1, 2, 3, 4, 5, 6, 7, 0, 0};
또한 1차원 배열처럼 배열의 크기를 알려주지 않고, 초기화 하는 방법이 있다. 하지만 이런 경우에 가로, 또는 세로값중 하나는 명시해주어야 하는데, 그 이유는 적어도 하나는 명시해야 가로와 세로를 결정할 수 있기 때문이다.
// (X) int arr[][] = {1,2,3,4,5,6,7,8};
// (O) int arr[][2] = {1,2,3,4,5,6,7,8};
// (O) int arr[][4] = {1,2,3,4,5,6,7,8};
3차원 배열도 크게 다르지 않다.
int arr[2][2][2] = {
{
{1, 2},
{3, 4}
},
{
{5, 6},
{7, 8}
},
};
결국 3차원 배열은 2차원 배열의 집합인 셈이다.
'Language > C' 카테고리의 다른 글
포인터(8) - 上 (0) | 2021.09.04 |
---|---|
포인터 (7) + 간단한 문제풀이 (0) | 2021.09.02 |
포인터(6) (0) | 2021.08.31 |
포인터(5) (0) | 2021.08.29 |
포인터(4) (0) | 2021.08.29 |