음악, 삶, 개발
vector 기초 본문
소개
vector 는 C++ 에서 가장 우선적으로 사용해야할 필수적인 컨테이너다.
나는 vector 를 배우기전, vector 를 사용하는것은 JS 의 array 와 비슷할거라 생각했지만,
불행히도 완전히까지는 아니지만 매우 다른 사용법을 필요로한다.
이 C++ 약점공략부분은 원래는 나의 약점에만 집중하지만(예를 들어, vector 의 push_back 에 대해서만 다룬다던지)
나는 vector 의 전체적인 것들을 처음부터 정리하고자한다.
vector 의 초기화
std::vector<int> v1 {1, 2, 3}; // v1 : {1, 2, 3} (리터럴)
std::vector<int> v2 (3, 6); // v2 : {6, 6, 6} (3개의 6을 만들라)
std::vector<int> v3 (2); // v3 : {0, 0} (2개의 int 초기값을 만들라)
std::vector<int> v4 (v3); // v4 : {0, 0} (v3 을 복사 - 자기자신 넣으면 안됨!)
std::vector<int> v5 = v3; // v5 : {0, 0} (v3 을 복사 - 자기자신 넣으면 안됨!)
std::vector<int> v6 (std::move(v5)); // v6 : {0, 0} (C++11 : v5 을 v6 로 옮겨, v5 는 empty - 자기자신 넣으면 안됨!)
std::vector<int> v7 = std::move(v6); // v7 : {0, 0} (C++11 : v6 을 v7 로 옮겨, v6 는 empty - 자기자신 넣으면 안됨!)
std::vector<int> v8 {1, 2, 3, 4, 5, 6, 7, 8 }; // v8 : {1, 2, 3, 4, 5, 6, 7} (v1 과 같은 방식)
std::vector<int> v9 (v8.begin(), v8.end()); // v9 : {1, 2, 3, 4, 5, 6, 7} (iterator 를 통한 복사 - 주의 one-past-last 까지 해야함)
std::vector<int> v10 (v8.begin(), v8.begin() + 3); // v10 : {1, 2, 3} (iterator 를 통한 복사 - 주의 one-past-last 까지 해야함)
std::vector<int> v11 (v8.begin() + 2, v8.begin() + 4); // v11 : {3, 4} (iterator 를 통한 복사 - 주의 one-past-last 까지 해야함)
std::vector<int>::const_iterator first {v8.begin() + 3};
std::vector<int>::const_iterator last {v8.begin() + 7};
std::vector<int> v12 (first, last); // v12 : {4, 5, 6, 7} (iterator 를 통한 복사 - 주의 one-past-last 까지 해야함)
std::vector<int> v13;
v13.assign(4, 100); // v13 {100, 100, 100, 100}
std::vector<int> v14;
v14.assign(v13.begin(), v13.begin() + 3); // v14 : {100, 100, 100, 100}
std::vector<int> v15(std::make_move_iterator(v8.begin()), std::make_move_iterator(v8.end())); // v8 : {1, 2, 3, 4, 5, 6, 7}
여기서 가장 기억해야할 중요한 점은,
iterator 를 통한 초기화, 즉 복사할때는 반드시 원하는 마지막 요소에서 + 1 을 해줘야한다. (제발!!!!!!!!)
begin() 은 가장 처음 요소를 가리키는 반면,
end() 가 return 하는 iterator 는 마지막 요소의 그 다음 (아무요소도 없는 곳) 을 가리킨다.
(왜 그런지, 더는 이해하려하지말고, 복사의 시작지점, 복사의 끝지점 + 1 로 외우자)
iterator
iterator 의 종류
iterator : 요소 수정 가능. begin(), end() 가 return 함.
reverse_iterator : 요소 수정 가능 rbegin(), rend() 인 경우 return 함.
const_iterator : 요소 수정 불가능. cbegin(), cend(), const vector 인경우 return 함.
const_reverse_iterator : 요소 수정 불가. crbegin(), crend() 인 경우 return 함.
std::vector<int> v1 {1, 2, 3, 4};
auto it1 {v1.begin()}; // iterator
auto it2 {v1.cbegin()}; // const_iterator
(*it1) = 4; // ok : v1[0] is now 4.
(*it2) = 9; // error : const_iterator can't modify an element.
const std::vector<int> v2 {1, 2, 3, 4};
auto it3 {v2.begin()}; // const_iterator
auto it4 {v2.cbegin()}; // const_iterator
요소에 접근하기
std::vector<int> v {99, 2, 3, 4};
int n1 = v.at(0); // 99
int n2 = v[0]; // 99
int n3 = v.front(); // 99
int n4 = v.back(); // 4
auto p = v.data(); // v 에 대한 pointer 를 return.
int n5 = *(p); // 99
int n6 = *(p + 1); // 2
Capacity (능력)
empty() : vector 가 비어있으면 true, 아니면 false 를 return.
size() : vector 안에 요소의 갯수를 size_t 로 return.
max_size() : vector 가 가질수있는 요소들의 최대 갯수를 size_t 로 return.
reserve(n) : n 개의 요소를 가질 vector 의 공간을 예약한다.
capacity() : vector 가 현재 할당한 요소의 갯수를 size_t 로 return. capacty() 는 size() 보다 큰데, 보통 2배씩 늘어남.
shrink_to_fit() : C+11 사용하지않는 capacity 를 제거해준다.
std::vector<int> v (100);
size_t s1 = v.size(); // 100
size_t c1 = v.capacity(); // 100
v.clear(); // 모든 요소 제거.
size_t s2 = v.size(); // 0
size_t c2 = v.capacity(); // 100 <- clear() 해도 변하지않음.
v.shrink_to_fit();
size_t c3 = v.capacity(); // 0 <- shrink_to_fit() 후, 사용하지않는 capacity 는 제거.
나는 reserve() 가 왜 필요한지에 대해 의문이 생겼다.
vector 는 스스로 성장하지않는가?
스스로 성장한다 = 즉 스스로, resize 한다.
인데, 이 비용이 꽤나 비쌀수있다고 한다.
이에 대한 대답으로 stackoverflow 의 링크를 첨부한다.
Benefits of using reserve() in a vector
Why is it useful to use std::vector::reserve?
하지만 내 스스로 직접적인 성능저하를 경험하기전까지는 reserve() 를 사용하지않으려한다.
Modifier (수정자)
clear() : vector 의 모든 요소를 제거하고, clear() 이후에 size() 는 0 이다. 하지만 capacity() 는 바뀌지않는다.
insert() : vector 에 원하는 위치에 요소를 추가. (constructor, copy, move, destructor 호출)
erase() : vector 에 요소를 삭제.
push_back() : vector 에 끝에 요소를 추가. (constructor, copy, move, destructor 호출)
pop_back() : vector 에 끝에 요소를 제거.
emplace() : C+11 vector 에 원하는 위치에 요소를 추가. (constructor, destructor 호출)
emplace_back() : C+11 (constructor, destructor 호출)
resize() :
swap() : 다른 vector 와 요소를 교환한다.
insert 와 emplace 와의 차이, push_back 과 emplace_back 의 차이를 유념해야한다.
아래 stackoverflow 링크들을 첨부한다.
또한 class 에 대한 포스트에도 이와 관련 내용이 있으니 꼭 읽도록 한다.
C++ std::vector emplace vs insert
vector 와 for loop
vector 와 루프의 결합은 우리가 뭘 하려는지에 따라 다르다.
"뭘 하려는지" 를 구체화해보면 아래와 같다.
1. 읽기 또는 쓰기
2. 추가
3. 삭제
4. 검색
1. vector 안에 요소를 읽거나, 요소를 수정하려할때.
struct Point {
int x {4};
int y {0};
};
int main () {
/* 클래스를 가진 vector 루프 */
std::vector<Point> v(10);
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << it->x << std::endl; // -> 연산자 사용. (포인터처럼 사용하면됨)
}
for (auto& element : v) {
std::cout << element.y << std::endl;
}
/* 내장형 객체를 가진 vector 루프 */
std::vector<int> v2 {3, 9, 3, 2};
for (auto it = v2.begin(); it != v2.end(); ++it) {
std::cout << *it << std::endl; // 역참조
}
for (auto& element : v2) {
std::cout << element << std::endl;
}
}
2. vector 안에 요소를 추가하려할때
위의 1번 경우와 달리, 요소의 추가시에는 iterator 를 사용할수없다. (reserve() 를 사용하면 가능하다는데, 추후 확인)
range-based for 루프도 결국 iterator 기반이므로 역시 사용할수없다.
vector 가 resize 되버리면 현재 iterator 들은 모두 무효화가 되버리기때문.
따라서 index 를 사용해야한다.
std::vector<int> v;
for (int i = 0; i < 10; ++i) {
v.emplace_back(std::move(i)); // 흠..std::move(i) 쓰는게 맞나? 일단 감으로..추후 확인.
}
std::cout << v.size() << std::endl; // 5 출력.
관련 링크 : 생각보다 관련링크가 별로 없었다..루프에서 요소를 추가하는것은 좋은 생각이 아닌것인가...
Adding an element to a Vector while iterating over it
C++: push_back in std::vector while iterating it
3. vector 안에 요소를 삭제하려할때 : erase-remove idiom 사용
for 루프를 사용할수없다! 이유는? 추후확인.
이때 <algorithm> 이 우리는 필요하다.
<algorithm> 안에는 std::remove_if 같은 함수들이 구현되어있다. (깊게 공부 필요)
bool isFive(int x) {
if (x == 5) return true;
return false;
}
int main () {
std::vector<int> v = { 0,1,5,3,4,5,6,5,8,9 };
v.erase(std::remove(v.begin(), v.end(), 5), v.end()); // 값이 5인 모든 요소 제거.
v.erase(std::remove_if(v.begin(), v.end(), isFive), v.end()); // isFive 가 true 인 모든 요소 제거.
}
관련 링크
How to remove vector elements in a loop?
vector 와 <algorithm> 콜라보.
약점공략
- 나와 같이 for 루프로 vector 의 요소를 어떻게 지우나요? 라는 질문이 많은데, 대답은 "for 루프로 지우지말라" 였다.
- for 루프 는 vector 의 요소에 접근하기위해 사용하는것이지, 요소를 삭제할때 사용하지말라. (이유! : 삭제중 iterator 의 모효화)
- range-based for 루프 또한, 일반 for 루프의 문법적 설탕일뿐. 따라서 위처럼 요소의 삭제에 사용하면 안됨.
참고자료
cppreference.com : std::vector
[C++] vector container 정리 및 사용법
[프로그래머스] 체육복, 벡터 삭제시 주의점 (iterator, erase)
vector에서 reserve 와 resize를 하는 이유
Stackoverflow : Removing item from vector, while in C++11 range 'for' loop?
Stackoverflow : how to erase from vector in range-based loop?