vector<Type> vect; // vect는 stack, 각 원소들은 heap
vector<Type> *vect = new vector<Type>; // vect와 각 원소들 모두 heap
vector<Type*> vect; // vect는 stack, 각 원소들은 heap 그리고 가리키는 대상들은 heap 또는 stack
속도적인 측면에서 array(배열)에 비해 성능은 떨어지지만 메모리를 효율적으로 관리하고 예외처리가 쉽다는 장점이 있다.
Vector의 초기화
vector<자료형> 변수명 | 백터 생성 |
vector<자료형> 변수명(숫자) | 숫자만큼 백터 생성 후 0으로 초기화 |
vector<자료형> 변수명 = { 변수1, 변수2, 변수3... } | 백터 생성 후 오른쪽 변수 값으로 초기화 |
vector<자료형> 변수명[] = {, } | 백터 배열(2차원 백터)선언 및 초기화(열은 고정, 행은 가변) |
vector<vector<자료형> 변수명 | 2차원 백터 생성(열과 행 모두 가변) |
vector<자료형>변수명.assign(범위, 초기화할 값) | 백터의 범위 내에서 해당 값으로 초기화 |
vector<int> v; //int형 백터 생성
vector<int>v(4); //int형 백터 생성 후 크기를 4로 할당(모든 백터요소 0으로 초기화)
vector<int>v = { 1, 2, 3 }; //int형 백터 생성 후 1, 2, 3 으로 초기화
vector<int>v[] = { { 1, 2}, {3, 4} }; //int형 백터 배열 생성(행은 가변이지만 열은 고정)
vector<vector<int>> v; //2차원 백터 생성(행과 열 모두 가변)
vector<int> v = { 1, 2, 3, 4, 5 }; //백터 범위를 5로 지정하고 정수 10으로 초기화
v.assign(5, 10); //output : 10 10 10 10 10
Vector의 Iterators
v.begin() | 백터 시작점의 주소 값 반환 |
v.end() | 백터 (끝부분 + 1) 주소값 반환 |
v.rbegin() (revers begin) | 백터의 끝 지점을 시작점으로 반환 |
v.rend() (revers end) | 백터의 (시작 + 1) 지점을 끝 부분으로 반환 |
begin(), end() 그림
rbegin(), rend() 그림
#include <iostream>
#include <algorithm>
#include <array>
#include <vector>
using namespace std;
int main()
{
vector<int> v = { 1, 2, 3, 4 };
for_each(v.begin(), v.end(), [&](int& n)
{
cout << n << endl; //output : 1 2 3 4
});
for_each(v.rbegin(), v.rend(), [&](int& n)
{
cout << n << endl; //output : 4 3 2 1
});
for (auto itor = v.begin(); itor != v.end(); itor++)
cout << *itor << endl; //output : 1 2 3 4
for (auto itor2 = v.rbegin(); itor2 != v.rend(); itor2++)
cout << *itor2 << endl; //output : 4 3 2 1
}
Vector Element access(백터의 요소 접근)
v.at(i) | 백터의 i번째 요소 접근 (범위 검사함) |
v.[i] (operator []) | 백터의 i번째 요소 접근 (범위 검사 안함) |
v.front() | 백터의 첫번째 요소 접근 |
v.back() | 백터의 마지막 요소 접근 |
※vector의 at과 []에 대한 차이점을 설명하자면 다음과 같다.
둘다 똑같이 N번째의 요소 접근이지만 at은 범위를 검사하여 범위 밖의 요소에 접근 시 예외처리를 발생시킵니다. (std::out_of_range) 하지만 [](operator [])는 범위검사를 하지 않으며 예외처리를 발생시키지 않는다. 또한 해당범위 밖의 요소에 접근을 시도한다면 일반적인 디버깅(g++ or VC++)이 발생한다. 백터는 효율을 중점으로 둔 라이브러리 함수여서 보통 []를 권장하고 있으며 여러가지 방법으로 유효성 검사가 가능하기 때문에 []를 많이 사용하고 있다.
vector<int> v = { 1, 2, 3, 4};
cout << v.front() << endl; //output : 1
cout << v.back() << endl; //output : 4
cout << v.at(1) << endl; //output : 2
cout << v[2] << endl; //output : 3
Vector에 요소 삽입
v.push_back() | 백터의 마지막 부분에 새로운 요소 추가 |
v.pop_back() | 백터의 마지막 부분 제거 |
v.insert(삽입할 위치의 주소 값, 변수 값) | 사용자가 원하는 위치에 요소 삽입 |
v.emplace(삽입할 위치의 주소 값, 변수 값) | 사용자가 원하는 위치에 요소 삽입(move로 인해 복사생성자 X) |
v.emplace_back() | 백터의 마지막 부분에 새로운 요소 추가(move로 인해 복사생성자 X) |
v.erase(삭제할 위치) or v.erase(시작위치, 끝위치) | 사용자가 원하는 index값의 요소를 지운다. |
v.clear() | 백터의 모든 요소를 지운다.(return size = 0) |
v.resize(수정 값) | 백터의 사이즈를 조정한다.(범위 초과시 0으로 초기화) |
v.swap(백터 변수) | 백터와 백터를 스왑한다. |
#include <vector>
int main(){
vector<int> v;
v.push_back(10);
v.push_back(20); //v = { 10, 20 }
v.inset(v.begin() + 1, 100); // v = { 10, 100, 20 }
v.pop_back(); // v = { 10, 100 }
v.emplace_back(1); //v = { 10, 100, 1 }
v.emplace_back(2); //v = { 10, 100, 1, 2 }
v.emplace(v.begin() + 2, -50); //v = { 1, 100, -50, 1, 2 }
v.erase(v.begin() + 1); // v = { 1, -50, 1, 2 }
v.resize(6); // v = { 1, -50, 1, 2, 0, 0 }
v.clear(); // v = empty()
}
기본적으로 push_back() 함수는 값을 넣는 과정에서 복사생성자를 호출하게 된다. 뿐만 아니라 insert를 하게 된다면 모든 값들을 새로운 메모리에 복사한 후 해당 자리에 값을 넣게 된다. (일반적인 배열 중간에 값을 끼워 넣는거랑 똑같다.)
이렇게 되버리면 복사생성자로 인한 오버헤드가 커지게 되며 성능 저하를 야기함으로 대신 emplace와 emplace_back은 백터 내부에서 값들을 생성하는 걸(생성자만 호출) 사용하자.
vector의 크기에는 size()와 capacity() 두개가 있는데, size()는 백터가 생성된 크기이며 capacity()는 백터의 최대 할당 크기다.
위 사진처럼 되어 있는데 백터의 크기가 capacity()의 크기를 초과해 버린다면 reallocate(재할당)이 발생한다. 재할당이 발생한다면 모든 값들을 새로운 메모리 공간에 복사한 후 기존 백터를 파괴해 버린다. 여기서 복사 과정에서도 복사생성자가 발생하게 됨으로서 프로그램의 퍼포먼스가 저하된다. 또한 reallocate이 자주 일어나는 현상을 막기 위해서 reserve()라는 함수를 사용해주면 되는데 이 함수는 백터의 capacity() 크기를 설정해주는 함수로 충분한 백터를 만들어서 불필요한 생성과정을 없애주는 역할을 한다. 하지만 reserve()를 너무 크게 잡게되면 백터가 불필요하게 늘어나 메모리를 많이 잡아먹을 수 있다. 따라서 남은 공간을 잡아주는 함수가 바로 shrink_to_fit()이라는 함수를 통해 capacity()의 크기를 조정해 줄 수 있다.
clear()로 백터의 값들을 지우게 되면 백터의 요소들은 없어지지만 capacity()는 남아있다. 즉, clear()로 백터의 값들을 지운 후 다시 사용하지 않는다면 해당 백터의 메모리 공간은 잉여로 남게 된다. 이걸 해결할 수 있는 방법이 swap을 이용한 방법인데, 사용법은 다음과 같다.
vector<int> v = { 1, 2, 3, 4};
v.clear();
cout << v.capacity() << endl; //output : 10
vector<int>().swap(v);
cout << v.capacity() << endl; //output : 0
이렇게 하면 아무것도 없는 백터공간과 swap이 일어나 capacity() 공간을 없앨 수 있다. 하지만 함수가 끝나면(중괄호를 벗어나면) 자동으로 힙에서 메모리 해제가 된다. insert()와 erase() 해당 함수를 사용 시 변수를 찾은 후 해당 공간을 만들기 위해 데이터를 일일이 이동시킨다. 만약 capacity()값을 초과될 경우 reallocate도 일어나게 되므로 메모리 오버헤드 및 성능저하도 야기할 수 있다. 따라서 삽입과 삭제가 빈번히 일어날 경우 vector보단 list나 deque를 사용하는 것이 바람직하다.
Vector Capacity(백터 용량)
v.empty() | 백터가 빈공간이면 true, 값이 있다면 false |
v.size() | 백터의 크기 반환 |
v.capacity() | heap에 할당된 백터의 실제크기(최대크기) 반환 |
v.max_size() | 백터가 system에서 만들어 질 수 있는 최대 크기 반환 |
v.reserve(숫자) | 백터의 크기 설정 |
v.shrink_to_fit() | capacity의 크기를 백터의 실제 크기에 맞춤 |
vector<int>v = { 1, 2, 3, 4 };
cout << v.size() << endl; //output : 4
cout << v.capacity() << endl; //output : 10 (컴파일 환경에 따라 달라질 수 있음)
v.reserve(6);
cout << v.capacity() << endl; //output : 6
cout << v.max_size() << endl; //output : 1073741823(시스템 성능에 따라 달라질 수 있음)
v.shrink_to_fit();
cout << v.capacity() << endl; //output : 4
cout << v.empty() << endl; //output : false
v.clear();
cout << v.empty() << endl; //output : true
'CS > 자료구조 & 알고리즘' 카테고리의 다른 글
가중치 그래프와 임계 경로(Critical Path) (0) | 2022.08.14 |
---|---|
C++ vector empty()와 size()간 차이 (0) | 2022.08.04 |
C 정렬 코드 (0) | 2022.07.20 |
컨테이너 어댑터 (스택, 큐, 우선순위 큐) (0) | 2022.07.04 |
[C++] Deque 데크 (0) | 2022.07.04 |