음악, 삶, 개발

vector 기초 본문

개발 공부/C++ 약점공략

vector 기초

Lee_____ 2020. 9. 20. 09:35

소개

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 를 return 하는 vector 메소드.
begin() 과 end() 의 위치
rend() 와 rebegin() 의 위치.


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

push_back vs emplace_back


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 사용법 및 설명(장&단점)

[STL] vector 벡터 정리 및 예제

std::vector 초기화 하기

[C++] vector container 정리 및 사용법

[프로그래머스] 체육복, 벡터 삭제시 주의점 (iterator, erase)

C++ STL 벡터(vector) 의 사용법

c++닷컴 : std::vector

vector에서 reserve 와 resize를 하는 이유

[C++] emplace 함수

 

 

Stackoverflow : Removing item from vector, while in C++11 range 'for' loop?

Stackoverflow : how to erase from vector in range-based loop?