관리 메뉴

Kim's Programming

C언어 - 배열(Array)(1/2) 본문

Programming/C

C언어 - 배열(Array)(1/2)

Programmer. 2015. 8. 13. 16:28

배열의 정의


배열이란 동일한 타입을 가지는 변수들의 유한 집합입니다. 배열을 선언하는 기본 형식은 다음과 같습니다. 일반 변수 선언문과 동일하되 변수명 뒤에 [ ] 괄호(bracket)와 배열 크기 지정문이 온다는 것만 다릅니다. 배열 선언문에 [ ]괄호가 하나 있으면 1차원 두개있으면 2차원이라고 합니다. 뭐 더 있으면 더 높은 차원 배열이라고 할 수 있습니다.


type 배열명[크기][크기].....;


type은 어떤 타입의 변수들이 모여 있는지를 지정하는데 정수형 변수들의 모임이면 int, 실수형 변수의 모임이면 double이라고 적습니다. 기본형 외에도 포인터, 구조체, 사용자 정의형 등 임의의 타입이 모두 배열을 구성할 수 있습니다. 타입 T가 있으면 T형 배열은 언제나 가능하며 심지어 배열의 배열도 선언할 수 있습니다. 배열명은 말 그대로 배열의 이름입니다. 배열명도 명칭이므로 명칭 규칙에 맞게만 작성하면 됩니다. 관습적으로 배열명에는 ar 이나 a같은 ( array(배열)을 뜻하는 ) 접두어를 붙여 이 변수가 배열이라는 것을 쉽게 알 수 있습니다. 크기는 이 배열이 몇 개의 요소를 가지는지, 즉 몇 개의 변수가 모여서 배열을 구성하는지를 지정하는데 자연수로 된 상수를 적어줍니다. 개수이기 때문에 음수나 실수는 당연히 들어 갈 수 없으며 선언할 때 필요한 메모리양을 계산할 수 있어야 하므로 반드시 상수만 쓸 수 있습니다. 배열의 차원만큼 크기를 지정하되 1차월 배열이면 [ ] 괄호를 한번만 써줍니다. 다음 몇 개의 예시를 보이겠습니다.

1
2
3
int ar[5];            //크기가 5인 정수형 배열 ar 
double avg[10];    //크기가 10인 실수형 배열 avg
char st[128];        //크기가 128인 문자형 배열 st
cs

int ar[5]; 선언에 의해서 컴파일러는 정수형 변수 5개를 저장할 수 있는 연속적인 메모리 공간을 확보합니다. 이 배열은 다음과 같은 형태로 생성되게 됩니다.


 ar[0]

 ar[1]

 ar[2]

 ar[3]

 ar[4]


배열을 구성하는 각각의 개별 변수들을 배열 요소(Element)라고 한다. int ar[5] 선언에 의해 ar[0]부터 ar[4]까지 정수형 배열 요소 다섯 개가 동시에 생성됩니다. 배열 요소 다섯 개가 동시에 생성됩니다. 배열 요소는 동일한 타입을 가지는 변수와 완전히 같은 자격을 가집니다. 즉,arp[0]는 int형으로 선언된 변수 i나 j와 같은 정수형 변수 i나 j와 같은 정수형 변수로서 정수값 하나를 저장할 수 있으며 i,j가 사용될 수 있는 곳이며 ar[0]도 항상 사용할 수 있습니다. 자료 구조에는 배열 외에도 연결 리스트, 스택, 큐, 트리 같은 것들이 있습니다.


배열의 특징


배열은 기본형과는 달리 여러 개의 변수를 하나의 이름으로 모아 놓은 것입니다. 그래서 기본형 변수들과는 다른 면이 많습니다. C의 배열의 특징에 대해서 알아보겠습니다.

  1. 배열의 요소의 변호인 첨자는 항상 0부터 시작(Zero Base)합니다. 컴퓨터의 세계에서는 컴퓨터의 세계에서는 0이 언제나 첫 번째 숫자가 됩니다. 첫 번쨰 요소의 첨자가 1번이 아니라 0번이기 때문에 마지막 요소의 첨자는 배열의 크기보다 항상 하나가 더 작습니다. ar 배열의 크기가 5라면 첫 번째 요소는 ar[0]이 되고 마지막 배열 요소는 ar[4]가 됩니다. ar[5]라는 요소는 존재하지 않습니다. 실생활에서 사용하는 자연수는 항상 1부터 시작하기 떄문에 첨자의 번호가 실제 배열이 표현하고자 하는 대상과 일치하는 경우가 있습니다. 예를 들어 30명의 학생의 성적을 저장하려면 크기 30의 정수형 배열이 필요합니다. 그래서 int ar0[30]이라는 배열을 선언하는데 이때 생성되는 배열 요소는 ar[0]~ar[29]까지이므로 0첨자에 1번 학생의 성적을 저장하고 1번 첨자에 2번 학생의 성적을 저장해야 합니다. 하지만 헷갈릴 경우에는 ar[0]을 버려버리고 int ar[31]; 이라고 선언하여 학생 번호와 배열 번호를 일 치 시킬 수도 있습니다. 최근 고급 언어들은 배열 첨자의 시작값을 사용자가 직접 정할 수 있지만, C언어는 따로 그런 기능이 없습니다. 정확한 이유를 따지하면 배열의 요소를 구하는 첨자 연산이 따로 정의되어 있지 않고 포인터를 사용한 간접적인 연산으로 정의 되어 있기 떄문입니다. 첨자연산이 단순해지면 배열을 참조하는 속도가 전반적으로 빨라지는데 C는 편의성 보다는 성능위주로 설계되어 있습니다.
  2. 배열이 차지하는 총 메모리양은 배열의 크기에 배열 요소의 크기를 곱해서 구할 수 있습니다. 즉 배열의 총 크기는 sizeof(타입)*크기 입니다. 예를들어 int ar[5]라는 배열은 정수형 변수 다섯 개의 집합이므로 정수형의 크기 4바이트 * 5(개) = 20바이트가 됩니다. 배열이 5개라고 5바이트를 차지한다거나 하지는 않습니다. 배열을 동일한 다른 배열에 복사하거나 모든 배열 요소를 특정한 값으로 채우고 싶을 때는 메모리 복사를 해야하는데 이떄 복사할 바이트 수를 정확하게 계산하여야 합니다. 배열의 총 바이트수는 sizeof연산자로 쉽게 구할 수 있으므로 이 연산자가 구해주는 크기를 사용하면 됩니다. 다음의 공식을 이용하면 요소의 갯수를 구할 수 있습니다.

    배열 크기 = sizeof(배열)/sizeof(배열[0])

    배열의 총 바이트 수를 배열 요소의 크기로 나누면 요소의 개수가 됩니다.
  3. 배열을 선언할 때 크기값은 반드시 상수로 주어야 합니다. 하지만 배열이 일단 만들어지고 나면 첨자로 변수를 사용할 수 있습니다. ar배열의 첫 번째 요소값을 알고 싶으면 ar[0]을 읽고 두 번째 요소의 값을 알고 싶으면 ar[1]을 읽으면 됩니다. 괄호 안에 조사할 요소의 첨자 번호를 상수로 지정하는데 이 자리에 다른 변수를 쓸 수도 있습니다. 만약 i라는 변수값을 첨자로 사용하고 싶으면 ar[i]로 사용하면 됩니다. 루프의 제어 변수를 첨자로 사용하면 마무리 큰 배열이라고 하더라도 반복적인 처리가 가능합니다. 다음을 통해서 배열 전체를 0으로 만들어 줄 수 있습니다.
    1
    2
    3
    4
    for(int i = ; i < ; i++ )
    {
        ar[i];
    }
    cs

    i가 0에서 부터 4까지 반복이 되며 ar[i]에 모두 0을 대입함으로써 ar[0]부터 ar[4]까지 모두 0으로 초기화 하는 코드입니다. 다음 소스는 5명의 성적을 입력받아 배열에 저장한 후 총합과 평균을 구해 출력하는 소스입니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #define _CRT_SECURE_NO_WARNINGS
    #include<cstdio>
     
    void main()
    {
        int arScore[5];
        int sum = 0;
     
        for (int i = 0; i < sizeof(arScore) / sizeof(arScore[0]); i++)//배열에 입력
        {
            printf("%d번 학생의 성적을 입력하세요 : ", i + 1);
            scanf("%d"&arScore[i]);
        }
        for (int i = 0; i < sizeof(arScore) / sizeof(arScore[0]); i++)//합계 계산
        {
            sum += arScore[i];
        }
     
        printf("\n총점은 %d점이고 평균 %d점입니다.\n", sum, sum / (sizeof(arScore) / sizeof(arScore[0])));
    }
    cs

    실행하면 다음과 같은 성적을 표기합니다.

     

  4. C언어는 배열의 범위를 전혀 점검하지 않습니다. 이것은 배열의 특징이라기보다는 다른 고급 언어와 구별되는 C언어의 고유한 특징이며 알아둘 필요가 있습니다. 배열을 선언할 때 그 크기를 지정하도록 되어 있으므로 컴파일러는 배열 요소의 끝번호를 알 고 있습니다. 시작은 항상 0번이므로 int ar[5]의 시작은 ar[0]이고 끝은 ar[4]가 됩니다. 하지만 그 이상의 범위를 하는경우엔 어떻게 될까요? 다음의 경우를 보겠습니다.

    1
    2
    int ar[5];
    ar[8= 1234;
    cs

    원래크기는 5밖에 되지 않는데 8에다가 1234라는 값을 넣으려는 코드인데 에러로 처리 되지 않습니다. 왜냐하면 컴파일러는 배열의 길이를 점검하지 않았기 때문에 어디까지인줄 모르기 때문입니다. 그뿐만 아니라 음수를 사용하여 ar[-12]로 음수로 된 첨자를 사용하여도 에러는 뜨지 않습니다. 물론 컴파일 까지는 문제가 없지만 실제 실행에는 문제가 생깁니다. 하지만 길이를 점검하지 않는 이유는 뭘까요? 속도 때문입니다. 배열의  길이 및 첨자의 유효성을 계산하면 그만큼 시간이 더 걸리기 떄문에 뺀것입니다. 따라서 컴파일러는 배열 참조문에 대해 아무런 처리도 하지 않으며 배열의 범위를 점검하는 것은 개발자의 몫입니다. 물론 포인터 개념에서는 ar크기가 5여서 ar[8]이 무의미 하지도 않고 첨자를 음수로 쓰는 것도 특수기법으로 활용된다는것이 또 다른 이유이긴 합니다.

다차원 배열

 

2차원 배열은 첨자 두 개를 사용하는 배열입니다. 선언할 때 배열명 다음에 다음 소스들과 같이두 개의 크기를 나란히 적어 주면 됩니다.

1
2
int ar[3][5]
double rating[3][30]
cs

int ar[3][5] 선언에 의해서 정수형 변수 5개 묶음이 3개 생성됩니다. 이 선언에 의해 생성되는 배열 요소는 총 5 * 3 = 15개를 ar이라는 배열명으로 선언 한 것이다. 이배열이 메모리상에 생성된 모양은 다음과 같습니다.

 

 ar[0][0]

 ar[0][1]

ar[0][2] 

ar[0][3] 

ar[0][4] 

 ar[1][0]

 ar[1][1]

ar[1][2]

ar[1][3]

ar[1][4]

 ar[2][0]

 ar[2][1]

ar[2][2]

ar[2][3]

ar[2][4] 

물론 실제 메모리 상에서 보면 평면이 아니라 선형적인(Linear) 1차원 형태로 되어 있습니다. 메모리가 1차원의 공간이기 떄문입니다. 한줄로 순서로는 ar[0][4]뒤에 ar[1][0]이 오고 ar[1][4]뒤에는 ar[2][0]이 오게 되어 한줄이 됩니다. 물론 위에서 했던 것과 모두 같으면 단순하게 뒤에 첨자가 하나 더 붙은 것입니다. 또한 ar[n][n]의 가장 마지만 요소는 ar[n-1][n-1]입니다.

 

배열명

 

배열을 구성하는 각각의 요소는 배열 타입의 변수와 완전히 동등합니다. int ar[5];로 선언된 배열의 요소인 ar[0]. ar[1] 등은 정수값 하나를 저장할 수 있는 일반적인 정수형 변수입니다. ar이라는 배열에 같이 못여있습니다. 하지만 배열 그 자체의 문법적인 자격을 알아 보겠습니다.

 

배열명이 단독으로 사용되면 배열의 시작번지값을 가지는 포인터 상수이다.

 

배열명이 단독으로 사용된다는 말은 첨자없이 배열의 이름만 적는다는 뜻입니다. ar[0], ar[1]과 함께 쓰면 배열 요소 변수지만 ar과 같이 배열명만 쓰면 이 값은 배열의 시작번지를 가르키는 포인터값이 됩니다. 배열명이 포인터라는 것을 확인하기 위해선 다음 문장을 실행해보면 됩니다.

1
2
char str[6]={'K','O','R','E','A'};
printf("%s\n",str);
cs

크기 6의 문자형 배열 str을 선언하되 KOREA라는 문자열을 가지도록 초기화를 하였습니다. 그리고 이 배열에 저장된 문자열을 printf %d서식으로 출력 시키는 형태입니다 이렇게 되면 KOREA가 출력이 되게 될 것입니다. 이 소스에서 볼 수 있듯이 str이라는 배열명 자체는  "KOREA"라는 문자열이 들어 있는 배열의 시작 번지를 가리키며 그래서 str 배열명을 printf의 %s 서식에 대응시키면 이 번지에 들어 있는 문자열이 출력되게 됩니다.  

K

O

R

E

A

\0 

여기서의 첫번쨰 K를 가르키게 됩니다. 배열도 변수이기때문에 어딘가에 메모리를 차지하게 되며 모든 배열 요소들이 연속적으로 배치되어 있습니다. 배열명은 이 메모리의 시작 번지를 가르키는 포인터가 됩니다. 그래서 배열명 그 자체는 항상 첫 번째 배열요소의 번지와 같으며 str의 다른 표현은 &str[0]이라고 할 수 있습니다. 위 그림에서 K가 저장되어 있는 번지가 str이 가르키는 번지와 같아 진다는 의미입니다. 배열명은 배열의 시작을 가리키는 포인터이면서 배열이 선언될 때 메모리가 할당되므로 이 포인터는 변할 수 없는 상수값입니다. 다시말해 배열명은 포인터 변수가 아니라 포인터 상수입니다.  str이라는 것은 항상 str의 시작번지를 가르키지 다른 번지를 가르킬 수는 없습니다. 따라서 str이라는 배열을 다른 값으로 바꾸는 것은 불가능합니다. 상수는 좌변값이 될 수 없기 때문입니다. 배열명이 포인터 상수이기 떄문에 배열끼리 대입도 불가능합니다. 크기등 모든것이 같아도 불가능합니다. 그래서 배열의 사본을 다음과 같이 만드는 것도 불가합니다.

1
2
3
int ar[3]={1,2,3};
int ar2[3]
ar2=ar //불가
cs

동일하긴 하지만 둘다 상수이기 때문에 불가능하나 복사할 법이 아주 없는 것은 아닙니다. 완전히 같게 만들고 싶다면 루프를 돌려서 배열 요소를 하나하나 개별적으로 대입시켜서 같게 만들어야 합니다.

1
2
3
4
for (i=0;i<sizeof(ar)/sizeof(ar[0]);i++)
{
    ar2[i]=ar[i];
}
cs

이렇게 개별적으로 대입시켰을때만 같아지게 됩니다. 또한 다른방법으로 memcpy같은 메모리 복사함수를 사용하여 복사도 가능합니다.

1
2
char st[20];
scanf("%s",str);
cs

str이라는 배열명 그자체가 포인터이기 때문에 scanf의 인수로 넘겨줄때 & 연산자를 붙이지 않습니다. scanf 함수는 키보드로 부터 입력된 문자열을 이 배열에 복사하기 때문에 배열의 내용은 바뀔 것입니다. 흠 그러면 또 의문이 생깁니다. str은 상수라고 해놓고서 scanf는 또 어떻게 입력을 하는것인가. 그 이유는 str 자체는 상수지만 str 배열의 내용은 상수가 아니기 때문입니다. scanf가 바꾸는 것은 str의 내용이지 str의 시작 번지를 다른 곳으로 옮기는 것은 아닙니다. 배열명은 포인터 상수이되 예외가 있습니다. sizeof 연산자의 피연산자로 사용될 떄만은 포인터로 취급 되지 않고 배열 그 자체로 취급됩니다. 그래서 sizeof(ar)은 포인터 크기 4바이트가 리턴되는 것이 아니라 배열의 전체 크기를 리턴하게 됩니다. 만약 이 예외가 인정되지 않으면 배열의 총 크기는 구할 수 없을 것입니다.

 

배열 초기화

 

배열은 여러 변수의 집합이라는점을 제외하고는 정수, 실수형 변수들과는 동등한 자격의 변수입니다. 그래서 변수에 적용되는 모든 규칙들이 배열에도 똑같이 적용됩니다. 배열 선언에 사용되는 기억 부류 지정자도 일반 변수와 동일하고 그 효과도 완전히 동일합니다. 배열을 함수안에 선언하면 지역변수가 되고 함수 밖에 선언하면 전역변수가 되는데 기억 장소나 통용 범위 또한 일반 변수와 같습니다. 파괴 시기나 초기화 여부도 일반 변수와 동인한데 이에 따라 지역 배열은 초기화가 되지 않습니다. 초기화가 안되는데 예제를 통해서 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
#include<cstdio>
 
void main()
{
    int ar[5];
 
    for (int i = 0; i < 5; i++)
    {
        printf("ar[%d]=%d\n", i, ar[i]);
    }
}
cs

결과는 다음과 같습니다.

ar[0]~ar[4]까지 초기화되지 않은 쓰레기값들만 잔뜩 들어있는것을 확인 할 수 있습니다. 컴파일러가 지역적으로 선언된 배열을 초기화하지 않고 내버려 두는 이유는 대개의 경우 배열을 선언하자 마자 곧바로 어떤 값을 채워넣기 때문입니다. 앞으로 성적 처리 프로그램들은 arScore 배열은 선언한 후 scanf로 사용자에게 입력을 받거나 직접 값을 대입했습니다. 프로그램이 알아서 배열을 잘 사용하는데 컴파일러가 실행 시간을 낭비해 가며 개입 할 필요는 없습니다. C언어는 성능이 최우선 가치이기 떄문에 불필요한 동작은 하지 않습니다. 물론 전역으로 선언하게 되면 컴파일러가 배열을 초기화 합니다. 보다시피 일반 변수의 초기화 규칙과 동일한 규칙이 적용됩니다. 만약 배열 요소를 원하는 값으로 초기화 하려면 루프를 돌면서 배열 요소에 일일이 값을 입력하여야합니다.

1
2
3
4
for ( int i=; i<; i++)
{
ar[i]=0;
}
cs

이 루프는 ar 배열의 모든 요소를 0으로 초기화하는 코드입니다. for문을 이용하여 0뿐만 아니라 규칙적인 형태의 초기값들은 넣어 줄 수는 있습니다만 임의의 값으로 초기화를 해야할 때는 루프로는 할 수가 없으며 별도의 배열 초기화 방법을 사용해야합니다.

 

1차 배열 초기화

 

배열을 선언할 때 초기값을 주는 방법도 일반 변수와 마찬가지 입니다. 예를들어 int i=0;과 같이 변수명 다음에 =기호를 쓰고 초기값을 지정해줍니다. 배열의 초기화가 일반 변수와 다른 점은 값의 묶음을 한꺼번에 전달해야 하기 떄문에 초기식에 { } 괄호와 초기값들 사이에 콤마를 사용한다는 점입니다.

 

type 배열명[크기] = {초기화 값};

 

선언문 바로 다음 = 기호와 { }괄호를 쓰고 괄호 안에 초기값들을 콤마로 구분하여 다음과 같이 나열하게 됩니다. 물론 일반 변수에서도 초기화시에 int i=[5];같이 할 수도 있는데 일반 변수들은 단일 변수이기 떄문에 괄호를 생략하게 됩니다. 하지만 그에반에 배열은 여러개의 값을 넣어야하기 때문에 { } 괄호를 생략할 수가 없습니다.

1
int array[5= {6,1,-5,23,123}
cs

이렇게 선언과 동시에 초기화를 하면 메모리를 할당받음과 동시에 초기화 값들로 메모리를 채웁니다.(=초기화 한다). 선언 직후의 메모리 모양은 다음과 같을 것입니다.

 

6

1

-5

23

123

 

초기식에 있는 값들이 순서대로 각 배열 요소에 대입되었습니다. 만약 선언과 동시에 배열을 초기화하지 않고 선언 후에 따로 초기화 한다면 다음과 같이 일일이 값을 대입하는 방법밖에 없습니다.

 

'Programming > C' 카테고리의 다른 글

C언어 - 포인터(Pointer)(1/3)  (2) 2015.08.16
C언어 - 배열(Array)(2/2)  (0) 2015.08.15
C언어 - 표준 함수  (0) 2015.08.11
기억 부류(Storage Class) (2/2)  (0) 2015.08.10
기억 부류(Storage Class) (1/2)  (0) 2015.08.09