Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
C/Array

C - Array 1차원 배열

by Toughie 2024. 2. 18.

 

우리는 프로그램에서 다양한 종류의 수많은 데이터를 관리해야 한다.

가장 처음 나온 개념은 '변수'였다. 하지만 데이터가 많아지면서 하나하나 변수를 선언하는 것이 너무 비효율적이었다.

따라서 '같은 타입의 데이터'연속적으로 묶어서 관리하면 더 효율적이겠다! 해서 나온 개념이 바로 배열이다.

추가적으로 이제 다양한 타입의 관련된 데이터를 묶어서 관리하기 위해 '구조체'도 등장하고, 

함수까지 같이 묶어서 관리하기 위해 '클래스'도 등장한다.(클래스는 C++에서 사용 가능하다. C에서는 구조체만 존재함)

클래스를 통해 객체지향 프로그래밍의 구현이 이루어졌다. (C는 절차, 함수형 프로그래밍 언어이다.)


배열 Array

배열은 하나의 이름으로 참조되는 같은 타입연속적인 메모리 공간을 의미한다.

 

하나의 이름으로 참조, 같은 타입, 연속적 이 개념들에 대해서는 1차원 배열을 통해 이해해보자.


1차원 배열

* 배열의 요소는 '열'이라고 부르기에 1차원 배열은 열의 집합이기도 하다. (2차원 배열부터 행/열의 개념이 있으니)

* 배열 선언 방법 type name [size]

// A
int numArray[5]; //쓰레기 값이 들어 있다.

// B
// numArray = {1, 2, 3, 4, 5}; 이것은 불가능하다. numArray는 시작주소이기 때문이다.
// numArray[0] = 1; numArray[1] = 2 ... 선언 이후 요소 하나하나 할당해줘야 한다.

// C
int numArray[5] = { 1, 2, 3, 4, 5 }; //바로 초기화

// D
int numArray[] = {1, 2, 3, 4, 5}; // Unsized Array

// E
int numArray[5] = {1, 2, 3}; // {1, 2, 3, 0, 0}

 

배열 초기화

A. 배열 선언과 메모리 할당

배열을 선언하면 원소 수만큼 메모리에 할당된다. 

다만 바로 초기화해주지 않으면 배열에는 쓰레기 값들이 들어 있다.

int는 4byte이기 때문에 총 20byte만큼 메모리 공간을 차지하게 될 것이다. 

 

B. 선언 이후 요소 할당 방법

선언 이후 값을 바로 할당하는 것은 불가능하다. 따라서 요소별로 값을 할당해줘야 하며

같은 맥락으로 배열 전체를 다른 배열로 치환할 수도 없다.

같은 타입, 같은 사이즈의 배열이라도 바로 대입이 불가능하기에 배열을 복사하기 위해서는

각각의 원소를 순회하면서 하나씩 대입해줘야 한다.

 

* 배열 포인터 상수

포인터 부분에서 다뤘듯, numArray 즉 배열의 이름은 포인터 상수로 배열의 시작 주소, 정확하게는 첫 번째 원소의 시작 주소를 가리킨다.

 

C. 선언과 동시에 초기화

배열은 선언만으로는 쓰레기 값만 갖기 때문에, 선언과 동시에 원하는 값으로 바로 초기화해 줄 수 있다.

값들은 메모리에 순서대로 저장된다.

 

D. 언사이즈드 배열(Unsized Array)

컴파일러가 초기화 상수의 개수를 체크해 개수만큼 배열의 크기를 지정해 준다. 

 

E. 자동 초기화

배열의 사이즈는 5개인데, 초기화 요소는 3개만 있다.

c에서 배열은 남은 자리에 자동으로 0이 초기화된다.


배열 요소 접근

배열의 요소에 접근하기 위해 서브스크립트(첨자) 문법을 사용한다. 이미 다른 언어에서도 대부분 사용하기 때문에 익숙할 것이다.

혹은 인덱스, 배열 인덱싱 한다라는 표현도 많이 쓴다. 배열의 인덱스는 0부터 시작한다. 

printf("%d", numArray[0]); //1
numArray[0] = 100;
printf("%d", numArray[0]); //100

 

서브스크립트 문법은 사실상 포인터를 활용한 것이다.

numArray가 배열의 첫 번째 요소를 가리키는 포인터 상수이기 때문이다. 

시작주소에서 인덱스만큼 정수 연산을 한 후 (타입에 따라 주소 증감) 포인터 연산자를 통해 값을 참조하는 것이다.

numArray[1] == *(numArray + 1)

 

배열 사이즈

배열은 컴파일 타임에 크기를 알아야 하기 때문에 사이즈는 반드시 상수로 선언해줘야 한다.

(물론 동적 할당을 하는 방법도 있지만 상수 선언이 기본이다.)

#include <stdio.h>

#define arrSize 5 // 매크로 상수
// 컴파일 타임에 소스코드에서 해당 값으로 대체됨. (상수 값이 코드에 직접 삽입됨)

int main() {
    //성공
    int a[5];
    int b[arrSize];

    int num = 5;
    ❗️int c[num];
    // 에러. 배열 사이즈는 컴파일 타임에 상수로 지정해야함.
    // num에 5라는 값은 런타임에 동적 할당되기 때문임.
    
    ❗️int d[];
    //배열의 크기가 없기 때문에 컴파일 에러가 발생함.
    
    return 0;
}

 

1차원 배열을 활용한 버블정렬 예시

버블 정렬은 붙어있는 두 요소들을 비교해서 자리를 바꾸며 정렬하는 알고리즘을 말한다.

시각화하면 값이 방울처럼 뽁뽁 올라오는 것 같아서 버블 정렬이라고 한다. 

아래는 배열의 요소를 오름차순으로 정렬하는 버블정렬의 예시코드이다.

포인터를 활용해서 함수를 구현해 보았다.

#include <stdio.h>
#define arrSize 5

void ascBubbleSort(int *ptr);

int main() {
    
    int arr[arrSize] = {20, 3, 7, 12, 1};
    
    for (int i = 0; i < 5; i++) {
    printf("%d ",arr[i]);
    }

    ascBubbleSort(arr);
    printf("\n");

    for (int i = 0; i < 5; i++) {
    printf("%d ",arr[i]);
    }

    return 0;
}

void ascBubbleSort(int *ptr) {
    // 각 요소를 정렬시키기 위해 배열 사이즈 -1만큼 루프를 돌린다. (마지막 요소는 자동으로 정렬됨)
    for(int i = 0; i < (arrSize - 1); i++) {
        // 인접한 값끼리 비교를 한다. 
        // 첫 번째 요소의 경우 배열 사이즈 -1만큼 비교를 해야 하지만,
        // 요소가 넘어갈 수록 비교가 필요한 횟수가 줄어든다. (앞이 아니라 계속 뒤의 요소랑만 비교하니)
        for (int j = 0; j < (arrSize - 1 - i); j++) {
            int tmp;
            //왼쪽의 요소가 더 큰가?
            if ( *(ptr + j) > *(ptr + j + 1) ) {
                // 값을 변경해준다.
                tmp = *(ptr + j);
                *(ptr + j) = *(ptr + j + 1);
                *(ptr + j + 1) = tmp;
            }
        }
    }
}

 

1차원 배열의 대표 사용처인 문자열과, 2차원, 3차원 등 다차원 배열은 다음 포스팅에서 다뤄보겠다.

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

C - 문자열, 다차원 배열(2차원, 문자열 배열)  (0) 2024.02.18