[C++ STL] Vector Container 사용 방법 & 관련 예제 총 정리

    반응형

    *개인적인 공부 내용을 기록하는 용도로 작성한 글 이기에 잘못된 내용을 포함하고 있을 수 있습니다.

     

    #1 About Vector Container

    #2 Vector 사용방법

     - 사전 작업

     - Vector 선언 및 초기화

     - Vector 값 추가 (push_back, insert)

     - Vector 값 삭제 (pop_back, erase)

    - Vector 원소 위치 변경 (iter_swap)

    #3 SIZE & CAPACITY

    - size() , max_size() , capacity() 

    - resize() [size 축소] 

    - reserve()  [사전 메모리 할당] 

    #4 원소 접근

    - []연산자 [범위점검X]

    - at() [범위점검O]


    #1 About Vector Container

    Vector 컨테이너는, C++ 표준 라이브러리[STL]에 정의된 대표적인 시퀀스 컨테이너사용자의 편의를 위해 제공되는 class의 일종이다. 마치 배열과 비슷하여 사용이 쉽기에, 자주 사용되며 가변적으로 크기가 변하는 동적 배열 이라고 생각하면 된다.

    메모리를 효율적으로 관리할 수 있다는 장점을 갖고 있지만, 빈번한 삽입과 삭제가 일어나는 상황에서는 비효율적 이라는 단점 또한 갖고 있다. 속도적인 측면에서 배열[ARRAY]에 비해서 떨어지지만, 메모리를 효율적으로 관리할 수 있어서, 많이 사용된다.

    다음은 vector container의 전체적인 구조를 그림으로 나타낸 것이다.

     

     

    vector는 앞쪽이 막혀져 있는 형태라서 앞쪽에는 원소를 추가 /제거 할수 없으며, 뒤에서 부터 차례로 push_back() , pop_back() 을 이용해 원소를 추가 / 제거 한다.

    중간에도 insert() 를 이용해 삽입할 수 있으나 "배열 기반 컨테이너" 이기에 효율이 매우 떨어진다.

    첫 원소와 마지막 원소를 참조할 수 있는, fornt()/begin()back()/end() 를 갖고있다.

    size()vector가 지닌 원소의 개수를 의미하며, capcity할당된 메모리 값을 의미한다.


    #2 Vector 사용방법

    사전작업

    ⓐ <vector> 헤더파일을 추가한다. #include <vector>

    ⓑ 편의를 위해 std 네임스페이스를 선언한다. using namespace std;

    ⓒ vector의 기본적인 선언 방식은 vector<[datatype]> [name] 이다. int형 vector 선언 → vector<int> v;

     

    Vector 선언 및 초기화

    다음은 Vector의 기본적인 선언 및 생성자를 이용한 초기화 방식이다.

    #include <iostream>
    #include <vector> // vector 사용을 위한 헤더
    using namespace std;
    
    int main()
    {
    	vector<int> v1; // int type 벡터 생성
    	vector<int> v2(5); // 0으로 초기화된 size가 5인 벡터이다.
    	vector<int> v3(5,10); // 지정값 10으로 초기화된 size가 5인 벡터이다.
    	vector<int> v4 = {1,2,3,4,5} // 벡터 생성 후 1,2,3,4,5 의 원소를 초기화한다.
    	vector<int> v5(v4) // v5는 v4의 복사본(벡터) 이다.  
    	
    	return 0;
    } 

     

    Vector 값 추가

    - push_back(value) : 맨 끝에 추가

    #include <iostream>
    #include <vector> // vector 사용을 위한 헤더
    using namespace std;
    
    int main()
    {
    	vector<int> v1; // int type 벡터 생성
    	v1.push_back(10); // 10 추가 
    	v1.push_back(20); // 20 추가 
    	v1.push_back(30); // 30 추가 
    	return 0;
    } 

    push_back(value) 함수를 이용해 벡터의 맨 끝에 차례로 원소(10, 20, 30)를 추가한다.

     

    - insert(삽입위치,value) : 중간 삽입

    #include <iostream>
    #include <vector> // vector 사용을 위한 헤더
    using namespace std;
    
    int main()
    {
    	vector<int> v1; // int type 벡터 생성
    	v1.push_back(10); // 10 추가 
    	v1.push_back(20); // 20 추가 
    	v1.push_back(30); // 30 추가
    	v1.insert(v1.begin(), 100); // 시작 위치에 100을 삽입한다.
    	v1.insert(v1.begin() + 2, 101); // 시작 위치+2에 101을 삽입한다.	
    	return 0;
    } 
    

     

    insert(삽입위치,value) 함수를 이용해 중간에 값을 삽입한다. 삽입한 위치 뒤의 원소는 모두 한 칸 씩 밀려난다. (단, 앞서 말한 바와 같이 벡터에서 중간삽입은 매우 비효율적이며, 중간에 원소를 삽입할 경우 reallocate[메모리 재할당]이 발생할 수 있으니 사용에 주의해야 한다.)

     

     

    Vector 값 삭제

    - pop_back() : 맨 끝 값 삭제

    #include <iostream>
    #include <vector> // vector 사용을 위한 헤더
    using namespace std;
    
    int main()
    {
    	vector<int> v1; // int type 벡터 생성
    	v1.push_back(10); // 10 추가 
    	v1.push_back(20); // 20 추가 
    	v1.push_back(30); // 30 추가
    	v1.push_back(40); // 40 추가
    	v1.push_back(50); // 50 추가
    	v1.pop_back(); // 마지막 원소(50) 삭제
    	return 0;
    } 

    pop_back() 함수를 이용해 마지막 원소(50)을 삭제(pop) 한다.

     

    - erase(삭제위치) : 중간 삭제

    #include <iostream>
    #include <vector> // vector 사용을 위한 헤더
    using namespace std;
    
    int main()
    {
    	vector<int> v1; // int type 벡터 생성
    	v1.push_back(10); // 10 추가 
    	v1.push_back(20); // 20 추가 
    	v1.push_back(30); // 30 추가
    	v1.push_back(40); // 40 추가
    	v1.push_back(50); // 50 추가
    	v1.erase(v1.begin()+2);
    	return 0;
    } 
    

    erase() 함수를 이용해 중간값을 삭제한다. 삭제한 위치로 부터 뒤의 원소들은 모두 한칸씩 앞으로 당겨진다. 단, insert(삽입)과 마찬가지로, 중간에서 값을 삭제하는 것은 비효율 적이니 빈번한 삭제는 삼가해야 한다.

     

    - erase(first, end] : 범위 삭제

    erase() 함수에 2개의 인수를 보내주면 (first,end] 범위의 값을 삭제할 수 있다.

    #include <vector>
    #include <iostream>
    using namespace std;
    
    int main(){
    	vector<int> v = {1, 2, 3, 4, 5};
    	cout << "Before" << endl; 
    	for(const int& i : v)
    		cout << i << " ";
    
    	cout << endl;
    	cout << "After" << endl;
    	v.erase(v.begin(), v.begin() + 2); // delete 1 & 2
    	for(const int& j : v)
    		cout << j << " ";
    	
    	return 0;
    }
    Before
    1 2 3 4 5
    After
    3 4 5

     

    Vector 원소 위치 변경

    - iter_swap

    iter_swap 함수를 사용해 vector 내부 원소의 위치를 변경할 수 있다. 다음은 iter_swap 함수를 이용해 벡터의 첫 번째 원소와 마지막 원소의 위치를 변경한 예제이다.

    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main(){
      vector<int> v = {1, 2, 3, 4, 5};
      cout << "Before iter_swap" << '\n';
      for(int i : v)
        cout << i << " ";
      cout << '\n';
      
      iter_swap(v.begin(), v.begin() + 4);
      cout << "After iter_swap" << '\n';
      for(int i : v)
        cout << i << " ";
      cout << '\n';
    
      return 0;
    }
    [실행 결과]
    Before iter_swap
    1 2 3 4 5 
    After iter_swap
    5 2 3 4 1

    #3 SIZE & CAPACITY

    size() , max_size() , capacity() 멤버 함수를 이용해 size와 capacity의 관계에 대해 설명하도록 하겠다. size()와 max_size() 는 vector 뿐 만 아니라 모든 컨테이너가 지니고 있는 멤버함수이다. 그러나, capacity()는 유일하게 vector만이 갖고 있는 멤버함수이다. capacity는 재할당에 드는 성능 문제를 보완하기 위해 만들어진 vector만이 가진 중요한 특징으로, 원소가 추가될 때 마다 메모리를 재할당 하는것은 비효율 적이기에, 미리 넉넉한 메모리를 확보해 재할당에 드는 비용을 줄여준다.

    글로만 보면 size와 capacity의 차이점이 잘 와닿지 않을테니, 코드를 통해서 알아보자.

     

    ■ size(), max_size(), capacity()

    vector는 크기를 반환하는 세가지 멤버 함수 size(), max_size(), capacity()를 가진다.

    size()는 컨테이너에 저장된 원소의 개수이고, max_size()는 컨테이너가 담을 수 있는 최대 원소의 개수이다.

    마지막으로 capacity()는 실제로 할당된 메모리 공간의 크기이다.

    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main()
    {
    	vector<int> v;
    	
    	v.push_back(10);
    	v.push_back(20);
    	v.push_back(30);
    	v.push_back(40);
    	v.push_back(50);
    	
    	cout << v.size() << endl;
    	cout << v.capacity() << endl;
    	cout << v.max_size() << endl;
    	
    	return 0;
    }
    <출력결과>
    5
    8
    4611686018427387903

    그림으로 나타내 보면 다음과 같다.

    Capacity가 늘어나는 기준은 컴파일러 마다 다르다. 여기서 꼭 기억해 두어야 하는 것은 size와 capacity는 다르다는 것이다. 그리고 size()는 원소의 개수를 반환해 주기 때문에 마치 자바의 length()나, C언어의 strlen처럼 활용 가능하다.

    for (int i = 0 ; i < v.size() ; ++i)
    	cout << v[i] << " ";
    <출력결과> 10 20 30 40 50

     

    ■ resize()

    resize() 멤버함수를 이용하면, 컨테이너의 size를 변경할 수 있다.

    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main()
    {
    	vector<int> v;
    	
    	v.push_back(10);
    	v.push_back(20);
    	v.push_back(30);
    	v.push_back(40);
    	v.push_back(50);
    	
    	cout << "BEFORE resize() ";
    	cout << v.size() << " " << v.capacity() << endl; 
    	
    	v.resize(4);
    	
    	cout << "AFTER resize() ";
    	cout << v.size() << " " << v.capacity() << endl;
    	
    	return 0;
    }
    <출력결과>
    BEFORE resize() 5 8
    AFTER resize() 4 8

    그러나, 출력 결과를 보면 알 수 있듯이 resize() 멤버함수로 size를 줄인다고 해서, capacity가 같이 줄지는 않는다.

     

    ■ reserve()

    vector 컨테이너는 동적으로 원소를 계속 추가할 수 있다는 장점을 갖고 있지만, 그만큼 메모리 재할당 측면에서는 비용이 많이 발생한다는 단점이 있다. 그래서 이러한 단점을 조금이나마 보완하고자 미리 capacity를 결정할 수 있는 reserve() 멤버 함수를 제공한다.

    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main()
    {
    	vector<int> v;
    	
    	v. reserve(10);
    	
    	v.push_back(10);
    	v.push_back(20);
    	v.push_back(30);
    	v.push_back(40);
    	v.push_back(50);
    	
    	cout << v.size() << " " << v.capacity() << endl; 
    	
    	return 0;
    }
    <출력결과>
    5 10

    얼마나 메모리를 확보해야 하는지 사전에 알고있는 상황이라면 reserve() 멤버함수를 사용해 재할당에 드는 비용을 조금이나마 축소할 수 있다.

     

    #4 원소접근

    Vector는 임의 위치의 원소를 참조할 수 있도록 [] 연산자와 at() 멤버함수 2가지 인터페이스를 제공한다.

    두 인터페이스의 기능은 동일하지만, []연산자범위 점검을 하지않아서 속도가 빠르지만 자칫하면 out_of_range 에러가 날 수 있어서 위험하다. 반대로 at() 멤버함수범위 점검을 수행하여 속도는 느리지만 안전하다.

    []와 at()의 예제 코드를 보고 사용방법을 익혀보도록 하자.

     

    ■ [] 연산자

    사실 앞에서 size()를 설명할 때 이미 한 번 사용했다. []연산자는 배열처럼 안에 탐색하고자 하는 인덱스를 적어 주면 된다.

    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main()
    {
    	vector<int> v;
    	
    	v.push_back(10);
    	v.push_back(20);
    	v.push_back(30);
    	v.push_back(40);
    	v.push_back(50);
    	
    	for (int i = 0 ; i < v.size() ; ++i)
    	{
    		cout << v[i] << " ";
    	}
    	
    	return 0;
    }
    <출력결과>
    10 20 30 40 50

    앞서 말한 바와 같이 범위 점검을 하지 않기에, []연산자를 사용할 때는 항상 범위에서 벗어나지 않았는지 체크해야만 한다.

     

    ■ at() 멤버함수

    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main()
    {
    	vector<int> v;
    	
    	v.push_back(10);
    	v.push_back(20);
    	v.push_back(30);
    	v.push_back(40);
    	v.push_back(50);
    	
    	for (int i = 0 ; i < v.size() ; ++i)
    	{
    		cout << v.at(i) << " ";
    	}
    	
    	return 0;
    }
    <출력결과> 
    10 20 30 40 50

    []연산자와 기능은 동일하다. 그런데 만약 다음과 같이 범위에서 벗어난 인덱스를 지정해보면

    v.at(10);

    터미널에 에러 메시지가 발생한다. ([]연산자 였다면, 가비지값이 출력되었을 것이다.)

    terminate called after throwing an instance of 'std::out_of_range'
      what():  vector::_M_range_check: __n (which is 10) >= this->size() (which is 5)

    그래서 try ~ catch 와 같은 예외 처리문을 at() 멤버함수와 같이 이용해 주곤한다.


     

    반응형

    댓글

    Designed by JB FACTORY