초보 강좌를 쓰기 위해서는 내가 초보가 되어야 한다.

완전히 초보 입장에서 쓰지 못하면 인터넷에 널린 또 하나의 쓰레기가 되고 만다.

솔직히 이런 글 적으면서 오글오글 거린다.

역시 초보 강좌는 인내가 필요하다.

시불 내가 언제 초보였더라...


인터넷에 널리고 널린 자료들을 일일이 설명하는건 솔직히 시간 아깝다.

아쉽지만 이 강좌만 본다고 초보딱지 떼지는건 아닐 것이다.

이 시리즈는 연재속도가 빠를 수 없어서 복습용 정도로 보면 좋을 것 같다.

다 떠먹여 주는 수준의 강좌는 엄청난 난이도가 될 것 같으니 말이다.

그래도 노력중이란걸 알아주기 바란다. ( 어쩌면 초보 여러분 보다 훨씬 )


대신, 내 강좌에 약속할 수 있는 것은,

어느책에도 나와 있지 않은 나만의 해석, 해설, 비유가 녹아 있을 것이란 거다.


*   *   *


오늘은 STL 과 함께 하겠다.


STL 에는 데이타를 보관하기 위한 다양한 컨테이너를 제공한다.


순차 컨테이너

1. 배열 ( array )

2. 동적 배열 ( vector )

3. 양방향 큐 ( deque )

4. 단방향 리스트 ( forward_list )

5. 양방향 리스트 ( list )


연관 컨테이너

1. 정렬된 유일 key 집합 ( set )

2. 정렬된 유일 key 와 값의 집합 ( map ) - 별명은 dictionary 다. 사전처럼 쓰면 좋다.

3. 정렬된 중복허용 key 집합 ( multiset )

4. 정렬된 중복허용 key 와 값의 집합 ( multimap )


비정렬 연관 컨테이너 ( ordered 는 sorted 와 같은 비슷한 의미다 )

1. 유일 key 해시 ( unordered_set )

2. 유일 key 와 값의 해시 ( unordered_map )

3. 중복허용 key 해시 ( unordered_multiset )

4. 중복허용 key 와 값의 해시 ( unordered_multimap )


그밖에.

1. 스택 ( stack )

2. 큐 ( queue )

3. 우선순위큐 ( priority_queue )


대충 이름보고 특징을 알 수 있는건,

multi 가 들어가면 중복을 허용한다는 것이다.


유일 key ( unique key ) 라는건,

보통 파일 시스템이나 데이타베이스에서 자세히 언급하게 되는데,


원하는 데이타를 찾아가기 위한 검색 대상이 key 이고,

주민등록 번호 같은것은 유일하니까 unique key 라고 부르지만,

이름 같은건 유일하지 못하니까 ( 동명이인 ) 그냥 key 라고 부른다.

위에 중복허용 이라고 적어놓은건 나의 에드립.


그리고 set 은 키만 보관하는것,

map 은 key + 추가데이타 라고 보면 된다. ( std::pair 로 둘을 묶어서 사용한다 )


unordered 는 정렬형태가 아닌 bucket 형태로 접근하기 때문에,

정렬비용은 들지 않고 꽤 빠르게 찾아갈 수 있는 컨테이너다.

자료구조에서 hash 를 참고하기 바란다. counter sort 나 radix sort 에도 개념이 나온다.


여기선 대충 이런게 있다 정도만 알고 넘어가자. 레퍼런스에 잘 나오니까 각자 읽어보시고.

예제에 사용할땐 사용되는 method 나 member 위주의 간단한 설명만 하겠다.


*   *   *


닥치고 소스가 등장한다.


#include<iostream>

#include<vector>


int main()

{

    int a{ 0, 1, 2, 3, 4 };

    std::vector< int > b{ 0, 1, 2, 3, 4 };


    return  0;

}


아직은 낯설지 모르지만,


std:: 란 접두사가 등장했다.

지난 시간에 이야기했던 이름 뒤 <...> 기호도 등장했다.


배운대로라면 ( scope operator ) :: 를 가진 std:: 는

클래스/구조체/공용체/네임스페이스 들 중 하나일 것이다.

std 는 STL 에 포함된 기능들의 공통적인 namespace 다.


이 std::vector 란 녀석은 STL 의 네임스페이스를 가진 템플릿이란 것이지.


< int > 표현으로 타입을 지정했다는건,

int 말고도 다양한 타입의 벡터를 만들 수 있다는 의미다.


int 배열과 vector< int > 둘의 차이를 알겠는가?


미리공부한 분들은 훌륭하다.

전자는 크기가 고정된 배열이지만,

후자는 push_back, emplace_back, insert 와 같은 멤버 함수를 사용해서 크기를 늘일 수도 있고,

pop_back, erase 들을 사용해서 줄어들게 할 수도 있다.


b.emplace_back( 5 );


같은 문법을 쓸 수 있다는거지. ( 그러면 b 는 0, 1, 2, 3, 4, 5 를 갖게 된다. )


벡터는 크기와 방향을 가진 양 이란 뜻인데, 개인적으로는 부적절한 이름이라고 생각한다.

동적 배열이라는 이름이 더 어울리고,

많은 다른언어에서 dynamic_array 라는 이름을 사용한다.

하지만 일단 스펠링이 짧으니 존중해주자.


vector 는 미리 확보한 크기보다 크기가 커질 때, 스스로 메모리를 재할당( realloc ) 한다.

그래서 성능은 순수 array 보다 느려지는 경우가 있을 수 있지만,

array 를 제외한 다른 컨테이너 보다는 순차 및 임의 접근( random access ) 속도가 가장 빠른 편이다.

그리고 약간의 최적화 요령이 있으면 array 와 비슷한 성능으로 사용할 수 있다.

2 차원 3 차원으로 배열한다면 인접생성하지 않는 이상 느린건 어쩔수 없겠지만.

( 어려운 이야기 해서 미안하다 넘어가자. )

어쨌든, vector 는 써보면 무진장 편하다. 그걸로 존재의 가치는 충분.


위의 소스코드는 뭔가 허전하다.

단 하나의 출력코드도 없기 때문이다.


modern c++ 에서 추가된 ranged for 를 이용해서 간편하게 출력해 보자.


for( auto one : a )

    std::cout << one << std::endl;


for( auto one : b )

    std::cout << one << std::endl;


이런식이면 된다.


ranged for 가 없던 시절에는


for( auto it = b.begin(); it != b.end(); ++it )

    std::cout << *it << std::endl;


처럼 사용했다. ( auto 가 없던시절은 끔찍하니 패스 )


여기서 전위형 단항연산자 ( unary operator : 피연산항이 하나라는 뜻 ) ++it 에 주목하자.


for 문의 마지막 절에서 ++it 를 하나 it++ 하나 보통은 성능 차이가 없다.

기본 자료형들은 한번에 증가 연산을 할 수 있기 때문인데,

구조체들(클래스포함)을 사용할때는 별개의 이야기가 된다.

++it 는 자기 자신을 1 증가시켜서 그 자리에 놓게 되고,

it++ 은 자기를 복사해서 그 자리에 놓고 자신은 그 후에 증가되게 된다.


auto a = ++it; 는 이시점에서 it 와 a 가 같은 값이지만,

auto a = it++; 에서는 a 와 it 의 값이 다르게 되는 이치다.


그래서 iterator 라는 이름의 클래스로 만들어진 it는 it++ 표현에서 조금 더 느리다.


설명이 길었지만 그냥 ++it 를 쓰는 습관을 들이자.


감질나지만 이 강좌는 다음을 기약해야겠다. 분량이 좀 길어서 눈에 들어오지 않을게 염려되고,

일단 강좌에서 STL 을 쓰기 시작할 염치를 마련했다는데 의의를 두겠다.