icon
5장 : 배열과 문자열

1차원 배열

프로그램에서는 종종 여러 개의 동일한 종류의 데이터를 한꺼번에 다루어야 할 필요가 있습니다.

예를 들어 5명의 학생 점수, 100개의 제품 가격, 또는 한 달간의 일일 기온 등 말이죠.

각각의 데이터를 독립적인 변수에 저장하는 것은 비효율적이고 비실용적입니다 (예: score1, score2, ..., score5).

이러한 문제의 해결책이 바로 배열(Array) 입니다.

배열은 동일한 데이터 타입의 여러 데이터들을 하나의 이름으로 묶어 순서대로 저장하는 자료 구조입니다.

이 장에서는 배열의 가장 기본적인 형태인 1차원 배열에 대해 상세히 알아보겠습니다.


배열이란 무엇인가?

배열은 메모리에 연속적으로 할당된 동일 타입의 데이터 집합입니다.

배열의 각 데이터는 요소(Element) 라고 불리며, 고유한 인덱스(Index) 또는 첨자(Subscript) 를 통해 접근할 수 있습니다.

배열의 주요 특징

  • 동일한 데이터 타입: 배열의 모든 요소는 반드시 같은 데이터 타입이어야 합니다. (예: int 배열은 int만 저장, double 배열은 double만 저장)
  • 연속적인 메모리 할당: 배열의 요소들은 메모리상에 서로 붙어 연속적으로 저장됩니다. 이는 데이터 접근 속도를 빠르게 합니다.
  • 고정된 크기 (일반적으로): 배열은 선언될 때 크기(저장할 수 있는 요소의 개수)가 결정되며, 프로그램 실행 중에는 이 크기를 변경할 수 없습니다. (동적 배열은 추후 학습)
  • 인덱스 기반 접근: 배열의 첫 번째 요소는 인덱스 0부터 시작하며, 마지막 요소는 크기 - 1 인덱스를 가집니다.

1차원 배열의 선언

1차원 배열을 선언하는 방법은 일반 변수를 선언하는 것과 비슷하지만 배열의 크기를 대괄호 [] 안에 명시해야 합니다.

1차원 배열 선언 형식
데이터타입 배열이름[배열크기];
  • 데이터타입: 배열 요소들의 타입 (예: int, double, char 등).
  • 배열이름: 배열을 식별하는 이름 (변수 명명 규칙과 동일).
  • 배열크기: 배열이 저장할 수 있는 요소의 개수를 나타내는 양의 정수 상수 (또는 상수 표현식).
1차원 배열 선언 예시
int scores[5];           // 5개의 정수를 저장할 수 있는 scores 배열 선언
double temperatures[30]; // 30개의 실수를 저장할 수 있는 temperatures 배열 선언
char grades[10];         // 10개의 문자를 저장할 수 있는 grades 배열 선언

배열크기는 컴파일 시점에 결정되어야 하므로, 변수를 사용하여 배열 크기를 지정할 수 없습니다. (C++11부터는 특정 경우에 한해 가변 길이 배열(VLA)이 지원되기도 하지만, 표준 C++에서는 비권장됩니다. 대신 std::vector를 사용합니다.)


1차원 배열의 초기화

배열을 선언함과 동시에 초기값을 지정할 수 있습니다.

초기화 방법은 여러 가지가 있습니다.

1. 중괄호 {}를 사용한 초기화 (리스트 초기화):

리스트 초기화
int scores[5] = {85, 92, 78, 95, 88}; // 5개 요소 모두 초기화
double prices[3] = {10.5, 20.0, 5.75}; // 3개 요소 모두 초기화

2. 초기화 리스트의 요소 개수 생략 (컴파일러가 자동 계산): 배열의 크기를 명시하지 않으면, 초기화 리스트의 요소 개수에 따라 컴파일러가 자동으로 배열 크기를 결정합니다.

초기화 리스트의 크기 자동 결정
int numbers[] = {1, 2, 3, 4, 5}; // numbers 배열의 크기는 5로 자동 결정
double data[] = {1.1, 2.2};      // data 배열의 크기는 2로 자동 결정

이 방법은 배열의 크기를 명시적으로 세지 않아도 되므로 편리하며, 특히 문자열 초기화(다음 장에서)에 유용합니다.

3. 일부 요소만 초기화: 배열의 크기보다 적은 수의 초기값을 제공하면, 나머지 요소들은 0 (zero) 으로 자동 초기화됩니다.

일부 요소만 초기화
int arr1[5] = {1, 2}; // arr1은 {1, 2, 0, 0, 0}으로 초기화됨
double arr2[3] = {1.5}; // arr2는 {1.5, 0.0, 0.0}으로 초기화됨

4. 모든 요소를 0으로 초기화: 리스트 초기화 시 첫 요소만 0으로 초기화하거나 빈 중괄호 {}를 사용하면 모든 요소가 0으로 초기화됩니다.

모든 요소를 0으로 초기화
int zeros1[5] = {0}; // 모든 요소를 0으로 초기화 -> {0, 0, 0, 0, 0}
int zeros2[5] = {};  // C++11 이후 가능. 모든 요소를 0으로 초기화 -> {0, 0, 0, 0, 0}

1차원 배열 요소에 접근하기

배열의 각 요소는 배열 이름과 대괄호 [] 안에 인덱스를 사용하여 접근합니다.

C++에서 배열의 인덱스는 항상 0부터 시작합니다.

배열 요소 접근 형식
배열이름[인덱스];
  • 인덱스는 0부터 배열크기 - 1까지의 정수입니다.
  • scores[0]은 첫 번째 요소, scores[1]은 두 번째 요소, scores[scores크기 - 1]은 마지막 요소를 나타냅니다.
배열 요소 접근 예시
#include <iostream>

int main() {
    int scores[5] = {85, 92, 78, 95, 88};

    // 배열 요소 읽기
    std::cout << "첫 번째 학생의 점수: " << scores[0] << std::endl;  // 출력: 85
    std::cout << "세 번째 학생의 점수: " << scores[2] << std::endl;  // 출력: 78

    // 배열 요소 변경
    scores[1] = 90; // 두 번째 학생의 점수를 92에서 90으로 변경
    std::cout << "변경된 두 번째 학생의 점수: " << scores[1] << std::endl; // 출력: 90

    // 루프를 사용하여 모든 요소 출력
    std::cout << "모든 학생의 점수: ";
    for (int i = 0; i < 5; ++i) { // 인덱스 0부터 4까지 반복
        std::cout << scores[i] << " ";
    }
    std::cout << std::endl; // 출력: 85 90 78 95 88

    return 0;
}

인덱스 범위 초과 오류

배열을 사용할 때 가장 흔하게 발생하는 오류 중 하나가 인덱스 범위 초과(Out-of-Bounds) 오류입니다.

C++은 배열 인덱스가 유효한 범위(0에서 크기 - 1) 내에 있는지 자동으로 검사하지 않습니다.

프로그래머가 인덱스 범위를 벗어나는 값으로 배열 요소에 접근하려고 하면 프로그램은 예상치 못한 결과를 초래하거나, 메모리 액세스 위반(Segmentation Fault)과 같은 심각한 런타임 오류로 인해 비정상적으로 종료될 수 있습니다.

인덱스 범위 초과 예시
#include <iostream>

int main() {
    int data[3] = {10, 20, 30};

    // 유효한 접근
    std::cout << data[0] << std::endl; // 10
    std::cout << data[2] << std::endl; // 30

    // 인덱스 범위 초과 (컴파일 에러가 아님! 런타임에 문제 발생)
    std::cout << data[3] << std::endl;  // 3은 유효한 인덱스가 아님 (0, 1, 2만 유효)
    data[5] = 100; // 존재하지 않는 메모리 영역에 값을 씀

    // 이 이후의 프로그램 동작은 예측 불가능하거나 비정상 종료될 수 있습니다.

    return 0;
}

따라서 배열을 다룰 때는 for 문 등의 루프에서 인덱스 조건을 정확히 설정하여 배열의 유효한 범위 내에서만 접근하도록 항상 주의해야 합니다.


배열의 크기 얻기 (sizeof 활용)

선언된 배열의 크기를 명시적으로 알지 못할 때, sizeof 연산자를 활용하여 배열의 전체 크기(바이트 단위)와 요소 하나의 크기(바이트 단위)를 얻어 배열의 요소 개수를 계산할 수 있습니다.

배열 크기 계산 예시
#include <iostream>

int main() {
    int scores[] = {85, 92, 78, 95, 88}; // 크기를 명시하지 않음

    // 배열의 전체 크기 (바이트)
    std::cout << "scores 배열의 전체 크기: " << sizeof(scores) << " 바이트" << std::endl; // 5 * 4 = 20 (int가 4바이트일 경우)

    // 배열 요소 하나의 크기 (바이트)
    std::cout << "scores 배열 요소 하나의 크기: " << sizeof(scores[0]) << " 바이트" << std::endl; // int의 크기 (예: 4 바이트)

    // 배열의 요소 개수 계산
    int numElements = sizeof(scores) / sizeof(scores[0]);
    std::cout << "scores 배열의 요소 개수: " << numElements << std::endl; // 20 / 4 = 5

    // 이 정보를 사용하여 루프를 안전하게 돌릴 수 있습니다.
    std::cout << "배열의 모든 요소 (계산된 크기 사용): ";
    for (int i = 0; i < numElements; ++i) {
        std::cout << scores[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

이 방법은 특히 배열의 크기가 코드에서 변경될 때 유용하며 for 문에서 하드코딩된 숫자 대신 사용하면 실수를 줄일 수 있습니다.