표준 템플릿 라이브러리(STL : Standard Template Library)는 컨테이너, 이터레이터, 함수 객체, 알고리즘을 나타내는 템플릿들의 집합을 제공한다. 이를 통해 검색, 정렬, 무작위화 등등 더 다양한 코드를 짤 수 있게 도와주는 그런 도구라고 생각하면 될 것 같다. (사실은 복잡하게) 굉장히 많이 사용하니 필수로 알아야 함. '템플릿' 단어를 통해 다양한 데이터형에 적용 될 수 있다는 점을 알 수 있다.
vector은 컨테이너다. 컨테이너는 배열같이 여러 원소들을 저장할 수 있는 구성 단위이다. 물리에서 나오는 벡터가 아니다. <vector> 라이브러리 안에 std 이름 공간 안에 있다. 사용은 뭐 이런 식으로 한다.
std::vector<int> ratings(5); // 5개의 int형을 가진 배열 생성.
std::vector<double> scores(7); // 7개의 double형을 가진 배열 생성.
//이렇게 초기화 하면 각각 0, 0.0000...으로 초기화 됨.
또한 [ ]연산자를 통해 원소에 접근할 수 있다. 시작 인덱스는 당연히 0이다.
int value = ratings[0];
double value1 = scores[6];
vector로는 여러 가지를 할 수 있다. 하나씩 알아보자.
생성자 | 설명 |
---|---|
vector<int> v | 비어있는 vector v를 생성. |
vector<int> v(5) | 기본값(0)으로 초기화 된 5개의 원소를 가지는 vector v 생성. |
vector<int> v(5,2) | 2로 초기화된 5개의 원소를 가지는 vector v 생성. |
vector<int> v1(5,2); vector<int> v2(v1) | v1를 v2로 복사. |
메서드 | 설명 |
---|---|
v.assign(5,2) | 2의 값으로 5개의 원소 할당. |
v.at(idx) | idx 번째 원소를 참조. v[idx]보다 속도는 느리지만, 안전. |
v[idx] | idx 번째 원소를 참조. 속도가 v.at(idx)보다 빠름. |
v.front() | 첫 번째 원소를 참조. |
v.back() | 마지막 원소를 참조. |
v.clear() | 모든 원소를 제거. 하지만 메모리는 남아있음. size만 줄어들고 capacity는 그대로 남아있음. |
v.push_back(7) | 마지막 원소 뒤에 원소 7 삽입. |
v.pop_back() | 마지막 원소를 제거. |
v.reserve(n) | n개의 원소를 동적할당 해 놓음. |
v.resize(n) | 크기를 n으로 변경. n이 현재 size보다 크면 추가된 원소들은 0으로 초기화. |
v.resize(n,3) | 크기를 n으로 변경. n이 현재 size보다 크면 추가된 원소들은 3으로 초기화. |
v.size() | 원소의 갯수 반환. |
v.capacity() | 할당된 공간의 크기를 리턴 |
v2.swap(v1) | v1와 v2의 원소 및 capacity를 바꿔줌. |
v.empty() | vector가 비어있으면 true, 그렇지 않다면 false |
capacity와 size의 관계는 string 클래스에서 capacity를 생각하면 편하다. vector도 마찬가지로, push_back을 할 때마다 주소를 바꿔가며 동적할당하면 비효율적이라서 애초에 메모리 크기를 크게 받는다.
벡터는 이터레이터가 있다. 이터레이터는 포인터와 비슷하다. 나중에 가면 포인터로는 하지 못 하는 것을 이터레이터로는 할 수 있다. 이터레이터는 이렇게 사용한다.
vector<double>::iterator pd; // 선언. pd는 이터레이터.
vector<double> scores;
pd = scores.begin(); // pd는 scores의 첫 번째 원소를 가리킨다.
*pd = 22.3; // 역참조를 이용해 값 대입했다.
++pd; // pd가 다음 원소를 가리킨다.
vector<double>::iterator pd = scores.begin() // 이렇게도 초기화 가능.
iterator::begin()과 반대인 메서드로 iterator::end()가 있다. begin()이 맨 처음 원소를 가리켜서 end()가 맨 뒤의 원소를 가리킬 거라 생각하기 쉽다. 하지만 이것은 아니다. 결론부터 말하자면 맨 뒤 원소의 다음 원소를 가리킨다. 다음의 코드를 보자.
std::vector<int> v1;
std::vector<int> v2;
for (int i = 0; i < 20; i++)
{
v1.push_back(i);
}
std::vector<int>::iterator pv = v1.end();
cout << *pv << endl; // 에러 나온다.
cout << *(pv - 1) << endl; // 마지막 원소를 가리킴.
*pv는 에러를 출력한다. 왜? 마지막 원소 다음 원소는 출력을 못 하기 때문이다. 그래서 1을 빼면, 그 이전 원소인 벡터 배열의 마지막 원소를 가리키게 된다. 왜 이렇게 만들었을까? 나도 모른다. 문자열에서 마지막 원소 다음이 널 문자인 거랑 비슷하게 만들었다고는 하는데,, 이게 더 편한지는 모르겠다. 다음은 이터레이터를 사용한 메서드이다.
메서드 | 설명 |
---|---|
v.begin() | 첫 번째 원소를 가리킴. |
v.end() | 마지막의 "다음"을 가리킴. |
v.rbegin() | reverse begin을 가리킨다. 기존 이터레이터랑 자료형이 거꾸로다. |
v.rend() | reverse end를 가리킨다. 기존 이터레이터랑 자료형이 거꾸로다. |
v.insert(iter, 3) | iter 위치에 3을 대입한다. 뒤의 원소들은 뒤로 밀려난다. |
v.insert(iter, 3, 5) | iter 위치에 원소 5를 3개 대입한다. 뒤의 원소들은 뒤로 밀려난다. |
v.insert(iter1, iter2, iter3) | iter1 위치에 iter2부터 iter3까지의 값을 대입한다. 뒤의 원소들은 뒤로 밀려난다. iter2와 iter3은 같은 벡터의 이터레이터여야 한다. |
v.erase(iter) | iter 위치의 원소를 제거하고 뒤의 원소를 앞당긴다. |
지금부터는 <algorithm> 라이브러리를 추가해야 쓸 수 있는 메서드들도 보자. for_each는 for문을 이터레이터와 쓸 때, 좀 더 간단히 쓸 수 있다.
vector<int> books;
vector<int>::iterator pr;
for (pr = books.begin(); pr != books.end(); pr++)
ShowReview(*pr);
이 구문을
for_each(books.begin(), books.end(), ShowReview);
이렇게 바꿀 수 있다.
random_shuffle() 함수는 이터레이터 범위를 지정하여, 범위 내의 원소를 무작위로 바꿔준다.
random_shuffle(books.begin(), books.end());
sort() 함수는 매개 변수 사이의 벡터 원소들을 오름차순으로 정리할 수 있다.
sort(books.begin(), books.end()); // 오름차순 정렬.
books는 int형이라 operator<가 정의되어 있어서 가능했다. 다른 클래스를 오름차순으로 정리하려면, operator< 연산자를 통해 어떤 게 비교 우위에 있다고 판단할 것인지 먼저 정의를 해주어야 한다. 근데 내림차순도 가능한가? 가능하다. 두 가지 방법이 있다.
bool compare(int a, int b)
{
return b < a;
}
...
std::sort(v1.begin(), v1.end(), compare); // 사용자 정의 함수 사용.
std::sort(v1.begin(), v1.end(), greater<>()); // 단순히 greater<>() 삽입.
덤으로 vector은 범위 기반 반복문이랑 쓰기 참 좋다.
vector<int> vecs;
for (int &vec : vecs)
...
push_back과 비슷한 메서드로 emplace_back이 있다. 이 둘은 벡터의 맨 뒤에 원소를 하나 더 추가한다는 게 같다. 하지만 그 과정은 조금 다르다. 예를 들어 Item 객체를 넣는다고 하자. push_back의 과정은 이렇다.
1. push_back(Item)에서 Item 자체가 임시 객체를 하나 만듦.
2. 임시 객체가 이동생성자를 통해 push_back 함수 내부에서 임시 객체를 만들어냄.
3. vector 에 객체 삽입.
4. push_back에서 빠져나온 후 Item()을 통해 만들어진 임시 객체 소멸.
5. main 이 끝난 후 vector에 삽입된 객체 소멸.
반면 emplace_back은 이렇다.
결국 push_back의 1번 과정 덕분에 emplace_back이 더 효율적이라고 할 수 있다. 특히나 Item 객체의 크기가 크면 클수록 말이다. 그럼 push_back은 안 쓰면 되지 않나? 근데 또 그렇지만은 않다고 한다. 그런 코드를 만나면 여기다가 쓰겠다!^_^