C스타일 배열의 단점:
- 메모리 할당과 해제를 수동으로 처리해야 합니다. 메모리를 해제하지 못하면 메모리 릭 (memory leak)이 발생할 수 있고, 이 경우 해당 메모리 영역을 사용할 수 없습니다.
- [ ]연산자에서 배열 크기보다 큰 원소를 참조하는 것을 검사하지 못합니다. 잘못 사용하면 세그멘테이션 결함(segmentation fault) 또는 메모리 손상으로 이어질 수 있습니다.
- 배열을 중첩해서 사용할 경우, 문법이 너무 복잡해서 코드를 이해하기 어렵습니다.
- 깊은 복사(deep copy)가 기본으로 동작하지 않습니다. 이러한 동작은 수동으로 구현해야 합니다.
위 문제점을 커버하기위해 C++은 C스타일 배열을 대체하는 std::array를 제공합니다.
std::array는 메모리를 자동으로 할당하고 해제합니다.
원소타입과 배열크기를 매개변수로 사용하는 클래스 템플릿입니다.
std::array<int, 10> arr1;
//크기가10인 int타입 배열선언
std::array는 [ ]연산자를 사용하던가 at(index)를 사용하여 배열 원소에 접근합니다.
그 차이는 [ ]연산자는 인덱스값이 배열의 범위를 벗어났는지 검사하지 않지만, at()함수는 인자로받은 index값이 유효하지않으면 std::out_of_range 예외를 발생시킵니다. 따라서 at()은 안전한 대신 속도는 []방식보다는 느립니다.
std::array<int, 4> arr = {1, 2, 3, 4};
arr[3]=9;
arr.at(3)=10;
//위 두 방식 모두 가능
std::array 객체를 다른 함수에 전달하는 방식은 기본 데이터 타입을 전달하는 것과 유사합니다.
값 또는 참조(reference)로 전달할 수 있고, const를 함께 사용할 수도 있습니다.
C 스타일 배열을 함수에 전달할 때처럼 포인터 연산을 사용한다거나 참조 또는 역참조(de-reference) 연산을 하지 않아도 됩니다.
그러므로 다차원 배열을 전달하는 경우에도 std::array를 사용하는 것이 가독성이 훨씬 좋습니다. 아래는 그 예시코드입니다.
template <size_t N>
void print(const std::array<int, N>& arr)
{
for(auto element : arr)
{
cout<<element<<", ";
}
//범위기반 for문을 사용해 손쉽게 원소 접근가능
}
int main()
{
array<int,6> arr={1,2,3,4,5,6};
print(arr);
}
//출력결과: 1, 2, 3, 4, 5, 6,
함수에 std::array 객체를 전달할 경우, 기본적으로 새로운 배열에 모든 원소가 복사됩니다. 즉, 자동으로 깊은 복사가 동작합니다. 만약 이러한 동작을 피하고 싶다면 참조 또는 const 참조를 사용할 수 있습니다. 즉, 프로그래머의 선택에 따라 동작을 결정할 수 있습니다.
위의 코드같은 경우도 깊은복사를 피하고 참조(reference)를 사용해서 성능적 이득을 얻기 위해 const참조를 print함수의 인자로 받을때 사용하였습니다.
범위 기반 for 반복문을 사용하여 std::array의 모든 원소에 접근할 수 있는 것은 반복자(iterator)를 사용하기 때문입니다.
std::array는 begin()과 end()라는 이름의 멤버 함수를 제공하며, 이들 함수는 가장 첫 번째 원소와 가장 마지막 원소의 위치(정확하게는 마지막 원소 다음 위치)를 반환합니다.
특정 원소 위치에서 다음 원소 위치로 이동하려면 반복자에 증가 연산자(++) 또는 덧셈 연산자(+) 같은 산술 연산을 수행할 수 있습니다. 즉 범위 기반 for 반복문은 begin() 위치부터 시작하여 증 가 연산자(+)를 통해 차례대로 원소를 이동하다가 end() 위치에 도달하면 종료합니다.
반복자는 std::array,std::vector,std::map,std::set. std::list처럼 반복 가능한 모든STL컨테이너에 대해 사용할 수 있습니다.
컨테이너 내부의 위치를 나타내는 데 필요한 모든 기능에 대해서도 반복자가 사용됩니다. 예를 들어 특정 위치에 원소를 삽입하거나, 특정 위치 또는 범위에 있는 원소를 삭제하는 등의 작업에서도 반복자가 사용됩니다. 반복자를 사용함으로써 소스 코드의 재사용성, 유지 보수. 가독성 측면 에서 이점을 얻을 수 있습니다
array::begin() 함수는 첫 번째 원소를 가리키는 반복자를 반환하고, array::end() 함수는 마지 막 원소 다음을 가리키는 반복자를 반환합니다. 따라서 범위 기반 반복문은 다음과 같이 바꿔서 작성할 수 있습니다.
for (auto it = arr.begin(); it != arr.end(); it++)
{
auto element = (*it);
std::cout << element <<'';
}
const_iterator 또는 reverse_iterator 같은 형태의 반복자도 사용할 수 있습니다.
const_ iterator 반복자는 일반 반복자의 const 버전입니다. const로 선언된 배열에 대해 begin() 또는 end() 같은 함수를 사용하면 const_iterator를 반환합니다.
reverse_iterator를 사용하면 배열을 역방향으로 이동할수 있습니다. 이 반복자를 같은 증가 연산자와 함께 사용할 경우, 일반 반복자와 반대 방향으로 이동하게 됩니다
front() , back(), data()함수를 이용해 접근할 수도 있습니다.
data()는 배열 객체 내부에서 실제 데이터 메모리 버퍼를 가리키는 포인터를 반환합니다. 반환된 포인터를 이용하여 다양한 포인터 연산을 수행할 수 있습니다. 이 기능은 포인터를 함수의 인자로 받는 예전 스타일의 함수를 사용할 때 유용합니다.
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << arr.front() << std::endl; // 1 출력
std::cout < arr.back() << std::endl; // 5 출력
std::cout << *(arr.data() + 1) << std::endl; //2 출력
std::array는 깊은 비교(deep comparison)를 위한 관계 연산자(relational operator)와 깊은 복사 를 위한 복사 할당 연산자(copy-assignment operator)도 지원합니다. std::array에 저장되는 데이터 타입에서 크기 비교(<, >, <=, >> == =)를 지원할 경우, 이들 관계 연산자를 이용하여 두 std::array 배열을 비교하는 용도로 사용할 수도 있습니다.
C 스타일 배열에 대해서도 관계 연산자를 사용할 수 있지만, 이 경우에는 배열 원소 값을 비교하는 것이 아니라 포인터 주소 값을 비교합니다.
즉, 깊은 비교 대신 얕은 비교(shallow comparison) 를 수행하기 때문에 실용적이지 않습니다.
할당(assignment)에 대해서도 C 스타일 배열은 메모리 를 새로 생성하여 값을 복사하지 않으며, 단순히 같은 배열 데이터를 가리키는 새로운 포인터를 생성할 뿐입니다.
단, std::array에서 관계 연산자를 사용할 경우 두 배열의 크기가 같아야합니다.
'C++ > STL' 카테고리의 다른 글
[C++] 쌍으로 저장하기(std::pair) (0) | 2022.08.11 |
---|---|
[C++] STL의 반복자(iterator) (0) | 2022.08.07 |
[C++] 아주 기본적인 연결 리스트(std::forward_list) (0) | 2022.08.05 |
[C++] 메모리 복사(memcpy와 std::copy) (0) | 2022.07.21 |
[C++] 배열의 진화형, 벡터(std::vector) (0) | 2022.07.21 |