초보 강좌를 쓰기 위해서는 내가 초보가 되어야 한다.
완전히 초보 입장에서 쓰지 못하면 인터넷에 널린 또 하나의 쓰레기가 되고 만다.
솔직히 이런 글 적으면서 오글오글 거린다.
역시 초보 강좌는 인내가 필요하다.
시불 내가 언제 초보였더라...
인터넷에 널리고 널린 자료들을 일일이 설명하는건 솔직히 시간 아깝다.
아쉽지만 이 강좌만 본다고 초보딱지 떼지는건 아닐 것이다.
이 시리즈는 연재속도가 빠를 수 없어서 복습용 정도로 보면 좋을 것 같다.
다 떠먹여 주는 수준의 강좌는 엄청난 난이도가 될 것 같으니 말이다.
그래도 노력중이란걸 알아주기 바란다. ( 어쩌면 초보 여러분 보다 훨씬 )
대신, 내 강좌에 약속할 수 있는 것은,
어느책에도 나와 있지 않은 나만의 해석, 해설, 비유가 녹아 있을 것이란 거다.
* * *
오늘은 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 을 쓰기 시작할 염치를 마련했다는데 의의를 두겠다.
우왕 초급 강좌 땔치고 중급강좌하고픈 생각이 막 굴뚝. ㅋㅋ 그래도 일단락은 지어야지.
초보일때 제일 서러울테니 ㅠㅠ
근데 형 map이 키 탐색 시간 때문에 많이 쓰면 엄청 비효율적이라던데 정말 그런거임? - return 0;
원래 sort 도 그렇고 컨테이너도 그렇고 bucket 류가 워낙 자료수가 많을때 사기적으로 유리해서 그런거임.
map 이야 2진탐색 성능이지뭐.
대신 hash류는 collision 처리가 제대로 되어야 성능발휘를 하는거구.
그냥 2진탐색 성능이구낭 bucket류를 공부해야겠다 ㄱㅅㄱㅅ - return 0;
자료구조나 컨테이너 선택을 단순히 크기만으로 할 노릇은 아니구, 저장장소 특성, 읽기 위주인가 쓰기 위주인가 등에 맞춰 선택해야함유.
어울리지않아요이딴곳에
끼얹기라고 해서 어댑터블 클래스 템플릿 얘기 나올중 알앗는데 저같은 초보을 위한 입문 자료네요 감사람다
감사합니다 잘보고있어요^^ 저같은 초보자들에게 너무 소중한글입니다