8장 배열

형식이 같은 자료 여러 개가 모여 새로운 하나를 이룬 형식이다.

5개의 int형 자료가 한데 모여 int형 배열이 된다.

배열이 기존의 변수와 가장 다른 점 중 하나는 배열의 이름은 변수의 이름과 달리 메모리의 주소라는 사실이다.

int형 배열에 부여하는 이름은 배열을 이루고 있는 여러 요소를 대표하는 첫 번째 요소의 메모리 주소에 부여하는 식별자 이다.

배열의 필요성 ?

다음 예제는 사용자로부터 숫자 다섯 개를 입력받아 출력하는 예제이다.

코드는 문제가 없지만 이런 방식은 부적절하다. 유지보수 과정에서 5000개를 입력 받는다면 이런 방식으로는 수정하기가 매우 어렵기 때문이다.

#include <stdio.h>

int main(void){
    int a,b,c,d,e;

    scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
    printf("%d\n",a);
    printf("%d\n",b);
    printf("%d\n",c);
    printf("%d\n",d);
    printf("%d\n",e);

    return 1;
}

실행결과
❯ ./welcome
1 2 3 4 5
1
2
3
4
5

배열과 반복문을 사용하면 구조가 간결하고 유지보수하기 쉬운 좋은 코드가 될 수 있다.

배열이름[인덱스] 형식을 갖고 각 배열의 요소의 인덱스는 0에서부터 전체요소 보다 1개 작은 범위 까지이다.

#include <stdio.h>

int main(void){

    int aList[5] = { 0 };
    int i = 0;

    for(i = 0; i< 5;i++){
        scanf("%d",&aList[i]);
    } 
    
    for(i = 0; i < 5;i++){
        printf("%d\n", aList[i]);
    }

    return 1;
}

실행결과
❯ ./welcome
1 2 3 4 5
1
2
3
4
5

int aList[5] 는 요소의 자료형은 int형이고 개수가 5개인 배열의 선언 및 정의 이다.

요소가 5개인 배열의 인덱스는 0~4까지 이다.

배열의 이름인 aList는 변수가 아니라 0번 요소의 주소이다. 즉, &aList[0]과 동일하다.

8.1. 1차원 배열의 기본 문법

다음 예제는 배열의 보편적인 활용 예이다.

배열연산을 통해 배열 요소에 접근하는데, 이 배열연산의 결과가 l-value가 될 수 있다는 점이다.

#include <stdio.h>

int main(void){

    int aList[5] = {10,20,30,40,50};
    int i = 0;

    for(i=0; i<5; ++i)
        printf("%d\t", aList[i]);
    putchar('\n');

    aList[0] = 100;
    aList[3] = 200;
    
    for(i=0; i<5; ++i)
        printf("%d\t", aList[i]);
    putchar('\n');

    return 1;
}

실행결과
❯ ./welcome
10      20      30      40      50
100     20      30      200     50

1차원 배열의 각 요소는 변수이다. 여기서 int aList[] = {10,20,30,40,50}; 라고 기술해도 상관없다. 컴파일러가 소스코드에 기술된 초기값의 개수를 판별하고 요소의 개수를 자동으로 확정 하기 때문이다.

#include <stdio.h>

int main(void){

    int aList[5] = {10,20,30,40,50};
    int aListNew[5] = {0};
    int i = 0;
    
    aListNew = aList;

    for(i=0;i <5; i++)
        printf("%d\t", aListNew[i]);
    putchar('\n');

    return 1;
}

실행결과
welcome.c:9:14: error: array type 'int[5]' is not assignable
왼쪽  피연산자는 l-value이어야 합니다.
    aListNew = aList;

오류의 원인은 단순대입 연산자의 왼쪽 피연산자가 변수가 아니라서 발생한것이다. 배열이름의 실체는 '주소상수'이다. 상수는 정보이지 메모리가 아니므로 대입 연산의 대상이 될 수 없다.

배열은 여러 인스턴스가 모여 한 덩어리를 이룬 것이다. 배열의 이름이 변수처럼 하나라고 결코 '한 개'가 아니다. 값을 대입할때는 배열 전체가 아니라 요소 하나하나에 대해 이루어져야 한다.

#include <stdio.h>

int main(void){

    int aList[5] = {10,20,30,40,50};
    int aListNew[5] = {0};
    int i = 0;
    
    for(i=0; i<5;++i){
        aListNew[i] = aList[i];
    }

    for(i=0;i <5; i++)
        printf("%d\t", aListNew[i]);
    putchar('\n');

    return 1;
}

실행결과
❯ ./welcome
10      20      30      40      50

8.2 최댓값/최솟값

최댓값을 구하는 예제 코드이다.

변수 i가 최초 1에서 시작했음을 주의한다.

#include <stdio.h>

int main(void){

    int aList[5] = {30,40,20,10,50};
    int i = 0;
    int nMax = aList[0];

    for(i=1;i<5;i++){
        if(aList[i] > nMax)
            nMax = aList[i];
    }

    for(i=0; i< 5;++i){
        printf("%d\t", aList[i]);
    }
    putchar('\n');
    printf("MAX : %d\n", nMax);
    return 1;
}

실행결과
❯ ./welcome
30      40      20      10      50
MAX : 50

먼저 배열의 첫번째 요소를 최댓값으로 확정하고 두번쨰 요소와 비교하여 큰수를 최댓값에 대입 하는 방식이다.

08-01 배열에서 최댓값 구하기
앞에서 본 예제를 변형하여 최댓값을 출력하는 프로그램을 완성한다. 단, 기존에 최댓값이 저장되었던 nMax변수를 사용하지 않아야 하며, 추가로 새로운 변수를 선언할 수도 없다.
#include <stdio.h>

int main(void){

    int aList[5] = {30,40,10,50,20};
    int i = 0;
    
    for(i = 1;i < 5; i++){
        if(aList[i] > aList[0]){
            aList[0] = aList[i];
        }
    }

    for(i=0; i< 5;++i){
        printf("%d\t", aList[i]);
    }
    putchar('\n');

    printf("MAX : %d\n", aList[0]);
    return 1;
}

실행결과
❯ ./welcome
50      40      10      50      20
MAX : 50
08-02 요소의 값을 교환하는 방식으로 배열에서 최솟값 구하기
이번에는 지금까지 예제들과 달리 최솟값을 구한다. 최솟값이 저장되는 변수는 별도로 선언하지 않고 배열의 0번 요소에 저장한다. 그러나 1~4번 요소들을 각각 비교하여 작은 값을 무조건 0번 요소에 대입하는 것이 아니라 교환한다. 예를 들어, 0번 요소에 30이 들어있고 2번 요소에 10이 들어있을경우 10이 0번 요소에 대입되는 것이 아니라 0번 요소와 2번 요소의 값을 서로 교환 하여 0번 요소에 10이 2번 요소에 30이 저장되도록한다.
#include <stdio.h>

int main(void){

    int aList[5] = {30,40,10,50,20};
    int i = 0;
    int nTmp = 0;
    
    for(i=1;i<5;i++){
        if(aList[i] < aList[0]){
            nTmp = aList[0];
            aList[0] = aList[i];
            aList[i] = nTmp;
        }
    }


    for(i=0; i< 5;++i){
        printf("%d\t", aList[i]);
    }
    putchar('\n');

    printf("MIN : %d\n", aList[0]);
    return 1;
}

실행결과
❯ ./welcome
10      40      30      50      20
MIN : 10

8.2 문자의 배열

문자열의 실체는 char형 배열 이다.

문자열을 다룬다는 말은 배열을 다룬다는 것으로 생각해야한다. 그리고 문자열의 끝은 '₩n'(NULL 문자)임을 알고 있어야 한다.

8.2.1 문자열의 기본 구조

#include <stdio.h>

int main(void){
    int aList[5] = {30, 40, 10, 50, 20};
    
    //배열의 각 요소의 값을 하나씩 기술하는 방식으로 초기화
    char szBuffer[6] = {'H','e', 'l','l','o','\0'};

    //문자열 형태로 문자집합을 기술하는 방식으로 배열 초기화
    char szData[8] = {"Hello"};
    
    //문자열 상수를 가르키는 포인터 변수 선언 및 초기화
    char *pszBuffer = "Hello";

    puts(szBuffer);
    puts(szData);
    puts(pszBuffer);
    return 1;
}

"Hello" 라는 문자열 표기를 풀어서 표시하면, 'H','e', 'l','l','o','\0' 이다.

szData는 char형 8개가 모인 배열로 선언 및 정의한것이다. 그런데 "Hello"는 끝에 '\0'를 포함해 char가 6개인 배열이다. 별로도 기술하지 않은 요소는 0으로 자동 초기화 된다.

char *pszBuffer는 char형에 대한 포인터 변수의 선언 및 정의 이다. 포인터 변수라는 것은 메모리의 주소를 저장하기 위한 전용변수이다.

8.3.2 문자열의 끝이 '\0'인 이유?

다음 예제는 사용자로부터 영문이름을 입력받아 이름을 이루고 있는 영문자의 개수를 출력하는 프로그램이다. 이프로그램이 문자열의 길이를 측정하는 방법은 문자배열의 0번 요소부터 탐색하여 0이 나올 때까지 반복해 확인하는 것이다.

#include <stdio.h>

int main(void){
    char szBuffer[32] = {0};
    int nLength = 0;

    printf("Input your name : ");
    gets(szBuffer);

    while(szBuffer[nLength] != '\0')
        nLength++;
    
    printf("Your name is %s(%d).\n", szBuffer, nLength);
    return 1;
}

실행결과
❯ ./welcome
Input your name : seongsu
Your name is seongsu(7).

실제로 이런 방식으로 문자열의 길이를 측정하고 만일 배열요소 안에 '₩0' 이 들어있지 않다면 무한루프가 발생할 가능성이 있기 때문이다.

char szBuffer[32] 의 배열의 선언으로 확보되는 메모리의 크기는 32바이트 임을 알수있고 NULL 문자는 구분자가 되어준다.

8.4 다차원 배열

1차원 배열은 메모리가 1차원 선형 구조인것을 말한다. 1차원 선형 구조를 여러 겹으로 쌓으면 2차원 면 구조가 된다. 이런 배열들을 다차원 배열이라고 한다. 실제 모습과 상관없이 논리적인 구조가 2차원, 3차원인 배열을 의미한다.

8.4.1 2차원 배열의 기본 문법

기존의 1차원 배열과 달리 2차원 배열은 행과 열로 구성된 2차원 구조이다. 그래서 2차원 배열을 선언할 때는 자료형 배열이름[행][열]; 형식으로 기술한다.

다음 예제는 배열의 요소가 int형이고 열의 길이가 4, 행의 길이가 3인 2차원 배열의 선언 및 정의를 보인 예제이다.

#include <stdio.h>

int main(void){
    int aList[3][4] = {
        {10, 20, 30, 40},
        {50, 60, 70, 80},
        {90, 100, 110, 120}
    };
    
    int i = 0, j = 0;

    for(i=0;i<3;++i){
     
        for(j=0;j<4;++j){
            printf("%d\t", aList[i][j]);
        }

        putchar('\n');
    }
    
    return 1;
}

실행결과
❯ ./welcome
10      20      30      40
50      60      70      80
90      100     110     120

다음 예제는 계수기를 사용해 1씩 증가시킨 값으로 2차원 배열의 요소를 채웠다.

#include <stdio.h>

int main(void){
    
    int aList[3][4] = {0};
    int i = 0,j = 0, nCounter = 0;

    for(i=0;i<3;++i){

        for(j=0;j<4;++j){
            aList[i][j] = ++nCounter;
        }
    }

    for(i=0;i<3;++i){

        for(j=0;j<4;++j){
            printf("%d\t", aList[i][j]);
                }
        putchar('\n');
    }

    return 1;
}

실행결과
❯ ./welcome
1       2       3       4
5       6       7       8
9       10      11      12

전위식으로 표기한 ++nCounter를 최초로 0초기화를 했고 대입되기 전에 1이 증가하여 1이 되고 요소에 1을 단순대입한다. 이와 같은 연산이 반복하여 전체요소에 접근하여 1씩 증가시킨 값을 대입한다.

이 예제는 세 부분으로 구성되어있다.

  1. 변수의 선언 및 정의
  2. 반복문을 이용하여 배열요소 채우기
  3. 배열에 담긴 값 출력하기

1번 3번은 달라질것이 거의 없지만 2번은 계속해서 달라져야 한다. 오로지 채우는 방법을 변경하여 문제가 요구하는 결과를 얻을 수 있어야 한다.

08-03 2차원 배열의 행과 열의 총합 계산하기
요소가 int형이고 열의 길이가 4, 행의 길이가 3인 배열의 행, 열의 총합을 계산하는 프로그램을 작성. 예를 들어, 0번의 행의 0~2열 요소의 총합을 0번 행의 3번열에 저장한다. 같은 방법으로 1번 행의 3번 열에도 같은 행에 속한 나머지 요소들의 총합을 저장한다. 그리고 마지막 2번 행은 각 열의 총합이 2번 행에 속한 요소에 저장되도록 한다. 즉, aList[0][0], aList[0][1] 요소의 총합을 aList[0][2]에 저장해야한다. 
#include <stdio.h>

int main(void){
    int aList[3][4] = {
        {10,20,30},
        {40,50,60}};

    int i = 0, j = 0;

    for(i=0;i<2;++i){

        for(j=0;j<3;++j){
            aList[i][3] += aList[i][j];
        }
    }

    for(i=0;i<2;++i){

        for(j=0;j<4;++j){
            aList[2][j] += aList[i][j];
        }
    }

    for(i = 0;i<3; i++){
        for(j=0;j<4; j++){
            printf("%d\t", aList[i][j]); 
        }
        putchar('\n');
    }

    return 1;
}

실행결과
❯ ./welcome
10	20	30	60
40	50	60	150
50	70	90	210

8.4.2 3차원 배열

2차원 배열이 개념적으로 행과 열의 구성인데, 3차원 배열은 여기에 면을 더해 3차원을 이룬다.

면의 길이가 4이고 행의 길이가 2이고 열의 길이가 3인 3차원 배열의 선언 및 정의와 반복문을 3중첩 해서 배열요소 전체를 출력하는 코드이다.

#include <stdio.h>

int main(void){
    int aList[4][2][3] = {0};
    int i = 0,j = 0, k = 0, nCounter = 0;

    for(i= 0;i < 4; i++){
        //면
        printf("Plane number : %d\n", i);
        for(j = 0;j < 2; j++){
            //행
            for(k = 0;k < 3;k++){
                //열
                aList[i][j][k] = ++nCounter;
                printf("%d\t", aList[i][j][k]);
            }
            putchar('\n');
        }
        printf("\n\n");
    }
    return 1;
}

실행결과
❯ ./welcome
Plane number : 0
1	2	3
4	5	6


Plane number : 1
7	8	9
10	11	12


Plane number : 2
13	14	15
16	17	18


Plane number : 3
19	20	21
22	23	24

aList[i][j][k] 는 aList 배열의 i면, j행, k열의 요소를 의미한다.