안녕하세요. 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<int, 3> arr1 = { 91, 92, 93 }; array<int, 0> arr2; array<int, 1> arr3; array<int, 2> 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<int, 3> arr1 = { 91, 92, 93 }; //test1 : iterator cout << " 1. iterator : "; array<int, 3>::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<string, 3> 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<string, 3> 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<int, 5> arr1 = { 76, 6, 3, 1, 42}; //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 container의 front(), 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가 무엇인지 궁금하다면 [바로가기])
감사합니다.
'<개인공부> > [C++]' 카테고리의 다른 글
[C++] trunc 버림 함수에 대해서 (0) | 2019.03.19 |
---|---|
[C++] round 반올림 함수에 대해서. (1) | 2019.03.19 |
[C++] to_string 함수에 대해서 (int to string) (4) | 2019.03.17 |
[C++] stoi, stof, stol, stod 함수에 대해서 (string to int) (4) | 2019.03.16 |
[C++] new, delete 동적할당과 해제에 대해서 (0) | 2018.12.04 |
[C++] 가상함수와 순수가상함수의 차이(virtual, pure virtual)에 대해서 (8) | 2018.08.13 |
[C++] dynamic_cast (타입캐스트 연산자) (1) | 2018.07.25 |
[C++] reinterpret_cast (타입캐스트 연산자) (5) | 2017.11.29 |