배열

자료구조의 기초 이론을 학습합니다

배열의 개념과 C++에서 배열을 사용하는 방법

배열은 컴퓨터 프로그래밍에서 가장 기본적이고 중요한 자료구조 중 하나로, 프로그램의 성능을 높이고 데이터를 효율적으로 관리하는 데 필수적인 도구이다. 프로그래밍에서는 다량의 데이터를 다루는 경우가 빈번하며, 배열을 활용하면 데이터를 빠르고 효율적으로 접근하고 처리할 수 있다. 이번 글에서는 배열의 개념과 작동 원리, C++에서 배열을 사용하는 방법, 그리고 배열과 포인터를 함께 다루는 방식에 대해 알아본다.


배열의 개념과 주요 특징

배열은 다음과 같은 주요 특징을 갖는다.

  1. 연속된 메모리 할당: 배열은 모든 요소가 연속된 메모리 위치에 저장된다. 예를 들어, int형 배열 arr[5]는 5개의 정수를 저장하기 위해 연속된 20바이트(5 * 4바이트)의 메모리 공간을 할당받는다. 이는 배열의 첫 번째 요소 주소를 기준으로, 각 요소가 데이터 타입 크기만큼 떨어진 주소에 배치됨을 의미한다.

  2. 고정된 크기: 배열의 크기는 선언 시 정해지며, 실행 중에는 변경할 수 없다. 예를 들어 int arr[5]는 정수형 데이터를 5개 저장할 수 있는 배열로, 선언 후 크기를 늘리거나 줄일 수 없다. 따라서 배열 선언 시 필요한 크기를 신중하게 결정해야 한다.

  3. 인덱스를 통한 빠른 접근: 배열은 인덱스를 사용해 각 요소에 접근하며, 인덱스는 0부터 배열 크기 - 1까지의 값을 가진다. 예를 들어, 배열 arr[5]에서 세 번째 요소에 접근하려면 arr[2]와 같이 인덱스를 사용할 수 있다. 배열 요소에 접근하는 시간 복잡도는 **O\(1\)**로, 인덱스 접근 방식 덕분에 특정 위치의 데이터를 빠르게 읽고 쓸 수 있다.


배열의 작동 원리와 메모리 배치

배열이 선언되면 프로그램은 배열이 필요한 메모리 공간을 확보하며, 배열의 각 요소는 배열의 첫 번째 요소 주소를 기준으로 데이터 타입 크기만큼의 간격을 두고 저장된다. 예를 들어, int형 배열 arr[5]는 다음과 같이 메모리에 배치된다.

  1. 배열의 첫 번째 요소 arr[0]은 배열의 시작 주소 arr에 위치한다.
  2. 배열의 두 번째 요소 arr[1]은 첫 번째 요소 주소에 sizeof(int)(4바이트)만큼 떨어진 위치에 저장된다.
  3. 세 번째 요소 arr[2]는 시작 주소에 2 * sizeof(int)만큼 떨어진 곳에 저장되는 식으로, 각 요소는 데이터 타입 크기만큼의 메모리 오프셋을 두고 연속적으로 배치된다.

이와 같은 배열의 메모리 배치 덕분에, 배열의 i번째 요소에 접근할 때 배열의 시작 주소 arri * 데이터 타입 크기를 더하여 해당 메모리 위치에 빠르게 접근할 수 있다. 이는 포인터와 유사한 방식으로, 배열 arr[i]는 내부적으로 *(arr + i)와 동일하게 동작한다.

배열의 이러한 메모리 배치와 작동 방식은 특정 위치의 데이터를 빠르게 검색하고 수정하는 데 매우 유리하다. 하지만 크기가 고정되어 있어, 실행 중에는 데이터를 삽입하거나 삭제하는 데 비효율적일 수 있다. 이러한 특성으로 인해 배열은 빠른 데이터 접근이 필요한 상황에 적합한 자료구조로 사용된다.


C++에서 배열 사용 방법

C++에서는 배열을 선언할 때 배열의 크기와 데이터 타입을 명시해야 한다. 배열 선언과 초기화는 다음과 같이 이루어진다.

1
2
3
4
5
6
// 배열 선언 및 초기화
int arr[5] = {1, 2, 3, 4, 5};  // 크기가 5인 정수 배열 선언과 초기화

// 배열 요소에 접근
cout << arr[0];  // 첫 번째 요소 출력 (결과: 1)
arr[2] = 10;     // 세 번째 요소에 10을 할당

C++에서는 배열의 크기를 명시하지 않고 초기화 값만으로 배열을 선언할 수도 있다. 컴파일러가 초기화 값에 맞춰 배열 크기를 자동으로 설정한다.

1
int arr[] = {1, 2, 3, 4, 5};  // 컴파일러가 크기를 5로 설정

이 외에도, 배열의 크기를 sizeof 연산자를 통해 계산할 수 있으며, 이를 활용해 배열의 전체 크기를 알아낼 수 있다.

1
int size = sizeof(arr) / sizeof(arr[0]);  // 전체 크기 계산

배열과 포인터의 관계

배열은 메모리에서 연속적으로 배치되기 때문에 포인터와 유사한 특성을 가진다. C++에서 배열의 이름은 배열의 첫 번째 요소의 주소를 나타내며, 배열을 포인터처럼 다룰 수 있다.

1
2
3
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;  // 배열의 첫 번째 요소 주소를 포인터 ptr에 할당
cout << *(ptr + 2);  // 세 번째 요소(3)를 출력

포인터를 사용하여 배열 요소에 접근할 때는 *(ptr + i) 형태로 포인터 연산을 활용할 수 있으며, 이는 배열 인덱스 접근 방식과 동일하게 작동한다. 포인터와 배열의 이러한 관계를 이해하면 함수에 배열을 포인터로 전달하여 메모리 사용을 최적화할 수 있다.


다차원 배열

배열은 다차원으로도 확장할 수 있으며, 특히 2차원 배열은 행과 열로 구성된 데이터 구조를 나타내기 때문에 행렬 또는 테이블 데이터를 저장하는 데 유용하다. 예를 들어, int matrix[3][3]는 3x3 크기의 정수형 2차원 배열로, 3행 3열의 데이터를 저장할 수 있다.

1
2
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
cout << matrix[1][2];  // 두 번째 행, 세 번째 열 요소(6)를 출력

2차원 배열은 배열의 배열로 볼 수 있으며, 포인터를 통해 다차원 배열의 요소에 접근할 수 있다. 이러한 다차원 배열은 이미지 데이터나 행렬 계산과 같은 작업에서 효과적으로 사용된다.


배열의 장점과 한계

배열은 다음과 같은 장점을 가진다.

  1. 빠른 접근 속도: 배열 인덱스를 통해 특정 위치의 데이터를 O(1) 시간 복잡도로 빠르게 읽고 쓸 수 있다.

  2. 간결한 코드 구조: 배열을 통해 동일한 데이터 타입의 여러 값을 일괄적으로 관리할 수 있으며, 반복문과의 조합을 통해 데이터를 쉽게 처리할 수 있다.

그러나 배열은 고정된 크기를 가지고 있기 때문에 데이터를 삽입하거나 삭제하는 데 어려움이 있으며, 이로 인해 많은 데이터의 동적 관리를 필요로 하는 경우에는 연결 리스트와 같은 자료구조가 더 적합할 수 있다. 배열과 포인터의 관계를 이해하면, 메모리를 효과적으로 관리하고, 더 나아가 효율적인 코드 작성이 가능해진다.

배열은 프로그래밍의 기초이자 필수적인 자료구조로, 메모리와 효율적인 접근 방식을 이해하는 데 중요한 역할을 한다. 공학도로서 배열의 작동 원리와 사용 방식을 충분히 익혀 효율적인 프로그래밍을 실현하는 것이 중요하다.

이전글: 메모리 레이아웃

다음글: 단방향 링크드 리스트

Hugo로 만듦
JimmyStack 테마 사용 중