배열이란
배열은 자료형이 같은 변수들의 모음이다. 유사한 유형의 자료들을 사용할 때 배열을 사용하면 관리가 쉽다.
배열을 만들려면 변수를 선언하듯이 자료형과 배열의 이름을 적고 대괄호[]와 대괄호 안에 배열의 크기를 적어주면 된다.
자료형 배열명[배열의 크기];
ex) int array[5];
예시와 같이 배열을 선언하면 다음과 같이 ‘array’라는 이름으로 사용할 수 있는 변수가 5개가 생성된다.
변수명[인덱스] | array[0] | array[1] | array[2] | array[3] | array[4] |
자료 |
이때 배열명과 함께 대괄호[] 안에 들어가는 숫자는 배열 내에 자료의 순서를 나타내고 ‘인덱스’라고 한다.
꼭 기억해야 할 점은 배열의 인덱스는 0부터 시작한다는 것이다.
앞에서 반복문을 배울 때 카운터 변수의 시작이 0인 이유도 배열을 다루기 쉽게 하기 위함이다. 그러니 특별한 상황이 아니면 반복문의 카운터 변수는 0부터 시작하는 습관을 들이는 것이 좋다.
배열에 값을 배정
배열에 값을 저장하는 방식은 두 가지가 있다.
배열을 선언할 때 중괄호{}를 통해 저장하는 방식
//크기가 5인 정수형 배열에 배열 선언과 동시에 자료를 저장
int a[5] = { 1, 2, 3, 4, 5 };
//크기를 적지 않아도 된다.
int b[] = { 1, 2, 3, 4, 5 };
//지정된 크기보다 자료의 개수가 적어도 된다.
int c[5] = { 1, 2, 3 };
위와 같은 방식으로 배열에 값을 배정할 때는 배열의 크기를 지정해주지 않아도 된다.
중괄호{}안에 있는 자료의 개수만큼 배열의 크기가 자동으로 정해진다.
또한 배열의 크기를 정해주었을 때 중괄호{} 안의 자료의 개수가 배열의 크기보다 작아도 괜찮다.
이 경우에는 중괄호{}안의 자료들이 배열의 인덱스 0부터 순서대로 저장된다.
하지만 중괄호{}를 통해 값을 저장하는 방식은 배열을 선언하는 시점에서만 가능하다.
또한 일반적으로 배열에 값을 미리 저장하는 것 보단 프로그램이 실행되며 값을 저장하기 때문에
이는 많이 쓰이는 방법은 아니다.
값을 인덱스를 통해 직접 저장하는 방식
//크기가 5인 정수형 배열 선언
int array[5];
//인덱스를 통해 값을 직접 저장
array[0] = 11;
array[1] = 22;
array[2] = 33;
array[3] = 44;
array[4] = 55;
배열을 사용할 때는 대체로 배열을 미리 선언하고 이후에 필요한 값을 배열에 저장한다.
이때 변수에 값을 저장해주듯이 배열명과 원하는 인덱스를 적어주고 대입연산자(=)를 통해 값을 저장해주면 된다.
하지만 이 방식으로 배열에 값을 저장한다면 배열을 선언할 때 꼭 배열의 크기를 정해주어야 한다.
만약 배열의 크기를 선언 시에 정해주지 않으면 컴파일 에러가 발생하니 배열의 크기를 꼭 적어주어야 한다.
배열 사용
점수와 같이 동일한 유형의 자료들을 다룰 때 다음 예제와 같이 배열을 사용하면 변수 선언이 간결해진다.
#include<stdio.h>
int main(void)
{
int score[4] = { 90 , 87 , 65 , 76 };
float average = (score[0] + score[1] + score[2] + score[3]) / 4.0;
printf("Averge : %.2f", average);
// (출력) Average : 79.50
return 0;
}
하지만 위 코드에서 점수의 평균을 구하기 위해 배열의 모든 원소를 일일이 더해야 하는 번거로움이 있다.
만약 자료의 개수 4개가 아니라 100개 이상이라면 이를 모두 코드로 입력하기엔 무리일 것이다.
이때 반복문을 사용하면 배열을 다루기가 훨씬 쉬워진다.
#include<stdio.h>
int main(void)
{
int score[4] = { 90 , 87 , 65 , 76 };
float sum = 0; // 점수들의 총합을 저장할 변수 선언
int i = 0; // 반복문에 사용할 카운터 변수 선언
for (i = 0; i < 4; i++)
{
sum += score[i];
}
printf("Averge : %.2f", sum / 4.0);
// (출력) Average : 79.50
return 0;
}
앞서 for 문이 배열을 다루기 좋다고 했던 이유는
이 코드에서 처럼 카운터 변수를 배열의 인덱스로 바로 사용할 수 있기 때문이다.
for 문을 이용해 배열을 다루면 코드를 읽을 때 while 문에 비해 어떤 작업을 하는 반복문인지 한눈에 알아보기 쉽다.
이처럼 배열과 반복문을 사용하면 유사한 코드의 반복을 대폭 줄일 수 있다.
이어서 다음 예제는 사용자에게 10개의 정수를 입력받고, 이를 모두 더해서 출력한다.
#include<stdio.h>
int main()
{
int number[10]; //크기가 10인 정수형 배열 선언
int i; //반복문에 사용할 카운터 변수
int sum = 0; //입력 받은 수의 합을 저장할 변수
printf("10개의 정수를 입력하세요: "); // (출력) 10개의 정수를 입력하세요:
/*반복문을 통해 사용자에게 값을 연속적으로 입력받고
이를 배열에 순서대로 저장한다.*/
for (i = 0; i < 10; i++)
{
scanf("%d", &number[i]);
sum += number[i];
}
// (입력 예시) 1 2 3 4 5 6 7 8 9 10
printf("%d", sum); // (출력 예시) 55
return 0;
}
만약 배열을 사용하지 않았다면 변수를 10개를 각각 선언해주고, scanf()문 역시 10번을 써줘야한다.
하지만 크기가 10인 배열과 반복문을 이용하여 훨씬 간결한 코드로 작성이 가능하다.
추가로 이 프로그램을 실행시키면 두 가지 방식으로 값을 입력해 줄 수 있다.
먼저, 각 숫자를 입력할 때마다 엔터를 치는 것이다. 앞서 입력에 대해 공부할 때 이 방식만 소개를 했는데,
scanf()는 입력 시에 공백이 있어도 해당 변수에 대한 입력이 종료된다.
즉 위 코드에서 “1 2 3 4 5 6 7 8 9 10”과 같이 한 줄에 각 숫자의 사이마다 공백을 입력하고 엔터를 쳐도
배열 안에 차례로 10개의 값이 저장된다.
이는 사용자에게 좀 더 편리한 입력 환경을 제공할 수 있다.
2차원 배열
프로그래밍에서 배열을 사용할 때 차원이라는 개념이 등장하는데 이때의 차원은 우리가 실생활에서 쓰는 차원이랑은 다른 뜻이다.
단순히 인덱스를 1개 사용하면 1차원 배열, 2개 사용하면 2차원 배열, 3개 사용하면 3차원 배열, n개 사용하면 n차원 배열이라고 한다.
앞에서 먼저 배운 배열은 인덱스가 하나인 1차원 배열이다.
1차원 배열은 일련의 정보들만 저장할 수 있다. 하지만 상황에 따라 데이터를 표와 같이 행렬의 형태로 다뤄야 할 때도 있다.
이런 경우에는 배열 안에 배열이 저장된 형태인 2차원 배열을 사용하면 된다.
다음 표는 각각의 행과 열에 해당하는 인덱스 표현을 나타낸 것이다.
1열 | 2열 | 3열 | 4열 | |
1행 | [0][0] | [0][1] | [0][2] | [0][3] |
2행 | [1][0] | [1][1] | [1][2] | [1][3] |
3행 | [2][0] | [2][1] | [2][2] | [2][3] |
4행 | [2][0] | [3][1] | [3][2] | [3][3] |
일반적으로 앞의 인덱스가 행을 나타내고, 뒤의 인덱스가 열을 나타낸다.
2차원 배열을 선언할 때는 자료형과 배열의 이름을 적고 대괄호 두 개에 각각 행의 개수와 열의 개수를 적어주면 된다.
자료형 배열명[행의 개수][열의 개수];
2차원 배열 역시 두 가지 방식으로 배열에 값을 저장할 수 있다.
배열을 선언할 때 중괄호{}를 통해 저장하는 방식
//중괄호를 통해 2차원 배열에 값을 배정
int a[3][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
//행의 개수가 주어지지 않아도 된다.
int b[][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, {9, 10, 11, 12} };
앞서 중괄호{}를 통해 미리 배열의 값을 저장하는 방식은 많이 쓰이지 않는다고 했지만,
이를 통해 2차원 배열의 구조를 쉽게 이해할 수 있다.
위의 형태를 살펴보면 2차원 배열은 결국 행에 대한 배열이 열에 대한 배열을 자료로 갖는 구조이다.
즉, 배열 내의 자료가 배열인 형태이다. 선언 시에 배열의 값을 저장할 때는 행의 개수는 주어지지 않아도 된다.
하지만 열의 개수는 적어주지 않으면 컴파일 에러가 발생하니 꼭 적어주어야 한다.
값을 인덱스를 통해 직접 저장하는 방식
//행의 크기가 3, 열의 크기가 4인 2차원 배열 선언
int a[3][4];
//인덱스를 통해 값을 직접 저장
a[0][0] = 1;
a[0][1] = 2;
a[0][2] = 3;
...
a[2][3] = 12;
2차원 배열도 1차원 배열과 마찬가지로 배열에 값을 미리 저장하지 않는다면 행의 크기와 열의 크기를 미리 정해야 한다.
앞에서 설명했듯이 2차원 배열은 다음과 같이 표의 형태로 관리해야 하는 자료들을 다룰 때 사용한다.
국어 | 수학 | 영어 | 평균 | |
학생1 | 80 | 75 | 87 | 80.6 |
학생2 | 90 | 85 | 96 | 90.3 |
학생3 | 100 | 95 | 70 | 80.3 |
#include<stdio.h>
int main()
{
// 행의 크기가 3, 열의 크기가 4인 2차원 배열 선언
// 행은 학생을 나타내고, 열은 각 과목의 점수를 저장한다
// 각 행의 [][3] 에는 각 학생들의 평균을 저장한다
float StudentScore[3][4];
int i, j; // 반복문에 사용할 카운터 변수
// 행에 대한 반복문
for (i = 0; i < 3; i++) // 점수를 입력받을 학생을 바꾼다
{
printf("%d번째 학생의 성적(국어 수학 영어) : ", i+1);
// 열에 대한 반복문
for (j = 0; j < 3; j++) // 입력받을 과목을 바꾼다
{
scanf("%f", &StudentScore[i][j]);
}
}
// (출력) 1번째 학생의 성적(국어 수학 영어) : (입력 예시) 80 75 87
// (출력) 2번째 학생의 성적(국어 수학 영어) : (입력 예시) 90 85 96
// (출력) 3번째 학생의 성적(국어 수학 영어) : (입력 예시) 100 95 70
// 각 행의 4번째 열에 평균을 저장하기 위한 이중 for문
for (i = 0; i < 3; i++)
{
StudentScore[i][3] = 0; // 평균을 저장할 공간을 0으로 초기화
for (j = 0; j < 3; j++)
{
//평균을 저장할 공간에 각 학생의 과목별 성적을 더한다
StudentScore[i][3] += StudentScore[i][j];
}
StudentScore[i][3] /= 3;
// 과목의 개수로 나누어 평균을 저장한다
printf("%d번째 학생의 평균 : %.1f\n", i + 1, StudentScore[i][3]);
}
// (출력 예시) 1번째 학생의 평균 : 80.6
// (출력 예시) 2번째 학생의 평균 : 90.3
// (출력 예시) 3번째 학생의 평균 : 88.3
return 0;
}
위 코드는 학생들의 성적과 평균을 2차원 배열에 저장하는 예제이다.
먼저 각 학생의 국어, 수학, 영어 성적을 차례대로 입력받는다.
이때 배열의 공간은 각 행의 세 번째 열까지만 사용된다.
학생의 성적이 전부 입력되면 각 행의 일단 네 번째 열에 성적의 합을 저장한 후에 과목의 개수로 나눠서 같은 위치에 평균을 저장한다.
이처럼 2차원 배열을 사용하면 여러 분류의 자료들을 한 번의 선언으로도 편리하게 다룰 수 있다.
다차원 배열
인덱스의 개수를 늘리면 3차원, 4차원, n차원 배열을 사용할 수도 있다. 하지만 2차원 배열까지만 사용하는 것이 관행이고, 3차원 배열부터는 다루기 어렵기 때문에 잘 사용하지 않는다. 만약 3차원 이상의 배열을 사용해야만 한다면 인덱스의 개수만 달라질 뿐 배열의 사용법이 바뀌진 않는다.
배열의 크기
상황에 따라 프로그램 실행 도중 배열의 크기를 구해야 할 때도 있을 것이다. 이때 주로 sizeof() 함수를 사용한다.
sizeof() 함수는 전달받은 인자가 차지하는 메모리 크기를 반환한다.
이때 인자는 자료형의 이름이 될 수도 있고 변수명이 될 수도 있다.
주어진 인자가 자료형이면 자료형이 차지하는 메모리의 크기가 반환되고,
변수명이면 그 변수가 할당받는 메모리의 크기가 반환된다. 다음 예제를 통해 각 자료형의 크기를 알 수 있다.
#include<stdio.h>
int main()
{
char c;
int i;
float f;
double d;
printf("size of char : %d \n", sizeof(char));
// (출력) size of char : 1
printf("size of 변수'c' : %d \n", sizeof(c));
// (출력) size of 변수'c' : 1
// sizeof(변수명)의 형태는 해당 변수의 자료형의 크기를 반환한다
printf("size of int : %d \n", sizeof(int));
// (출력) size of int : 4
printf("size of 변수'i' : %d \n", sizeof(i));
// (출력) size of 변수'i' : 4
// sizeof(변수명)의 형태는 해당 변수의 자료형의 크기를 반환한다
printf("size of float : %d \n", sizeof(float));
// (출력) size of float : 4
printf("size of 변수'f' : %d \n", sizeof(f));
// (출력) size of 변수'f' : 4
// sizeof(변수명)의 형태는 해당 변수의 자료형의 크기를 반환한다
printf("size of double : %d \n", sizeof(double));
// (출력) size of double : 8
printf("size of 변수'd' : %d \n", sizeof(d));
// (출력) size of 변수'd' : 8
// sizeof(변수명)의 형태는 해당 변수의 자료형의 크기를 반환한다
return 0;
}
정리하자면 변수는 자료형에 따라 다음 표와 같이 메모리를 할당받는다. 참고로 이 값은 컴파일러와 컴퓨터에 따라 달라질 수 있다.
자료형 | 할당되는 메모리의 크기 |
char | 1 |
int | 4 |
float | 4 |
double | 8 |
배열은 각 원소가 하나의 변수가 할당받는 메모리만큼 공간을 차지하게 된다. 따라서 다음 그림과 같이 크기가 4인 정수형 배열은 메모리의 크기가 총 16이 된다.
따라서 sizeof() 함수를 통해 구한 메모리의 크기를 자료형이 할당받는 메모리의 크기로 나눠주면 배열의 크기가 된다. 이를 이용해 다음 예제와 같이 배열의 크기를 구할 수 있다.
#include<stdio.h>
int main()
{
int arr[10];
printf("배열 'arr'가 차지하는 메모리의 크기 : %d\n", sizeof(arr));
// (출력) 배열 'arr'가 차지하는 메모리의 크기 : 40
printf("배열 'arr'의 크기 : %d", sizeof(arr) / sizeof(int));
// (출력) 배열 'arr'의 크기 : 10
return 0;
}
배열의 크기가 구해지는 과정을 알아보자. 먼저 크기가 10인 정수형 배열은 int형 변수가 차지하는 메모리 4만큼을 10개 할당받는다. 그렇기에 sizeof(arr)는 배열이 차지하는 총 메모리의 크기인 40을 반환하고, 이를 다시 sizeof(int)가 반환하는 4로 나눠서 배열의 크기인 10을 구할 수 있다.
'C language' 카테고리의 다른 글
C언어) 재귀함수 (0) | 2023.06.24 |
---|---|
C언어) 함수 사용 (0) | 2023.06.24 |
C언어) 함수에 대하여 & 함수 정의 방법 (0) | 2023.06.23 |
C언어) 포인터의 이해 (0) | 2023.06.21 |
C언어) 논리 연산 (0) | 2023.06.07 |