모두의 코드
씹어먹는 C 언어 - <11 - 2. C 언어의 아파트2 (고차원의 배열)>

작성일 : 2009-10-29 이 글은 46999 번 읽혔습니다.

이번 강좌에서는

  • 2 차원 배열을 이해한다.

  • 2 차원 배열이 메모리 상에 어떻게 나타나는지 이해한다.

  • 3 차 이상의 고차원 배열을 이해한다.

씹어먹는 C 언어

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

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

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

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

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

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

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

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

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

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

int arr[3][2];

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

배열을 원소로 하는 배열을 생각하면 됩니다. 즉, 원소가 두 개인 배열을 원소로 하여, 이 원소를 3 개 가진 배열이 바로 arr[3][2] 에 해당하는 이차원 배열 입니다.

가 됩니다.

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

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

arr[3] 은 그냥 아파트에 집이 3개, arr[3][2] 는 집이 3 개 인데, 각 집에 방이 2개

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

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

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

/* 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;
}

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

실행 결과

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

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

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

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

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] 순으로 메모리 상에 선형으로 배열됩니다.

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

3 곱하기 3 정사각형에 위에서 부터 차례로 arr[0][0], arr[0][1], arr[0][2] 가 쭉 써있는 것을 생각해보세요

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

/* 학생 점수 입력 받기 */
#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;
}

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

실행 결과

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

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

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 등등
꼴로 보면 됩니다.

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 배열의 값을 집어 넣게 되는 것 이지요.

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 이면 수학 점수를 입력해라 라고 물어 보는 것이 달라 집니다.

마지막으로

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

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

2 차원 배열 정의하기

앞선 예제에서

int arr[2][3] = {1, 2, 3, 4, 5, 6};

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

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

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

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

int arr[] = {1, 2, 3, 4};

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

int arr[4] = {1, 2, 3, 4};

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

int arr[];

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

컴파일 오류

 error C2133: 'arr' : 알 수 없는 크기입니다.

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

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] 이 되어야 하는 것이지요.

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

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

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

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

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

컴파일 오류

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

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

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

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

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

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

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

int arr[3][4];

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

int brr[2][3][4];

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

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

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

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

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

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

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

생각해 보기

문제 1

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

강좌를 보다가 조금이라도 궁금한 것이나 이상한 점이 있다면 꼭 댓글을 남겨주시기 바랍니다. 그 외에도 강좌에 관련된 것이라면 어떠한 것도 질문해 주셔도 상관 없습니다. 생각해 볼 문제도 정 모르겠다면 댓글을 달아주세요.

현재 여러분이 보신 강좌는 <씹어먹는 C 언어 - <11 - 2. C 언어의 아파트2 (고차원의 배열)>> 입니다. 이번 강좌의 모든 예제들의 코드를 보지 않고 짤 수준까지 강좌를 읽어 보시기 전까지 다음 강좌로 넘어가지 말아주세요
댓글이 94 개 있습니다!
프로필 사진 없음
강좌에 관련 없이 궁금한 내용은 여기를 사용해주세요

    댓글을 불러오는 중입니다..