dev-miri
1-2. std::array 본문
std::array
std::array는 메모리를 자동으로 할당하고 해제한다.
std::array는 원소의 타입과 배열 크기를 매개변수로 사용하는 클래스 템플릿입니다.
다음은 크기가 10인 int 타입의 std::array 배열을 선언한 후, 원소 값을 설정하는 예제 코드이다.
//크기가 10인 int 타입 배열 선언
std::array<int, 10> arr1;
//첫 번째 원소를 1로 설정
arr1[0] = 1;
//arr2의 모든 원소 : [1, 2, 3, 4]
std::array<int, 4> arr2 = {1, 2, 3, 4};
std::array는 C 스타일 배열과 똑같은 방식으로 배열 원소에 접근할 수 있는 [ ] 연산자를 제공한다.
[ ] 연산자에 접근하고자 하는 배열 원소 인덱스(index)를 지정할 경우, 빠른 동작을 위해 전달된 인덱스 값이 배열의 크기보다 작은지를 검사하지는 않는다.
대신 std::array는 at(index)형식의 함수도 함께 제공하며, 이 함수를 인자로 전달된 index 값이 유효하지 않으면
std::out_of_range 예외(exception)를 발생시킨다.
그러므로 at( )함수가 [ ] 연산자보다 조금 느린 편이지만, at( ) 함수를 잘이용하면 예외를 적절하게 처리할 수 있다.
배열 인덱스를 사용자 입력으로 받는다든가, 또는 다른 이유로 인해 유효하지 않은 인덱스를 접근할 수 있는 상황이라면 다음과 같은 예외 처리 코드를 만들 수 있다.
std::array<int, 4> arr3 = {1, 2, 3, 4};
try
{
std::cout<< arr3.at(3) << std::endl; //에러 아님
std::cout<< arr3.at(4) << std::endl; //std::out_of_range 예외 발생
}
catch (const std::out_of_range& ex)
{
std::cerr << ex.what() <<std::endl;
}
std::array 객체를 다른 함수에 전달하는 방식은 기본 데이터 타입을 전달하는 것과 유사하다.
값 또는 참조(reference)로 전달할 수 있고, const를 함께 사용할 수도 있다.
C 스타일 배열을 함수에 전달할 때처럼 포인터 연산을 사용한다거나 참조 또는 역참조(de-reference) 연산을 하지 않아도 된다.
그러므로 다차원 배열을 전달하는 경우에도 std::array를 사용하는 것이 가독성이 훨씬 좋다.
다음은 사용자 정의 함수 print()에 std::array 배열을 값으로 전달하는 예제 코드이다.
void print(std::array<int, 5> arr)
{
for(auto ele : arr)
std::cout << ele << ", ";
}
std::array<int, 5> arr = {1, 2, 3, 4, 5};
print(arr);
//코드 출력 : 1, 2, 3, 4, 5
앞 예제에서는 print() 함수의 매개변수 데이터 타입에 전달받을 배열 크기가 고정되어 있기 때문에 다른 크기의 배열을 전달할 수 없다.
만약 다양한 크기의 std::array 객체에 대해 동작하는 범용적인 배열 출력 함수를 만들고 싶다면 print()를 함수 템플릿으로 선언하고, 배열 크기를 템플릿 매개변수로 전달하면 된다.
즉, print()함수를 다음과 같은 형태로 작성한다.
template <size_t N>
void print(const std::array<int, N>& arr);
**템플릿이란?
함수나 클래스를 개별적으로 다시 작성하지 않아도, 여러 자료형으로 사용할 수 있도록하게 만들어놓은 틀
**함수 템플릿이란?
함수를 만드는 도구를 의미한다. 함수를 만드는 도구는 무엇으로 이뤄져서 만들지는 정하지는 않는다. 그래서 함수 템플릿이 만들어 내는 함수의 자료형도 결정되어 있지 않는다.
template <typename T>
T Adder(T n1, T n2)
{
return n1+n2;
}
위의 함수의 기능은 덧셈이고, 대상 자료형(파라미터와 리턴 값)은 결정되어 있지 않다.
여기서 T는 자료형을 결정짓지 않겠다는 의미로 사용한 것이기에, 우리는 컴파일러가 알기 위해서 상단에 template <typename T>를 붙여야 한다.
함수에 std::array 객체를 전달할 경우, 기본적으로 새로운 배열에 모든 원소가 복사된다. 즉, 자동으로 깊은 복사가 동작한다.
만약 이러한 동작을 피하고 싶다면 참조 또는 const 참조를 사용할 수 있다. 즉, 프로그래머의 선택에 따라 동작을 결정할 수 있다.
**참조형 변수?
C++에서 지원하는 변수 타입이다. 참조형은 다른 객체 또는 값의 별칭으로 사용되는 C++타입이다.
C++은 세 가지 종류의 참조형을 지원한다.
- non-const 값 참조형
- const 값 참조형
- r-value 참조형
-references to non-const values
non-const 값에 대한 참조형은 자료형 뒤에 앰퍼샌드(&)를 사용하여 선언한다
int value = 5; //normal integer
int& ref = value; //reference to variable value
위 코드에서 & 는 주소(address)를 의미하지 않고 참조(reference)를 위미한다.
-references as aliases
참조형은 참조하는 값과 동일하게 작동한다. 이런 의미에서 참조형은 참조되는 객체의 별칭으로 사용된다
#include <iostream>
int main()
{
int value = 5; //normal integer
int& ref = value; //reference to variable value
value = 6; //value is now 6
ref = 7; //value is now 7
std::cout << value; //prints 7
++ref;
std::cout << value; //prints 8
return 0;
}
위 예제에서 ref와 value는 동의어로 취급된다
참조형은 선언과 동시에 반드시 초기화해야 하고, null 값을 저장할 수 있는 포인터와 다르게, null 참조 같은 것은 없다.
non-const 값에 대한 참조는 non-const 값으로만 초기화할 수 있다. const 값 또는 r-value로 초기화할 수 없다.
초기화된 후에는 다른 변수를 참조하도록 변경할 수 없다.
참조형은 함수 매개 변수로 가장 많이 사용된다. 이때 매개 변수는 인수의 별칭으로 사용되며, 복사본이 만들어지지 않는다.
이렇게 하면 복사하는데 비용이 많이드는 경우 성능이 향상될 수 있다.
함수에 포인터 인수를 전달하면 함수 안에서 포인터를 역참조하여 인수의 값을 직접 수정할 수 있었다. 이런 점에서 참조형은 유사하게 작동한다.
배열의 원소를 차례대로 접근하는 연산은 매우 자주 발생한다.
std::array는 반복자(iterator)와 범위 기반 for 문법을 이용하여 원소에 차례대로 접근할 수 있다.
std::array<int, 5> arr = {1, 2, 3, 4, 5};
for(auto element : arr)
{
std::cout<<element<< ' ';
}
범위 기반 for 반복문을 사용하여 std::array의 모든 원소에 접근할 수 있는 것은 반복자를 사용하기 때문이다.
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를 사용하면 배열을 역방향으로 이동할 수 있다. 이 반복자를 ++같은 증가 연산자와 함께 사용할 경우, 일반 반복자와 반대 방향으로 이동하게 된다.
[ ]연산자와 at() 함수 외에 std::array에서 원소 접근을 위해 사용할 수 있는 멤버 함수를 정리한 것이다.
<std::array의 원소 접근 함수>
| 함수 | 설명 |
| front() | 배열의 첫 번째 원소에 대한 참조를 반환 |
| back() | 배열의 마지막 원소에 대한 참조를 반환 |
| 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스타일 배열은 메모리를 새로 생성하여 값을 복사하지 않으며, 단순히 같은 배열 데이터를 가리키는 새로운 포인터를 생성할 뿐이다.
'CSE > Algorithm(c++)' 카테고리의 다른 글
| 1-6. std::deque (0) | 2023.07.24 |
|---|---|
| 1-5. 반복자, std::list (0) | 2023.07.21 |
| 1-4. std::forward_list (0) | 2023.07.21 |
| 1-3. std::vector (0) | 2023.07.21 |
| 1-1. 연속된 자료 구조와 연결된 자료구조 (0) | 2023.07.20 |