[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