<개인공부>/[C++]

[C++] array container 정리 및 사용법 (std::array)

BlockDMask 2019. 3. 15. 02:52
반응형

안녕하세요. BlockDMask 입니다.

<목차>

0. what is array container?

1. array container 헤더파일

2. array container 생성자

3. array container 멤버 함수

4. array container 예제 1 - size, empty, max_size, sizeof (사이즈에 대해서)

5. array container 예제 2 - begin, end, data, at, operator[] (순회와 접근)

6. array container 예제 3 - front, back, fill, swap (인자들의 값 변경, 채우기)

7.  array container 예제 4 - auto, range based for, sort (범위기반 for문으로 순회, 정렬)

8. C/C++ 기존 배열 vs C++11 array container의 비교

오늘은 C++11에 나온 array container에 대해서 알아볼 것 입니다.

아니! C++에 이미 배열이 있는데 C++11에서 갑자기 std::array가 나왔다고?

뭣하러 STL 에서 std::array container를 만들어서 포함시킨것인지. 한번 알아보겠습니다.

우리가 알고있는 배열과 std::array container가 무엇이 다른것인가를 비교해보면서 작성해 보겠습니다.

예제 정말 정성스럽게 작성했습니다. 각 예제의 해설도 도움이 될 것입니다.


0. what is array container?


고정길이의 배열을 표현할때 사용 하는 C++11에 추가된 array container 입니다. (std::array)

기존 배열과 마찬가지로 stack에 저장이 됩니다.

다른 container (vector, deque ... )들과 마찬가지로 여러 유용한 멤버 함수를 사용할 수 있습니다.

기존의 C 스타일, C++ 11이전의 배열에서의 불편한 점들이 array container가 생겨나면서 많이 해결이 되었습니다. (경험담)

(기존에 있던 배열이 일반 커피라면 std::array 는 TOP 입니다.)


1. array container 헤더파일


C++11 에서 부터 사용가능하다는 점 알려드립니다.

- 헤더파일 : <array>


2. array container 생성자


▼ 먼저 템플릿 선언 부분을 보겠습니다.


- template < typename T, size_t N > class array;


: 첫번째 T는 데이터 타입, 클래스 등 배열로 관리할 타입이 들어옵니다. 

: 두번째 N은 인자의 개수를 말합니다. 위에서 말씀드렸듯이 사이즈가 고정된 배열을 표시하기 때문에 생성할때 사이즈까지 넣어서 생성해야 합니다.

(template 공부가 필요할 것 같으시면 [바로가기] 이쪽에서 공부하고 돌아오셔도 됩니다.)


▼ 선언 및 초기화 예시

using namespace std; 사용했다고 치겠습니다.


ex1) array<int, 3> arr1 = {1, 2, 3};  //생성과 동시에 초기화

ex2) array<double, 6> arr2;  //초기화 하지 않았으므로, 쓰레기값 6개가 들어가 있습니다.

ex3) array<int, 10> arr3 = {0};  //10개의 배열 값들이 다 0으로 초기화 됩니다.

ex4) array<int, 10> arr4 = {2};  //첫번째 배열 값만 2로 초기화 되고 남은 9개는 0으로 초기화 됩니다.

ex5) array<int, 4> array5;

array5 = {1, 2, 3, 4};  //생성 후에 초기화 가능합니다만, 복사가 되는 것이라서 이왕이면 생성과 동시에 초기화 하는것이 성능향상에 좋습니다.


3. array container 멤버 함수


▼멤버 함수들을 보겠습니다.

array<int, 10> arr = {1,2,3,4,5,6,7,8,9,10};으로 arr 변수 명을 사용했다고 가정하겠습니다.


함수 사용법

함수 설명

함수 원형

 arr.begin()

 배열의 맨 첫번째 원소를 가리킵니다. (iterator와 사용)

 iterator begin() noexcept;

 arr.end()

 배열의 맨 마지막 "다음" 원소를 가리킵니다. (with iterator)

 iterator end() noexcept;

 arr.rbegin()

 배열을 거꾸로 했을때 첫번째 원소를 가리킵니다. (with iterator)

 reverse_iterator rbegin() noexcept;

 arr.rend()

 배열을 거꾸로 했을때 마지막의 "다음" 원소를 가리킵니다. (with iterator)

 reverse_iterator rend() noexcept;

 arr.cbegin(), cend()

 위쪽 begin, end와 같지만 const가 붙어서 iterator를 이용해서 원소를 수정할 수 없습니다.

 const_iterator --() noexcept;

 arr.crbegin(), crend()

 위와 동일합니다.

 const_reverse_iterator --() nocept;

 arr.front()

 배열의 맨 앞의 원소를 반환합니다.

 reference front();

 arr.back()

 배열의 맨 뒤의 원소를 반환합니다. 

 reference back();

 arr.data()

 배열을 포인터 타입으로 반환합니다. 
 (배열의 첫번째 주소를 반환)

 value_type* data() noexcept;

 arr.fill(val)

 배열의 인자를 val으로 다 바꿔줍니다.

 void fill(const value_type& val);

 arr.swap(arr2)

 arr2의 배열 인자와 arr의 인자들은 스왑 합니다. 
 (당연히 길이와 타입이 같아야합니다.)

 void swap(array& arr) noexcept....;

 arr.at(N)  N 번째 인자를 반환 합니다.

 reference operator ar(size_type n);

 arr[N]

 N 번쨰 인자를 반환 합니다.

 reference operator[](size_type n);
 arr.empty() 

 비어있는지 확인합니다.

 constexpr bool empty() noexcept;

 arr.max_size() 

 배열의 최대 사이즈를 반환합니다. (size와 같음)

 constexpr size_type max_size() noexcept;

 arr.size() 

 배열의 사이즈를 반환합니다. (max_size와 같음)

 constexpr size_type size() noexcept;


4. array container 예제1 - size, empty, max_size, sizeof


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
31
32
33
34
35
36
37
38
39
40
41
42
43
//C++11 Array Container Example.
//BlockDMask.
#include<iostream>
#include<array>
using namespace std;
 
int main(void)
{
    array<int3> arr1 = { 919293 };
    array<int0> arr2;
    array<int1> arr3;
    array<int2> arr4;
    
    //texs1 : empty
    cout << "1. empty" << endl;
    cout << "arr1.empty() : " << arr1.empty() << endl;
    cout << "arr2.empty() : " << arr2.empty() << endl;
    cout << "arr3.empty() : " << arr3.empty() << endl;
    cout << "arr4.empty() : " << arr4.empty() << endl;
    cout << endl;
 
    //test2 : size, max_size, sizeof
    cout << "2. size, max_size,  sizeof" << endl;
    cout << "arr1.size() : " << arr1.size();
    cout << " || max_size() : " << arr1.max_size();
    cout << " || sizeof() : " << sizeof(arr1) << endl;
 
    cout << "arr2.size() : " << arr2.size();
    cout << " || max_size() : " << arr2.max_size();
    cout << " || sizeof() : " << sizeof(arr2) << endl;
 
    cout << "arr3.size() : " << arr3.size();
    cout << " || max_size() : " << arr3.max_size();
    cout << " || sizeof() : " << sizeof(arr3) << endl;
 
    cout << "arr4.size() : " << arr4.size();
    cout << " || max_size() : " << arr4.max_size();
    cout << " || sizeof() : " << sizeof(arr4) << endl;
 
    cout << endl;
    system("pause");
    return 0;
}
cs


 예제 1 결과 화면

예제 1번의 결과화면을 보실때  군데를 주의깊게 보셔야 합니다.

첫번째. 배열이 empty() 라는 것은 배열의 사이즈가 0 인 것을 empty라고 한다. 배열이 비어있으면 empty()는 true (=1)을 뱉어낸다.

두번째. size(), max_size() 는 같다.

세번째. size(), max_size()는 배열의 길이(=인덱스 개수)을 말하지만, sizeof() 는 바이트 단위로 내보냅니다. (데이터 타입 크기 * 개수)

네번째, arr2는 왜 sizeof가 4일까? 당연하게도. 

 (위에서부터 순서대로 &arr1, &arr2, &arr3 입니다.)

&arr1, &arr2 를 이용해서 주소를 보면, arr1의 크기만큰 딱 12차이가 나는곳에 arr2의 주소가 위치해 있고, arr3은 arr2보다 4 큰 곳에 위치하고 있었습니다. 그러므로 arr2는 메모리를 잡고 있다는 것을 알 수 있습니다.

이것은 우리가 int a; 선언만 해놓는다 하더라도 메모리를 잡아 먹듯이, array<int, 0> 으로 선언만 해도 메모리가 잡히게 됩니다.


5. array container 예제2 - begin, end 순회, data, at, operator[]


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
31
32
33
34
35
36
37
38
39
40
//C++11 Array Container Example.
//BlockDMask.
#include<iostream>
#include<array>
using namespace std;
 
int main(void)
{
    array<int3> arr1 = { 919293 };
    
    //test1 : iterator
    cout << " 1. iterator : ";
    array<int3>::iterator iter;
    for (iter = arr1.begin(); iter != arr1.end(); ++iter)
    {
        cout << *iter << " " ;
    }
    cout << endl << endl;
 
    //test2 : data
    cout << " 2. data()" << endl;
    cout << " arr1.data() : " << arr1.data() << endl;
    cout << " *(arr1.data())     : " << *(arr1.data()) << endl;
    cout << " *(arr1.data() + 1) : " << *(arr1.data() + 1<< endl;
    cout << " *(arr1.data() + 2) : " << *(arr1.data() + 2<< endl;
              
    cout << endl;
    //test3 : at, operator[]
    cout << " 3. at(n), operator[n]" << endl;
    cout << " arr.at(0) : " << arr1.at(0<< endl;
    cout << " arr.at(1) : " << arr1.at(1<< endl;
    cout << " arr.at(2) : " << arr1.at(2<< endl;
    cout << " arr[0] : " << arr1[0<< endl;
    cout << " arr[1] : " << arr1[1<< endl;
    cout << " arr[2] : " << arr1[2<< endl;
 
    cout << endl;
    system("pause");
    return 0;
}
cs


예제2의 결과화면

이 결과화면으로 설명드릴것은 네가지 입니다.

첫번째. iterator로 가지고올때는 <타입, 개수>까지 일치 시켜줘야 합니다.

두번째. iterator로 가지고왔을때 배열의 원소 접근은 *iter 으로 합니다.

세번째. data()는 원소의 주소를 나타내므로 *(arr.data()) 로 원소를 출력하면 됩니다. 하지만 at(N), operator[N] 이거 두개가 있으니 원소를 가지고 올때는 at과 []을 사용하는걸 추천합니다.

네번째. at(n), operator[n]으로 배열의 원소가 아주아주 잘 출력 되는걸 볼 수 있습니다.


6. array container 예제3 - front, back, fill, swap, 대체


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
31
32
//C++11 Array Container Example.
//BlockDMask.
#include<iostream>
#include<array>
#include<string>    //string 다루기 위해 추가
using namespace std;
 
int main(void)
{
    array<string3> arr1 = {"blockdmask""C++""coding" };
 
    //1. front, back
    cout << " 1. front, back" << endl;
    cout << " arr1.front() : " << arr1.front() << endl;
    cout << " arr1.back() : " << arr1.back() << endl;
    cout << endl;
    
    //2. fill, swap
    cout << " 2. fill, swap" << endl;
    cout << " arr1 default : " << arr1[0<< ", " << arr1[1<< ", " << arr1[2<< endl;
    
    arr1.fill("array example");
    cout << " arr1 fill() : " << arr1[0<< ", " << arr1[1<< ", " << arr1[2<< endl;
 
    array<string3> arr2 = { "tmp1""tmp2""tmp3" };
    arr1.swap(arr2);
    cout << " arr1 swap() : " << arr1[0<< ", " << arr1[1<< ", " << arr1[2<< endl;
 
    cout << endl;
    system("pause");
    return 0;
}
cs


 예제3의 결과화면

이번 결과 화면은 명확하게 알 수 있을거라 생각이 됩니다. 질문이 있으면 댓글 남겨주세요.


7. array container 예제4 - auto, range based for (범위기반 for문), sort (정렬)


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
31
32
33
34
35
36
37
38
39
//C++11 Array Container Example.
//BlockDMask.
#include<iostream>
#include<array>
#include<algorithm> //sort 
using namespace std;
 
int main(void)
{
    array<int5> arr1 = { 7663142};
 
    //1. auto
    cout << " 1. auto            : ";
    for (auto iter = arr1.begin(); iter != arr1.end(); ++iter)
    {
        cout << *iter << " ";
    }
    cout << endl << endl;
 
    //2. range based for
    cout << " 2. range based for : ";
    for (auto elem : arr1)
    {
        cout << elem << " ";
    }
    cout << endl << endl;
    
    //3. sort
    cout << " 3. sort            : ";
    sort(arr1.begin(), arr1.end());
    for (auto elem : arr1)
    {
        cout << elem << " ";
    }
 
    cout << endl << endl;
    system("pause");
    return 0;
}
cs


 예제4의 결과화면

이번 결과화면을 통해 보여드리고 싶었던 것은.

1) iterator 쓸때 auto로 쓰면 된다는 것과

2) 범위기반 for문을 이용해서 순회 할 수 있다는것과

3) 알고리즘의 std::sort 함수를 이용해서 배열을 정렬 할 수 있다는 것을 보여드리고 싶었습니다.

(C++11 의 범위기반 for문 (range based for)은 다음에 포스팅 하겠습니다.)


8. C/C++ 기존 배열 vs C++11 array container의 비교


 array containerfront(), back()를 사용한다는 것.

과거의 C/C++ 배열을 이용해서 맨 첫번째 인자 arr[0], 맨 끝번째 인자 arr[MAX - 1] 을 이용해서 접근을 했었습니다.

첫번째 인자는 그렇다 쳐도, 맨 끝번째 인자에서 #define MAX 0 으로 들어가거나 다른 방법을 통해서 버그가 발생할 확률이 존재했습니다. 하지만 멤버변수 front(), back()을 이용함으로써 버그와 한발짝 멀어지게 되었습니다.


 배열 원소를 바꿔줄때는 순회 대신 fill

과거의 C/C++ 배열은 원소를 바꾸어 주려면 반복문을 통해서 일일히 바꾸어 주어야 했습니다.

하지만 이제는 fill() 멤버 함수를 통해서 동일한 인자로 한번에 다 바꾸어 줄 수 있습니다.

물론 모두 다른 인자로 바꾸어 주는것은 여전히 반복문을 통해야 합니다.


 객체이기 떄문에 사이즈를 알 수 있다.

배열의 사이즈. 여러분은 어떤식으로 저장 하셨었나요. #define MAX_SIZE 10 저처럼 매크로를 이용해서 저장 하셨나요?

이제는 그럴 필요가 없습니다. size() 멤버함수가 있거든요! 하하핫! (개이득)


 여러 유용한 함수들을 사용할 수 있다.

위에서 말한것 이외에도 유용한 멤버 함수들이 존재합니다.


 제가 생각하는 또하나의 이점이 있습니다.

보통 데이터 타입의 순서는 "Data Type + name" 이런순서인데,

기존의 C/C++의 배열은 "data type + name[N]" (=int arr[10]) 이런식이었는데, C언어를 처음 배웠을때 데이터 타입은 변수명의 왼쪽 거 입니다. 이렇게 배웠는데..... 그러면 int arr[10] 의 데이터 타입은 arr의 왼쪽 꺼니까, int 인가요? 아니요! 이것의 데이터 타입은 int[10] 이잖아요. 

아니 왜 배열만 유난히 "데이터타입반쪽 + 변수명 + 데이터타입반쪽" 이었을까요. 그래서 옛날부터 많이 헷갈렸죠.

하지만 이제 array container에서는 array<int, 5> arr1 이런식으로 선언하기 때문에 좀더 보기 편해졌다는 것 입니다.

별거 아닌것 처럼 말했지만, 배열의 데이터 타입을 물어보면 정확하게 대답하지 못하는 사람들이 왕왕 있습니다.


결론1 : 과거의 C/C++ 배열보다 C++11의 array container를 사용하면 더 편리하다.

결론2 : 고정된 사이즈의 배열을 사용할 것이라면 C++ array container 를 사용하고,
이즈가 변하는 배열을 사용해야 한다면 C++ vector container를 사용하자. 

(vector container가 무엇인지 궁금하다면 [바로가기])


감사합니다.

참고 : http://www.cplusplus.com/reference/array/

반응형