*개인적인 공부 내용을 기록하는 용도로 작성한 글 이기에 잘못된 내용을 포함하고 있을 수 있습니다.
#1 About Vector Container
#2 Vector 사용방법
- 사전 작업
- Vector 선언 및 초기화
- Vector 값 추가 (push_back, insert)
- Vector 값 삭제 (pop_back, erase)
- Vector 원소 위치 변경 (iter_swap)
#3 SIZE & CAPACITY
- size() , max_size() , capacity()
- resize() [size 축소]
- reserve() [사전 메모리 할당]
#4 원소 접근
- []연산자 [범위점검X]
- at() [범위점검O]
#1 About Vector Container
Vector 컨테이너는, C++ 표준 라이브러리[STL]에 정의된 대표적인 시퀀스 컨테이너로 사용자의 편의를 위해 제공되는 class의 일종이다. 마치 배열과 비슷하여 사용이 쉽기에, 자주 사용되며 가변적으로 크기가 변하는 동적 배열 이라고 생각하면 된다.
메모리를 효율적으로 관리할 수 있다는 장점을 갖고 있지만, 빈번한 삽입과 삭제가 일어나는 상황에서는 비효율적 이라는 단점 또한 갖고 있다. 속도적인 측면에서 배열[ARRAY]에 비해서 떨어지지만, 메모리를 효율적으로 관리할 수 있어서, 많이 사용된다.
다음은 vector container의 전체적인 구조를 그림으로 나타낸 것이다.
vector는 앞쪽이 막혀져 있는 형태라서 앞쪽에는 원소를 추가 /제거 할수 없으며, 뒤에서 부터 차례로 push_back() , pop_back() 을 이용해 원소를 추가 / 제거 한다.
중간에도 insert() 를 이용해 삽입할 수 있으나 "배열 기반 컨테이너" 이기에 효율이 매우 떨어진다.
첫 원소와 마지막 원소를 참조할 수 있는, fornt()/begin() 과 back()/end() 를 갖고있다.
size()는 vector가 지닌 원소의 개수를 의미하며, capcity는 할당된 메모리 값을 의미한다.
#2 Vector 사용방법
사전작업
ⓐ <vector> 헤더파일을 추가한다. #include <vector>
ⓑ 편의를 위해 std 네임스페이스를 선언한다. using namespace std;
ⓒ vector의 기본적인 선언 방식은 vector<[datatype]> [name] 이다. int형 vector 선언 → vector<int> v;
Vector 선언 및 초기화
다음은 Vector의 기본적인 선언 및 생성자를 이용한 초기화 방식이다.
#include <iostream>
#include <vector> // vector 사용을 위한 헤더
using namespace std;
int main()
{
vector<int> v1; // int type 벡터 생성
vector<int> v2(5); // 0으로 초기화된 size가 5인 벡터이다.
vector<int> v3(5,10); // 지정값 10으로 초기화된 size가 5인 벡터이다.
vector<int> v4 = {1,2,3,4,5} // 벡터 생성 후 1,2,3,4,5 의 원소를 초기화한다.
vector<int> v5(v4) // v5는 v4의 복사본(벡터) 이다.
return 0;
}
Vector 값 추가
- push_back(value) : 맨 끝에 추가
#include <iostream>
#include <vector> // vector 사용을 위한 헤더
using namespace std;
int main()
{
vector<int> v1; // int type 벡터 생성
v1.push_back(10); // 10 추가
v1.push_back(20); // 20 추가
v1.push_back(30); // 30 추가
return 0;
}
push_back(value) 함수를 이용해 벡터의 맨 끝에 차례로 원소(10, 20, 30)를 추가한다.
- insert(삽입위치,value) : 중간 삽입
#include <iostream>
#include <vector> // vector 사용을 위한 헤더
using namespace std;
int main()
{
vector<int> v1; // int type 벡터 생성
v1.push_back(10); // 10 추가
v1.push_back(20); // 20 추가
v1.push_back(30); // 30 추가
v1.insert(v1.begin(), 100); // 시작 위치에 100을 삽입한다.
v1.insert(v1.begin() + 2, 101); // 시작 위치+2에 101을 삽입한다.
return 0;
}
insert(삽입위치,value) 함수를 이용해 중간에 값을 삽입한다. 삽입한 위치 뒤의 원소는 모두 한 칸 씩 밀려난다. (단, 앞서 말한 바와 같이 벡터에서 중간삽입은 매우 비효율적이며, 중간에 원소를 삽입할 경우 reallocate[메모리 재할당]이 발생할 수 있으니 사용에 주의해야 한다.)
Vector 값 삭제
- pop_back() : 맨 끝 값 삭제
#include <iostream>
#include <vector> // vector 사용을 위한 헤더
using namespace std;
int main()
{
vector<int> v1; // int type 벡터 생성
v1.push_back(10); // 10 추가
v1.push_back(20); // 20 추가
v1.push_back(30); // 30 추가
v1.push_back(40); // 40 추가
v1.push_back(50); // 50 추가
v1.pop_back(); // 마지막 원소(50) 삭제
return 0;
}
pop_back() 함수를 이용해 마지막 원소(50)을 삭제(pop) 한다.
- erase(삭제위치) : 중간 삭제
#include <iostream>
#include <vector> // vector 사용을 위한 헤더
using namespace std;
int main()
{
vector<int> v1; // int type 벡터 생성
v1.push_back(10); // 10 추가
v1.push_back(20); // 20 추가
v1.push_back(30); // 30 추가
v1.push_back(40); // 40 추가
v1.push_back(50); // 50 추가
v1.erase(v1.begin()+2);
return 0;
}
erase() 함수를 이용해 중간값을 삭제한다. 삭제한 위치로 부터 뒤의 원소들은 모두 한칸씩 앞으로 당겨진다. 단, insert(삽입)과 마찬가지로, 중간에서 값을 삭제하는 것은 비효율 적이니 빈번한 삭제는 삼가해야 한다.
- erase(first, end] : 범위 삭제
erase() 함수에 2개의 인수를 보내주면 (first,end] 범위의 값을 삭제할 수 있다.
#include <vector>
#include <iostream>
using namespace std;
int main(){
vector<int> v = {1, 2, 3, 4, 5};
cout << "Before" << endl;
for(const int& i : v)
cout << i << " ";
cout << endl;
cout << "After" << endl;
v.erase(v.begin(), v.begin() + 2); // delete 1 & 2
for(const int& j : v)
cout << j << " ";
return 0;
}
Before
1 2 3 4 5
After
3 4 5
Vector 원소 위치 변경
- iter_swap
iter_swap 함수를 사용해 vector 내부 원소의 위치를 변경할 수 있다. 다음은 iter_swap 함수를 이용해 벡터의 첫 번째 원소와 마지막 원소의 위치를 변경한 예제이다.
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v = {1, 2, 3, 4, 5};
cout << "Before iter_swap" << '\n';
for(int i : v)
cout << i << " ";
cout << '\n';
iter_swap(v.begin(), v.begin() + 4);
cout << "After iter_swap" << '\n';
for(int i : v)
cout << i << " ";
cout << '\n';
return 0;
}
[실행 결과]
Before iter_swap
1 2 3 4 5
After iter_swap
5 2 3 4 1
#3 SIZE & CAPACITY
size() , max_size() , capacity() 멤버 함수를 이용해 size와 capacity의 관계에 대해 설명하도록 하겠다. size()와 max_size() 는 vector 뿐 만 아니라 모든 컨테이너가 지니고 있는 멤버함수이다. 그러나, capacity()는 유일하게 vector만이 갖고 있는 멤버함수이다. capacity는 재할당에 드는 성능 문제를 보완하기 위해 만들어진 vector만이 가진 중요한 특징으로, 원소가 추가될 때 마다 메모리를 재할당 하는것은 비효율 적이기에, 미리 넉넉한 메모리를 확보해 재할당에 드는 비용을 줄여준다.
글로만 보면 size와 capacity의 차이점이 잘 와닿지 않을테니, 코드를 통해서 알아보자.
■ size(), max_size(), capacity()
vector는 크기를 반환하는 세가지 멤버 함수 size(), max_size(), capacity()를 가진다.
size()는 컨테이너에 저장된 원소의 개수이고, max_size()는 컨테이너가 담을 수 있는 최대 원소의 개수이다.
마지막으로 capacity()는 실제로 할당된 메모리 공간의 크기이다.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
cout << v.size() << endl;
cout << v.capacity() << endl;
cout << v.max_size() << endl;
return 0;
}
<출력결과>
5
8
4611686018427387903
그림으로 나타내 보면 다음과 같다.
Capacity가 늘어나는 기준은 컴파일러 마다 다르다. 여기서 꼭 기억해 두어야 하는 것은 size와 capacity는 다르다는 것이다. 그리고 size()는 원소의 개수를 반환해 주기 때문에 마치 자바의 length()나, C언어의 strlen처럼 활용 가능하다.
for (int i = 0 ; i < v.size() ; ++i)
cout << v[i] << " ";
<출력결과> 10 20 30 40 50
■ resize()
resize() 멤버함수를 이용하면, 컨테이너의 size를 변경할 수 있다.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
cout << "BEFORE resize() ";
cout << v.size() << " " << v.capacity() << endl;
v.resize(4);
cout << "AFTER resize() ";
cout << v.size() << " " << v.capacity() << endl;
return 0;
}
<출력결과>
BEFORE resize() 5 8
AFTER resize() 4 8
그러나, 출력 결과를 보면 알 수 있듯이 resize() 멤버함수로 size를 줄인다고 해서, capacity가 같이 줄지는 않는다.
■ reserve()
vector 컨테이너는 동적으로 원소를 계속 추가할 수 있다는 장점을 갖고 있지만, 그만큼 메모리 재할당 측면에서는 비용이 많이 발생한다는 단점이 있다. 그래서 이러한 단점을 조금이나마 보완하고자 미리 capacity를 결정할 수 있는 reserve() 멤버 함수를 제공한다.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v. reserve(10);
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
cout << v.size() << " " << v.capacity() << endl;
return 0;
}
<출력결과>
5 10
얼마나 메모리를 확보해야 하는지 사전에 알고있는 상황이라면 reserve() 멤버함수를 사용해 재할당에 드는 비용을 조금이나마 축소할 수 있다.
#4 원소접근
Vector는 임의 위치의 원소를 참조할 수 있도록 [] 연산자와 at() 멤버함수 2가지 인터페이스를 제공한다.
두 인터페이스의 기능은 동일하지만, []연산자는 범위 점검을 하지않아서 속도가 빠르지만 자칫하면 out_of_range 에러가 날 수 있어서 위험하다. 반대로 at() 멤버함수는 범위 점검을 수행하여 속도는 느리지만 안전하다.
[]와 at()의 예제 코드를 보고 사용방법을 익혀보도록 하자.
■ [] 연산자
사실 앞에서 size()를 설명할 때 이미 한 번 사용했다. []연산자는 배열처럼 안에 탐색하고자 하는 인덱스를 적어 주면 된다.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
for (int i = 0 ; i < v.size() ; ++i)
{
cout << v[i] << " ";
}
return 0;
}
<출력결과>
10 20 30 40 50
앞서 말한 바와 같이 범위 점검을 하지 않기에, []연산자를 사용할 때는 항상 범위에서 벗어나지 않았는지 체크해야만 한다.
■ at() 멤버함수
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
for (int i = 0 ; i < v.size() ; ++i)
{
cout << v.at(i) << " ";
}
return 0;
}
<출력결과>
10 20 30 40 50
[]연산자와 기능은 동일하다. 그런데 만약 다음과 같이 범위에서 벗어난 인덱스를 지정해보면
v.at(10);
터미널에 에러 메시지가 발생한다. ([]연산자 였다면, 가비지값이 출력되었을 것이다.)
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 10) >= this->size() (which is 5)
그래서 try ~ catch 와 같은 예외 처리문을 at() 멤버함수와 같이 이용해 주곤한다.
'Archive2 > C&C++' 카테고리의 다른 글
[C++ STL] Deque Container 사용 방법 & 관련 예제 총 정리 (0) | 2021.05.10 |
---|---|
[C++ STL] sort 정렬 함수 사용 방법 정리 (오름차순 & 내림차순) (0) | 2021.05.09 |
[C++ STL] Queue Container 사용법 정리 (0) | 2021.05.06 |
[C/C++] Queue(큐) 자료구조 정리 (1) | 2021.05.06 |
[C++ STL] Pair Container 사용법 정리 (With Vector, Typedef, Sort) (0) | 2021.05.03 |