[1. 리스트, 스택, 큐] C++ std::array
C 스타일 배열의 단점
- 메모리 할당과 해제를 수동으로 처리해야 한다. memory leak이 발생할 수 있다.
- [] 연산자에서 배열 크기보다 큰 원소를 참조하는 것을 검사하지 못한다.(segmentation fault)
- 배열을 중첩하여 사용할 경우, 문법이 너무 복잡하여 코드의 가독성이 떨어진다.
- 깊은 복사(deep copy)가 기본으로 동작하지 않는다. 수동으로 구현해야 한다.
이와 같은 문제점을 회피할 수 있도록 C++에서는 std::array를 제공한다.
std::array
- std::array는 메모리를 자동으로 할당하고 해제한다.
- 원소의 타입과 배열 크기를 매개변수로 사용하는 클래스 템플릿이다.
std::array<type, size>
의 형식으로 선언할 수 있다.
#include <iostream>
#include <array>
using namespace std;
int main(){
array<int, 10> arr1;
arr1[0] = 1;
cout << arr1[0];
}
위 코드처럼 사용할 수 있다.
원소 접근
C 스타일 배열과 같은 방식으로 [] 연산자를 사용할 수 있다.
[] 연산자와 비슷하게 작동하는 함수로는 at(index)가 있다.
[] 연산자에서는 빠른 동작을 위해 전달된 인덱스 값이 배열의 크기보다 작은지 검사하지 않는다.
at(index)에서는 index 값이 유효하지 않으면 std::out_of_range 예외를 발생시킨다. 물론 [] 연산자보다는 느리게 동작한다.
at을 이용하여 예외 처리 코드도 만들 수 있다.
#include <iostream>
#include <array>
using namespace std;
int main(){
array<int, 4> arr3 = {1, 2, 3, 4};
try {
cout << arr3.at(3) << endl;
cout << arr3.at(4) << endl;
}
catch (const out_of_range& ex){
cerr << ex.what() << endl;
}
}
함수 전달
std::array 객체를 다른 함수에 전달하는 방식은 기본 데이터 타입을 전달하는 것과 유사하다.
값, 참조로 전달할 수 있고, const를 사용할 수 있다.
#include <iostream>
#include <array>
using namespace std;
void print(array<int, 5> array1){
for (auto elem : array1) {
cout << elem << ", ";
}
}
int main(){
array<int, 5> array1 = {1, 2, 3, 4, 5};
print(array1);
return 0;
}
위 코드처럼 print 함수의 매개변수로 전달할 수 있다.
그러나 이 코드는 매개변수 데이터 타입에 전달받을 배열 크기가 고정되어 있어 다른 크기의 배열은 전달할 수 없다.
print 함수를 함수 템플릿으로 선언하고, 배열 크기를 템플릿 매개변수로 지정하면 이 문제를 해결할 수 있다.
#include <iostream>
#include <array>
using namespace std;
template <size_t N>
void print(array<int, N> array1){
for (auto elem : array1) {
cout << elem << ", ";
}
}
int main(){
array<int, 5> array1 = {1, 2, 3, 4, 5};
array<int, 10> array2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
print(array1);
cout << "\n";
print(array2);
return 0;
}
std::array는 기본적으로 깊은 복사가 동작하기 때문에 새로운 배열이 모든 원소가 복사된다.
이를 피하기 위해 참조 또는 const 참조를 사용한다.
template <size_t N>
void print1(array<int, N>& array1){
array1[0] = 5;
}
배열의 원소에 순차적으로 접근
1. 반복자(iterator)를 이용한 접근
#include <iostream>
#include <array>
using namespace std;
int main(){
array<int, 5> array1 = {1, 2, 3, 4, 5};
for(auto it = array1.begin(); it != array1.end(); it++){
cout << *it << "\t";
}
return 0;
}
위 코드처럼 반복자를 사용하여 원소에 차례대로 접근할 수 있다.
array::begin(): 함수의 첫 번째 원소를 가리키는 반복자를 반환한다.
array::end(): 함수의 마지막 원소 다음을 가리키는 반복자를 반환한다.
const_iterator, reverse_iterator와 같은 형태의 반복자도 사용할 수 있다.
2. 범위 기반 for(range-based for) 문법을 이용한 접근
#include <iostream>
#include <array>
using namespace std;
int main(){
array<int, 5> array1 = {1, 2, 3, 4, 5};
for (auto elem : array1) {
cout << elem << ", ";
}
return 0;
}
위 코드처럼 for 반복문을 사용해서도 모든 원소에 접근할 수 있다.
인덱스 값을 사용하는 for 반복문은 배열의 크기를 정확하게 지정하여 인덱스 값이 유효한지 확인해야 한다.
각종 원소 접근 함수
#include <iostream>
#include <array>
using namespace std;
int main(){
array<int, 5> array1 = {1, 2, 3, 4, 5};
cout << "front(): " << array1.front() << "\n"; // 배열의 첫 번째 원소에 대한 참조 반환
cout << "back(): " << array1.back() << "\n"; // 배열의 마지막 원소에 대한 참조 반환
cout << "data(): " << array1.data() << "\n"; // 실제 데이터 메모리 버퍼를 가리키는 포인터 반환
return 0;
}
관계 연산자
깊은 비교를 위한 관계 연산자를 지원한다. (C 스타일 배열에서는 얕은 비교를 수행한다.)
#include <iostream>
#include <array>
using namespace std;
int main(){
array<int, 5> array1 = {1, 2, 3, 4, 5};
array<int, 5> array2 = {2, 3, 4, 5, 6};
array<int, 5> array3 = {1, 2, 3, 4, 5};
cout << (array1 == array2) << endl; // 출력: 0
cout << (array1 == array3) << endl; // 출력: 1
return 0;
}