배열(array)은 동일한 하나의 데이터형을 가진 연속된 원소들로 구성된다. 배열을 원한다면, 선언(declaration)을 사용하여 이를 컴파일러에게 알려야 한다. 배열 선언(array declaration)은 그 배열이 몇개의 원소를 가지고 있으며, 원소들의 데이터형이 무엇인지 컴파일러에게 알려 준다. 이 정보가 있어야만, 컴파일러는 배열을 바르게 설정할 수 있다. 배열 원소들은 보통의 변수들이 가질 수 있는 것과 동일한 데이터형들을 가질 수 있다. 다음과 같은 배열 선언의 예를 살펴보자

/* 배열 선언의 몇 가지 예 */
int main(void)
{
        float candy[365]; /* 465개의 float 형 값을 가지는 배열 */
        char code[12];  /* 12개의 char 형 값을 가지는 배열 */
        int states[50];  /*50개의 int형 값을 가지는 배열 */
        ......
}

각괄호([])가 candy, code, states가 배열이라는 것을 나타낸다. 각괄호 안에 있는 수는 그 배열 안에 있는 원소의 개수를 나타낸다.

배열 안에 있는 각 원소들은, 인덱스(index)라 부르는 첨자 번호를 사용함으로서 개별적으로 접근할 수 있다. 인덱스는 0부터 시작한다. 따라서, candy[0]은 candy 배열의 첫 번재 원소다. candy[364]는 365번째 원소, 즉 마지막 원소다.

이것은 우리가 이미 아는 내용이다. 이제 좀더 새로운 내용을 살펴보자.

초기화...
일반적으로, 배열은 프로그램에 필요한 데이터를 저장하는 데 사용된다. 예를 들면, 12개의 원소를 가지는 배열은 1년의 각 달의 날짜 수를 저장할 수 있다. 이러한 경우에는 프로그램의 시작 부분에서 배열을 초기화 시키는 것이 편리하다. 배열을 어떻게 초기화하는지 살펴보자

우리는, 단일 값을 가지는 변수를 -흔히 스칼라(scalar) 변수라고 부른다- 을 선언해서 다음과 같은 수직으로 초기화 시킬 수 있다는 것을 알고 있다.

int fix = 1;
float flax = PI * 2;

여기서 PI는 매크로로 이미 정의되어 있다고 가정했다. C는 다음과 같이, 새로운 신택스를  사용하여 초기화를 배열에 까지 확장한다.


int main(void)
{
        int powers[8] = {1,2,4,6,8,16,32,64}; /* ANSI에서만 가능하다 */
        ........
}

여기서 볼 수 있듯이, 배열은 콤마로 분리된 값들의 리스트를 중괄호로 감싸서 초기화한다. 원한다면 값과 콤마 사이에 스페이스를 넣을 수도 있다. 첫 번째 원소 (powers[0])에 값 1이 대입되고, 나머지 원소들에도 차례로 값이 대입된다. (컴파일러가 이 형식의 초기화를 신택스 에러로 인식한다면, ANSI 이전의 컴파일러 이기 때문이다. 그러한 경우에는 배열 선언 앞에 키워드 static를 붙이면 해결된다. 이 키워드의 의미에 대해서는 '12장 : 기억부류, 연계, 메모리 관리;에서 설명한다.) 리스트 10.1 은 각 달의 수를 출력하는 짧은 프로그램이다.

리스트10.1

/* day_mon1.c -- 각 달의 날짜 수를 출력한다 */
#include <stdio.h>
#define MONTHS 12
int main(void)
{
    int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
    int index;

    for (index = 0; index < MONTHS; index++)
        printf("%2d월: 날짜 수 %2d\n", index+1, days[index]);

    return 0;
}



출력 결과는 다음과 같다.
 1월 : 날짜 수 31
 2월 : 날짜 수 28
 3월 : 날짜 수 31
 4월 : 날짜 수 30
 5월 : 날짜 수 31
 6월 : 날짜 수 30
 7월 : 날짜 수 31
 8월 : 날짜 수 31
 9월 : 날짜 수 30
10월 : 날짜 수 31
11월 : 날짜 수 30
12월 : 날짜 수 31

그다지 훌륭하지는 않지만, 이 프로그램은 4년에 딱 한달만 날짜 수가 틀린다. 이 프로그램은 콤마로 분리된 값들의 리스트를 중괄호({})로 감싸서 days[]를 초기화 한다.

이 예제는 배열 크기를 기호 상수 MONTHS를 사용하여 나타내고 있다. 이것은 일반적으로 추천할 만한 테크닉이다. 예를 들어, 갑자기 1년이 13개월로 바뀐다면, 해당하는 #defind 지시문만 수정하면 되기 때문에, 프로그램에서 배열 크기가 사용된 모든 곳을 일일이 찾아다니며 고칠 필요가 없다.

NOTE
배열에 const 사용하기
때로는 배열을 읽기 전용으로만 사용하고 싶을 때가 있다. 즉, 프로그램이 배열에서 값을 꺼내 오기는 하지만, 배열에 새로운 값을 써 넣지 않는 것이다. 이러한 경우에는, 배열을 선언하고 초기화할 때 const 키워드를 사용할 수 있고, 또한 사용해야 한다. 그러므로 리스트 10.1에서 배열을 다음과 같이 초기화하는 것이 더 나은 선택이다.

const int days [MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};

이것은, 프로그램이 배열에 있는 각 원소를 상수로 취급하게 만든다. 보통의 변수와 마찬가지로, 일단 const 로 선언되면 나중에 값들을 대입할 수 없기 때문에, const 데이터를 초기화하려면 선언을 사용해야 한다. 이것을 알게 되었으므로, 이제부터 나오는 예제에 const를 사용할 수 있다.


그런데 배열을 초기화 하는데 길패하면 어떻게 될까? 리스트 10.2는 그와 같은 경우에 무슨 일이 벌어지는지 보여준다.

리스트 10.2

/* no_data.c -- 초기화시키지 않은 배열 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
    int no_data[SIZE];  /* 초기화시키지 않은 배열 */
    int i;
    printf("%2s%14s\n",
           "i", "no_data[i]");
    for (i = 0; i < SIZE; i++)
        printf("%2d%14d\n", i, no_data[i]);
 
    return 0;
}


 

다음은 프로그램의 실행 예다. (시스템에 따라 다른 결과가 나올 수 있다.)
i        no_date[!]
0        -858993460
1        -858993460
2        -858993460
3        -858993460

배열 원소는 보통의 변수와 같다. 사용자가 이들을 초기화하지 않는다면 그들은 아무 값이나 가질 수 있다. 컴파일러는 우연히 그 메모리 위치에 놓여 있는 값들을 사용한다. 사용자의 시스템이 이 실행 예와 다를 결과가 나오는 것도 바로 이 때문이다.

NOTE
기억 부류에 대한 사전 통고
배열도, 다른 변수들과 마찬가지로, 여러 가지 기억 부류(storage class)를 사용하여 생성할 수 있다. 이 주제에 대해서는 12장에서 설명한다. 지금 당장은, 이 장에서 설명하는 배열들이 모두 자동 기억 부류에 속한다는 것만 알면 된다. 이것은 그 배열들이 static 키워드 없이 함수 안에서 선언되고 있다는 것을 의미한다. 지금까지 사용한 모든 변수들과 배열은 자동 기억 부류에 속한다.

이 시점에서 기억 부류를 언급하는 이유는, 서로 다른 기억 부류는 서로 다른특성을 가지고 있기 때문이다. 그래서 이 장에서 설명하는 모든 것을 다른 기억 부류로 일반화시킬 수 ㅇ없다. 특히, 일부 다른 기억부류에 속하는 변수와 배열들은 사용자가 초기화하지 않을 경우 그들의 내용을 0으로 설정한다.

초기화 리스트에 들어 있는 항목들의 개수는 배열의 크기와 일치해야 한다. 이것이 일치하지 않으면 무슨 일이 벌어질까? 초기값의 개수가 두 개 모자란 상태의 리스트를 가지고 앞의 예제를 다시 시도해 보자

리스트 10.3
/* some_data.c -- 일부만 초기화된 배열 */
#include <stdio.h>
#define SIZE 4
int main(void)
{
    int some_data[SIZE] = {1492, 1066};
    int i;

    printf("%2s%14s\n",
           "i", "some_data[i]");
    for (i = 0; i < SIZE; i++)
        printf("%2d%14d\n", i, some_data[i]);

    return 0;
}

다음은 프로그램의 실행 예다. (시스템에 따라 다른 결과가 나올 수 있다.)
i        some_data[!]
0                    1492
1                    1066
2                        0
3                        0

여기서 볼 수 있듯이, 컴파일러는 문제를 알아채지 못했다. 컴파일러는, 초기값 리스트에 있는 값들을 다 사용하고 나서, 나머지 원소들을 0으로 초기화했다. 즉, 사용자가 배열을 전혀 초기화하지 않으면, 배열 원소들은, 초기화하지 않은 보통의 변수들처럼 쓰레기 값들을 갖게 된다. 그러나 배열을 일부분만 초기화하면, 나머지 원소들이 0으로 설정된다. 컴파일러는 이런 넉넉함을 에러로 간주한다. 그러나 컴파일러의 이런 변덕에 속앓이를 할 필요가 없다. 각괄호 안의 배열 크기를 생략하면 컴파일러가 스스로 초기값 리스트에 맞에 배열 크기를 설정한다.

리스트 10.4

/* day_mon2.c -- 컴파일러가 원소 개수를 카운트한다 */
#include <stdio.h>
int main(void)
{
    const int days[] = {31,28,31,30,31,30,31,31,30,31};
    int index;
    for (index = 0; index < sizeof days / sizeof days[0]; index++)
        printf("%2d월: 날짜 수 %2d\n", index +1,
               days[index]);

    return 0;
}

리스트 10.4에서 주목할 점은 다음 두 가지다.
■ 빈 각괄호를 사용하여 배열을 초기화하면, 컴파일러는 초기값리스트에 있는 항목들의 개수를 카운트하여 그것을 배열 크기로 가지는 배열을 만든다.
■ for 루프 제어 명령문에서 우리가 무엇을 했는지 주목하라. 우리는 정확하게 카운트하는 능력이 (당연히) 부족하기 때문에, 컴퓨터가 배열 크기를 계산해서 우리에게 알려 주도록 부탁한다. sizeof 연산자는 객체 또는 데이터형(type)의 크기를 바이트 수로 알아낸다. 그러므로 sizeof days는 바이트 수로 배열 전체의 크기다. sizeof days[0]은 바이트 수로 배열 원소 하나의 크기다. 벼열 전체의 크기를 배열 원소 하나의 크기로 나누면, 그 배열에 몇 개의 원소가 있는지 알 수 있다.

프로그램의 실행 결과는 다음과 같다.
 1월: 날짜 수 31
 2월: 날짜 수 28
 3월: 날짜 수 31
 4월: 날짜 수 30
 5월: 날짜 수 31
 6월: 날짜 수 30
 7월: 날짜 수 31
 8월: 날짜 수 31
 9월: 날짜 수 30
10월: 날짜 수 31
 
에라! 값을 10개만 입력했내 ㅡㅡ.. 배열 크기를 프로그램이 직접 알아내게 하는 이 방법은, 배열의 끝을 지나쳐서 출력하는 사태를 예방한다. 그러나 또한 이것은 자동 카운트의 잠재적인 단점을 경고한다. 원소의 개수가 틀렸더라도 컴파일러가 이 에러를 잡아내지 못한다는 것이다.

배열을 초기화하는 간단한 방법이 한 가지 더 있다. 그 방법은 문자열에만 적용되기 때문에, 다음 장에서 설명한다.


젠장... 손가락이 아프군요... 나중에 다시 이어서...ㅠㅠ

'C 언어' 카테고리의 다른 글

16장 링크리스트  (0) 2019.06.02
17장 디스크 파일의 사용  (0) 2019.06.02
API 윈도우 창 띄우기  (0) 2019.05.25
CreateWindow() - 10개의 인수  (0) 2019.05.25
ASCII 코드표  (0) 2019.05.25

+ Recent posts