29. 배열(Array)
1. 키워드
- 배열(Array)
- 요소(Element)
2. 배열 사용하기
- 배열은 같은 자료형의 변수를 일렬로 늘어놓은 형태이며 반복문과 결합하면 연속적이고 반복되는 값을 손쉽게 처리할 수 있다.
3. 배열을 선언하고 요소에 접근하기
- 배열은 변수 이름 뒤에
[]
(대괄호)를 붙인 뒤 크기를 설정한다. - 그리고 배열을 선언하면서 값을 초기화할 때는
{}
(중괄호)를 사용한다.
#include <stdio.h>
int main()
{
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 배열을 생성하고 값 할당
printf("%d\n", numArr[0]); // 배열의 첫 번째(인덱스 0) 요소 출력
printf("%d\n", numArr[5]); // 배열의 여섯 번째(인덱스 5) 요소 출력
printf("%d\n", numArr[9]); // 배열의 열 번째(인덱스 9) 요소 출력
return 0;
}
// 11
// 66
// 110
int numArr[10]
은 크기가10
인int
타입 배열을 선언한다는 뜻이다.- 배열을 선언하면서 값을 초기화할 때는
{}
안의 값 개수는 배열의 크기보다 작아도 되지만 크면 안 된다. - 또한,
{}
를 사용하여 배열에 값을 할당하는 방법은 배열을 선언할 때만 사용할 수 있으며 이미 선언된 배열에는 사용할 수 없다.
- 배열에 값이 저장된 공간을 요소라고 한다.
- 배열에서 각 요소에 접근하려면 배열 뒤에
[]
를 사용하며[]
안에 각 요소의 인덱스를 지정해 주면 된다.
- 여기서 주의할 점은 배열의 인덱스는 항상
0
부터 시작한다는 점이다. - 따라서 배열
numArr
의 첫 번째 요소는numArr[0]
이 된다.
배열 선언과 배열 인덱스
- 배열을 선언할 때도
[]
를 사용하고 배열의 요소에 접근할 때도[]
를 사용한다. - 같은
[]
기호를 사용해서 헷갈리기 쉽지만 선언과 사용을 구분해서 생각하면 된다. - 즉, 배열을 선언할 때
[]
는 "이 변수가 배열이고 크기는 얼마다"라고 알려주는 역할이고, 배열을 사용할 때[]
는 "배열의 요소에 접근하겠다"는 뜻이다.
- 요소가
0
개인 배열은 선언할 수 없다. - 따라서 배열을 선언할 때 크기는 항상
1
이상이다. - 하지만 배열에 접근할 때는 인덱스가
0
부터 시작한다.
int numArr[0]; // 컴파일 에러. 크기가 0인 배열은 선언할 수 없음
int numArr[1]; // 크기가 1인 배열. 요소가 1개
numArr[0]; // 배열의 첫 번째 요소, 인덱스 0에 접근
- 배열의 인덱스가
0
부터 시작하는 이유는 메모리 주소가0
부터 시작하기 때문이다. - 배열도 포인터이므로 인덱스가
0
부터 시작하면 요소 접근과 포인터 연산이 일치하게 된다.
배열을 선언할 때 크기 생략하기
- 배열을 선언할 때 값을 초기화한다면 배열의 크기를 생략할 수 있다.
- 초기화를 하지 않을 때는 생략할 수 없다.
4. 배열을 0
으로 초기화하기
- 이번에는 배열의 모든 요소를 간단하게
0
으로 초기화해 보자.
#include <stdio.h>
int main()
{
int numArr[10] = { // 배열의 요소를 모두 0으로 초기화
0,
};
printf("%d\n", numArr[0]); // 배열의 첫 번째(인덱스 0) 요소 출력
printf("%d\n", numArr[5]); // 배열의 여섯 번째(인덱스 5) 요소 출력
printf("%d\n", numArr[9]); // 배열의 열 번째(인덱스 9) 요소 출력
return 0;
}
// 0
// 0
// 0
- 배열을 선언할 때
{0,}
을 할당하여 배열의 요소를 모두0
으로 초기화했다. - 이렇게 하면 초기화할 때
0
을 일일이 나열하지 않아도 된다.
5. 배열의 요소에 값 할당하기
- 배열은
[]
로 요소에 접근한 뒤 값을 할당할 수 있다.
#include <stdio.h>
int main()
{
int numArr[10]; // 크기가 10인 배열 선언
numArr[0] = 11; // 인덱스가 0인 배열의 요소에 값 할당
numArr[1] = 22; // 인덱스가 1인 배열의 요소에 값 할당
numArr[2] = 33; // 인덱스가 2인 배열의 요소에 값 할당
numArr[3] = 44; // 인덱스가 3인 배열의 요소에 값 할당
numArr[4] = 55; // 인덱스가 4인 배열의 요소에 값 할당
numArr[5] = 66; // 인덱스가 5인 배열의 요소에 값 할당
numArr[6] = 77; // 인덱스가 6인 배열의 요소에 값 할당
numArr[7] = 88; // 인덱스가 7인 배열의 요소에 값 할당
numArr[8] = 99; // 인덱스가 8인 배열의 요소에 값 할당
numArr[9] = 110; // 인덱스가 9인 배열의 요소에 값 할당
printf("%d\n", numArr[0]); // 배열의 첫 번째(인덱스 0) 요소 출력
printf("%d\n", numArr[5]); // 배열의 여섯 번째(인덱스 5) 요소 출력
printf("%d\n", numArr[9]); // 배열의 열 번째(인덱스 9) 요소 출력
return 0;
}
// 11
// 66
// 110
- 배열의 요소를 출력할 때와 마찬가지로
[]
에 인덱스를 지정한 뒤 값을 할당하면 된다.
- 배열의 범위를 벗어난 인덱스에 접근하면 어떻게 되는지 확인해 보자.
#include <stdio.h>
int main()
{
int numArr[10]; // 크기가 10인 배열 선언
numArr[0] = 11; // 인덱스가 0인 배열의 요소에 값 할당
numArr[1] = 22; // 인덱스가 1인 배열의 요소에 값 할당
numArr[2] = 33; // 인덱스가 2인 배열의 요소에 값 할당
numArr[3] = 44; // 인덱스가 3인 배열의 요소에 값 할당
numArr[4] = 55; // 인덱스가 4인 배열의 요소에 값 할당
numArr[5] = 66; // 인덱스가 5인 배열의 요소에 값 할당
numArr[6] = 77; // 인덱스가 6인 배열의 요소에 값 할당
numArr[7] = 88; // 인덱스가 7인 배열의 요소에 값 할당
numArr[8] = 99; // 인덱스가 8인 배열의 요소에 값 할당
numArr[9] = 110; // 인덱스가 9인 배열의 요소에 값 할당
printf("%d\n", numArr[-1]); // 음수이므로 잘못된 인덱스
printf("%d\n", numArr[10]); // 배열의 범위를 벗어난 인덱스
printf("%d\n", numArr[20]); // 배열의 범위를 벗어난 인덱스
return 0;
}
// -858993460 (쓰레기 값)
// -858993460 (쓰레기 값)
// 13651968 (쓰레기 값)
- 배열의 요소에 접근할 때 인덱스로 음수를 지정하거나, 배열의 크기를 벗어난 인덱스를 지정해도 컴파일 에러가 발생하지 않는다.
- 하지만 실행을 해보면 쓰레기 값이 출력된다.
- 즉, 배열의 범위를 벗어난 인덱스에 접근하면 배열이 아닌 다른 메모리 공간에 접근하게 된다.
6. 배열의 크기 구하기
- 반복문으로 크기가
10
인 배열의 요소를 출력한다면 다음과 같이 만들 수 있을 것이다.
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 요소가 10개
for (int i = 0; i < 10; i++) // 0부터 10까지 반복
{
// 요소에 접근
}
- 코드를 다 작성했는데 배열의 크기를
15
개로 늘려야 하는 상황이 발생했다. - 그래서 배열의 크기를
15
로 늘렸지만 실수로 반복문의 조건식은 수정하지 못했다.
// 배열의 크기를 15로 늘림
int numArr[15] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 132, 143, 154, 165};
for (int i = 0; i < 10; i++) // 실수로 조건식은 수정하지 못했음
{
// 요소에 접근
}
- 버그는 이런 과정으로 발생하게 된다.
- 여기서는 수정할 부분이 두 곳이지만 반복문이 여러 개로 늘어나면 실수할 가능성은 그만큼 높아진다.
- 실수를 방지하기 위해서는 배열의 크기가 바뀌었을 때 배열의 크기를 알아서 계산하도록 만들면 된다.
- 이미 선언된 배열이 차지하는 전체 공간과 요소의 개수는
sizeof
연산자를 활용하면 간단하게 구할 수 있다.
#include <stdio.h>
int main()
{
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 크기가 10인 int 타입 배열
printf("%d\n", sizeof(numArr)); // 4바이트 크기의 요소가 10이므로 40
printf("%d\n", sizeof(numArr) / sizeof(int)); // 배열의 크기를 구할 때는
// 전체 공간을 요소의 크기로 나눠줌
return 0;
}
// 40
// 10
- 배열에
sizeof
연산자를 사용해 보면 배열이 차지하는 전체 공간이 출력된다. - 따라서
int numArr[10];
은 크기가 4바이트인int
타입 요소가10
개 모여있으므로40
이 출력된다.
- 배열의 크기(요소 개수)를 구할 때는 배열이 차지하는 전체 공간에서 요소의 크기로 나눠준다.
sizeof(numArr) / sizeof(int); // 40 / 4는 10, 요소의 개수는 10개
// sizeof(numArr) / sizeof(numArr[0]) 형태도 가능
배열의 크기와 인덱스
- C에서는 인덱스가 배열의 범위를 벗어났는지 검사하지 않으므로 프로그래머가 항상 이 부분을 생가하면서 작성해야 한다.
- 배열의 크기(요소 개수)를 구해놓고, 배열에 배열에 접근하기 전에 인덱스가
요소 개수 - 1
을 넘지 않는지 확인하는 것도 좋은 방법이다.
7. 반복문으로 배열의 요소를 모두 출력하기
- 이번에는 반복문을 사용하여 배열의 요소를 모두 출력해 보자.
#include <stdio.h>
int main()
{
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 크기가 10인 int 타입 배열
for (int i = 0; i < sizeof(numArr) / sizeof(int); i++) // 배열의 요소 개수만큼 반복
{
printf("%d\n", numArr[i]); // 배열의 인덱스에 반복문의 변수 i를 지정
}
return 0;
}
// 11
// 22
// 33
// 44
// 55
// 66
// 77
// 88
// 99
// 110
- 반복문에서
for (int i = 0; i < sizeof(numArr) / sizeof(int); i++)
처럼 조건식에sizeof
연산자를 사용하여 배열의 요소 개수를 구한 뒤 반복했다.
- 반복문의 변수
i
는 변화식을 통해1
씩 증가하므로 배열의 인덱스에i
를 넣으면 배열의 요소를 순서대로 모두 접근할 수 있다.
for (int i = 0; i < sizeof(numArr) / sizeof(int); i++) // 배열의 요소 개수만큼 반복
{
printf("%d\n", numArr[i]); // 배열의 인덱스에 반복문의 변수 i를 지정
}
- 반복문으로 배열의 요소를 역순으로 출력할 수도 있다.
#include <stdio.h>
int main()
{
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 크기가 10인 int 타입 배열
for (int i = sizeof(numArr) / sizeof(int) - 1; i >= 0; i--) // 요소 개수 - 1부터 역순으로 반복
{
printf("%d\n", numArr[i]); // 배열의 인덱스에 반복문의 변수 i를 지정
}
return 0;
}
// 110
// 99
// 88
// 77
// 66
// 55
// 44
// 33
// 22
// 11
- 반복문으로 배열의 요소를 역순으로 출력할 때 주의할 점이 있다.
- 초깃값에 배열의 크기(요소 개수)를 바로 넣어버리면 처음부터 배열의 인덱스를 벗어난 상태가 된다.
int numArr[10]; // 요소가 10개
numArr[10]; // 10을 지정하면 배열의 인덱스를 벗어난 상태가 됨
numArr[9]; // 마지막 요소의 인덱스는 요소의 개수 - 1
- 배열의 인덱스는
0
부터 시작하므로 마지막 요소의 인덱스는 요소의 개수에서1
을 빼줘야 한다. - 즉, 요소가
10
개인 배열에서 마지막 요소의 인덱스는9
가 된다. - 따라서 다음과 같이 초기식에는 배열의 요소 개수를 구한 뒤
1
을 빼주고, 조건식에는i >= 0;
을 지정하여0
까지 반복할 수 있도록 만든다.
8. 배열의 요소 합계 구하기
- 이번에는 배열에서 요소의 합을 구해 보자.
#include <stdio.h>
int main()
{
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 크기가 10인 int 타입 배열
int sum = 0; // 합을 저장할 변수는 0으로 초기화
for (int i = 0; i < sizeof(numArr) / sizeof(int); i++) // 배열의 요소 개수만큼 반복
{
sum += numArr[i]; // sum과 배열의 요소를 더해서 다시 sum에 저장
}
printf("%d\n", sum);
return 0;
}
// 605
- 배열의 요소 개수만큼 반복하면서 변수
sum
과 요소를 더한 뒤 다시sum
에 저장해 준다. - 즉, 요소의 값을
sum
에 누적시키는 것이다.
+=
연산자를 풀어서 쓰면 다음과 같다.
- 여기서 변수
sum
을0
으로 초기화해 주지 않으면 쓰레기 값이 들어있게 된다. - 그래서 쓰레기 값과 요소의 값을 더하게 되므로 잘못된 결과가 나온다.
- 값을 누적시킬 때는 변수를 반드시
0
으로 초기화해야 한다.
9. 배열의 요소에 저장된 값을 두 배로 만들기
- 이번에는 배열의 요소에 저장된 값을 두 배로 만들어보자.
#include <stdio.h>
int main()
{
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 크기가 10인 int 타입 배열
for (int i = 0; i < sizeof(numArr) / sizeof(int); i++) // 배열의 요소 개수만큼 반복
{
numArr[i] *= 2; // 배열의 요소에 2를 곱해서 다시 요소에 저장
}
for (int i = 0; i < sizeof(numArr) / siof(int); i++) // 배열의 요소 개수만큼 반복
{
printf("%d\n", numArr[i]);
}
return 0;
}
// 22
// 44
// 66
// 88
// 110
// 132
// 154
// 176
// 198
// 220
- 배열의 요소에 저장된 값을 두 배로 만들려면 배열의 요소에 접근하여
2
로 곱한 뒤 다시 요소에 저장해 주면 된다.
*=
연산자를 풀어서 쓰면 다음과 같다.
- 이처럼 배열은 반복문으로 반복하면서 배열의 요소에 저장된 값을 변경할 수 있다.
10. 배열을 포인터에 넣기
- 배열은 사실 첫 번째 요소의 주솟값만 담고 있다.
- 즉, 배열은 주솟값이기 때문에 포인터에 넣을 수 있다.
- 따라서 다음과 같이 포인터에 배열을 넣은 뒤 포인터에서 인덱스로 요소에 접근할 수 있다.
#include <stdio.h>
int main()
{
int numArr[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 110}; // 크기가 10인 int 타입 배열
int *numPtr = numArr; // 포인터에 int 타입 배열을 할당
printf("%d\n", *numPtr); // 배열의 주소가 들어있는 포인터를 역참조하면
// 배열의 첫 번째 요소에 접근
printf("%d\n", *numArr); // 배열 자체를 역참조해도 배열의 첫 번째 요소에 접근
printf("%d\n", numPtr[5]); // 배열의 주소가 들어있는 포인터는 인덱스로 접근할 수 있음
printf("%d\n", sizeof(numArr)); // sizeof로 배열의 크기를 구하면
// 배열이 메모리에 차지하는 공간이 출력됨
printf("%d\n", sizeof(numPtr)); // sizeof로 배열의 주소가 들어있는 포인터의 크기를 구하면
// 포인터의 크기가 출력됨
return 0;
}
// 11
// 11
// 66
// 40
// 8
int *numPtr = numArr;
처럼 배열을 포인터에 바로 할당할 수 있다.- 단, 자료형이 같아야 하며 1차원 배열이라면
*
가 한 개인 단일 포인터이어야 한다.
- 배열을 포인터에 할당한 뒤 포인터를 역참조해 보면 배열의 첫 번째 요소의 값이 나온다.
- 마찬가지로 배열 자체도 역참조해 보면 배열의 첫 번째 요소의 값이 나온다.
- 즉, 실제로는 배열도 포인터라 할 수 있다.
printf("%d\n", *numPtr); // 배열의 주소가 들어있는 포인터를 역참조하면
// 배열의 첫 번째 요소에 접근
printf("%d\n", *numArr); // 배열 자체를 역참조해도 배열의 첫 번째 요소에 접근
- 배열의 주소가 들어있는 포인터는 인덱스를 통해 요소에 접근할 수 있다.
- 배열과 포인터가 다른 점은
sizeof
로 크기를 계산했을 때이다. - 배열에
sizeof
연산자를 사용하면 배열이 차지하는 전체 공간이 출력되지만sizeof
로 배열의 주소가 들어있는 포인터의 크기를 구해 보면 그냥 포인터의 크기만 나온다.
printf("%d\n", sizeof(numArr)); // sizeof로 배열의 크기를 구하면
// 배열이 메모리에 차지하는 공간이 출력됨
printf("%d\n", sizeof(numPtr)); // sizeof로 배열의 주소가 들어있는 포인터의 크기를 구하면
// 포인터의 크기가 출력됨
11. 배열을 활용하여 10진수를 2진수로 변환하기
- 이번에는 배열을 응용해서 10진수를 2진수로 변환한 뒤 배열에 넣어보자.
- 참고로 10진수를
0
이 될 때까지2
로 계속 나눈 뒤 나머지를 역순으로 읽으면 2진수가 된다.
- 예를 들어
13
을 2진수로 변환한다면,13
을2
로 나누면 몫은6
이 나오고 나머지는1
이 나온다. 6
은13
아래에 적고1
은6
옆에 적는다.- 이런 식으로 계속
2
로 나눠서 몫과 나머지를 구해 몫이0
이 나오면 가장 아래의 나머지부터 역순으로 읽는다.
- 이제 위의 식을 코드로 표현해 보자.
#include <stdio.h>
int main()
{
int decimal = 13;
int binary[20] = {
0,
};
int position = 0;
while (1)
{
binary[position] = decimal % 2; // 2로 나누었을 때 나머지를 배열에 저장
decimal = decimal / 2; // 2로 나눈 몫을 저장
position++; // 자릿수 변경
if (decimal == 0) // 몫이 0이 되면 반복을 끝냄
break;
}
// 배열의 요소를 역순으로 출력
for (int i = position - 1; i >= 0; i--)
{
printf("%d", binary[i]);
}
printf("\n");
return 0;
}
// 1101
- 먼저 변환할 10진수
13
을 변수decimal
에 저장하고, 2진수를 저장할 배열binary
를 선언한 뒤0
으로 초기화했다.
- 10진수를 2진수로 변환하는 식은 같은 계산을 계속 반복하는 구조이다.
- 따라서
while
로 무한 루프를 만들었다. - 반복문 안에서는
binary[position] = decimal % 2
와 같이 10진수를2
로 나눴을 때 나머지를 구해binary
배열에 넣어준다. - 그리고
decimal
을2
로 나눈 몫을 다시decimal
에 저장해 준다. - 계산이 끝났다면 다음 자리를 저장할 수 있도록
position
의 값을 증가시킨다.
int position = 0;
while (1)
{
binary[position] = decimal % 2; // 2로 나누었을 때 나머지를 배열에 저장
decimal = decimal / 2; // 2로 나눈 몫을 저장
position++; // 자릿수 변경
- 계속 반복하다가
decimal
이1
이 되면1
을2
로 나눌 수가 없으므로 몫은0
이 된다. - 이때
break
로 반복문을 종료한다.
- 가장 아래의 나머지부터 역순으로 읽어야 하므로 배열의 요소를 역순으로 출력해 준다.
- 여기서 주의할 점은 배열의 인덱스가
0
부터 시작하므로position - 1
부터0
까지 감소하도록 만들어야 한다. - 만약
position
부터 반복하면 처음부터 배열을 벗어난 상태가 된다.
References
- https://dojang.io/mod/page/view.php?id=292
- https://dojang.io/mod/page/view.php?id=293
- https://dojang.io/mod/page/view.php?id=294
- https://dojang.io/mod/page/view.php?id=295
- https://dojang.io/mod/page/view.php?id=296
- https://dojang.io/mod/page/view.php?id=297
- https://dojang.io/mod/page/view.php?id=298
- https://dojang.io/mod/page/view.php?id=299
- https://dojang.io/mod/page/view.php?id=300
- https://dojang.io/mod/page/view.php?id=301