15. string 클래스와 표준 템플릿 라이브러리(3) - 표준 템플릿 라이브러리(vector, emplace_back)

WanJu Kim·2022년 12월 19일
0

C++

목록 보기
67/81

표준 템플릿 라이브러리(STL : Standard Template Library)는 컨테이너, 이터레이터, 함수 객체, 알고리즘을 나타내는 템플릿들의 집합을 제공한다. 이를 통해 검색, 정렬, 무작위화 등등 더 다양한 코드를 짤 수 있게 도와주는 그런 도구라고 생각하면 될 것 같다. (사실은 복잡하게) 굉장히 많이 사용하니 필수로 알아야 함. '템플릿' 단어를 통해 다양한 데이터형에 적용 될 수 있다는 점을 알 수 있다.

vector 템플릿 클래스

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)
	...

emplace_back vs push_back

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은 이렇다.

  1. emplace_back(Item); 통해 emplace_back 함수에 Item 객체를 만들 수 있는 인자 (매개 변수)를 넘겼음.
  2. emplace_back 내부에서 임시객체를 만들어냄.
  3. vector 에 객체 삽입.
  4. main 이 끝난 후 vector에 삽입된 객체 소멸.

결국 push_back의 1번 과정 덕분에 emplace_back이 더 효율적이라고 할 수 있다. 특히나 Item 객체의 크기가 크면 클수록 말이다. 그럼 push_back은 안 쓰면 되지 않나? 근데 또 그렇지만은 않다고 한다. 그런 코드를 만나면 여기다가 쓰겠다!^_^

profile
Question, Think, Select

0개의 댓글