EXCELSIOR

[C] Chap08 - 배열 Array 본문

C&C++/C - Programming

[C] Chap08 - 배열 Array

Excelsior-JH 2018. 5. 16. 10:32

Chap 08 - 배열 Array

배열(array)은 형식이 같은 자료 여러 개가 모여 새로운 하나를 이룬 형식이다. 예를 들어, int aList[5]는 5개의 의 int형 자료가 모여 int형 배열이 된다. 배열은 지금까지 배운 변수와 다르게 배열의 이름은 변수의 이름과 달리 메모리의 주소다.

배열은 배열이름[인덱스] 형식을 가지며, 각 배열요소의 인덱스는 0에서 부터 전체요소의 개수보다 1 작은(n-1) 범위까지이다.


아래의 예제는 다섯 개의 수를 입력받아 배열(aList)에 저장한 후 출력하는 코드이다. 아래의 예제 코드에서 확인할 수 있듯이, aList변수가 아니라 0번 요소의 주소 즉, &aList[0] 이다.

  // array01.c
#include <stdio.h>

int main(void){

   // int 5개가 한 덩어리인 배열 선엄 및 0 초기화
   int aList[5] = { 0 };

   // 다섯 번 반복해 사용자 입력을 받고 순차적으로 인덱스를
   // 증가시켜 각각의 배열요소에 저장한다.
   for(int i=0; i < 5; ++i)
       scanf("%d", &aList[i]);

   // 배열에 담긴 내용을 요소 하나씩 꺼내서 출력한다.
   for (int i=0; i < 5; ++i)
       printf("%d\n", aList[i]);

   printf("aList = %d\n", aList);  // aList는 변수가 아니라 0번 요소의 주소이다.
   printf("&aList = %d\n", &aList);  // 즉 aList == aList[0]과 같다.

   return 0;
}
/* 출력결과
1 2 3 4 5
1
2
3
4
5
aList = 6422292
&aList = 6422292
*/


8.1 1차원 배열의 기본 문법

다음 예제는 1차원 배열의 활용 예제 코드이다. 이 예제에서 중요한 점은 배열연산의 결과가 'l-value가 될수 있다' 즉, 변수가 될 수 있다는 것이다. 아래의 코드에서 aList[0]aList[3]의 값이 각각 100200으로 변경된 것을 확인 할 수 있다.

  
// array02.c
#include <stdio.h>

int main(void){

   // 요소가 5개인 int형 배열의 선언 및 정의
   // int aList[] = { 10, 20, 30, 40, 50 };
   int aList[5] = { 10, 20, 30, 40, 50 };
   
   // 전체 요소의 값을 화면에 출력
   for(int i=0; i < 5; ++i)
       printf("%d\t", aList[i]);
   putchar('\n');

   // 일부 배열 요소의 값을 변경
   aList[0] = 100;
   aList[3] = 200;

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

   return 0;
}

/*출력 결과
10 20 30 40 50
100 20 30 200 50
*/


다음 예제코드는 aList의 요소를 aListNew에 복사하는 코드이다. 아래 코드의 주석과 같이 aListNew = aList라고 할 경우, 에러가 난다. 그 이유는 aListNew의 값은 &aListNew[0]의 값 즉, 메모리 주소값이므로 l-value가 아니기 때문이다. 따라서, aList의 각 요소값을 복사하려면 for 문을 이용하여 하나씩 복사해야 한다.

  
// arraycpy01.c
#include <stdio.h>

int main(void){

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

   // aListNew = aList;와 같은 코드는 불가능하며,
   // 아래와 같이 반복문으로 하나씩 복사해야 한다. (혹은 메모리 복사)
   for(int i=0; i<5; ++i)
       aListNew[i] = aList[i];

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

   return 0;
}
/*출력결과
10 20 30 40 50
*/


8.2 문자열의 배열

문자열은 본래 char 형 배열이다. 그리고 문자열의 끝은 \0(NULL 문자) 이다.

8.3.1 문자열의 기본 구조

다음 예제는 문자열의 본질이 배열임을 설명하는 코드이다.

  
// arraystring01.c
#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 0;
}
/* 출력결과
Hello
Hello
Hello
*/

아래의 그림은 위의 코드에서 문자열 배열의 논리적 메모리 구조를 나타낸 것이다.



원래 "Hello"라는 문자열을 풀어서 표시하면, 'H', 'e', 'l', 'l', 'o', '\0'이다. 문자상수 하나하나를 일일이 표기하려면 매우 번거롭기 때문에 보통은 문자열("Hello")로 표기한다.


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

[독하게 시작하는 C 프로그래밍]의 저자이신 최호성님의 전적인 사견임을 책에서도 밝혀 놓았듯이 명확한 정답은 있는 듯 하진 않지만, 공부하는 입장인 나로써는 충분히 일리가 있는 설명인듯하다.

다음 예제는 사용자로부터 영문이름을 입력받아 문자열의 길이를 출력하는 코드이다.

  
// arraystring02.c
#include <stdio.h>

int main(void){

   char szBuffer[32] = { 0 };
   int nLength = 0;

   // 이름을 입력받아 배열에 저장한다.
   printf("Input your name : ");
   gets(szBuffer);

   // 배열의 시작부터 '\0'가 나올 때까지 계속 다음으로 넘기고 확인한다.
   while(szBuffer[nLength] != '\0')
       nLength++;

   // 이름과 문자열의 길이를 출력한다.
   printf("Your name is %s(%d).\n", szBuffer, nLength);
   return 0;
}
/*출력결과
Input your name : cjh
Your name is cjh(3).
*/

위의 코드에서 만약에 문자열 안에 '\0' 이 들어있지 않다면 무한루프가 발생할 수 있다. 하지만, 문자열의 끝에는 항상(?) '\0'이 들어 있으므로 위의 코드는 에러없이 동작한다.

8.4 다차원 배열

1차원 배열은 메모리가 1차원 선형 구조다. 이러한 1차원 선형 구조를 여러겹으로 쌓으면 2차원 면 구조가 된다. 위에서 배운 내용은 1차원 구조를 전제로 배열에 대해 배웠다. 이번 절에서는 2차원 또는 3차원인 다차원 배열에 대해 알아보자.

8.4.1 2차원 배열의 기본 문법

2차원 배열은 행(row)과 열(column)로 구성된 2차원 구조다. 2차원 배열을 선언할 때는 자료형 배열이름[행][열]; 형식으로 선언한다.

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

  
// array04.c
#include <stdio.h>

int main(void){

   // 3행 4열 int 배열 선언 및 정의
   int aList[3][4] = {
      {10, 20, 30, 40},
      {50, 50, 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 0;
}
/* 출력결과
10 20 30 40
50 50 70 80
90 100 110 120
*/


위의 코드에서 2차원 배열에 대해 배열연산을 한번만 수행하면, 즉 aList[0]라고 한다면 이것은 주소를 의미하는 r-value 이다. 따라서, aList[0]은 0번 행 전체를 의미하는 주소상수이다.

이번에는 2차원 배열의 열의 우측 경계를 벗어나 입/출력을 시도하면 어떤 결과가 발생하는지 살펴보자.

  
// arraysum01.c
#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;

   // 2차원 배열의 열의 경계를 벗어남
   aList[0][4] = 300;

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

       putchar('\n');
  }

   return 0;
}
/* 출력결과
10 20 30 40
300 60 70 80
90 100 110 120
*/


위의 코드에서 aList[0][4] = 300;aList[3][4]의 열의 범위에서 벗어나는 곳에 300이라는 값을 대입한다. 그 결과 출력결과를 보면 aList[1][0]300이라는 값이 출력된다. 그 이유가 무엇일까?

이런 현상이 발생하는 이유는 2차원 배열이라는 구조는 논리적으로 2차원인 것이지 실제 메모리의 구조가 2차원인 것은 아니기 때문이다. 즉, 1차원 구조의 메모리를 논리적으로 2차원인 방법으로 접근한 것이다.


Comments