배열 사용법 기본
목차
이번 포스팅에서는 배열의 사용법에 대하여 알아보겠습니다.
배열 생성과 인덱스 접근(Traverse)
배열은 같은 자료형이 연속으로 나열되는 자료형입니다. 아래의 코드는 정수형(int) 변수가 7개 있는 배열을 선언합니다. 인덱스는 0에서부터 출발하여 6까지 총 7개입니다.
배열의 모든 요소에 순차적으로 접근하는 것을 Traverse(횡단)이라고 합니다. 어떤 자료구조라도 그 자료구조의 모든 요소를 한번씩 접근하는 기능이 있어야 검색이 가능해집니다.
배열은 순차적으로 나열되는 자료형이기 때문에 초보자도 직관적으로 이해하기 쉽습니다.
#include <iostream> using std::cout; using std::endl; int main() { int arr[7] = { 0, }; int length = sizeof arr / sizeof arr[0]; arr[0] = 3; arr[6] = 7; for (int i = 0; i < length; i++) { cout << arr[i] << ", "; } return 0; }
3, 0, 0, 0, 0, 0, 7,
우선 배열을 생성한 후 초기화를 시켜줍니다. 초기화 전에는 쓰레기 값이 들어있습니다. 프로그래밍 언어에 따라서는 변수나 배열을 선언하면 바로 초기화를 시켜주기도 합니다만, 명시적으로 초기화하는 습관을 가지는게 좋습니다.
배열이 인덱스를 사용하는 것은 직관적으로 보입니다. 0부터 6으로 인덱스를 이동할 때 0을 아무것도 없다고 해석하는게 아니라 하나의 메모리 공간으로 보는 시각이 필요합니다. 수학에서는 0을 아무것도 없는 것으로 해석하지만 메모리 공간에서 0은 주소의 시작점입니다.
출력 결과를 보면 인덱스에 따라 연속적으로 배치되어 있다는 것을 시각적으로 알 수 있습니다. 많은 교재들에서 배열 메모리의 구조를 그림으로 표현하는데요. 결국은 붙어있다는 아이디어가 중요합니다. 좀더 깊은 내용이지만 배열의 인덱스가 순차적인 점은 CPU가 미리 예측하여 L1 캐시를 사용하여 속도를 높일 수 있다는 의미입니다.
자료구조를 학습할 때 효율성에 대하여 수학적인 사고 뿐 아니라 직관적인 접근도 중요합니다. 메모리에 데이터를 이렇게 배치하는게 좋겠다 라는 말을 할 때는 어떤 근거가 있기 마련인데, 알고리즘의 효율성은 성능 테스트를 통해 확인이 되지만 모든 것의 성능을 다 테스트할 수는 없기 때문에 직관성도 필요합니다.
때문에 배열이 메모리상에 서로 붙어있다는 것은 붙어있지 않은 다른 자료구조와 근본적인 차이가 있습니다.
동적 배열 할당
일반적 문법의 배열은 컴파일 시 크기를 할당해야 합니다.
배열의 크기 arr[크기] 에 변수를 할당할 수가 없습니다. 반면 동적 배열 할당은 실행시간에 가변적으로 늘리거나 줄일 수가 있습니다. 힙메모리를 사용하며 C++에서는 calloc 같은 메모리 할당에 관한 함수를 new 연산자로 대체해서 훨씬 간편합니다.
메모리를 할당했다면 당연히 해제도 해줘야 합니다. 배열의 경우 delete[] 처럼 [] 배열에 대한 메모리해제를 합니다.
#include <iostream> using std::cout; using std::endl; int main() { int size = 7; int *arr = new int[size] { 0, }; arr[0] = 7; arr[1] = 3; for (int i = 0; i < size; i++) { cout << arr[i] << ", "; } delete[] arr; return 0; }
7, 3, 0, 0, 0, 0, 0,
정수형 포인터 * 를 선언하여 정수형 배열을 동적 할당합니다.
배열처럼 사용할 수도 있고 *(arr+i) 처럼 포인터 형식으로 요소에 접근할 수 있습니다.
배열의 최댓값 구하기
배열의 최댓값은 다음과 같이 구할 수 있습니다.
#include <iostream> using std::cout; using std::endl; int main() { int size = 7; int *arr = new int[size] { 0, }; arr[0] = 11; arr[1] = 90; arr[2] = 23; arr[3] = 74; arr[4] = 7; arr[5] = 99; arr[6] = 62; for (int i = 0; i < size; i++) { cout << *(arr+i) << ", "; } //최댓값 구하기 int maxArr = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] > maxArr) maxArr = arr[i]; } cout << endl << "최댓값은 다음과 같습니다. " << maxArr << endl; delete[] arr; return 0; }
11, 90, 23, 74, 7, 99, 62, 최댓값은 다음과 같습니다. 99
포인트는 배열 인덱스의 처음 부터 끝까지 순차적으로 접근하는 것입니다. for 루프는 배열을 위해 태어난 문법 같아 보입니다.
이렇게 하나씩 접근하는 것을 traverse (횡단, 주사 – 달릴 주 + 조사할 사)라고 하는데 한글로는 별로 쓰이지 않는 개념이니 영어로 기억해 두는게 좋습니다. 다른 자료구조에서도 traverse 라는 단어를 사용합니다.
최댓값을 확인하는 알고리즘에 주목합니다. 알고리즘이라고 거창한 기술이 있을 것 같지만 그렇지 않습니다. for 루프로 인덱스를 하나씩 올리면서 맨처음에 배열의 0번 값을 할당한 maxArr와 비교하여 큰 쪽을 maxArr 에 할당할 뿐입니다.
배열 역순 정렬 (Reverse)
배열을 역순 정렬하는 것은 아래와 같습니다.
기본 원리는 배열의 처음 요소와 끝 요소를 바꾸고 다시 인덱스를 아래에서 하나 올리고 뒤에서 하나 내리는 식으로 바꿔나갑니다. n개 요소가 있으면 n/2 번 교환하면 됩니다.
#include <iostream> using std::cout; using std::endl; int main() { int size = 7; int *arr = new int[size] { 0, }; arr[0] = 11; arr[1] = 90; arr[2] = 23; arr[3] = 74; arr[4] = 7; arr[5] = 99; arr[6] = 62; for (int i = 0; i < size; i++) { cout << *(arr+i) << ", "; } cout << "\n배열을 역순으로 정렬합니다" << endl; for (int i = 0; i < size/2; i++) { int temp = arr[i]; arr[i] = arr[size - i - 1]; arr[size - i - 1] = temp; } for (int i = 0; i < size; i++) { cout << *(arr + i) << ", "; } delete[] arr; return 0; }
11, 90, 23, 74, 7, 99, 62, 배열을 역순으로 정렬합니다 62, 99, 7, 74, 23, 90, 11,
인덱스가 0부터 6까지 있다는 것을 감안하여 size = 7 에서 -1 을 해줘야 합니다.
즉 0과 6을 바꾸고, 그 다음에 1과 5를 바꾸고, 그 다음에 2와 4를 바꿉니다. 정수형의 연산은 나머지를 버리기 때문에 3번만 하면 됩니다. 어차피 가운데 있는 숫자는 역순해도 가운데기 때문에 바꿀 필요가 없습니다.
요약
배열 사용법 기본에 대하여 알아봤습니다.
처음부터 배열의 아이디어를 사용할 수 있다면 컴퓨터 공학에 소질이 있는 것 입니다. 많은 사람들이 C++의 메모리와 포인터 레벨에서 학습을 포기합니다만, 그것은 포인터가 어려워서 라기보다는 배열에 대한 이해가 없는 상태에서 진도를 나갔기 때문입니다.
배열에 대한 열정이 있다면 아래 외부참고문서의 영어 문서들을 읽어 보시길 바랍니다. 아래 자료들은 기초를 쌓는데 큰 도움이 됩니다.
외부참고문서
배열 사용법 기본 콘셉트
C++ Arrays – C++ 배열 사용법 기본 Tutorialspoint
Arrays in C/C++ – GeeksforGeeks
Arrays – C++ Tutorials (cplusplus.com)
C++ Arrays (With Examples) (programiz.com)