본문 바로가기

씨플플

[C++] vector container 정리

내가 느꼈을 땐 c++의 가장 강력한 장점 중 하나가 바로 이 벡터인 것 같다..

배열을 사용하면서 고려해야 했던 점들이 거의 다 알아서 처리된다는 게 참 대단하다.


1. 벡터란?

 

그림으로 표현하자면 이와 같다.(선이 많아서 조금 지저분하다)

벡터란, '자동으로 메모리가 할당되는 배열' 이라고 생각하면 된다.

임의 접근 반복자(Random Access Iterator)가 지원되는 배열 기반 컨테이너이다.

배열처럼 연속적인 메모리 블록에 저장되며 사용법도 거진 비슷하다.

 

삽입과 삭제는 stack처럼 맨 뒤에서 이뤄지지만, 중간에 값을 삽입하거나 삭제도 가능은 하다.

(하지만 이런 경우가 빈번하게 발생한다면 비효율적이다.)


2. 생성자, 멤버함수

 

많은 멤버함수들이 있지만 간단히 자주 사용하고 헷갈리는 몇 개만 적어보자.

 

0) 생성자

  • vector<int> v;  -  비어있는 벡터 v를 생성한다.
  • vector<int> v(5);  -  기본값(0) 원소 5개를 가진 벡터 v를 생성한다.
  • vector<int> v(5,2);  -  2로 초기화 된 원소 5개를 가진 벡터 v를 생성한다.
  • vector<int> v2(v1);  -  벡터 v1의 원소들을 복사한 벡터 v2를 생성한다.

1) 멤버함수

  • v.assign(5,2) vs v.resize(5,2)&v.resize(3) vs v.reserve(5)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v;
	v.push_back(1);
	cout << v[0] << ' ' << v.size() << ' ' << v.capacity() << endl;
	v.assign(5, 2);
	cout << v[0] << ' ' << v.size() << ' ' << v.capacity() << endl;
}

 

v.assign(5,2)의 결과 확인

먼저 빈 벡터 v에 1을 삽입하면 v의 첫 번째 원소 v[0]과 size, capacity가 모두 1이 된다.

이때, v.assign(5,2); 을 실행하면 5개의 '2'가 기존의 원소들을 모두 덮어버린다.

 

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v;
	v.push_back(1);
	cout << v[0] << ' ' << v.size() << ' ' << v.capacity() << endl;
	v.resize(5, 2);
	cout << v[0] << ' ' << v.size() << ' ' << v.capacity() << endl;
	v.resize(3);
	cout << v[0] << ' ' << v.size() << ' ' << v.capacity() << endl;
}

 

v.resize(5,2)와 v.resize(3)의 결과 확인

반면, v.resize(5,2); 을 실행하면 기존 원소의 값은 그대로 둔 채 크기를 5로 변경한다.

만약 원래 크기보다 커지는 경우에는 추가되는 원소의 값을 2로 초기화한다. (default는 0)

v.resize(3); 을 실행한 결과를 살펴보면 capacity는 그대로인데 size만 3으로 줄어든 걸 확인할 수 있다.

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v;
	v.push_back(1);
	cout << v[0] << ' ' << v.size() << ' ' << v.capacity() << endl;
	v.reserve(5);
	cout << v[0] << ' ' << v.size() << ' ' << v.capacity() << endl;
	//v[1]부터 v[5]는 초기화되지 않은 상태
}

 

v.reserve(5)의 결과 확인

v.reserve(5); 는 추가적으로 메모리를 미리 동적할당 해둔다. (값은 따로 초기화되지 않음)

 

  • v[i] vs v.at(i)

벡터는 배열과 같이 v[i]로 i번째 원소에 임의 접근이 가능하다.

하지만 이처럼 i번째 원소에 접근케 하는 멤버함수 v.at(i)가 있는데 차이가 뭘까?

 

우선, v[i] 는 따로 범위를 점검하지 않는다.

그 말은 i가 벡터의 범위를 벗어나더라도 알아차리지 못해 나중에 에러가 발생할 수 있다.

하지만 그만큼 속도는 빨라진다.

 

v.at(i) 는 반대로 i가 벡터의 범위에 적합한지를 점검하기 때문에 안전하다. (속도는 상대적으로 느림)

 

  • v.size() vs v.capacity()

엄연히 size와 capacity는 다르다.

 

간단하게 말해서 size는 벡터에 들어있는 원소의 개수를 의미하고

capacity는 벡터에 할당된 메모리(즉, 벡터가 받아들일 수 있는 원소의 최대 개수)를 의미한다.

 

size와 capacity의 관계

그림으로 표현하자면 위와 같다.

 

그렇다면 capacity가 꽉 찼을 때 원소가 삽입되면 어떻게 될까?

늘어나는 개수에 맞춰서 하나씩 capacity를 늘려갈까? 한꺼번에 엄청 늘려놓고 원소를 저장할까?

 

정답은, 기존 capacity의 2배씩 늘려나간다.

 

이유는 메모리 할당을 빈번하게 하는 경우를 줄이고, 사용하는 메모리 양도 줄이도록 최적화하기 위해서다.