배열은 임의의 자료값을 포함하고 있는 자료 구조로, 이 자료들은 모두 같은 자료형을 가진다. 가장 간단한 종류의 배열은 1차원 배열이다. 개념상으로 1차원 배열에서는 하나의 행에서 각 열마다 이어서 자료가 나열되어 있다. 배열을 선언하기 위해서 우리는 반드시 배열내에 들어가는 원소의 자료형과 갯수를 정의해주어야만 한다. 이때 자료형에는 제한이 없다.
배열상의 각 원소에 접근하기 위해서 반드시 배열 이름 뒤에 []에 숫자를 넣어서 사용해주어야 한다. 파이썬과 동일하다. 배열의 길이가 n인 경우에 인덱싱은 0부터 n-1까지다.
[주의] C는 인덱싱이 배열의 크기를 벗어나는지 검사하지 않는다. 만약에 벗어난다면 이는 정의되지 않은 행위다. 인덱싱이 0으로부터 시작하기 떄문에, 일반적으로 1개를 뺴먹는 실수를 종종하게 된다.
배열 인덱싱을 위해 정수 표현식도 사용될 수 있다.
a[i+j*10] = 0;
부-효과를 가질 수도 있다.
i = 0;
while (i < N)
a[i++] = 0;
이 경우에 i는 배열에 0이 저장된 뒤 증가한다. 다만 부-효과를 가지는 표현을 복수로 사용했을 경우 우선순위에 대한 정의되지 않은 행위가 발생할 수 있다. 특히 증감효과를 가지는 단항연산자 ++, --의 경우에 특히 그렇다. 하지만 이 문제는 증감연산자를 분리하여 작성하는 것만으로 간단하게 해결된다.
배열 또한 변수처럼 선언과 동시에 초기값을 부여받을 수 있다. 가장 일반적인 형식은 상수표현식을 중괄호로 묶고, 이들을 콤마로 분리하여 작성하는 형식이다. 만약 배열보다 짧은 값이 주어진다면 배열의 나머지 값들은 0으로 채워진다.
하지만 그렇다고 중괄호 내부를 아예 비워두는 것은 허용되지 않으며, 배열의 길이보다 긴 값을 주는 것 또한 허용되지 않는다. 만약 배열에 초기값이 주어지는 경우 []내에 배열의 크기를 줄 필요가 없다. 이 경우 배열은 가장 최소한의 길이를 가질 것이다.
int a [15] = {0, 0, 29, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 48};
의 경우를 살펴보면 배열이 매우 길다. 0이 많긴 하지만 중간마다 다른 값이 있다. 이정도는 괜찮지만 만약 배열의 길이가 100 이상으로 길어지면 입력할 떄 곤란할 것이다. C99에서는 이 문제를 해결할 수 있다 다음이 바로 그 예시다.
int ctl[1O] = {5, 1, 9, [4] = 3, 7, 2, [8] = 6};
파이썬은 len함수가 있으나 C는 그렇지 않다. 다음과 같은 테크닉을 통해 배열의 길이를 알 수 있다.
i < sizeof (a) / sizeof(a[0]) // i는 int임
이 방법을 사용하면 배열 길이를 매크로로 정의하지 않아도 되며, 배열의 길이가 차후에 변하더라도 이부분은 수정하지 않아도 된다. 다만 컴파일러가 경고메시지를 띄울 수 있다. 왜냐하면 sizeof 값은 unsigned이고 i는 signed int기 때문이다. 무부호형과 유부호형이 같이 연산되는 경우 형변환으로 인해 매우 위험한 상황이 일어날 수 있다. 하지만 i가 음수일 경우는 없으므로 위험하지는 않겠지만, 경고메시지를 피하기 위해 sizeof 앞에 (int) 캐스팅을 해주자. 하지만 이렇게 쓰면 너무 길어지므로 매크로 정의를 해주면 좋을 것이다.(매크로 안쓸려고 했는데 다시 돌아온 셈이지만 나중에 살펴보자.)
배열은 다차원으로 구성될 수 있다. 매트릭스라고 부르는 2차원 배열이 가장 대표적인 예시다.
2차원 배열의 원소에 접근하는 방법은 m [i] [j]로 i행 j열에 접근한다. 여기서 주의해야하는 것은 행이 먼저 앞에 온다는 사실이다.
for문을 통해 1차원 배열을 탐색할 수 있듯, 다중 for문을 통해 다차원 배열을 탐색할 수 있다.
하지만 C언어에서 다차원 배열은 다른 프로그래밍 언어처럼 많이 사용되지 않는다. 주로 C언어는 다차원 자료를 다루기 위해서 포인터 배열을 사용한다.
다차원 배열은 다음과 같이 초기화가 가능하다.
{0, 1, 0, 1, 0, 1, 0, 1, 0},
{0, 1, 0, 1, 1, 0, 0, 1, 0},
{1, 1, 0, 1, 0, 0, 0, 1, 0},
{1, 1, 0, 1, 0, 0, 1, 1, 1}};
하지만 위에서 살펴봤듯이 행 길이가 부족하면 나머지를, 행 갯수가 부족하면 나머지를 0으로 채울 것이다. 심지어 {}로 행을 구분하지 않아도 데이터는 입력된다. 하지만 {}을 생략하는 것은 배열에 들어가지 못하고 남아있는 자료로 인해 매우 위험하다. 컴파일러는 이에 "missing braces around initializer" 라는 경고메시지를 출력한다. 참고로 C99에서는 위에서 살펴본 것 처럼 특정 위치에 자료를 넣을 수 있다.
상수배열은 기존 배열 선언방식에서 앞에 const를 붙여주면 된다. 그러면 추후에 이 배열이 변경되는 것은 불가능하다. 이는 마치 파이썬의 튜플과도 같다. 수정이 불가능하지만 중요한 데이터, 레퍼런스, 타인과 함꼐하는 프로젝트에서 함부로 변경될 위험 측면에서 굉장히 유용하다.
C99에서 배열의 길이는 상수가 아닌 표현식으로 정할 수 있다. 우리는 이걸 VLA라고 부른다. VLA는 프로그램이 컴파일 될 때 길이가 계산되는 것이 아닌, 프로그램이 실행될 때 결정된다. VLA의 가장 큰 장점은 프로그래머가 임의의 길이를 설정해주지 않아도 되고, 필요로 하는 만큼만 길이를 설정할 수 있다는 점이다. 단 VLA는 static storage duration을 가질 수 없다는 점에서 제한을 가지는데 이는 나중에 살펴볼 개념이다.