전문가보다 더 고약한 사람은 제가 전문가라고 생각하는 사람. ―A.A.C.
[[Include(분류/Cpp)]]
- 주요링크 :
한글로 작성된 list 메뉴얼 - 좀 어렵습니다.
1 도대체 vector와 list에는 무엇을 담을 수 있는가? #
여기서 설명하는 vector와 list를 STL에서는 컨테이너(container)라고 부르는데, 이는 지정된 타입의 데이타를 담고있다라는 뜻에서 기인한 것이다. 이 컨테이너에는 기본 데이타형은 무엇이든지 들어갈 수 있지만, 다음과 같은 경우는 주의가 필요하다.
- 컨테이너에 담을 수 있는 것은 객체이다. 그러므로 레퍼런스(reference)를 담을 수 없음을 명심하자.
- 컨테이너에 포인터를 담을 경우는, 해당 포인터에 할당된 메모리에 대한 할당/해제는 알아서 관리해주어야한다. (컨테이너가 관리해주지 않는다.
- 컨테이너에 직접 작성한 클래스를 담을 경우는 반드시 그 클래스내에 기본(default) 생성자, 복사 생성자, 대입 연산자, 소멸자를 가지고 있어야 한다.
2 list? #
STL을 개인적으로 즐겨쓰는 이유가 간단한 자료구조들을 손쉽게 운용할 수 있는 점 때문이라고 생각하고 있다. 한두줄로 연결리스트가 생긴다고 생각하면 정말 매력적이지 않을까? list는 STL에서 제공하는 양방향 연결리스트 구조를 제공하기 위한 템플릿 클래스이다. (STL용어로 container라고 한다) 이를 짬짬히 사용할 때 초보자가 궁금할만한 것들을 토막내서 나열해볼까 한다.
기본사용법은 "STL 튜토리얼, 레퍼런스 가이드 제2판"(인포북)을 참고하기 바란다. 아참, 개인적으로 STLPort를 설치해서 사용하고 있으므로 이곳의 예제는 STLPort위주로 설명하겠다.
3 n번째 위치에 삽입하기 #
list의 iterater는 + 연산자가 잘 먹지 않는다. 그러므로 어떠한 종류의 컨테이너에서도 동작하는 advance()함수를 사용하는 것이 바람직하다. 만일, 내부적으로 10개의 요소가 들어가 있는 list가 다음과 같이 존재할 때,
list<float> sample_list;5번째에 데이타를 넣고 싶다면 다음과 같이 실행한다.
list<float>::iterator pList = sample_list.begin(); advance(pList, 4); // C 인덱스와 같이 0부터 시작한다. ^^a sample_list.insert(pList, 3.4f);
번거롭지만 iterator선언을 해주고 위와 같이 해주어야 한다. -_-a 실제로 연결리스트를 사용할 때에는 임의의 위치에 삽입한다기 보다는 조건을 따져서 맞는 위치에 삽입하는 경우가 많기 때문에 아래에 설명할 find를 사용하는 것이 일반적이다. 이럴 때는 반드시 iterator 변수가 따라오니까 그것을 이용하면 된다.
4 모든 요소에 대하여 하나하나 처리하기 #
예를 들어 list안에 데이타가 가득들어있다고 가정하자. 이들에 대해 모두 어떤 처리(예를 들면 화면 출력)을 하고 싶을때는 다음과 같이 하면된다.
list<float> sample_list;
...
for(list<float>::iterator i_ = sample_list.begin(); i_ != sample_list.end(); advance(i_, 1)) {
float value_ = *i_;
// 이제 value_를 가지고 어떤 작업을 한다.
}
5 정렬하기 #
list와 같은 컨테이너는 별도의 외장함수(STL에서는 알고리즘이라고 한다)를 쓰지 않고 내장 맴버함수를 사용하여 정렬을 수행한다. 두가지 방법이 있는데, 첫번째는 인자를 넣지 않고 그냥 실행하는 것이다.
list<float> sample_list; ... sample_list.sort();위 표현은 무조껀 내림차순으로 정렬을 수행한다. 근데, 이 방법은 몇가지 단점이 있다. 타입이 float같은 기본타입이어야만 한다는 점이고(string도 가능) 오름차순에 대한 옵션이 없다는 점이다. 자, 이제 두번째 방법이다. 인자가 한개 추가된다.
list<float> sample_list; ... sample_list.sort(less<float>());less<float>()이 추가되었다. 이것은 특별히 비교를 수행하기 위한 탬플릿 객체인데 STL에서만 볼 수 있는 특이한 넘이다. -_-a (비교 객체라고 부른다) 위 예제는 앞서 실행한 예제와 동일하게 수행된다. 즉, 두 값을 비교할때 앞넘보다 뒷넘이 작으면(less) 둘을 교환한다라는 의미인 것이다. 그러므로 결과는 내림차순이 된다. 자, 오름차순도 삘이 오지 않는가? ^_^a 위 인자부분을 greater로 바꿔보자.
list<float> sample_list; ... sample_list.sort(greater<float>());()를 빼먹으면 안된다. 주의하도록 한다. 이제 오름차순으로 정렬된 것을 확인해보면 된다. 근데 아직도 문제가 있다. 구조체를 list로 선언하면 어떻한단 말인가? 구조체를 greater<>, less<>에 적용해서 쓸 수는 없다. 예를 들면 아래와 같은 경우이다.
typedef struct _sample_type {
int index; // 이걸 기준으로 정렬한다고 가정하자.
string data;
} SAMPLETYPE;
list<SAMPLETYPE> list2;
자, 이것을 index 기준으로 정렬한다고 생각해보자. 이때에는 방법이 없다. greater<>와 같은 비교객체를 직접 작성하는 수 밖에 없다. -_-a 즉, index만을 비교하려면 아래와 같이 작성할 수 있다.
class SAMPLETYPE_index_less {
public:
bool operator() (const SAMPLETYPE &a, const SAMPLETYPE &b) const {
return a.index < b.index;
};
};
허걱. 연산자 오버로딩에 괴상한 const까지... 하지만 이게 비교 객체(STL에서는 predicate 라고 한다)의 정체다. 다른 건 몰라도 ()연산자 함수는 정확히 기술해야한다. (연산자 오버로딩이 궁금하신 분은 C++책에서 자습하기 바란다. _ 노동부 수강생분들은 강의해드릴께요~ ^_^) 위에서 클래스 명을 보면 SAMPLETYPE의 index를 작을때(less) 교환하도록 지정하기 위한 의미임을 알 수 있다. 구미에 맞게 바꾸는 방법은 다음과 같다.
- SAMPLETYPE_index_less와 같은 클래스 명은 아무거나 가능하다. 원하는 이름을 지어서 써라.
- operator() 맴버함수의 인자중 SAMPLETYPE은 원하는 구조체의 이름으로 지정한다.
- operator() 맴버함수의 내용중 a.index < b.index는 조건에 따라 작성한다. 예를 들어, 문자열(data)끼리 비교하고 싶다면 a.data < b.data라고 적으면 된다. (string형이므로 비교연산자를 사용할 수 있다)
list2.sort(SAMPLETYPE_index_less());<>이 빠진 것에 주목하자. greater<>, less<>는 기본 데이타형을 위한 범용 비교객체이므로 템플릿으로 작성되어있기 때문에 <>이 필요했던 것이다. SAMPLETYPE_index_less은 SAMPLETYPE 전용이므로 굳이 템플릿을 쓸필요가 없는 것이다. 다시 한번 강조하지만 ()는 반드시 넣어주어야 한다. 이것은 클래스의 기본 생성자를 동작시킨 후 그 포인터를 인자로 넘겨준다는 의미이다. 무슨 뜻인지 골치아프다고 생각되시는 분은 ()를 꼭 써주어야한다정도만 알아도 사용가능하다. (약간 불안하겠지만...^_^)
오름차순으로 정렬하는 것과 문자열 기준으로 정렬하는 것도 직접 해보자. 이 비교객체라는 게 선언할 때는 귀찮지만, 미리 만들어놓으면 코드 중간에서 상당히 편하게 정렬할 수 있다는 것을 알 수 있을 것이다.
6 중복값이 없게 만들기 #
이것은 정말 간단하다. 대신 sort()가 이미 실행되어있어야 한다. unique() 함수를 사용하면 된다.
1 1 1 2 2 2 2 3 3 4 5 5 5 -> 1 2 3 4 5즉, 다음과 같이 실행하면 된다.
list2.unique();
7 find()와 find_if() #
자, 이제 원하는 인자가 어디에 있는지 찾아주는 find()와 find_if()를 알아보자. 이들은 STL용어로 알고리즘이라고 한다. 즉, list에서만 쓰이는 것이 아니라 vector와 같은 다른 컨테이너에서도 사용될 수 있다. 이들의 주 목적은 원하는 조건을 충족하는 첫번째 칸을 찾아 그 칸을 나타내는 iterator를 반환시키는 것이다. 헉헉...=.=a 참 거창하지 않을 수 없다. 간단히 말하면 원하는 값이 어디에 있는지 찾아주는 것이다. ^^; (iterator라는 것은 STL세계의 '포인터'같은 존재라는 것을 명심하자)
list<float> list1;
...
list<float>::iterator pFound = find(list1.begin(), list1.end(), 0.4f);
printf("%f\n", *pFound);
위는 find()의 사용예제이다. pFound는 0.4f값이 담긴 부분의 위치를 담게 된다. 즉, 위 예제는 같은 0.4f를 출력하거나, 만일 0.4f가 리스트내에 없다면 list1.end()를 반환하게 된다. 즉, 다음과 같이 쓰면 좀더 완벽한(?) 문장이 된다.
list<float>::iterator pFound = find(list1.begin(), list1.end(), 0.4f);
if (pFound != list1.end())
printf("%f\n", *pFound);
근데, 이것도 문제가 있다. 뭐냐면 위 sort()에서 언급한 구조체 리스트일 경우이다. 즉, 구조체는 같다라는 조건으로 비교하기는 애매모호하다. 대부분의 경우 내부적인 맴버변수가 어떤 값일 경우가 대부분인 것을 생각해 볼 때 위 find()함수는 기본 데이타형으로 이루어진 list에서만 사용할 수 있을 것이다. (근데 이런 경우는 또 거의 없다. 연결리스트를 쓰는 경우는 구조체가 각 칸일 경우가 대부분인 것을 생각해보자) 이것은 명백한 직무유기에 해당한다. ^^; 그러므로 약간은 더 범용적인 find_if()를 보자.
class my_equal_obj {
public:
bool operator() (float &a) const {
return (a.index == 0.007f);
};
};
list<float>::iterator pFound = find_if(list1.begin(), list1.end(), my_equal_obj());
if (pFound != list1.end())
printf("%f\n", *pFound);
허걱. 이번엔 또 비교객체란 말인가... 하는 생각이 들지도 모른다. 개념은 맞지만 가만 보면 이전의 sort()일때와는 다른 점이 있다. 인자가 1개다. 즉, find_if()는 리스트의 각 요소를 비교해가면서 찾는 것이기 때문에 인자가 1개밖에 필요하지 않은 것이다. 위 방법은 여러모로 쓸모있다. 즉, my_equal_obj를 다음과 같이 정의한다면
class my_equal_obj {
public:
bool operator() (float &a) const {
return (a >= 0.007f);
};
};
0.007이상인 칸을 찾으라는 의미가 된다. 게다가 구조체도 문제없다. sort()때 사용했던 SAMPLETYPE 구조체를 인용해보기로 한다.
class my_equal_obj {
public:
bool operator() (SAMPLETYPE &a) const {
return (a.index >= 5);
};
};
list<SAMPLETYPE>::iterator pFound = find_if(list1.begin(), list1.end(), my_equal_obj());
if (pFound != list1.end())
printf("%f\n", pFound->index);
float가 SAMPLETYPE으로만 변경되었다. (그에 따라 비교객체 내의 ()연산자 오버로딩 함수도 index를 참고하도록 변경되었다) 위 예제는 index가 5이상인 칸을 검색한다.
자, 위 비교객체를 선언해서 써먹는 방법까지 알아보았다. 이외에 함수 어댑터라는 객체를 상속받아 사용하는 아주 융통성있는 방법도 있지만 그것은 조금 복잡하므로 따로 설명하도록 하겠다. 위 방법까지만 잘 사용해도 왠만한 검색은 수행할 수 있을 것이다.









