안녕하세요, 이번 포스팅에서는 1차원 배열에 이어서 2차원 배열에 대해 살펴보겠습니다.
또한 2차원 배열은 실제 컴퓨터 메모리공간상에서 어떤 식으로 구현되는지 심층적으로 살펴보겠습니다.
| 2차원 배열
앞선 강좌에서 1차원 배열을 설명할 때, 이러한 예시를 들었었습니다.
void main()
{
int floor101, floor102, floor103, floor104; // 1층 101호부터 104호까지
int floor201, floor202, floor203, floor204; // 2층 201호부터 204호까지
int floor301, floor302, floor303, floor304; // 3층 301호부터 304호까지
int floor401, floor402, floor403, floor404; // 4층 401호부터 404호까지
.
.
.
}
앞선 강의에서 설명했듯이 아파트에 있는 모든 가구를 각각의 독립된 변수로 표현하기에는 한계가 있습니다.
4층짜리 빌라 건물이라면 어떻게든 가능하겠지만, 롯데타워처럼 수많은 층이 있는 건물의 호수를 독립된 변수로 나타낸다는 것은 상상만 해도 끔찍합니다.
그렇다면 이것을 1차원 배열로 나타내 봅시다.
건물이 4층이고 각 층마다 4개의 방이 존재한다면 4x4, 즉 16개의 칸이 필요하겠죠? 그렇다면
int floor[16];
라고 선언할 수 있겠네요.
그런데 데이터의 구조를 잘 살펴보면, 우리가 배열에 담으려고 하는 데이터는 일련의 선형적인 데이터 구조가 아니라,
행과 열로 나타낼 수 있는 2차원적인 데이터 구조라는 것을 알 수 있습니다.
1호 | 2호 | 3호 | 4호 | |
1층 | 101호 | 102호 | 103호 | 104호 |
2층 | 201호 | 202호 | 203호 | 204호 |
3층 | 301호 | 302호 | 303호 | 304호 |
4층 | 401호 | 402호 | 403호 | 404호 |
그래서 이러한 2차원적인 행렬의 형태를 띄고 있는 데이터 구조를 표현하기 위해 만들어진 것이 2차원 배열입니다.
| 2차원 배열 선언하기
2차원 배열을 선언하기 위해서는 달리 복잡한 방법이 있는 것이 아닙니다.
아까의 예시를 적용하여 4x4크기의 2차원 배열을 만들고 싶다면,
int floor[4][4];
라고 선언해 주면 됩니다.
읽는 순서는 행(row), 열(column) 순으로 읽어주시면 됩니다. 즉 우리가 방금 선언한 배열은 4행 4열짜리 배열인 것이죠.
저장된 값에 접근하는 방법도 1차원 배열과 동일합니다.
floor[0][0] = 101;
이 문은 101이라는 값을 floor의 0행 0열의 자리에 할당한다는 의미입니다.
printf("%d", floor[0][0]);
저장한 값에 접근하여 출력하고 싶다면 이렇게 작성하면 되겠죠?
floor[0][0] = 101;
floor[0][1] = 102;
floor[0][2] = 103;
...
하지만 매번 이렇게 일일이 값을 할당해 주어야 한다면 독립 변수를 하나씩 선언해 주는 것과 무슨 차이가 있겠습니까?
이러한 과정을 편리하게 해주는 것이 바로 반복문 입니다.
그래서 배열을 다룰 때는 항상 반복문이 필요한 것입니다. 배열을 쭉 돌면서 탐색할 때도 반복문이 필요하고,
배열의 모든 원소를 출력하고 싶을 때도 반복문을 사용합니다.
| 2차원 배열과 반복문
우리는 앞선 예제를 실현하기 위해 반복문을 이용하여 2차원 배열을 활용할 것입니다.
우선 예제 코드를 함께 보시죠.
int i, j;
for(i = 0; i < 4; i++){ // 배열 요소 할당
for(j = 0; j < 4; j++){
floor[i][j] = (i + 1) * 100 + (j + 1);
}
}
for(i = 0; i < 4; i++){ // 배열 요소 출력
for(j = 0; j < 4; j++){
printf("%d ", floor[i][j]);
}
printf("\n");
}
2차원 배열에 접근할 때는 중첩 반복문을 사용하여야 합니다.
그래야 2차원 데이터에서 행, 열 값에 대응되는 위치에 접근할 수 있기 때문입니다.
반복문 안의 코드를 하나씩 살펴봅시다.
floor[i][j] = (i + 1) * 100 + (j + 1);
이 문에서는 배열의 원소를 반복문의 변수 값을 통해 접근하여 값을 넣어주고 있네요.
i, j값이 변화하는 양상을 머리속으로 생각하면서 배열의 각 원소에는 어떤 값이 들어갈 지 생각해 보세요.
i | j | floor[i][j] |
0 | 0 | 101 |
0 | 1 | 102 |
0 | 2 | 103 |
0 | 3 | 104 |
아하! i, j값이 변화하는 양상에 맞추어 해당 호실이 몇 호실인지에 대한 정보를 할당하고 있는 것이었군요!
두 번째 중첩반복문은 배열의 모든 원소를 출력하기 위한 것입니다.
printf("%d ", floor[i][j]);
이전처럼 i와 j가 쭉 돌면서 2차원 배열의 모든 원소값을 출력하는 결과를 얻게 됩니다.
실행결과
101 102 103 104
201 202 203 204
301 302 303 304
401 402 403 404
| 컴퓨터 메모리 공간의 특성
하지만 여러분이 알아야 할 사실이 하나 있습니다.
바로 컴퓨터의 메모리 공간은 1차원으로 이루어져 있다는 사실인데요, 여기서 의구심을 품는 분들도 계실 것이리라 생각합니다. 왜냐하면 우리는 방금 전 까지만 해도 2차원 배열을 이용해 2차원 데이터를 다루었기 때문이죠.
하지만 컴퓨터의 메모리 공간은 정말 1차원으로 이루어져 있습니다. 그런데 어떻게 2차원 데이터를 다룰 수 있는 것일까요?
사실 컴퓨터에게는 우리가 2차원 배열로 표현한 것들도 전부 1차원으로 받아들이고 처리하게 됩니다.
그래서 우리가 스택이든, 큐이든, 트리든 어떤 자료구조를 사용하더라도, 이것은 우리가 받아들이기 편한 표현 방식으로 표현한 것일 뿐이고, 컴퓨터 입장에서는 메모리 공간이 물리적으로 1차원이기 때문에 실제로는 1차원으로 받아들이고 처리하게 되는 것입니다.
과연 정말 그러한지 예제 코드로 확인해볼까요?
int arr[2][2], i, j;
for(i = 0; i < 2; i++){
for(j = 0; j < 2; j++){
printf("%p \n\n", &arr[i][j]);
}
}
모르신 분들을 위해 잠깐 짚고 넘어가자면,
%p는 주소의 형식으로 출력하기 위해서 사용하는 서식문자이고, 그래서 출력하는 대상도
arr[i][j] 가 아니라 &arr[i][j]입니다. (&연산자에 대한 내용은 포인터 부분에서 자세히 설명하겠습니다.)
그렇다면 출력되어야 하는 값은
arr[0][0]
arr[0][1]
arr[1][0]
arr[1][1]
의 주소값이겠죠?
만약 한 행의 주소를 출력한 뒤 다음 행으로 바뀌는 시점에서 출력되는 주소가 연속되지 않는다면
컴퓨터가 2차원적 데이터를 1차원이 아닌 다른 방식으로 표현한다는 이야기가 되겠죠.
결과를 한번 봅시다.
실행결과
008FFE50
008FFE54
008FFE58
008FFE5C
실행 결과를 보면, 2차원 배열도 연속된 1차원 데이터 공간에 할당되었음을 알 수 있습니다.
여기까지 2차원 배열과 메모리의 특성에 대해서 살펴보았습니다. 메모리의 특성은 지금 단계에서 매우 어렵게 느껴질 수 있으니 부담되신다면 뒤쪽의 포인터 부분을 보고 다시한번 읽어보시면 도움이 될 것 같습니다. 꼭 기억해야 할 것은 2차원 배열을 선언하는 방법과 반복문으로써 다루는 방법이므로 꼭 잘 숙지하셨으면 좋겠고, 잘 이해가 되지 않으신다면 1차원 배열에 대해서 더 연습해보시는 것을 추천드립니다.
오늘 포스팅한 내용이 도움되셨다면 공감과 댓글 한번씩 부탁드립니다.
'C언어 강좌' 카테고리의 다른 글
[C언어_13] 사용자 정의 함수, 프로토타입(Prototype) (0) | 2020.11.21 |
---|---|
[C언어_12] C언어 문제 풀이 #4 (0) | 2020.11.19 |
[C언어_10] 1차원 배열 & 배열을 이용한 문자열 처리 (0) | 2020.11.09 |
[C언어_09] C언어 문제 풀이 #3 (0) | 2020.11.07 |
[C언어_08] 중첩 반복문/무한루프: 문제풀이에 어떻게 응용하는가 (0) | 2020.11.02 |