본문 바로가기
XR개발/C언어

C언어 배열

by 오머리쿠_OmaryKoo 2025. 2. 28.

1. 배열이란?

배열(array)은 같은 데이터 타입의 여러 개의 값을 하나의 변수로 관리할 수 있는 자료구조이다. 기존 변수는 하나의 값만 저장할 수 있지만, 배열을 사용하면 여러 개의 데이터를 연속된 메모리 공간에 저장할 수 있다. 배열의 각 원소는 인덱스(index)를 통해 접근할 수 있다.

a는 배열의 이름이고, 10은 베열의 크기이다.

배열의 개념

배열은 하나의 이름을 가지며, 내부적으로는 여러 개의 변수를 포함하고 있는 집합과 같다. 이를 예를 들어 단독 주택과 아파트로 비유할 수 있다. 일반적인 변수는 단독 주택처럼 하나의 값을 저장하지만, 배열은 아파트처럼 여러 개의 값을 저장할 수 있다.


A. 배열이 필요한 이유

프로그래밍에서는 다수의 데이터를 효율적으로 저장하고 관리할 필요가 있다. 예를 들어, 10명의 학생의 성적을 저장하고 평균을 계산해야 한다고 가정하면, 10개의 개별 변수를 선언해야 한다.

하지만 학생이 100명이라면? 1000명이라면? 모든 변수를 따로 선언하는 것은 매우 비효율적이다. 이 문제를 해결하기 위해 배열이 등장하게 되었다.

배열을 사용하면 데이터의 크기가 커지더라도 변수 선언이 간결해지고, 반복문을 활용하여 데이터를 효율적으로 관리할 수 있다.

 

배열을 구성하는 각각의 항목을 배열 요소(array element) 또는 배열 원소라고 한다. 배열 요소에는 번호가 붙어 있는데 이것을 인덱스(index) 또는 첨자(subscript)라고 부른다. 배열 이름을 쓰고 괄호[]안에 번호를 표시하면 배열 요소가 된다. 예를 들어 배열의 이름이 a라면 배열 요소는 a[0], a[1], a[2]...로 표시된다.


B. 배열의 특징

  • 연속된 메모리 공간: 배열의 원소들은 메모리에 연속적으로 저장된다.
  • 같은 데이터 타입: 배열의 모든 요소는 동일한 데이터 타입을 가져야 한다.
  • 인덱스를 통한 접근: 배열의 각 요소는 인덱스를 이용해 접근할 수 있다.
  • 고정된 크기: 배열의 크기는 선언 시에 정해지며, 실행 중에는 변경할 수 없다.


C. 배열의 선언과 사용

배열을 사용하려면 먼저 선언해야 한다. 선언 방법은 다음과 같다.

주의!>
✓ 배열의 크기를 나타낼 때는 항상 상수를 사용하여야 함!

int scores[size]; // 배열 크기를 변수로 할 수 없음
int scores[-5]; // 배열의 크기가 음수이면 안 됨.
int score[7.9]; //배열의 크기가 실수이면 안 됨.

 

예제:

int scores[10];   // 10개의 정수를 저장하는 배열
float weights[5]; // 5개의 실수를 저장하는 배열
char name[20];    // 20개의 문자를 저장하는 문자열 배열

배열을 초기화할 때는 다음과 같이 사용할 수 있다.

int scores[5] = {90, 80, 85, 70, 95};

만약 일부만 초기화하면 나머지 요소는 기본값(정수의 경우 0)으로 설정된다.

int numbers[5] = {1, 2}; // {1, 2, 0, 0, 0}으로 초기화됨

D. 배열 요소 접근

배열의 각 요소는 인덱스를 사용하여 접근할 수 있다. 인덱스는 0부터 시작하며, 배열 크기 - 1까지 사용 가능하다.

scores[0] = 90;  // 첫 번째 요소에 90 저장
scores[1] = scores[0];  // 첫 번째 요소 값을 두 번째 요소에 복사

 

배열을 사용하면 반복문을 활용하여 데이터 처리를 간단하게 할 수 있다.

#define SIZE 5
int scores[SIZE];
for(int i = 0; i < SIZE; i++) {
    scores[i] = i * 10;
    printf("scores[%d] = %d\n", i, scores[i]);
}

E. 배열의 활용 예제

반복문을 이용한 초기화

#define SIZE 5
int scores[SIZE];
for(int i = 0; i < SIZE; i++) {
    scores[i] = rand() % 100;  // 0~99 사이 난수 생성
    printf("scores[%d] = %d\n", i, scores[i]);
}

 

학생 성적 평균 계산

#define STUDENTS 5
int scores[STUDENTS];
int sum = 0;
double average;
for(int i = 0; i < STUDENTS; i++) {
    printf("학생들의 성적을 입력하세요: ");
    scanf("%d", &scores[i]);
    sum += scores[i];
}
average = (double)sum / STUDENTS;
printf("성적 평균 = %.2f\n", average);

F. 인덱스 범위 주의

 

배열을 사용할 때 주의할 점은 인덱스의 범위를 벗어나면 메모리 오류가 발생할 수 있다는 것이다.

int scores[5];
scores[5] = 60; // 배열 범위를 초과한 접근 (오류 가능성 있음)

 

 

C 언어에서는 이러한 인덱스 초과 오류를 자동으로 감지하지 않으므로, 반드시 유효한 인덱스 범위를 사용해야 한다.


2. 배열의 초기화란?

변수는 선언 후 초기값을 지정해야 한다. 배열도 마찬가지로, 여러 개의 변수를 하나의 집합으로 선언하는 만큼 초기화 방법도 조금 다르다. 배열을 올바르게 초기화하는 것은 데이터를 효율적으로 관리하는 데 필수적이다.

배열을 초기화할 때는 중괄호 {}를 사용하여 값들을 나열하면 된다. 예를 들어, 5개의 정수 데이터를 저장하는 배열을 선언하고 초기화하려면 다음과 같이 작성한다.

int scores[5] = {10, 20, 30, 40, 50};

위 코드에서 scores[0]은 10, scores[1]은 20, scores[2]는 30과 같이 순차적으로 초기화된다.


A. 초기값의 개수와 배열의 크기

배열을 초기화할 때, 배열 요소 개수보다 적은 값들을 지정하면 나머지 요소는 자동으로 0으로 초기화된다. 예를 들어 다음과 같다.

int scores[5] = {10, 20, 30};

위 코드에서는 scores[0]부터 scores[2]까지는 각각 10, 20, 30으로 초기화되지만, scores[3]scores[4]는 자동으로 0이 된다.

반대로, 배열 요소보다 많은 초기값을 지정하면 컴파일 오류가 발생한다.

int scores[3] = {10, 20, 30, 40}; // 오류 발생

배열의 크기를 지정하지 않고 초기화하면, 컴파일러가 자동으로 초기값의 개수에 맞게 배열 크기를 결정한다.

int scores[] = {10, 20, 30, 40, 50};

위 코드에서는 scores 배열의 크기는 5로 결정된다.


B. 배열의 모든 요소를 0으로 초기화하는 방법

배열의 모든 요소를 0으로 초기화하려면 {0}만을 지정하면 된다.

int scores[5] = {0};

위 코드에서는 scores[0]부터 scores[4]까지 모든 요소가 0으로 설정된다.


C. 초기화를 하지 않은 배열의 상태

지역 변수로 선언된 배열을 초기화하지 않으면 **쓰레기 값(garbage value)**이 들어 있을 수 있다. 이는 메모리에 남아 있던 이전 데이터가 남아있을 가능성이 있기 때문이다.

int scores[5];
// 초기화되지 않았기 때문에 예측 불가능한 값이 저장될 수 있음

따라서 배열을 선언할 때 명확한 초기값을 설정하는 것이 중요하다.


D. 배열 요소 개수를 계산하는 방법

배열 요소의 개수를 직접 세지 않고 자동으로 계산하려면 sizeof 연산자를 사용할 수 있다.

int scores[] = {6, 3, 2, 1, 4, 5, 7, 8, 9};
int size = sizeof(scores) / sizeof(scores[0]);

위 코드에서 sizeof(scores)는 배열 전체의 바이트 크기를 반환하고, sizeof(scores[0])는 한 개 요소의 크기를 반환하므로, 이를 나누면 배열의 총 요소 개수를 쉽게 구할 수 있다.


E. 배열의 복사

배열을 다른 배열로 복사하려면 반복문을 사용해야 한다. 단순히 = 연산자를 사용하면 전체 배열이 복사되지 않고, 배열의 주소값이 대입되므로 주의해야 한다.

 

잘못된 방법

int a[5] = {1, 2, 3, 4, 5};
int b[5];
b = a; // 잘못된 사용법 (컴파일 오류 발생)

 

올바른 방법

int a[5] = {1, 2, 3, 4, 5};
int b[5];
for (int i = 0; i < 5; i++) {
    b[i] = a[i];
}

반복문을 사용하여 배열의 각 요소를 하나씩 복사하면 정상적으로 동작한다.


F. 배열의 비교

배열을 비교할 때도 반복문을 사용하여 요소별로 비교해야 한다. 단순히 if (a == b) 같은 비교는 배열의 주소값을 비교하는 것이므로 올바른 결과를 얻을 수 없다.

 

잘못된 방법

int a[5] = {1, 2, 3, 4, 5};
int b[5] = {1, 2, 3, 4, 5};
if (a == b) {  // 두 배열의 주소를 비교하는 코드 (잘못된 방식)
    printf("같습니다.\n");
} else {
    printf("다릅니다.\n");
}

 

올바른 방법

int a[5] = {1, 2, 3, 4, 5};
int b[5] = {1, 2, 3, 4, 5};
int same = 1;
for (int i = 0; i < 5; i++) {
    if (a[i] != b[i]) {
        same = 0;
        break;
    }
}
if (same) {
    printf("같습니다.\n");
} else {
    printf("다릅니다.\n");
}

위 코드처럼 반복문을 사용하면 각 요소를 비교하여 정확한 결과를 얻을 수 있다.


3. 배열과 함수

A. 배열을 함수로 전달하기

일반적인 변수를 함수로 전달할 때는 "값에 의한 호출"이 이루어진다. 즉, 변수의 복사본이 함수로 전달되므로 원본 변수의 값에는 영향을 주지 않는다. 하지만 배열의 경우는 다르다. 배열을 함수에 전달하면 복사본이 아닌 배열의 주소가 전달되므로, 함수에서 배열 요소를 변경하면 원본 배열도 변경된다. 이를 "참조에 의한 호출"이라고 한다.

예를 들어 학생들의 성적을 저장한 배열의 평균을 구하는 프로그램을 보자.

#include <stdio.h>
#define STUDENTS 5

int get_average(int scores[], int size);

int main(void) {
    int scores[STUDENTS] = {1, 2, 3, 4, 5};
    int avg;
    
    avg = get_average(scores, STUDENTS);
    printf("평균은 %d입니다.\n", avg);
    
    return 0;
}

int get_average(int scores[], int size) {
    int i, sum = 0;
    for(i = 0; i < size; i++)
        sum += scores[i];
    
    return sum / size;
}

위 코드에서 get_average 함수는 배열의 크기를 받는 size 변수를 통해 루프를 돌면서 모든 요소의 합을 구하고 평균을 계산한다. 여기서 scores[]를 매개변수로 전달할 때 크기를 지정하지 않는 것이 특징이다.


B. 원본 배열의 변경

배열을 함수에 전달할 때 가장 주의할 점은 함수 내에서 배열 요소를 변경하면 원본 배열도 변경된다는 것이다. 이를 확인하는 예제를 살펴보자.

#include <stdio.h>
#define SIZE 7

void modify_array(int a[], int size);
void print_array(int a[], int size);

int main(void) {
    int list[SIZE] = {1, 2, 3, 4, 5, 6, 7};
    
    print_array(list, SIZE);
    modify_array(list, SIZE);
    print_array(list, SIZE);
    
    return 0;
}

void modify_array(int a[], int size) {
    int i;
    for(i = 0; i < size; i++)
        ++a[i];
}

void print_array(int a[], int size) {
    int i;
    for(i = 0; i < size; i++)
        printf("%d ", a[i]);
    printf("\n");
}

이 코드에서 modify_array 함수는 배열의 각 요소 값을 1 증가시키고 있으며, print_array 함수를 통해 변경 사항을 출력한다. 실행 결과를 보면 원본 배열이 변경된 것을 확인할 수 있다.


C. 원본 배열의 변경을 방지하는 방법

배열을 함수에 전달하면서 원본 데이터를 보호하고 싶다면 const 키워드를 사용하면 된다. 예제는 다음과 같다.

void print_array(const int a[], int size);

이렇게 하면 print_array 내부에서 배열 요소를 수정하려고 할 경우 컴파일 에러가 발생한다.


4. 정렬

A. 정렬의 개념

정렬(Sorting)이란 데이터를 특정 기준에 따라 순서대로 배치하는 것을 의미한다. 예를 들어, 오름차순(ascending order) 정렬은 작은 값부터 큰 값으로 정렬하는 것이고, 내림차순(descending order) 정렬은 그 반대다. 컴퓨터 과학에서 정렬은 검색 및 데이터 구조와 밀접한 관계가 있으며, 다양한 알고리즘이 존재한다.


B. 선택 정렬 (Selection Sort)

선택 정렬은 가장 기본적인 정렬 알고리즘 중 하나로, 이해하기 쉬운 방식이다. 정렬되지 않은 리스트에서 가장 작은 값을 찾아 첫 번째 원소와 교환하는 방식으로 동작한다. 다음은 선택 정렬 알고리즘을 구현한 코드다.

#include <stdio.h>
#define SIZE 10

int main(void) {
    int list[SIZE] = {3, 2, 9, 7, 1, 4, 8, 6, 0, 5};
    int i, j, temp, least;
    
    for(i = 0; i < SIZE-1; i++) {
        least = i;
        for(j = i + 1; j < SIZE; j++) {
            if(list[j] < list[least])
                least = j;
        }
        temp = list[i];
        list[i] = list[least];
        list[least] = temp;
    }
    
    for(i = 0; i < SIZE; i++)
        printf("%d ", list[i]);
    printf("\n");
    
    return 0;
}

C. 선택 정렬의 동작 방식

  1. 리스트에서 가장 작은 값을 찾는다.
  2. 해당 값을 리스트의 첫 번째 요소와 교환한다.
  3. 두 번째 요소부터 나머지 요소들에서 가장 작은 값을 찾고 두 번째 위치와 교환한다.
  4. 이 과정을 (배열 크기 - 1)번 반복하면 정렬이 완료된다.

D. 선택 정렬의 시간 복잡도

선택 정렬은 항상 O(n²)의 시간 복잡도를 가지며, 데이터 개수가 많아질수록 성능이 저하된다. 그러나 코드가 직관적이고 이해하기 쉬워 작은 규모의 데이터에 적합하다.


5. 탐색

탐색(Search)은 원하는 데이터를 찾는 과정이다. 예를 들어, 출근할 때 옷을 찾거나, 서류 속에서 특정 문서를 찾는 것과 같이 일상에서도 탐색을 많이 수행한다. 컴퓨터에서도 데이터 검색은 가장 많이 수행되는 작업 중 하나로, 효율적인 탐색 방법을 사용하는 것이 중요하다.

탐색은 크게 순차 탐색(Sequential Search)이진 탐색(Binary Search) 으로 구분된다.


A. 순차 탐색 (Sequential Search)

순차 탐색은 배열의 첫 번째 원소부터 마지막 원소까지 차례대로 비교하면서 원하는 값을 찾는 방법이다. 만약 원하는 값이 배열의 초반부에 있다면 빠르게 찾을 수 있지만, 배열의 끝에 위치하면 비교 연산이 많이 발생하여 비효율적일 수 있다.

 

순차 탐색 알고리즘 구현 (C 코드)

#include <stdio.h>
#define SIZE 10

int main(void) {
    int key, i;
    int list[SIZE] = {2, 4, 6, 1, 3, 5, 7, 8, 9, 10};

    printf("탐색할 값을 입력하세요: ");
    scanf("%d", &key);

    for(i = 0; i < SIZE; i++) {
        if (list[i] == key) {
            printf("탐색 성공: 인덱스 = %d\n", i);
            return 0;
        }
    }
    printf("탐색 실패\n");
    return 0;
}

 

B. 이진 탐색 (Binary Search)

이진 탐색은 데이터가 정렬되어 있을 때 사용할 수 있는 매우 효율적인 탐색 방법이다. 탐색을 수행할 때마다 검색 범위를 절반씩 줄여나가기 때문에, 시간 복잡도가 O(log n) 으로 매우 빠르다.

 

이진 탐색 알고리즘 구현 (C 코드)

#include <stdio.h>
#define SIZE 16

int binary_search(int list[], int n, int key) {
    int low = 0, high = n - 1, middle;

    while (low <= high) {
        middle = (low + high) / 2;
        if (key == list[middle])
            return middle;
        else if (key < list[middle])
            high = middle - 1;
        else
            low = middle + 1;
    }
    return -1;
}

int main(void) {
    int key;
    int grade[SIZE] = {2, 6, 11, 13, 18, 20, 22, 27, 29, 30, 34, 38, 41, 42, 45, 47};
    
    printf("탐색할 값을 입력하세요: ");
    scanf("%d", &key);

    int result = binary_search(grade, SIZE, key);
    if (result != -1)
        printf("탐색 성공: 인덱스 = %d\n", result);
    else
        printf("탐색 실패\n");
    return 0;
}

이진 탐색을 사용하면 탐색 범위가 반복적으로 절반씩 줄어들기 때문에 매우 빠른 검색이 가능하다. 다만, 데이터가 정렬되어 있어야만 사용할 수 있다는 점을 주의해야 한다.


6. 2차원 배열

지금까지 학습한 배열은 1차원 배열로, 데이터를 한 줄로 저장하는 방식이었다. 하지만 여러 개의 데이터를 행과 열을 가진 표 형태로 저장하려면 2차원 배열을 사용해야 한다. 예를 들어, 학생들의 성적을 2차원 배열로 저장할 수 있다.

 

A. 2차원 배열 선언 및 사용

int scores[3][5];  // 3행 5열 크기의 2차원 배열 선언

여기서 scores 배열은 3개의 행(row)5개의 열(column) 을 가지며, 총 15개의 데이터를 저장할 수 있다.


B. 2차원 배열 요소 접근

2차원 배열의 각 요소는 [행 인덱스][열 인덱스] 로 접근한다.

scores[0][1] = 90;  // 첫 번째 행, 두 번째 열의 값을 90으로 저장
printf("%d", scores[0][1]);  // 90 출력

C. 2차원 배열 초기화

2차원 배열을 선언하면서 동시에 값을 초기화할 수도 있다.

int scores[3][5] = {
    {87, 98, 80, 76, 3},
    {99, 89, 90, 90, 0},
    {65, 68, 50, 49, 0}
};

위 배열에서 각 행의 요소들은 중괄호 {} 로 묶여 있으며, 컴파일러가 자동으로 행을 구분한다.


D. 2차원 배열을 활용한 성적 평균 계산 프로그램

#include <stdio.h>
#define ROWS 3
#define COLS 5

int main(void) {
    int scores[ROWS][COLS] = {
        {87, 98, 80, 76, 3},
        {99, 89, 90, 90, 0},
        {65, 68, 50, 49, 0}
    };
    
    int i;
    for (i = 0; i < ROWS; i++) {
        double final_scores = scores[i][0] * 0.3 + scores[i][1] * 0.4 +
                              scores[i][2] * 0.2 + scores[i][3] * 0.1;
        printf("학생 #%d의 최종 성적=%.2f\n", i + 1, final_scores);
    }
    return 0;
}

위 코드에서는 2차원 배열을 사용하여 각 학생의 시험 점수를 저장하고, 가중치를 적용하여 최종 성적을 계산한다.


E. 2차원 배열을 함수로 전달하기

2차원 배열도 함수의 매개변수로 전달할 수 있다. 함수에서 배열 전체를 수정할 경우 원본 배열이 변경되므로 주의가 필요하다.

#include <stdio.h>
#define YEARS 3
#define PRODUCTS 5

int sum(int scores[YEARS][PRODUCTS]);

int main(void) {
    int sales[YEARS][PRODUCTS] = {
        {1, 2, 3}, {4, 5, 6}, {7, 8, 9}
    };
    int total_sale = sum(sales);
    printf("총매출은 %d입니다.\n", total_sale);
    return 0;
}

이처럼 2차원 배열은 다양한 데이터 구조를 표현하는 데 활용할 수 있으며, 함수로 전달할 수도 있다.

728x90

'XR개발 > C언어' 카테고리의 다른 글

C언어 문자와 문자열  (1) 2025.03.02
C언어 포인터  (0) 2025.03.01
10_Q&A 함수원형에 대한 물음.zip  (0) 2025.02.16
09_Q&A 알고리즘에 대한 물음.zip  (0) 2025.02.15
08_Q&A 함수 매개변수 선언에 대한 물음.zip  (0) 2025.02.12