1. 2차원? 3차원? 배열
다차원 배열은 2차원 이상의 배열을 의미한다. 다차원 배열은 1차원 배열의 연장선상에 놓여있다고 생각하면 된다.
2차원 배열과, 3차원 배열은 논리적으로 각각 2차원과 3차원의 형태를 띤다.
즉, 2차원 배열은 평면구조의 배열이고, 3차원 배열은 직육면체 구조의 배열이다.
C언어에서 문법적으로 4차원, 5차원 배열의 선언도 가능은 하지만 그것은 의미를 부여하기 힘든, 의미가 없는 배열이다.
따라서 다차원 배열은 2차원, 3차원 배열을 의미하는 것으로 이해하면 된다.
1차원, 2차원, 3차원 배열의 선언형태는 다음과 같다.
int arrOne[10]; //길이가 10인 1차원 int형 배열
int arrTwo[5][5]; //가로, 세로의 길이가 각각 5인 2차원 int형 배열
int arrThree[3][3][3]; //가로, 세로, 높이의 길이가 각각 3인 3차원 int형 배열
2. 다차원 배열을 의미하는 2차원 배열의 선언
다음 두 2차원 배열의 선언을 보자
int arr1[3][4]; //세로가3, 가로가4인 int형 2차원 배열
int arr2[2][6]; //세로가2, 가로가6인 int형 2차원 배열
위의 두 선언에서 보듯이 2차원 배열의 선언방식은 1차원 배열의 선언방식과 매우 유사하다.
다만 그 형태가 세로와 가로의 길이를 각각 명시하는 형태를 띌 뿐이다. 따라서 위와 같이 배열이 선언되면 아래와 같은 형태로 배열이 생성된다.
위 그림에서 배열요소 안에 삽입된 두 개의 숫자는 각각의 요소에 접근할 때 사용하는 인덱스 값이다.
정리하자면 배열의 이름이 arr이고, 배열요소의 자료형이 TYPE이라고 할 때, 2차원 배열의 선언형태는 다음과 같다.
TYPE arr[세로길이][가로길이];
2차원 배열도 마찬가지로 sizeof 연산자를 이용해 배열의 크기를 계산할 수 있다.
아래코드를 살펴보자.
#include<stdio.h>
int main(void) {
int arr1[3][4];
int arr2[7][9];
printf("arr1 size : %d\n", sizeof(arr1));
printf("arr2 size : %d\n", sizeof(arr2));
return 0;
}
출력결과▼
arr1 size : 48
arr2 size : 252
arr1 배열의 자료형이 int형(4Byte)이고, 세로길이, 가로길이가 각각 3,4 이므로 배열의 크기는 3x3x4 = 48
즉, 배열의 크기가 48 임을 확인할 수 있다.
arr2도 마찬가지로 배열의 크기가 252 임을 확인할 수 있다.
3. 2차원 배열요소의 접근
아래의 코드를 살펴보자.
#include<stdio.h>
int main(void) {
int arr[3][3] = { 0 };
arr[0][0] = 1;
arr[0][1] = 2;
arr[2][1] = 5;
for (int a = 0; a < 3; a++) {
for (int z = 0; z < 3; z++) {
printf("%d ", arr[a][z]);
}
printf("\n");
}
return 0;
}
1) 3번째 라인에서는 배열의 모든 요소들을 0으로 초기화해줬다.
2) 4번째 라인에서는 세로길이 0번째 인덱스, 가로길이 0번째 인덱스에 1을 저장해 주었다.
3) 5번째 라인에서는 세로길이 0번째 인덱스, 가로길이 1번째 인덱스에 2를 저장해주었다.
4) 6번째 라인에서는 세로길이 2번째 인덱스, 가로길이 1번째 인덱스에 5를 저장해주었다.
1,2,3,4, 차례로 출력결과를 통해 보이겠다.
이것들을 일반화하면 아래와 같다.
arr[N-1][M-1] = 20;
printf("%d", arr[N-1][M-1]);
세로 N, 가로 M의 위치에 값을 저장 및 참조했다.
이와 관련해서 2차원 배열요소 접근 관련 예제를 살펴보자.
프로그램 사용자로부터 층별로 두 가구가 사는 4층짜리 빌라의 가구별 거주인원수를 입력받아보자
가로가 2이고 세로가 4인 2차원 배열로 표현하기에 좋은 상황이다!
#include<stdio.h>
int main(void) {
int villa[4][2];
int population, sum_floor; //인구수, 각 층별 인구수 합
for (int k = 0; k < 4; k++) {
for (int a = 0; a < 2; a++) {
printf("%d 층 %d 호 인구수 : ", k + 1, a + 1);
scanf("%d", &villa[k][a]);
}
}
printf("\n빌라의 층별 인구수\n");
for (int k = 0; k < 4; k++) {
for (int a = 0; a < 2; a++) {
population = 0;
sum_floor = 0;
population += villa[k][a];
sum_floor += villa[k][0];
sum_floor += villa[k][1];
printf("%d 층 %d 호 인구수 : %d\n", k + 1, a + 1, population);
}
printf("%d 층 인구수 합 : %d\n", k + 1, sum_floor);
printf("\n");
}
return 0;
}
실행결과▼
위의 예제는 1차원 배열로도 구현이 가능하지만 2차원 배열로 만들었을 시 훨씬 유용하다.
4. 2차원 배열의 메모리상 할당의 상태
2차원 배열은 물리적으로도 2차원의 형태로 존재할까? 우리가 사용하고 있는 컴퓨터의 메모리는 2차원적 구조가 아니다. 이는 메모리의 주소 값을 통해서 알 수 있다. 우리가 실제 사용하는 메모리의 주소 값은 다음과 같이 1차원적 구조이다.
0x1001번지 0x1002번지 0x1003번지 0x1004 번지 0x1005번지...
(1차원적 메모리의 주소값)
실제로 배열이 2차원의 형태로 접근가능하도록 하는 것은 컴파일러의 역할 덕분이다.
프로그램을 실행하고 컴파일을 진행할 때 컴파일러는 메모리에 할당할 변수들의 크기와 위치를 결정한다. 2차원 배열도 마찬가지로 메모리에 할당되는 변수 중 하나이다.
2차원 배열을 선언하고 사용할 때, 컴파일러는 메모리에 적절한 위치에 2차원 형태로 배열을 배치하여 사용하게끔 도와준다.
#include<stdio.h>
int main(void) {
int arr[3][2];
for (int k = 0; k < 3; k++) {
for (int a = 0; a < 2; a++) {
printf("%p\n", &arr[k][a]);
}
}
return 0;
}
출력결과▼
위의 실행결과를 통해서, 배열요소 별 주소값은 int형 변수의 크기인 4바이트만큼 차이가 남을 알 수 있다.
이는 실제 2차원 배열 메모리의 할당 상태를 증명하는 결과이다.
5. 2차원 배열 선언과 동시에 초기화하기
1) 2차원 배열의 초기화 1
int arr[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
초기화 리스트 안에는 행 단위로 초기화할 값들을 별도의 중괄호로 명시한다.
2) 2차원 배열의 초기화 2
int arr2[3][3] = {
{1},
{2,3},
{7,8,9}
};
채워지지 않은 빈 공간은 0으로 채워진다.
3) 2차원 배열의 초기화 3
int arr3[3][3] = {
1,2,3,
4,5,6,
7
};
▼한 줄에 표현해도 된다.
int arr4[3][3] = { 1,2,3,4,5,6,7 };
▼빈 공간은 0으로 채워진다.
int arr4[3][3] = { 1,2,3,4,5,6,7,0,0 };
별도의 중괄호를 사용하지 않으면 좌 상단부터 시작해서 우 하단으로 순서대로 초기화된다.
아래는 2차원 배열의 초기화 예제를 모아뒀다.
#include<stdio.h>
int main(void) {
/*2차원 배열의 초기화 예 1*/
int arr[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
/*2차원 배열의 초기화 예 2*/
int arr2[3][3] = {
{1},
{2,3},
{7,8,9}
};
/*2차원 배열의 초기화 예 3*/
int arr3[3][3] = { 1,2,3,4,5,6,7 };
printf("arr1\n\n");
for (int k = 0; k < 3; k++) {
for (int a = 0; a < 3; a++) {
printf("%d ", arr[k][a]);
}
printf("\n");
}
printf("\n");
printf("arr2\n\n");
for (int k = 0; k < 3; k++) {
for (int a = 0; a < 3; a++) {
printf("%d ", arr2[k][a]);
}
printf("\n");
}
printf("\n");
printf("arr3\n\n");
for (int k = 0; k < 3; k++) {
for (int a = 0; a < 3; a++) {
printf("%d ", arr3[k][a]);
}
printf("\n");
}
printf("\n");
return 0;
}
실행결과▼
6. 배열의 크기를 알려주지 않고 초기화하기
1차원 배열선언 시, 초기화 리스트가 존재한다면, 배열의 길이를 명시하지 않아도 되었다.
2차원 배열에서도 마찬가지로 그 길이를 명시하지 않아도 된다. 단, 제약사항이 하나 있다.
다음 예를 살펴보자.
int arr[ ][ ] = {1,2,3,4,5,6,7,8};
만약에 우리들이 컴파일러라면 배열의 가로와 세로를 어떻게 결정하겠는가? 여기에는 여러 가지 경우의 수가 존재한다.
배열요소가 총 8개이므로 4x2도 가능하고, 8x1도 가능하고 1x8 도 가능, 2x4 도 가능하다.
이렇게 경우의 수가 많은데 어떻게 결정하는가?
이렇듯 가로와 세로의 길이를 모두 예측하는 것은 불가능하지만 하나만 알게 되면 나머지는 알 수 있다.
int arr[ ][4] = {1,2,3,4,5,6,7,8};
int arr[ ][2] = {1,2,3,4,5,6,7,8};
이제 예측이 가능하다. 위와 같이 가로길이를 채워 넣어주면 세로길이는 컴파일러가 알아서 세로 길이를 계산해 준다.
이처럼 2차원 배열에서 선언과 동시에 초기화를 시켰을 시 세로 길이만 생략할 수 있도록 약속되어 있다.
7. 3차원 배열
3차원 배열관련해서는 짧게 설명하고 넘어가겠다.
일단 첫번째 이유로는 3차원 배열은 흔히 사용하는 경우는 아니다. 두 번째로 2차원 배열을 지금까지 잘 이해했다면 3차원 배열도 이해하고 있다고 봐도 무방하다.
3차원 배열의 논리적 구조는 직육면체를 생각하면 된다.
앞서 우리는 가로와 세로로 이뤄진 2차원 배열을 공부하였는데, 3차원 배열은 여기에 높이의 개념이 추가되어 있다고 생각하면 된다.
따라서 다음과 같은 형태로 배열을 선언할 수 있다.
int arr1[2][3][4]; //높이2, 세로3, 가로4인 int형 3차원 배열
double arr2[5][5][5]; //높이, 세로, 가로가 모두 5인 double형 3차원 배열
sizeof 연산을 통해 3차원 배열의 크기를 살펴보자
#include<stdio.h>
int main(void) {
int arr1[2][3][4];
double arr2[5][5][5];
printf("높이 2, 세로3, 가로4 인 int형 배열 : %d\n", sizeof(arr1));
printf("높이 5, 세로5, 가로5 인 double형 배열 : %d\n", sizeof(arr2));
return 0;
}
실행결과▼
높이 2, 세로3, 가로4 인 int형 배열 : 96
높이 5, 세로5, 가로5 인 double형 배열 : 1000
간단한 예제를 통해서 3차원 배열의 선언과 접근에 대해 알아보자.
#include<stdio.h>
int main(void) {
int mean = 0, i, j;
int record[3][3][2] = {
{
{70,80},
{94,90},
{80,43}
},
{
{83,21},
{65,86},
{68,88}
},
{
{98,99},
{99,45},
{95,84}
}
};
for (int k = 0; k < 3; k++) {
for (int a = 0; a < 2; a++) {
mean += record[0][k][a];
}
}
printf("A학급 전체 평균 : %g\n", (double)mean / 6);
mean = 0;
for (int k = 0; k < 3; k++) {
for (int a = 0; a < 2; a++) {
mean += record[1][k][a];
}
}
printf("B학급 전체 평균 : %g\n", (double)mean / 6);
mean = 0;
for (int k = 0; k < 3; k++) {
for (int a = 0; a < 2; a++) {
mean += record[2][k][a];
}
}
printf("C학급 전체 평균 : %g\n", (double)mean / 6);
return 0;
}
출력결과▼
A학급 전체 평균 : 76.1667
B학급 전체 평균 : 68.5
C학급 전체 평균 : 86.6667
이로서 오늘은 다차원 배열에 대해서 알아보았다. 솔직히 3차원 배열에 대해서 조금 많이 적게 설명한 감이 있지만 2차원 배열만 제대로 잘 정리해서 알아놔도 3차원 배열은 2차원 배열 개념의 연장선이니 이해하는것은 그리 어려운 일은 아니다.다음 포스팅은 포인터의 포인터 라는 주제로 다시 오겠다.
'C언어' 카테고리의 다른 글
C언어 - 포인터의 포인터 (0) | 2023.07.27 |
---|---|
C언어 - 포인터와 함수에 대한 이해 (0) | 2023.07.21 |
C언어 - 상수 형태의 문자열을 가리키는 포인터 (0) | 2023.07.20 |
C언어 - 포인터와 배열 (0) | 2023.07.12 |
C언어 - 포인터의 이해(3) (0) | 2023.07.09 |