카테고리 없음

[1. 리스트, 스택, 큐] C++ std::array

꿀꺽람 2023. 1. 13. 16:45
반응형
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;
}

 

반응형