14. STL+Vector+Iterator
▶ 벡터란?
- STL (Standard Templete Library) 에 속해 있는 컬렉션 중 하나로서 배열처럼 동작하는 컬렌션 클래스를 의미한다.
◇ 일반적인 템플릿 라이브러리라는 뜻
◇ 컬렉션이란 데이터를 저장하는 임의의 공간
- 즉 일반적인 배열에 비해 데이터를 추가하기 위한 갯수가 제한되어 있지 않기 때문에 컴퓨터가 허락하는 한 많은 양의 데이터를 추가하는 것이 가능하다.
◇ 동적 배열이다.
- STL 벡터는 시퀀스 기반이다.
ㄴ 별다른 규칙이나 제약이 없는 일반적인 컨테이너라고 부를 수 있다.
◇ 어떤 자료형이 와도 받아 사용이 가능하며 이는 확장성이 좋다고 할 수 있다.
- 또한 시퀀스 컨테이너는 데이터를 선형으로 저장하며 시퀀스 컨테이너는 삽입 된 요소의 순서가 그대로 유지된다.
◇ 배열의 특징 대부분이 벡터에 적용이 가능하다.
▷ 시퀀스 컨테이너
- 기본 컨테이너 규칙에 아래 규칙을 만족한다면 시퀀스 컨테이너라고 부른다.
ㄴ 대표적으로 vector, list, deque 등이 있다.
ㆍ 규칙
1. 모든 요소는 직선 순서대로 배치되야 한다.
ㄴ 첫번째 요소와 마지막 요소를 제외한 나머지 요소들은 반드시 앞뒤로 인접한 요소를 하나씩 가지고 있어야 한다.
◇ 반드시 앞 혹은 뒤가 존재해야 성립한다.
2. 최소한 순방향 반복자를 가지고 있어야 한다. (forward iterator)
ㄴ 이는 반복자가 이동할 때마다 요소들의 순서가 변하지 않음을 보장해 주기 위함이다.
◇ reverse는 선택이며, iterator가 없으면 시퀀스가 아니다.
3. 시퀀스 컨테이너의 요소들은 명확한 순서를 가진다.
ㄴ 명확한 순서를 가지기 때문에 특정 위치를 참조하는 연산이 가능하다.
▶ 벡터의 특징
- 시퀀스 기반 + 배열 기반 컨테이너
- C++ 언어에서 활용 빈도가 가장 높고 사용하기가 비교적 쉽다.
- 검색 속도가 빠르다. (읽기 능력이 탁월하다는 뜻)
◇ 읽기 능력이 들어왔을 때만 검색 속도가 빠르다는 뜻이고, 일반적으론 느리다고 볼 수 있다.
- 반면에 입출력은 느리다.
◇ 벡터의 가장 큰 단점이다.
- 새로운 메모리를 할당해 새로 추가한 원소와 함께 "복사"한다.
◇ 값 복사? 주소 복사?
▶ 반복자 (Iterator) 란?
- 컬렉션 클래스에 보관되어 있는 각 데이터에 접근하기 위한 포인터 객체로써 STL에 포함되어 있는 모든 컬렉션 클래스는 데이터의 시작과 끝을 나타내는 begin과 end함수를 지니고 있다.
- begin과 end 함수를 지니고 있기 때문에 루프를 반복해서 컬렌션에 포함되어 있는 모든 데이터에 접근하는 것이 가능하다.
▶ for each문
- 주어진 범위의 모든 데이터를 하나씩 순회하며 동작하는 경우에 사용한다.
ㄴ for each는 컨테이너의 반복자 개념
- for each는 vector의 컨테이너 요소가 아니고 알고리즘 함수
- 모든 컨테이너는 반복자를 가지고 있으며 for each를 사용해서 컨테이너의 원소 접근이 가능하다.
- 이게 가능한 이유는 for each문 역시 iterator와 동일하게 스마트 포인터이며 컨테이너를 순회하면서 원하는 요소에 접근이 가능하다.
◈ auto
◇ STL에서만 사용이 가능하며 팀 단위로 협업을 할 때는 사용을 자제하는 편이 좋다.
- 컴파일러가 컴파일 시 자동으로 자료형을 추론한다.
- 컴파일러가 자료형을 추론하기 위해서는 명시적인 조건이 주어져야 한다.
C# 에서 var에 해당하는 문법이다.
- C / C++ 언어는 기본적으로 변수를 선언 할 때 해당 변수의 자료형을 명시해야 한다.
- auto 키워드 자체가 C#의 var과 유사하며 자료형이 확정되는 시기는 컴파일 타임이다.
◇ templete과의 호환성 때문에: 컴파일 타임??
- auto 키워드에 의한 자료형이 결정되는 시점은 변수에 할당되는 초기값을 가지고 판단하기 때문에 auto를 통한 변수 선언은 특정 값을 할당해야 한다.
#pragma once
#include <iostram>
#include <vector>
#include <string>
#include <Windows.h>
using namspace std;
struct STData
{
private:
int m_nValue;
std::string m_oString;
};
class MainGame_05
{
private:
vector<int> _vNumber;
vector<MainGame_05*> vMainGame_05;
vector<int>::iterator _viNumber;
vector<int>::reverse_iterator _vriNumber;
public:
void printVector();
/*
◇ inline 키워드가 들어가면 함수 임에도 스택 영역에서 관리하지않음.
◇ 실행속도 향상을 위해 사용하며 헤더파일에서 선언과 동시게 정의함.
*/
inline void LinPrint() { cout << "==========" << endl; }
MainGame_05();
~MainGame_05();
};
/*
포인터 접근이기 때문에 전위/후위 증감 선택이 중욯마. 전위 증감은 메모리를 확인하고 넘어가겠다는 의미
*/
for (auto Iterator = ValueListA.begin(); Iterator != ValueListA.end(); ++Iterator)
{
! Do Something
}
==================================================================
/*
- push_back()
ㄴ 데이터를 뒤에서 부터 추가한다. (값)
- emplace_back()
ㄴ 데이터를 뒤에서 부터 추가한다. (값)
- pop_back()
ㄴ 데이터를 뒤에서 부터 값을 삭제한다.
-size()
ㄴ 벡터의 크기 (갯수)
- resize()
ㄴ 커기 재연산
◇ 호출 했을 때 안정성이 올라가지만 코스트를 지불해야 한다.
- clear()
ㄴ 인덱스 전부 삭제
◇ 컴파일러가 메모리 할당하는 작업을 하지 않기 때문에 속도가 빠르다.
- begin()
ㄴ 벡터의 0번째 인덱스
- end()
ㄴ 벡터의 마지막 인덱스
- insert(위치, 값)
ㄴ 삽입
- insert(위치, 개수, 값)
ㄴ 삽입
- erase(위치)
ㄴ 지운다.
*/
/*
이 형태가 표준이다.
안전도: 최상
ㄴ 반복자를 통한 순회
◇ 시작점 끝점을 명시하고 메모리 포인터 확인까지 함.
◇ 컴퓨터가 알아서 연산하며 반복자가 순회하면서 값을 앙ㄹ아서 지워줌.
*/
_viNumber = _vNumber.begin();
for (_viNumber; _viNumber != _vNumber.end(); ++_viNumber)
{
cout << *_viNumber << endl;
}
for each (auto p in _vNumber)
{
cout << "for each문" << p << endl;
}
erase(_vNumber.begin() + index);
erase(_vNumber.begin() + s, _vNumber.begin() + e);
_vNumber.erase(_vNumber.begin() + 1);
//스타트 -> 엔드
_vNumber.erase(_vNumber.begin(), _vNumber.begin() + 3);
// 역순회로 일반적으로 자주 사용하지 않는 기능은 전용 함수가 있다.
for (_vriNumber = _vNumber.rbegin(); _vriNumber !- _vNumber.rend(); ++_vriNumber)
{
cout << *_vriNumber << endl;
}
/*
면접문제
ㄴ 차이점? -> 안정성
at: 임의 위치의 원소를 참조하는 인터페이스
at == [] 동일한 결과물을 보여준다.
[] : 범위 점검을 하지 않기 때문에 속도 면에서 유리하다.
at() : 범위 점검을 하기 때문에 속도는 느리다. 단, 안정성 면에서는 훨씬 유리하다.
*/
cout << _vNumber.at(4) << endl;
cout << _vNumber[4] << endl;
/*
첫번째 요소 참조
방이 전부 안착이 되어 있는 지 확인이 가능하다.
◇ begin이 빠지는 경우가 있으며 데이터를 싹 다 클리어 하고 재할당이 발생 했을 때
◇ 메모리적으로 안정이 안되었기 때문에 front를 찍었는데 주소값이 있으면 메모리에 안착이 되어있다.
◇ 벡터는 배열 기반이라 0번째 값을 살려야 한다.
*/
cout << _vNumber.front() << endl;
/*
마지막 원소 참조
ㄴ 인덱스가 전부 안착이 되어 있는지 확인한다.
*/
cout << _vNumber.back() << endl;
// 현재 벡터의 사이즈를 확인한다.
cout << _vNumber.size() << endl;
if (_vNumber.empty())
{
cout << "텅텅 비었다." << endl << endl;
return;
}
// 벡터 값이 비어 있으면 내부 로직을 실행 할 필요가 없다는 뜻이다.