컴공생의 다이어리
[c++] vector container(std::vector - 가변 크기 배열) 본문
std::vector는 std::array나 c/c++에서 지원하는 배열의 문제 중 하나인 고정크기 문제를 해결한다. 배열의 경우 한번 정해지면 고정이라 수정하기가 어렵지만 벡터는 동적 배열 구조 클래스이기 때문에 초기화 과정에 데이터의 크기를 제공하지 않아도 된다.
예를들어 배열의 경우 크기를 10으로 선언하면 10이 최대이며 더 늘릴 수가 없기 때문에 초기에 넉넉히 공간을 잡아줘야 한다. 하지만 벡터의 경우 처음에 크기를 10으로 선언해도 나중에 필요에 따라서 추가하거나 줄일 수 있다.
vector의 선언 및 초기화
아래는 벡터(vector)를 선언하고 초기화하는 방법이다.
vector<자료형> 변수명 | 벡터 생성 |
vector<자료형> 변수명(숫자) | 숫자만큼 벡터 생성 후 0으로 초기화 |
vector<자료형> 변수명(숫자, 변수1) | 숫자만큼 벡터 생성 후 변수1으로 모든 원소 초기화 |
vector<자료형> 변수명={변수1,변수2,변수3,...} | 벡터 생성 후 오른쪽 변수 값으로 초기화 |
vector<자료형> 변수명[]={{변수1,변수2},{변수3,변수4},...} | 벡터 배열(2차원 벡터) 선언 및 초기화(열은 고정, 행은 가변) |
vector<vector <자료형>> 변수명 | 2차원 벡터 생성(열과 행 모두 가변) |
vector<자료형>변수명.assign(범위, 초기화할 값) | 벡터의 범위 내에서 해당 값으로 초기화 |
#include <iostream>
#include <vector>
int main() {
// 크기가 0인 벡터 선언
std::vector<int> vec1;
// 크기가 10인 벡터 선언
std::vector<int> vec2(10);
// 크기가 10이고, 모든 원소가 3으로 초기화된 벡터 선언
std::vector<int> vec3(10, 3);
// 지정한 초기값으로 이루어진 크기가 5인 벡터 선언
std::vector<int> vec4 = { 1,2,3,4,5 };
// 벡터 배열 생성(행은 가변인지만, 열은 고정)
std::vector<int> vec5[] = { {1,2},{3,4} };
// 2차원 벡터 생성(행과 열 모두 가변)
std::vector<std::vector<int>> vec6;
// 벡터 범위를 5로 지정하고 10으로 초기화
std::vector<int> vec7 = { 1,2,3,4,5 };
vec7.assign(5, 10);
}
vector에 요소 삽입, 제거, 변경
v,push_back() | 벡터의 마지막 부분에 새로운 요소 추가 |
v.insert(삽입할 위치의 주소 값, 변수값) | 사용자가 원하는 위치에 요소 삽입 |
v.emplace(삽입할 위치의 주소 값, 변수 값) | 사용자가 원하는 위치에 요소 삽입(move로 인해 복사생성자 X) |
v.emplace_back() | 백터의 마지막 부분에 새로운 요소 추가(move로 인해 복사생성자 X) |
v.pop_back() | 백터의 마지막 부분 제거 |
v.erase(삭제할 위치) or v.erase(시작위치, 끝위치) | 사용자가 원하는 index값의 요소를 지운다 |
v.clear() | 백터의 모든 요소를 지운다.(return size = 0) |
v.resize(수정 값) | 백터의 사이즈를 조정한다.(범위 초과시 0으로 초기화) |
v.swap(백터 변수) | 백터와 백터를 스왑한다. |
#include <iostream>
#include <vector>
int main() {
// 크기가 0인 벡터 선언
std::vector<int> vec;
vec.push_back(10);
vec.push_back(20); //vec={10, 20}
vec.insert(vec.begin() + 1, 100); //vec={10, 100, 20}
vec.pop_back(); //vec={10,100}
vec.emplace_back(1); //vec={10,100,1}
vec.emplace_back(2); //vec={10,100,1,2}
vec.emplace(vec.begin() + 2, -50); //vec={10,100,-50,1,2}
vec.erase(vec.begin() + 1); //vec={1,-50,1,2}
vec.resize(6); //vec={1,-50,1,2,0,0}
vec.clear(); //vec=empty()
}
vector에 대한 복사생성자와 move에 대해 잠시 알아보겠다.
기본적으로 push_back()함수는 값을 넣는 과정에서 복사생성자를 호출하며, insert() 함수 사용시에도 모든 값들을 새로운 메모리에 복사한 후 해당 자리에 값을 넣게 된다. 이렇게 되버리면 복사생성자로 인한 오버헤드가 커지면서 성능저하로 이어진다. 그래서 push_back()과 insert()함수 대신에 사용하는 것이 emplace_back()과 emplac() 함수이다. emplace_back과 emplace는 벡터 내부에서 값들을 생성하는 것이다(생성자만 호출).
vector의 iterators
v.begin() | 백터 시작점의 주소 값 반환 |
v.end() | 벡터 (끝부분+1) 주소값 반환 |
v.rbegin() | 백터의 끝 지점을 시작점으로 반환 |
v.rend() | 백터의 (시작+1) 지점을 끝 부분으로 반환 |
vector의 요소 접근
v.at(i) | 백터의 i번째 요소 접근 (범위 검사함) |
v[i] (operator []) | 백터의 i번째 요소 접근 (범위 검사 안함) |
v.front() | 백터의 첫번째 요소 접근 |
v.back() | 백터의 마지막 요소 접근 |
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = { 1,2,3,4,5 };
std::cout << vec.front()<<std::endl;
std::cout << vec.back() << std::endl;
std::cout << vec.at(3) << std::endl;
std::cout << vec[2] << std::endl;
}
vector의 v.at(n)과 v.[n]은 둘다 똑같이 N번째의 요소 접근이지만 차이가 있다. at은 범위를 검사하여 범위 밖의 요소에 접근 시 예외처리(std::out_of_range)를 발생시킨다. 하지만 []는 범위검사를 하지 않으며 예외처리를 발생시키지 않는다. 또한 해당범위 밖의 요소에 접근을 시도한다면 일반적인 디버깅(g++ or VC++)이 발생한다. 백터는 효율을 중점으로 둔 라이브러리 함수여서 보통 []를 권장하고 있으며 여러가지 방법으로 유효성 검사가 가능하기 때문에 []를 많이 사용한다.
vector의 크기 size, capacity
vector의 크기에는 크기(size)와 용량(capacity)가 있다. 벡터의 크기는 벡터에 실제로 저장된 원소 개수를 나타내는 용어이며, 용량은 벡터의 최대 할당 크기라고 생각하면 된다.
벡터의 크기가 용량의 크기를 초과한다면 재할당(reallocate)이 발생한다. 재할당이 발생하면 모든 값들을 새로운 메모리 공간에 복사한 후 기존 벡터를 파괴한다. 이때 복사하는 과정에서, 복사 생성자가 발생하면서 성능이 저하될 수 있다. 이에 대한 해결책으로, reserve()라는 함수를 사용해서 벡터의 용량을 충분히 크게 생성하는 방법이 있다. 하지만 reserve()를 너무 크게 잡으면 벡터가 불필요하게 늘어나 메모리를 많이 차지할 수 있다.
clear() 함수를 통해 벡터의 값들을 지우게 되는 경우, 벡터의 요소들을 없어지지만 capacity는 그대로 남아있다. 이걸 해결 할 수 있는 방법이 swap함수이며 아래와 같은 방법으로 사용할 수 있다.
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = { 1,2,3,4,5 };
vec.clear();
std::cout << vec.capacity() << std::endl;
std::vector<int>().swap(vec);
std::cout << vec.capacity() << std::endl;
}
다음과 같이 실행하면 아무것도 없는 vec라는 벡터 변수와 벡터공간과 swap이 일어나 capacity공간을 없앨 수 있다. 하지만 함수가 끝나면 자동으로 힙에서 메모리 해제가 되기 때문에 한개의 함수에서 벡터를 계속 재활용해 사용하지 않는다면 굳이 저렇게 사용하지 않아도 된다.
cf) shrink_to_fit() : 여분의 메모리 공간을 해제하는 용도로 사용된다. 이 함수를 호출하면 벡터의 용량이 벡터 크기와 같게 설정된다. 벡터 크기가 더 이상 변경되지 않을 때 사용하면 유용한다.
vector를 언제 사용하면 좋을까?
아래와 같은 특징을 가지고 있을 때 사용하면 좋다. 삽입과 삭제가 빈번히 일어날 경우 vector보다는 list 혹은 deque를 사용하는 것이 성능 측면으로 봤을 때 좋다.
vector | |
크기 변경 가능 | ○ |
중간 삽입, 삭제 용이 | × |
순차 접근 가능 | ○ |
랜덤 접근 가능 | ○ |
www.yes24.com/Product/Goods/95863013
'Development > C & C++' 카테고리의 다른 글
[c++] forward_list container(std::forward_list) (0) | 2021.01.30 |
---|---|
[c++] std::vector의 reserve(), resize(), shrink_to_fit() 함수 (0) | 2021.01.17 |
[c++] array container(std::array) (0) | 2021.01.15 |
[c++] 범위 기반 for문 (range-based for statement) (0) | 2021.01.15 |
[c++] auto 키워드 (0) | 2021.01.15 |