귀찮은 일이 생겼다.
중대장님이 실망하지 않게 서두르자.
Before Starting
한 명의 팔굽혀펴기, 윗몸일으키기, 뜀걸음 등급을
배열로 처리해보면 다음과 같을 것이다.
만약 여러 명의 측정 결과를 처리하려면 같은 형태의 배열이 명 수 만큼 필요할 것이다.
인원이 적다면 괜찮겠지만,
그렇지 않다면 이렇게 따로 배열을 만들어서 관리하기는 힘들 것이다.
이럴 때 2차원 배열
을 사용한다.
이게 무슨 말 인지 한 번 알아보자.
형태가 같은 배열이 여러 개 필요한 경우 이들을 모아 배열을 만들 수 있다.
이 배열을 2차원 배열이라고 하고,
예를 들어, 한 명의 측정 결과를 처리할 때는 요소가 3개인 int형 배열 하나면 되지만,
3명으로 늘어나면 같은 형태의 1차원 배열이 3개 필요하다.
이렇게 2차원 배열을 선언하면 메모리 주소
에 변화가 생긴다.
1차원 배열을 하나씩 선언했을 때는 각각의 배열이
앞의 그림처럼 각기 다른 메모리 주소에 할당될 수 있지만,
2차원 배열로 한 번에 선언하면 각 배열은 연속성
을 지니게 된다.
병사 4명의 3 종목 평균을 구해보자.
#include <stdio.h> int main(void) { int rank[4][3]; int total, avg; int i, j; for (i = 0; i < 4; i++) { printf("3종목 등급 입력 : "); for (j = 0; j < 3; j++) { scanf("%d", &rank[i][j]); } } for (i = 0; i < 4; i++) { total = 0; for (j = 0; j < 3; j++) { total += rank[i][j]; } avg = total / 3; printf("평균: %d\n", avg); } return 0; }
rank[4][3]에서 3은 1차원 배열 요소의 개수
,
4는 1차원 배열을 요소로 가지는 2차원 배열 요소의 개수
이다.
20행의 total 변수는 병사별로 총 등급을 누적해야 하므로
이전 병사의 총 등급이 함께 누적되지 않도록 0으로 초기화해야 한다.
결국 rank 배열은 등급을 저장할 수 있는 int형 변수가 12개 있는 셈이며,
배열명에 행 첨자
와 열 첨자
를 지정하여 각 요소를 지목할 수 있다.
이때 위치를 지정하는 규칙은
행 첨자와 열 첨자는 0부터 시작하며,
최댓값은 행의 수 -1
과 열의 수 -1
이다.
예를 들어, 세 번째 병사의 윗몸 일으키기 등급은
3행 2열의 요소가 되며 행 첨자는 2, 열 첨자는 1을 사용한다.
2차원 배열은 논리적으로는 행렬의 구조를 가지고 있지만,
물리적으로는 1차원 배열의 형태로 메모리에 할당된다.
할당되는 방법은 한 행씩 차례로 할당된다.
예를 들어, 2차원 배열 int rank[2][2];은 다음과 같이 메모리에 할당된다.
이렇게 물리적으로 [2][2] 위치에 있는 요소가 rank 배열의 몇 번 첨자인지는
간단한 배열에서는 세기 쉽지만, [99][99] 정도의 배열이라면
하나씩 세기 어려울 것이다.
1차원 배열로 계산했을 때의 위치 / 열의 수
8 / 3 = 2
1차원 배열로 계산했을 때의 위치 % 열의 수
8 % 3 = 2
결국 3개씩 끊었을 때 몇 번째 그룹인지가 행 첨자가 되고
나머지가 그 그룹에서 몇 번째에 있는지 해당되므로 열 첨자가 된다.
할당된 메모리의 9번째 공간의 첨자는
rank[(9-1) / 3] [(9-1) % 3] → rank[2][2]
다음과 같이 계산할 수 있다.
2차원 배열을 함수 내에서 선언하면 자동 변수와 같이 메모리에 남아 있는 쓰레기 값을 지니게 된다.
따라서 선언과 동시에 초기화해야 한다.
2차원 배열의 초기화는 중괄호 쌍 2개
를 써서 행 부분을 표시한다.
#include <stdio.h> int main(void) { int num[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; // int num[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };와 같은 문장 int i, j; for (i = 0; i < 3; i++) { for (j = 0; j < 4; j++) { printf("%5d", num[i][j]); } printf("\n"); } return 0; }
10행과 같이 한 줄로 작성해도 되지만 행렬 구조를 코드에 표현하는 것이 좋다.
기본 배열 전체 초기화 이외에도 일부 초깃값을 생략하는 것도 가능하다.
5~9행을 다음과 같이 초깃값을 부족하게 수정 후 컴파일을 진행하면,
int num[3][4] = { {1}, {5, 6}, {9, 10, 11} };
각 행의 앞에서부터 차례로 값을 저장하고 남는 요소는 0으로 자동 초기화한다.
또한, 행의 수를 아예 생략하고 선언할 수 있다.
int num[][4] = { {1}, {2, 3}, {4, 5, 6} };
이 경우 컴파일러는 행의 중괄호의 개수를 행으로 결정하고,
한 행의 크기는 열의 수로 결정한다.
2차원 배열은 물리적으로 1차원 배열의 나열이기 때문에
초기화할 때 행 초기화 괄호를 생략하고 1차원 배열을 초기화하는 방식으로 같게 할 수 있다.
이 경우 전체 저장 공간의 수
만큼 초깃값을 나열하며, 행 단위로 차례로 저장한다.
int num[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
2차원 배열은 1차원 배열을 요소로 갖는 1차원 배열이므로
배열 요소의 개수 = 행의 개수 배열 요소의 형태 = 열의 개수
임을 기억하자!
이때 각 행은 1차원 배열의 형태를 가지며, 2차원 배열의 부분배열
이다.
부분배열은 2차원 배열에서 하나의 배열 요소가 되므로
배열명과 첨자를 사용하여 표현한다.
부분배열명
은 1차원 배열의 배열명이며, 각 행의 단위를 독립적으로 처리할 때 배열명의 역할을 수행!
따라서 행의 수(부분배열의 수)를 계산하고 싶으면
행의 수 = 배열 전체의 크기 / 부분배열 하나의 크기
기억하자!
하나의 문자열을 저장하기 위해 1차원 char 배열이 필요하듯이
2차원 char 배열은 여러 개의 문자열을 처리할 때 사용한다.
#include <stdio.h> int main() { char animal[5][20] = {}; int i; int count; count = sizeof(animal) / sizeof(animal[0]); for (i = 0; i < count; i++) { scanf("%s", animal[i]); } for (i = 0; i < count; i++) { printf("%s", animal[i]); } return 0; }
5행에서 animal 배열을 선언하면 다음과 같이 100개의 char형 저장 공간이 생긴다.
한 행은 하나의 문자열을 저장하는 부분배열이고, 부분배열명이 각 행의 배열명의 기능을 한다.
scanf("%s", animal[0]);
animal[0]은 배열명으로 부분배열의 시작 위치
값이다.
따라서 앞에 주소 연산자 &를 붙일 필요가 없다.
2차원 char 배열을 초기화하는 방법은 두 가지가 있다.
#include <stdio.h> int main() { char animal1[5][20] = { {'d', 'o', 'g', '\0'}, {'t', 'i', 'g', 'e', 'r', '\0'}, {'r', 'a', 'b', 'b', 'i', 't', '\0'}, {'h', 'o', 'r', 's', 'e', '\0'}, {'c', 'a', 't', '\0'} }; char animal2[][10] = {"dog", "tiger", "rabbit", "horse", "cat"}; int i; for (i = 0; i < 5; i++) { printf("%s ", animal1[i]); } printf("\n"); for (i = 0; i < 5; i++) { printf("%s ", animal2[i]); } return 0; }
2차원 char 배열은 char형 변수들의 집합이므로 문자 상수로 초기화할 수 있다.
또는 1차원 char 배열을 요소로 가지므로 각 행이 되는 1차원 char 배열을 문자열로 초기화할 수 있다.
1차원 배열에서 2차원 배열을 만드는 과정을 이해하면 3차원 이상의 배열도 가능하다.
마찬가지로, 3차원 배열은 2차원 배열을 요소로 가지며, 3개의 첨자를 사용하여 선언한다.
#include <stdio.h> int main() { char rank[2][4][3] = { { { 1, 1, 2 }, { 1, 2, 3 }, { 1, 1, 1 }, { 2, 2, 2 } }, { { 3, 3, 3 }, { 2, 1, 1 }, { 3, 2, 2 }, { 1, 2, 2 } } }; int i, j, k; for (i = 0; i < 2; i++) { printf("%d 생활관 등급...\n", i + 1); for (j = 0; j < 4; j++) { for (k = 0; k < 3; k++) { printf("%5d", rank[i][j][k]); } printf("\n"); } printf("\n"); } return 0; }
1 생활관이 체력 수준이 더 높다.
2차원 배열에서 각 행은 1차원 배열로서 하나의 2차원 배열의 부분배열이 되고,
3차원 배열에서는 2차원 배열이 부분배열이 된다.
2차원 배열을 행렬 구조로 본다면 3차원 배열은 면
, 행
, 열
의 구조가 된다.
따라서 3차원 배열 rank에는 rank[0], rank[1]의 2개의 면부분배열
과
각 면부분배열을 구분하는 행부분배열
이 존재한다.
3차원 배열의 초기화는 면을 구분하는 중괄호가 추가되어 중괄호 3쌍을 사용한다.
초깃값이 저장 공간에 채워지는 순서나 초깃값이 부족한 경우
남는 저장 공간을 채우는 방식은 2차원 배열과 같다.
각 저장 공간을 사용하는 방법은 면
, 행
, 열
순서로 3개의 첨자를 사용하며,
각 첨자들은 0부터 사용하므로 사용한 요소 수보다 하나 적은 값이 최댓값이 됨!