Notice
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Today
Total
관리 메뉴

Study Note

[C/C++ Syntax] #013 배열(Array) 본문

- 프로그래밍 언어(Programming Language)/C\C++

[C/C++ Syntax] #013 배열(Array)

mymir 2021. 1. 24. 17:03

많은 데이터를 다룰 때, 각각 변수를 만들어 사용해야 한다면 여간 번거로운 일이 아닐 것입니다. 이를 위해 이번 글에선 기본 자료구조(data structure)인 배열(array)에 대해 소개하려고 합니다.

 

 

배열(Array)


배열은 같은 자료형의 데이터들을 저장할 수 있는 자료구조입니다. 변수와 마찬가지로 배열의 이름이 필요하며 하나의 이름으로 다중 데이터들에 접근하기 위해 index를 사용하며 각각의 값을 요소(element)라 합니다. 우선 배열의 기본 선언 방식은 다음과 같습니다.

선언된 자료형의 데이터들을 배열크기에 해당하는 수 만큼 담을 수 있는 배열이 생성되는 것입니다. 다만 위와 같은 기본적인 선언에서 배열의 크기는 반드시 상수 자연수만 가능합니다. 특히 비워두거나 변수를 사용할 수 없습니다. 

 

그리고 배열은 초기화에서 여러가지 방법을 제공하고 있는데, 그 예시는 다음과 같습니다.

먼저 1번과 같이 배열의 초기화의 대입연산자 오른쪽 값은 중괄호로 묶인 값들을 사용함을 알 수 있습니다. 그 중에서 사용할 자료의 양인 배열의 크기가 초기화 시 정해진다면 2번과 같이 배열의 크기를 생략할 수 있습니다. (기본 선언에서는 불가능)

그리고 3번과 같이 배열의 크기보다 작은 개수의 자료를 초기화 하는 경우 배열의 앞에서 부터 초기화 값을 채우고, 채우지 못한 값들은 0으로 초기화됩니다.

 

 

 

index


배열을 사용하기에 앞서 index라는 개념이 필요합니다. 위에서 말했듯이, 하나의 배열 이름으로 여러 개의 데이터들에 접근하기 위해선 각 데이터의 저장 위치를 알아야 합니다. 이를 위해 배열의 이름 자체는 하나의 주소로 되어 있고(다른 주소 값을 담을 수 있는 포인터가 아닙니다.), 이 주소로부터 몇 칸 만큼 떨어져 있는지를 헤아리는 방법을 indexing이라 하며 연산자 [ ] 로 사용합니다. 이때, 데이터들은 배열의 이름에 해당하는 주소부터 담기기 때문에 index는 0부터 시작합니다.

 

다음 예를 살펴봅시다.

#include <iostream>

using namespace std;

int main() {
	int arr[5] = { 2, 3, 5, 7, 11 };

	for (int i = 0; i < 5; i++) {
		cout << "arr + " << i << " = " << arr + i << endl;
		cout << "&arr[" << i << "] = " << &arr[i] << endl;
		cout << "arr[" << i << "] = " << arr[i] << endl;
	}
}

이처럼 배열의 사용에서 변수를 사용하여 index를 나타낼 수 있으며, 첫 줄은 주소의 연산을 이용한 출력, 두 번째 줄은 주소 연산자를 이용한 출력, 세 번째 줄은 각 값의 출력을 확인할 수 있습니다. 즉, arr[i] 는 *(arr + i)와 같은 역할을 하는 것을 알 수 있습니다.

또한, 배열은 같은 속성의 데이터를 저장하기 때문에 필요한 경우를 제외하곤, 예와 같이 반복문을 사용하여 일괄적으로 데이터를 다루는 방법을 선호합니다.

 

또한, index 사용 시 선언된 크기를 초과하는 index를 사용하는 경우 쓰레기 값이 들어 있는 메모리의 자료를 사용하게 되므로 반드시 크기 내의 index(크기-1 까지)만을 사용하도록 합니다.

 

 

 

다차원 배열


배열은 데이터들의 속성만 동일하면 기본 자료형이 아닌 데이터들도 저장할 수 있습니다. 특히, 배열의 요소가 배열인 배열을 다차원 배열이라고 합니다. 다음 예시를 살펴보겠습니다.

#include <iostream>

using namespace std;

int main() {
	int arr[3][2] = { {1, 2}, {3, 4}, {5, 6} };

	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 2; j++)
			cout << "arr[" << i << "][" << j << "] = " << arr[i][j] << endl;
	}
}

이차원 배열의 예시입니다. 다차원 배열의 선언은 이름 옆에 대괄호쌍을 추가하여 선언하며, 예시에선 int형 요소가 2개짜리인 일차원 배열을 3개를 요소로 하는 이차원 배열을 선언한 것입니다. 중첩 방향을 잘 파악하시길 바랍니다. (행렬을 알고 있다면 행렬로써 이해하면 조금 더 쉽습니다.)

 

또한 이런 중첩의 형식에 의해 배열 사용 시 해당 차원만큼 index연산을 하지 않은 경우 주소값으로 잘못 사용할 수 있으니 주의하여 사용하여야 합니다. 예를 들어 위 예시에서 arr[1]은 3이 담긴 메모리의 주소입니다. 

 

다만 메모리 주소는 연속적인 16진수로 이루어져 있기 때문에 이런 다차원 배열은 그 입체감을 흉내낸 것에 불과합니다. 즉, 위의 이차원 배열은 메모리 상에서는 그냥 연속 주소의 int형 자료가 6개가 있는 것일 뿐입니다.

때문에 컴파일러는 다음과 같은 초기화 방법에서 자동적으로 요소 쌍을 구별할 수 있습니다.

int arr[3][2] = {1, 2, 3, 4, 5, 6}

하지만 이로 인해 다차원 배열 초기화에서 첫 대괄호의 배열 크기를 제외하곤 나머지를 생략할 수 없습니다.

 

 

 

배열과 포인터


배열의 이름은 해당 배열의 주소를 나타내므로 포인터에 저장할 수 있습니다. 그리고 반대로, 배열과 같이 연속된 주소에 데이터가 담긴 경우 해당 첫 주소를 포인터에 저장하면 index 연산자 [ ] 를 사용하여 배열과 같이 사용할 수도 있습니다.

다음 예시를 살펴보겠습니다.

#include <iostream>

using namespace std;

int main() {
	int arr[5] = { 2, 3, 5, 7, 11 };
	int* arr_pointer = arr;

	for (int i = 0; i < 5; i++) {
		cout << "arr_pointer[" << i << "] = " << arr_pointer[i] << endl;
	}
}

하지만 이런 식의 활용은 얕은 복사에 해당되어 배열을 함수에 전달하는 등 특수한 경우가 아니면 잘 사용하지 않습니다.

더보기

얕은 복사(shallow copy)란 동일한 메모리 공간을 복사하는 것이기 때문에, 복사한 객체와 복사된 객체의 수정이 동일하게 일어나는 복사 방법을 말합니다. 위의 예시에서 arr_pointer[0] = 1 과 같이 수정하면 arr[0]의 값도 1로 수정되어버립니다.

 

반대로 깊은 복사(deep copy)도 존재합니다. 얕은 복사와는 반대로 복사한 객체와 복사된 객체의 수정이 별도로 일어납니다. 이는 별도의 메모리 공간에 데이터만을 복사하는 방법으로 배열의 경우에선 새로운 배열을 생성하여 반복문으로 각 데이터를 복사하는 방법을 사용합니다.

 

추가로 이차원 이상의 배열의 이름은 형식 차이 문제로 포인터에 담을 수 없습니다.

 

배열과 함수


배열을 함수에 전달 할 때 인수로는 배열의 이름을 사용하고 매개 변수에선 배열의 초기화와 같은 방식으로 전달 받습니다. 이는 배열의 주소를 전달해 주는 것이므로, 얕은 복사가 일어나 전달 받은 배열에서 수정이 일어나면 전달한 배열에서도 동일하게 수정이 일어납니다.

#include <iostream>

using namespace std;

void func(int func_arr[]); // const X

int main() {
	int main_arr[3] = { 1, 2, 3 };

	func(main_arr);
	for (int i = 0; i < 3; i++) {
		cout << main_arr[i] << endl;
	}
}

void func(int func_arr[]) {
	func_arr[2] = 4;
}

 

따라서 변경을 원치 않는 경우 const 속성(변경 불가능)을 부여하여 사용합니다.

#include <iostream>

using namespace std;

void func(const int func_arr[]); // const O

int main() {
	int main_arr[3] = { 1, 2, 3 };

	func(main_arr);
	for (int i = 0; i < 3; i++) {
		cout << main_arr[i] << endl;
	}
}

void func(const int func_arr[]) {
	func_arr[2] = 4;
}

 

 

또한 다차원 배열을 전달하는 경우도 매개변수에서 초기화 방식과 동일하게 첫 대괄호를 제외하고 크기를 입력해주여야 합니다.

#include <iostream>

using namespace std;

#define row 2
#define col 3

void print_matrix(const int func_arr[][col]);

int main() {
	int main_arr[row][col] = {1, 2, 3, 4, 5, 6};
	print_matrix(main_arr);
}

void print_matrix(const int func_arr[][col]) {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			cout << func_arr[i][j] << " ";
			if (j == col - 1)
				cout << endl;
		}
	}
}

Comments