지금까지 배우고 사용한 배열은 1차원 배열이다. 하지만 우리가 사용하는 정보가 문자열처럼 1차원 형태로만 되어 있는 것은 아니다. 예를 들어 바둑판에 놓인 돌의 좌표는 어떻게 표시하는 게 효율적인가?
바둑판 정보는 가로와 세로가있는 2차원 형태이다.
<<바둑판에 놓인 돌의 정보>>
돌이 놓여 있지 않으면 0, 검은 돌이 놓여 있으면 1, 흰 돌이 놓여 있으면 2
따라서 1 바이트인 char형으로도 충분히 이 세 가지 값을 구별해서 저장할 수 있다.
정보를 저장할 단위가 정해졌으니, 바둑판 전체를 저장할 수 있는 메모리 크기를 결정하기 위해 데이터 개수를 계산해야 한다. 바둑판은 가로 방향으로 19개의 돌을 놓을 수 있고, 세로 방향으로도 19개의 돌을 놓을 수 있다. 따라서 총 361개의 돌을 놓을 수 있다.
배열 선언 : char data[361];
2차원 형태의 정보를 1차원 배열에 저장하기 위해서 한 행에 이어 그다음 행을 저장하는 방식을 사용한다. 즉 data 배열의 0번에서 18번까지 1행의 정보가 저장되고, 19번에서 37번까지 2행의 정보가 저장되며, 이런 식으로 마지막 19행의 정보는 348번에서 360번에 저장된다.
[19행 19열, 요소의 개수는 18개 18개]
그러나 이렇게 2차원 정보를 1차원 배열에 저장하면 소스 코드를 작성할 때 요소의 위치를 파악하기 어렵다.
배열에서 첫 번째 요소의 색인은 0이다, 따라서 179번 요소는 180번째 요소를 뜻한다.
차원은 공간에서 좌표를 구성하는 축의 개수를 의미한다. 따라서 2차원은 좌표를 적을 때 2개의 축을 사용하고 각 축의 이름은 X축, Y축이며 X축은 수평 방향, Y축은 수직 방향에 대한 기준 값이다.
바둑판을 5(행)X4(열)로 바꾸고 몇 개의 바둑돌을 놓아 보자. 바둑돌의 상태를 나타내는 값은 동시에 발생할 수 없기 때문에, 프로그램에서는 세 가지 상황 중 한 가지만 저장하면 되므로 0,1,2 중 하나를 저장할 수 있는 20개의 저장 공간이 필요하다.
char data[20]; 1차원 배열로 20바이트 크기의 변수를 선언함
하지만 2차원 형식의 데이터를 1차원 배열로 관리하면 프로그래밍하기에는 어려움이 있다. 예를 들어 data[7]에 저장된 돌의 상태가 바둑판에서 몇 행, 몇 열인지 판단하기가 어렵다는 말이다.
따라서, 이런 경우 2차원 데이터와 표현 방식이 같은 2차원 배열을 사용해야 한다.
char data1, data2, data3, data4; / 4개의 변수를 개별적으로 선언함
char data[4]; 4개의 변수를 그룹으로 묶어서 1차원 배열 형태로 선언함
그런데 다음처럼 같은 형식의 1차원 배열이 5개를 묶으려면 어떻게 해야 할까?
char data1[4], data2[4], data3[4], data4[4], data5[4];
5개의 1차원 배열은 모두 char[4] 형식으로 같기 때문에, 배열을 사용해서 묶을 수 있다.
char data[5][4]; char[4] 형식의 1차원 배열 5개를 묶어 2차원 배열로 선언함
1차원 배열을 선언할 때 대괄호 []를 이미 사용했기 때문에, 한 번 더 묶으려면 대괄호 []를 두 번 사용하면 된다. 이처럼 대괄호 []을 두 번 사용해서 선언하는 배열이 바로 2차원 배열이다.
그런데, 여기서 왜 char data[4][5]가 아닌 char data[5][4]로 적었을까?
연산자 우선순위로 인해, 왼쪽에서 오른쪽으로 연산을 수행하므로 char(data[5])[4]; 와 같다.
이렇게 선언한 char data[5][4]는 행이 5개이고 열이 4개인 2차원 배열이고, 각 요소에 char형으로 데이터를 저장할 수 있다.
data 변수의 크기는 '(자료형 크기)x(행 개수)x(열 개수)'이기 때문에 1x5x4 = 20 byte이다.
= data[1][2] = 10;
+) n번째 행/열은 (n-1)번째 요소와 같음.
컴퓨터가 사용하는 메모리는 2차원 개념을 제공하지 않기 때문에, data 변수를 위한 20바이트 메모리는 1차원 형태로 메모리에 저장된다. 그러나 소스 코드에서 2차원 형식으로 사용할 수 있는 이유는 C언어의 2차원 배열 문법이 내부적으로 수학 공식을 사용해서 2차원 개념을 제공하기 때문이다.
즉, 2차원 배열은 컴파일할 때 모두 1차원 형태로 변환된다.
char data[5][4]; 5행 4열: 행 단위로 묶음
or char data[4][5] 4행 5열: 열 단위로 묶음
2차원 배열로 변수를 선언하면 배열의 각 요소를 사용하기 위해 [] 연산자를 두 개씩 사용하기 때문에 x,y 좌표 형식으로 접근할 수 있다. 예를 들어 2행 4열(좌표는 (4,2))에 있는 검은색 돌을 data 변수에 저장하려면 다음과 같이 적으면 된다.
data[1][3]=1; 행 단위로 묶음. 2행 4열에 검은 돌이 놓인 상태 값 1을 대입함. 좌표는 (4,2)
data[3][1]=1; 열 단위로 묶음. 4열 2행에 검은 돌이 놓인 상태 값 1을 대입함. 좌표는 (4,2)
이것을 좌표 형식으로 바꿔보면 행 단위로 묶은 것은 data[y-1][x-1], 열 단위로 묶은 것은 data[x-1][y-1]의 형식이 될 것이다.
프로그래머에게는 (x,y)좌표처럼 익숙한 형식으로 사용하는 것이 편리한 표현일 테지만, 과연 컴퓨터는 그렇게 생각할까?
char data[4][5]; (x,y)형식
1열에 1,2,3,4,5행 2열에 1,2,3,4,5행으로 묶여있기 때문에, 같은 행에 위치해서 열만 다른 바둑돌들이 데이터로 나타내면 5 byte나 떨어져 있게 되는 상황이 발생한다.
또한 같은 행에 있는 항목들이 한 그룹이 되지 못하여 그룹 연산을 사용하는 기본 함수들도 사용할 수 없다.
반대의 경우는 어떨까?
char data[5][4]; (y,x)형식
프로그래머가 프로그래밍할 때는 (y,x) 형식으로 사용해야 하는 불편함이 있을 지라도, 2차원 형식의 데이터는 수평 방향으로 그룹에 묶여 저장된다.
연산도 단순해지고 같은 행에 있는 데이터가 자연스럽게 한 그룹이 되기 때문에 관리하기도 더 편리하다.
따라서 2차원 형식의 데이터를 2차원 배열에 저장할 때는 행을 기준으로 묶는 방식을 더 많이 사용한다.
다음 코드는 1차원 배열을 두 개 선언하고 각 배열을 1,2,3과 4,5,6으로 초기화하고있다.
char temp1[3] = {1,2,3};
char temp2[3] = {4,5,6};
... = char temp[2][3];
2차원 배열을 이용하여 temp 변수를 선언했으니 초기화도 해 주어야 한다. 1차원 배열은 하나의 그룹으로 이루어져 있기 때문에 한 개의 {}로 초기화를 했지만 2차원 배열은 그룹 안에 또 다른 그룹이 있는 형태이기 때문에 {}를 중복으로 사용해야 한다.
char temp[2][3] = {{1,2,3},{4,5,6}};
대부분의 경우 컴파일러가 2차원 데이터를 1차원 형태의 기계어로 잘 번역해 준다. 하지만 프로그래머가 좀 더 적극적으로 상황에 대처하고 싶거나 프로그램 실행 성능을 향상시키기 위해 1차원 배열과 차원을 변환하는 수학 공식을 사용하여 직접 2차원 형태를 구성하기도 한다.
프로그래머가 2차원 좌표를 1차원 좌표로 변환하는 공식이나 1차원을 2차원 좌표로 변환하는 공식을 이해하고 있따면 2차원 배열 문법에 의존하지 않고 1차원 배열을 사용해서도 얼마든지 2차원 데이터를 저장하고 관리할 수 있다.
char temp[M][N]; L,M,N 값은 숫자 상수로 가정함
char test[L]; MxN 값이 L값과 같다고 가정함
temp변수가 2차원이고 test 변수가 1차원 배열로 선언된 경우 다음과 같은 공식이 성립된다. (a,b,c는 숫자 상수)
test[a] 항목과 temp[a/N][a%N] 항목은 위치가 같다.
temp[b][c] 항목과 test[bxN+c]항목은 위치가 같다.
3행 4열 형태로 바둑판을 축소시켜놓고 돌이 놓여 있는 상황을 1차원 배열을 사용하여 초깃값으로 저장한 후 바둑판의 상황을 출력해보면 다음과 같다.
#include <stdio.h>
void main()
{
char data[12] = { 0,0,2,0,1,1,0,0,2,1,0,2 };
int i, x, y;
for (i = 0;i < 12;i++) {
x = i % 4 + 1; /*열 번호를 구함*/
y = i / 4 + 1; /* 행 번호를 구함*/
printf("%d행 %d열에", y, x);
if (data[i] == 1) printf(" 검은 돌이 놓여 있습니다. \n");
else if (data[i] == 2) printf(" 흰 돌이 놓여 있습니다. \n");
else printf("는 돌이 놓여 있지 않습니다. \n");
}
}
2차원 배열을 사용해서 재구성해보자.
#include <stdio.h>
void main()
{
char data[3][4] = { {0,0,2,0},{1,1,0,0,},{2,1,0,2} };
int x, y;
for (y=0;y<3;y++){
for (x = 0;x < 4;x++) {
printf("%d행 %d열에", y + 1, x + 1);
if (data[y][x] == 1) printf("검은 바독둘이 놓여 있습니다. \n");
else if (data[y][x] == 2) printf("흰 바둑돌이 놓여 있습니다. \n");
else printf("는 바둑돌이 놓여 있지 않습니다. \n");
}
}
}
이렇듯 2차원 배열을 사용하면 선언된 형태만 보아도 1차원보다는 간편하고 구분이 쉽다는 것을 알 수 있다.