----------------
title : 씹어먹는 C 언어 - <11 - 2. C 언어의 아파트2 (고차원의 배열)>
cat_title : 11 - 2. C 언어의 아파트2 (고차원의 배열)
next_page : 23
publish_date : 2009-10-29
path : /C
tex_title : 다차원 배열
--------------


이번 강좌에서는

* 2 차원 배열을 이해한다.
* 2 차원 배열이 메모리 상에 어떻게 나타나는지 이해한다.
* 3 차 이상의 고차원 배열을 이해한다.

![씹어먹는 C 언어](/img/ChewingClogo.png)


안녕하세요, 여러분. 아마 이쯤 되면 여태까지 공부하셨던 사람들 중 일부는 *아, 내 머리는 컴퓨터 언어를 배우기에 최적화 되어 있지 않나 보다* 하고 포기하는 사람들과 *오오... 내 맘대로 프로그램을 만들고 주물럭 주물럭 거릴 수 있는 것이 신기한데??* 하는 두 가지 부류의 사람들로 나뉘게 됩니다. 

사실, 여기 까지 온 것 만으로도 정말 대단하다고 말 할 수 있습니다. 왜냐하면 인터넷을 통해 무언가 짬짬히 보아서 공부해 나가는 것은 쉬운 일이 아니기 때문이죠. 여러 게임의 유혹도 있고, 내가 이걸 배우는 시간에 채팅이나 하면 좋을 것을.. 와 같은 생각도 들기 때문이죠.

하지만, 저는 여러분이 조금만 더 힘을 내어 이러한 유혹을 이겨내고 C 언어의 끝에 도달하시기 바랍니다. 사실 배열과 앞으로 나오는 포인터 부분만 넘어간다면 더이상 어려울 부분이 없기 때문이죠. 그 부분이 넘어가 C 언어 끝에 도달하게 되면, 윈도우 API 를 공부하여 윈도우 앱도 만들고, Direct X 나 OpenGL 등을 공부해서 3D 게임 까지. 뿐만 아니라 소켓 프로그래밍을 공부한다면 친구들과 채팅할 수 있는 프로그램도 만들 수 있고 게임 서버들도 만들 수 있습니다.

C 언어를 배움으로써 얻을 수 있는 위 많은 것들을 쉽게 포기하실 것 입니까? 물론, 나가실 분은 조용히 뒤로가기를 누르셔도 상관 없습니다. 다만, 이 순간의 클릭이 당신의 앞날을 좌우 할 지도 모른다는 사실을 잊지 마세요. 그리고 참, 이전 내용이 기억이 나지 않는다고 머리를 쥐어 뜯지 마세요. 그냥 이전 강좌를 다시 보면 됩니다. 이전 강좌의 내용이 잘 기억이 나지 않는 것은 '지극히 정상' 이니 다시 한 번 읽어 보므로써 기억을 강화시키도록 하세요 :)

그럼 잡담을 끝내고 본론으로 들어가도록 합시다. 이전 강좌에서 배열은 변수 들의 모임이라고 했습니다. 이 때 `arr` 이라는 배열의 `i` 번째 원소를 참조 하기 위해선 `arr[i]` 라고 써야 한다는 것도 알았습니다. 그런데 똑똑한 사람이라면 이 아이디어를 확장해서 다음과 같은 생각을 할 수 도 있을 것 입니다.

> 배열의 배열을 만들면 어떨까?

정말로 놀라운 생각 입니다. 일단 여기서 우리는 **배열의 배열**의 의미를 좀 더 명확하게 해야 겠습니다. *`int` 형의 배열* 이란 말은 *배열의 각 원소가 `int` 형 변수 인 것!* 입니다.

그렇다면 **`int` 형의 배열의 배열** 이란 말은 무엇일까요? 이 말은, **배열의 각 원소가 `int` 형의 배열 인 것** 을 말합니다. C 언어 에서 이러한 형태의 배열을 정의하기 위해선 아래와 같이 하면 써주면 됩니다.

```cpp-formatted
// 위 경우 (배열의 형) 부분에 int 가 오면 된다. ? 에는 배열의 크기
(배열의 형) (배열의 이름)[?][?]; 
```

이전에는 `(배열의 이름)[?]` 였지만 2 차원 배열은 옆에 `[?]` 하나가 더 붙었지요.
먼저 배열의 배열이 무엇인지 확실하게 알기 위해서 다음과 같은 배열을 정의해 봅시다.

```cpp-formatted
int arr[3][2];
```

앞에서 말했듯이 위 배열은 '배열의 각 원소 3 개가 원소를 2 개 가지는 `int` 형의 배열이고 이름은 `arr` 이다.' 을 의미 합니다. 따라서,


![caption=배열을 원소로 하는 배열을 생각하면 됩니다. 즉, 원소가 두 개인 배열을 원소로 하여, 이 원소를 3 개 가진 배열이 바로 `arr[3][2]` 에 해당하는 이차원 배열 입니다.](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile23.uf.tistory.com%2Fimage%2F197646024AE8E7E3483008)

가 됩니다.

즉, `arr[0]` 이라 하면 `int` 형의 원소를 2 개 가지는 배열을 말하는 것이며 그 배열의 원소는 각각 `arr[0][0], arr[0][1]` 이 되겠지요.

일차원 배열과 이차원 배열을 한 눈에 비교하자면 아래와 같습니다.


![caption=`arr[3]` 은 그냥 아파트에 집이 3개, `arr[3][2]` 는 집이 3 개 인데, 각 집에 방이 2개 ](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile6.uf.tistory.com%2Fimage%2F2075D8024AE8EB2657AF92)

어때요, 간단하죠? 따라서, `arr[m][n];` 과 같이 배열을 선언한다면 (`m` 과 `n` 은 임의의 정수값), $$m\times n$$ 개의 변수를 가지는 배열을 선언한 것이 됩니다.

그렇다면, 2 차원 배열을 가지고 무슨 짓을 할 수 있을 까요? 사실, 여러분도 이미 예상한 바 있지만 오히려 일차원 배열에 비해 활용도가 훨씬 높아지게 됩니다. 예를 들면, 33 명의 학생에 대해 국어, 수학, 영어, 과학 점수를 보관하는 배열을 만든다 (이전의 배열 하나만 이용해선 한 과목의 점수 밖에 보관할 수 없었죠) 도서 입출 관리 프로그램에서 개개의 도서에 대해서 이 도서를 빌려간 날짜, 반납한 날짜 등을 보관하는 배열을 만든다 등등이 있습니다.

아! 그런데 아직 왜 이러한 배열을 '2차원' 배열이라고 말하는지 이야기 하지 않았군요. 사실, 메모리에는 모든 배열이 일차원 배열과 다름없이 들어갑니다. 그런데, 아래 예제를 보고 나면 왜 '2차원' 배열이라 이야기 하는지 감이 확 올 것 입니다.

```cpp-formatted
/* 2 차원 배열 */
#include <stdio.h>
int main() {
  int arr[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

  printf("arr 배열의 2 행 3 열의 수를 출력 : %d \n", arr[1][2]);
  printf("arr 배열의 1 행 2 열의 수를 출력 : %d \n", arr[0][1]);
  return 0;
}
```

성공적으로 컴파일 하였으면

```exec
arr 배열의 2 행 3 열의 수를 출력 : 6 
arr 배열의 1 행 2 열의 수를 출력 : 2 
```

와 같이 나옵니다. 아래 부분은 2 차원 배열을 정의한 모습입니다. 

```cpp-formatted
int arr[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
```

2 차원 배열이나 1 차원 배열 모두 메모리 상에서 연속적으로 쭈르륵 존재하게 됩니다. (메모리는 항상 1 차원 입니다.)

![caption=`arr[0][0]`, `arr[0][1]`, `arr[0][2]`, `arr[1][0]`, `arr[1][1]`, `arr[1][2]`, `arr[1][3]`, `arr[2][0]`, `arr[2][1]`, `arr[2][2]` 순으로 메모리 상에 선형으로 배열됩니다.](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile1.uf.tistory.com%2Fimage%2F1126170B4AD1F29F014AC7)

마치 위 그림 처럼 말이죠. 하지만 2 차원 배열을 생각할 때 마치 해당 원소들이 아래 그림 처럼 2 차원 공간 상에서 배치되어 있다고 생각해도 됩니다.

![caption=3 곱하기 3 정사각형에 위에서 부터 차례로 `arr[0][0]`, `arr[0][1]`, `arr[0][2]` 가 쭉 써있는 것을 생각해보세요](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile23.uf.tistory.com%2Fimage%2F1940B00D4AD1EFB1226E92)

`arr[x][y]` 라고 한다면 `x` 는 몇 번째 줄에 있는지, `y` 는 몇 번째 열에 있는지를 나타냅니다. 예를 들어서 `arr[0][1]` 은 0 번째 줄, 1 번째 열에 있으므로 2 가 되겠지요. 결과적으로 말하자면 **일차원 배열은 한 개의 인덱스로 원소에 접근하는 것**이고, **이차원 배열은 두 개의 인덱스로 원소에 접근하는 것이다** 라고 보면 됩니다.

```cpp-formatted
/* 학생 점수 입력 받기 */
#include <stdio.h>
int main() {
  int score[3][2];
  int i, j;

  for (i = 0; i < 3; i++)  // 총 3 명의 학생의 데이터를 받는다
  {
    for (j = 0; j < 2; j++) {
      if (j == 0) {
        printf("%d 번째 학생의 국어 점수 : ", i + 1);
        scanf("%d", &score[i][j]);
      } else if (j == 1) {
        printf("%d 번째 학생의 수학 점수 : ", i + 1);
        scanf("%d", &score[i][j]);
      }
    }
  }

  for (i = 0; i < 3; i++) {
    printf("%d 번째 학생의 국어 점수 : %d, 수학 점수 : %d \n", i + 1,
           score[i][0], score[i][1]);
  }

  return 0;
}
```

성공적으로 컴파일 하였다면

```exec
1 번째 학생의 국어 점수 : 30
1 번째 학생의 수학 점수 : 60
2 번째 학생의 국어 점수 : 90
2 번째 학생의 수학 점수 : 100
3 번째 학생의 국어 점수 : 70
3 번째 학생의 수학 점수 : 80
1 번째 학생의 국어 점수 : 30, 수학 점수 : 60 
2 번째 학생의 국어 점수 : 90, 수학 점수 : 100 
3 번째 학생의 국어 점수 : 70, 수학 점수 : 80 
```

와 같이 됩니다. 사실 작동 원리는 간단 합니다.

```cpp-formatted
int score[3][2];
```

일단 위 구문을 통해 3 행, 2 열의 크기를 가지는 2 차원 배열 `score` 을 선언 하였습니다. 사실 우리가 프로그래밍 하고자 하는 목표에 따라 해석해 보면 '3 명의 학생의 2 과목의 데이터를 보관하는 `score 2` 차원 배열' 이라고 볼 수 도 있습니다. 이를 그림으로 나타내면


![학생 1 의 국어 점수는 score[0[0, 수학 점수는 score[0[1, 학생 2 의 국어 점수는 score[1[0, 수학은 score[1[1 등등](http://img1.daumcdn.net/thumb/R1920x0/?fname=http%3A%2F%2Fcfile9.uf.tistory.com%2Fimage%2F15758C294AD1F69944F937)
  꼴로 보면 됩니다.

```cpp-formatted
for (i = 0; i < 3; i++)  // 총 3 명의 학생의 데이터를 받는다
{
  for (j = 0; j < 2; j++) {
    if (j == 0) {
      printf("%d 번째 학생의 국어 점수 : ", i + 1);
      scanf("%d", &score[i][j]);
    } else if (j == 1) {
      printf("%d 번째 학생의 수학 점수 : ", i + 1);
      scanf("%d", &score[i][j]);
    }
  }
}
```

이제 `for` 문을 통해서 3 명의 학생의 데이터를 입력 받게 됩니다. 일단 오래간만에 두 개의 `for` 문이 같이 돌아가는데 어떠한 형식으로 작동되는 지는 알고 있겠지요? `i = 0` 일 때, `j = 0 ~ 1, i = 1` 일 때, `j = 0 ~ 1, i = 2` 일 때, `j = 0 ~ 1` 로 돌아가게 됩니다. 즉 위 부분을 통해 2 차원 `score` 배열의 값을 집어 넣게 되는 것 이지요.

```cpp-formatted
if (j == 0) {
  printf("%d 번째 학생의 국어 점수 : ", i + 1);
  scanf("%d", &score[i][j]);
} else if (j == 1) {
  printf("%d 번째 학생의 수학 점수 : ", i + 1);
  scanf("%d", &score[i][j]);
}
```

위 `for` 문 안의 위 부분을 살펴 보면 `j` 가 0 이면 국어점수를 입력해라, `j` 가 1 이면 수학 점수를 입력해라 라고 물어 보는 것이 달라 집니다.

마지막으로

```cpp-formatted
for (i = 0; i < 3; i++) {
  printf("%d 번째 학생의 국어 점수 : %d, 수학 점수 : %d \n", i + 1, score[i][0],
         score[i][1]);
}
```

를 통해 입력 받은 값을 깔끔하게 보여주게 됩니다.



### 2 차원 배열 정의하기

앞선 예제에서

```cpp-formatted
int arr[2][3] = {1, 2, 3, 4, 5, 6};
```

와 같이 2 차원 배열을 선언하였습니다. 그런데, 프로그래밍시 줄 수를 절약하고 싶은 사람들은 아래와 같이 해도 큰 문제는 없습니다.

```cpp-formatted
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
```

위 처럼 정의하는 것은 우리가 앞서 써 왔던 방법과 전혀 차이가 없으니 그냥 알아 두시면 됩니다. 여러분들 께서 원하는 방법으로 정의하시면 됩니다.

참고로, 이전 강의에서 이야기를 하지 않았는데, 다음과 같이 배열을 정의할 수 도 있습니다.

```cpp-formatted
int arr[] = {1, 2, 3, 4};
```

음... 무언가 이상하다는 느낌이 드나요? 사실, 알고보면 단순합니다. 위와 같이 정의할 수 있는 이유는 컴파일러가 원소의 개수를 정확하게 알기 때문입니다. 컴파일러는 우리가 배열을 정의한 것을 보고 '아, 이 사람이 원소를 4 개 가지는 `int` 배열을 정의하였구나!' 라고 알아서 대괄호 안에 자동적으로 4 를 집어 넣어서 생각하게 됩니다. 따라서 위와 같이 정의하나

```cpp-formatted
int arr[4] = {1, 2, 3, 4};
```

로 정의하나 같은 말이 되겠지요.
하지만 아래와 같이 정의하는 것은 안됩니다.

```cpp-formatted
int arr[];
```

이 것은 왜 안될까요? 아마, 이전 강의를 잘 보신 분들게서는 단박에 알아 차릴 수 있을 것 입니다. 그 이유는 '배열의 크기는 임의로 정해지지 않기 때문입니다. 즉, 위와 같이 배열을 정의한다면 컴파일러는 우리가 어떠한 크기의 배열을 정의하고 싶은지 모릅니다. 따라서 아래와 같은 오류를 내뿜게 됩니다.

```compiler-warning
 error C2133: 'arr' : 알 수 없는 크기입니다.
```

이 아이디어를 2 차원 배열에도 그대로 적용 시킬 수 있습니다.
일단, 아래와 같은 2 차원 배열의 정의를 살펴 보도록 합시다.

```cpp-formatted
int arr[][3] = {{4, 5, 6}, {7, 8, 9}};
```

위 정의를 보면 여러분은 비어있는 대괄호 안에 무슨 값이 들어갈지 맞출 수 있을 것입니다. 무엇이냐고요? 바로 2 이지요. 왜냐하면 `{4,5,6}` 를 가지는 `arr[3]` 배열 하나와, `{7,8,9}` 를 가지는 또다른 `arr[3]` 배열을 정의하여 총 2 개의 `arr[3]` 배열을 정의하였기에, `int arr[2][3]` 이 되어야 하는 것이지요.

그렇다면 아래와 같은 문장이 유효한지 살펴보세요.

```cpp-formatted
int arr[][2] = {{1, 2}, {3, 4}, {5, 6}, {7}};
```

어! 이상하네요. 마지막에 그냥 `{7}` 이라고 되어 있잖아요? 아마 여러분들 중 대다수는 이 것을 보고 위 문장이 틀렸고 성공적으로 컴파일 되지 않으리라 생각할 것입니다. 하지만, 위 2 차원 배열은 배열 정의시 `arr[][2]` 라고 하였기 때문에 무조건 원소가 2 인 1 차원 배열들이 생기게 됩니다. 즉, 7 이 속한 1 차원 배열에는 원소가 한 개인 것이 아니라 마치 `arr[3] = {1}` 고 해도 상관 없는 것 처럼 8 번째 원소가 들어갈 자리를 비워놓게 됩니다. 따라서, 위 문장은 틀린 것이 아닙니다. 그렇다면 아래 문장을 봐보세요.

```cpp-formatted
int arr[2][] = {{4, 5, 6}, {7, 8, 9}};
```

과연 될까요? 아마 여러분들 중 대다수는 될 것이라 생각하고 있을 것 입니다. 하지만 놀랍게도 컴파일해보면

```compiler-warning
error C2087: 'arr' : 첨자가 없습니다.
error C2078: 이니셜라이저가 너무 많습니다.
```

와 같은 오류들을 만나게 됩니다. **C 에서는 다차원 배열의 경우 맨 앞의 크기를 제외한 나머지 크기들을 정확히 지정해줘야 오류가 발생하지 않습니다**. 


###  3 차원, 그 이후 차원의 배열들


2 차원 배열을 잘 이해하였다면 3 차원 배열을 이해하는 것은 그리 어려운 것이 아니라 생각됩니다. 사실, 보통의 프로그래밍에서 3 차원 배열을 쓰는 경우는 그렇게 많지 않습니다. (물론 제가 만들어본 프로그램들에 한해서...) 그렇지만 쓸 수 도 있기에 간단하게 집고 넘어가기만 합시다.

3 차원의 배열의 정의는 2 차원 배열과 거의 동일합니다. (그 이후의 차원들도 마찬가지)

```cpp-formatted
(배열의 형)(배열의 이름)[x][y][z];  // 여기서 x,y,z 는 배열의 크기를 말합니다.
```

이제, 머리속으로 상상의 나래를 펼쳐 봅시다. 제가 그림판으로 3 차원 적인 그림을 그릴 수는 없으므로 여러분의 지능을 믿겠습니다! 일단, 아래의 배열을 머리에 그려 봅시다.

```cpp-formatted
int arr[3][4];
```

이는 가로 길이가 4 이고 세로 길이가 3 인 평면위에 `int` 변수들이 하나씩 놀고 있는 것을 상상하면 됩니다. 그렇다면 이제 아래 배열을 머리에 그려 봅시다.

```cpp-formatted
int brr[2][3][4];
```

아아악! 모르겠다고요? 아니요, 어렵지 않습니다. 위에서 상상한 평면 위에 동일한 평면이 한 층 더 있다고 생각하면 됩니다. 즉, 위에서 생각했던 평면이 2 개의 층으로 생겼다고 하면 됩니다. 어때요, 간단하죠?

하지만, 문제는 4차원 배열 부터 입니다. 뭐 우리는 3 차원 적인 세상에서 살고 있기 때문에 4 차원에 무엇인지 몸에 와닿기는 힘듭니다. (사실, 우리는 3차원 상의 공간에 시간의 축이 더해진 4차원 세상에서 살고 있다고 합니다) 그렇기에 4 차원 배열, 그리고 그 보다 더 놓은 차원의 배열이 무엇인지 머리속으로 그려보기란 고역이 아닐 수 없습니다.

그런데 말이죠. 제가 아까 굵은 글씨로 써 놓았던 것이 기억나시나요?

```info
일차원 배열은 한 개의 값(x)으로 원소에 접근하는 것이고, 이차원 배열은 두 개의 값(x,y)으로 원소에 접근하는 것이다!
```

이를 확장해서 생각해 보면 삼차원 배열은 세 개의 값 `(x,y,z)` 을 통해서 원소에 접근하는 것 입니다. 네, 맞아요. 우리가 원소가 몇 번째 층에 있고 (`x`), 그 층에 해당하는 평면에 몇 행(`y`), 그리고 몇 열(`z`) 를 알면 `int` 변수에 정확하게 접근할 수 있지 않습니까?

4 차원도 같습니다. 4 차원 배열은 4 개의 값 `(x,y,z,w)` 을 통해서 원소에 접근할 수 있습니다. 마찬가지로 5 차원은 5 개, `n` 차원은 `n` 개의 값을 통해서 원소에 접근하게 되는 것이지요. 이 아이디어를 적용시키면 어떠한 차원의 배열이 실제 프로그래밍 상에 필요하다고 하더라도 문제 없이 해결할 수 있으리라 생각합니다.

그렇다면 이번 강좌는 여기에서 마치도록 하겠습니다.

### 생각해 보기

#### 문제 1

제 강좌 제목에서 배열이 왜 C 언어의 아파트 인지 설명해 보세요. 즉, *동* 의 개념, *층* 의 개념, *호* 의 개념이 어떠한 배열을 형상화 하고 있는 지도 생각해 보세요. (난이도 : 下)

##@ chewing-c-end