[펌] About STL : C++ STL 프로그래밍(9)-알고리즘1

|

제공 : 한빛 네트워크
저자 : 최흥배
이전기사 :

제가 한빛 네트워크에 C++ STL 글을 연재한지 벌써 1년이 넘었습니다. template부터 시작하여 이전 회까지는 STL의 컨테이너들에 대해 설명했습니다. 이제 알고리즘에 대한 것만 남았습니다. 제가 쓴 글들을 보고 C++ STL을 공부하는데 얼마나 도움이 되는지 알 수 없지만 조금이나마 도움이 되었기를 바랍니다.
앞으로 2회에 걸쳐서 STL의 알고리즘 중 많이 사용하고 있는 것을 중심으로 설명하겠습니다.
STL의 컨테이너는 그 자체만으로도 대단히 유용한 것이지만 알고리즘과 결합되면 유용성은 더욱 더 커집니다.
STL의 알고리즘은 컨테이너처럼 제네릭(총칭적)합니다. 제네릭 하다고 하는 이유는 STL에 있는 다양한 알고리즘을 한 종류의 컨테이너에만 사용할 수 있는 것이 아닌 vector, list, deque, map 등 여러 가지 다양한 컨테이너에 사용할 수 있기 때문입니다(참고로 STL 컨테이너만이 아닌 일반 배열 구조도 알고리즘에 사용할 수 있습니다). 

9.1 STL 알고리즘 분류 

STL 알고리즘은 크게 네 가지로 분류 할 수 있습니다.

  • 변경 불가 시퀀스 알고리즘
  • 변경 가능 시퀀스 알고리즘
  • 정렬 관련 알고리즘
  • 범용 수치 알고리즘

변경 불가 시퀀스 알고리즘에는 find와 for_each 등이 있으며 대상 컨테이너의 내용을 변경하지 못합니다. 변경 가능 시퀀스 알고리즘으로는 copy, generate 등이 있으며 대상 컨테이너 내용을 변경합니다.
정렬 관련 알고리즘은 정렬이나 머지를 하는 알고리즘으로 sort와 merge 등이 있습니다.
범용 수치 알고리즘은 값을 계산하는 알고리즘으로 accumulate 등이 있습니다. 

STL의 모든 알고리즘을 설명하기에는 많은 지면이 필요하므로 위에 소개한 알고리즘 카테고리 별로 자주 사용하는 알고리즘 중심으로 어떤 기능이 있으며 어떻게 사용하는지 설명하겠습니다. 

9.2 조건자 

알고리즘 중에는 함수를 파라미터로 받아들이는 것이 많습니다. 알고리즘에서 함수를 파라미터로 받아들이지 않거나 기본 함수 인자를 사용하며 알고리즘을 사용하는 컨테이너의 데이터는 기본 자료 형만을 사용할 수 있습니다. 유저 정의형 자료 형(struct, class)을 담는 컨테이너를 알고리즘에서 사용하기 위해서는 관련 함수를 만들어서 파라미터로 넘겨줘야 합니다.
알고리즘에 파라미터로 넘기는 함수는 보통 함수보다는 함수객체를 사용하는 편입니다. 

9.3 변경 불가 시퀀스 알고리즘 

9.3.1. find 

컨테이너에 있는 데이터 중 원하는 것을 찾기 위해서는 find 알고리즘을 사용하면 됩니다.
참고로 알고리즘을 사용하려면 algorithm 헤더 파일을 추가해야 합니다.

#include < algorithm >

find의 원형

template<class InputIterator, class Type>
InputIterator find( InputIterator _First, InputIterator _Last, const Type& _Val );

첫 번째 파라미터에 찾기를 시작할 반복자 위치, 두 번째 파라미터에 마지막 위치(반복자의 end()와 같이 이 위치의 바로 앞까지 검색함), 세 번째 파라미터에는 찾을 값을 넘깁니다.
find가 성공하면 데이터가 있는 위치를 가리키는 반복자를 반환하고, 실패하면 반복자의 end()를 반환합니다. 

find 사용 방법

vector< int > ItemCodes;
……..
// ItemCodes 컨테이너의 시작과 끝 사이에서 15를 찾는다.
find( ItemCodes.begin(), ItemCodes.end(), 15 );

find를 사용하여 캐릭터의 아이템을 검색하는 예제 코드 입니다. 

< 리스트 1. 캐릭터 아이템 검색 >

#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;


int main()
{
  vector< int > CharItems;
  CharItems.push_back( 12 );
  CharItems.push_back( 100 );
  CharItems.push_back( 77 );

  vector< int >::iterator FindIter;

  // CharItems의 처음과 끝에서 12를 찾는다.
  FindIter = find( CharItems.begin(), CharItems.end(), 12 );
  if( FindIter != CharItems.end() )
  {
    cout << "CharItem 12를 찾았습니다." << endl;
  }
  else
  {
    cout << "CharItem 12는 없습니다" << endl;
  }

  // CharItems 두 번째와 끝에서12를 찾는다
  // ++CharItems.begin()로 반복자를 한칸 이동 시켰습니다.
        FindIter = find( ++CharItems.begin(), CharItems.end(), 12 );
  if( FindIter != CharItems.end() )
  {
    cout << "CharItem 12를 찾았습니다." << endl;
  }
  else
  {
    cout << "CharItem 12는 없습니다" << endl;
  }

  return 0;
}

< 결과 > 

1 

<리스트 1>에서는 vector 컨테이너를 대상으로 했지만 vector 이외의 list, deque도 같은 방식으로 사용합니다.
다만 map, set, hash_map의 연관 컨테이너는 해당 컨테이너의 멤버로 find()를 가지고 있으므로 알고리즘에 있는 find가 아닌 해당 멤버 find를 사용합니다. 

9.3.2 find_if 

find를 사용하면 컨테이너에 있는 데이터 중 찾기 원하는 것을 쉽게 찾을 수 있습니다. 그런데 find만으로는 많이 부족합니다. 이유는 find는 기본형만을 컨테이너에 저장하고 있을 때만 사용할 수 있습니다. 만약 유저 정의형을 저장하는 컨테이너는 find를 사용할 수 없습니다. 이럴 때는 9.2에서 짧게 설명한 조건자를 사용해야 합니다. 즉 조건자에 어떤 방식으로 찾을지 정의하고 알고리즘에서 이 조건자를 사용합니다.
find_if는 기본적으로 find와 같습니다. 다른 점은 조건자를 받아들인다는 것입니다. 

find_if 원형

template<class InputIterator, class Predicate>
InputIterator find_if( InputIterator _First, InputIterator _Last, Predicate _Pred );

find와 비교하면 첫 번째와 두 번째 파라미터는 동일합니다. 세 번째 파라미터가 다릅니다. find는 세 번째 파라미터에 찾기 원하는 값을 넘기지만 find_if는 조건자를 넘깁니다. 

find_if 사용법

// 컨테이너에 저장할 유저 정의형 
struct User
{
    …….
    int Money;
    int MobKillCount;
    ……
};

// 조건자
struct FindBestUser()
{
   …………………….
};

vector< User > Users;
……..

find_if( Users.begin(), Users.end(), FindBestUser() );

조건자가 있다는 것 이외에는 find_if는 find와 같으므로 조건자 부분만 잘 보시면 됩니다. 

< 리스트 2. 특정 돈을 가지고 있는 유저 찾기 >

#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;

struct User
{
  int Money;
  int Level;
};

struct FindMoneyUser
{
  bool operator() ( User& user ) const { return user.Money == ComparMoney; }
  int ComparMoney;
};

int main()
{
  vector< User > Users;

  User user1; user1.Level = 10; user1.Money = 2000;
  User user2; user2.Level = 5;  user2.Money = -10;
  User user3; user3.Level = 20; user3.Money = 35000;

  Users.push_back( user1 );
  Users.push_back( user2 );
  Users.push_back( user3 );

  vector< User >::iterator FindUser;

  FindMoneyUser tFindMoneyUser; 
  tFindMoneyUser.ComparMoney = 2000;
  FindUser = find_if( Users.begin(), Users.end(), tFindMoneyUser );
  if( FindUser != Users.end() )
  {
    cout << "소지하고 있는 돈은: " << FindUser->Money << "입니다" << endl;
  }
  else
  {
    cout << " 유저가 없습니다. " << endl;
  }

  return 0;
}

< 결과 > 

2 

<리스트 2>에서는 조건자 함수를 함수 포인터가 아닌 함수 객체를 사용하였습니다. 함수 객체를 사용한 이유는 함수 객체는 inline화 되므로 함수 포인터와 비교하면 함수 포인터 참조와 함수 호출에 따른 작업이 필요 없으며, 함수 객체를 사용하면 객체에 특정 데이터를 저장할 수 있어서 조건자가 유연해집니다. 

<리스트 2>의 FindMoneyUser를 일반 함수로 만들었다면 비교로 사용할 ComparMoney의 값은 함수를 정의할 때 고정됩니다. 그러나 함수 객체로 만들면 객체를 생성할 때 ComparMoney의 값을 설정할 수 있어서 유연해집니다. 

<참고>
조건자를 사용하는 알고리즘 : STL의 알고리즘은 조건자를 사용하지 않는 것과 조건자를 사용하는 알고리즘 두 가지 버전을 가지고 있는 알고리즘이 있습니다. 이중 조건자를 사용하는 알고리즘은 보통 조건자를 사용하지 않는 알고리즘의 이름에서 뒤에 '_if'를 붙인 이름으로 되어 있습니다. 

9.3.3. for_each 

for_each는 순차적으로 컨테이너들에 담긴 데이터를 함수의 파라미터로 넘겨서 함수를 실행시키는 알고리즘입니다.
온라인 게임에서 예를 들면 플레이하고 있는 유저 객체를 vector 컨테이너인 Users에 저장하고 모든 유저들의 현재 플레이 시간을 갱신하는 기능을 구현한다면 플레이 시간을 계산하는 함수 객체 UpdatePlayTime을 만든 후 for_each에 Users의 시작과 끝 반복자와 UpdatePlayTime 함수 객체를 넘깁니다. 

for_each 원형

template<class InputIterator, class Function>
Function for_each( InputIterator _First, InputIterator _Last, Function _Func );

첫 번째 파라미터는 함수의 파라미터로 넘길 시작 위치, 두 번째 파라미터는 함수의 인자로 넘길마지막 위치, 세 번째는 컨테이너의 데이터를 파라미터로 받을 함수입니다. 

for_each 사용 방법

// 함수 객체
struct UpdatePlayTime
{
……….
};

vector< User > Users;

for_each( Users.begin(), Users.end(), UpdatePlayTime );

아래는 위에서 예를 들었던 것을 완전한 코드로 구현한 예제 코드입니다. 

< 리스트 3. for_each를 사용하여 유저들의 플레이 시간 갱신 >

#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;

struct User
{
  int UID;
  int PlayTime;
};

struct UpdatePlayTime
{
  void operator() ( User& user )
  {
    user.PlayTime += PlayTime;
  }

  int PlayTime;
};

int main()
{
  vector< User > Users;

  User user1; user1.UID = 1; user1.PlayTime = 40000;
  User user2; user2.UID = 2; user2.PlayTime = 0;
  User user3; user3.UID = 3; user3.PlayTime = 25000;

  Users.push_back( user1 );
  Users.push_back( user2 );
  Users.push_back( user3 );

  // 현재 플레이 시간
  vector< User >::iterator IterUser;
  for( IterUser = Users.begin(); IterUser != Users.end(); ++IterUser )
  {
    cout << "UID : " << cout << IterUser->UID << "의 총 플레이 시간: " << IterUser->PlayTime << endl;
  }
  cout << endl;

  UpdatePlayTime updatePlayTime;
  updatePlayTime.PlayTime = 200;

  // 두 번째 유저부터 갱신
  for_each( Users.begin() + 1, Users.end(), updatePlayTime );
  
  for( IterUser = Users.begin(); IterUser != Users.end(); ++IterUser )
  {
    cout << "UID : " << cout << IterUser->UID << "의 총 플레이 시간: " << IterUser->PlayTime << endl;
  }

  return 0;
}

< 결과 > 

3 

변경 불가 시킨스 알고리즘에는 위에 설명한 find, find_if, for_each 이외에 count, search, equal, adjacent_find, equal 등이 있습니다. 

9.4. 변경 가능 시퀀스 알고리즘 

9.3의 알고리즘들이 대상이 되는 컨테이너에 저장한 데이터를 변경하지 않는 것들이라면 이번에 설명한 알고리즘들은 제목대로 대상이 되는 컨테이너의 내용을 변경하는 알고리즘들입니다. 컨테이너에 복사, 삭제, 대체 등을 할 때는 이번에 소개할 알고리즘들을 사용합니다. 

9.4.1. generate 

컨테이너의 특정 구간을 특정 값으로 채우고 싶을 때가 있습니다. 이 값이 동일한 것이라면 컨테이너의 assign() 멤버를 사용하면 되지만 동일한 값이 아니라면 assign()을 사용할 수 있습니다.
이 때 사용하는 알고리즘이 generate입니다. generate 알고리즘에 값을 채울 컨테이너의 시작과 끝, 값을 생성할 함수를 파라미터로 넘깁니다. 

generate 원형

template<class ForwardIterator, class Generator>
void generate( ForwardIterator _First, ForwardIterator _Last, Generator _Gen );

첫 번째 파라미터는 값을 채울 컨테이너의 시작 위치의 반복자, 두 번째는 컨테이너의 마지막 위치의 반복자, 세 번째는 값을 생성할 함수 입니다. 

generate 사용 방법

// 값 생성 함수
struct SetUserInfo
{
  …….
};

vector< User > Users(5);

generate( Users.begin(), Users.end(), SetUserInfo() );

generate 알고리즘의 대상이 되는 컨테이너는 값을 채울 공간이 미리 만들어져 있어야 합니다. 즉 generate는 컨테이너에 데이터를 추가하는 것이 아니고 기존의 데이터를 다른 값으로 변경하는 것입니다. 

< 리스트 4. generate를 사용하여 유저의 기초 데이터 설정>

struct User
{
  int UID;
  int RaceType;
  int Sex;
  int Money;
};

struct SetUserInfo
{
  SetUserInfo() { UserCount = 0; }

  User operator() ()
  {
    User user;

    ++UserCount;
    
    user.UID = UserCount;
    user.Money = 2000;

    if( 0 == (UserCount%2) )
    {
      user.RaceType = 1;
      user.Sex = 1;
      user.Money += 1000;
    }
    else
    {
      user.RaceType = 0;
      user.Sex = 0;
    }

    return user;
  }

  int UserCount;
};

int main()
{
  vector< User > Users(5);

  generate( Users.begin(), Users.end(), SetUserInfo() );
  
  char szUserInfo[256] = {0,};

  vector< User >::iterator IterUser;
  for( IterUser = Users.begin(); IterUser != Users.end(); ++IterUser )
  {
    sprintf( szUserInfo, "UID %d, RaceType : %d, Sex : %d, Money : %d",
    IterUser->UID, IterUser->RaceType, IterUser->Sex, IterUser->Money );

    cout << szUserInfo << endl;
  }
  
  return 0;
}

< 결과 > 

4 

9.4.2. copy 

copy 알고리즘은 컨테이너에 저장한 것과 같은 자료 형을 저장하는 다른 컨테이너에 복사하고 싶을 때 사용합니다. 

컨테이너 A의 데이터를 컨테이너 B에 copy 하는 경우 컨테이너 B에 데이터를 추가하는 것이 아니고 덧쓰는 것이므로 A에서 10개를 복사하는 경우 B에는 10개만큼의 공간이 없다면 버그가 발생합니다. 또 A와 B 컨테이너는 같은 컨테이너 필요는 없습니다만 당연히 컨테이너에 저장하는 자료 형은 같아야 합니다. 

copy 원형

template<class InputIterator, class OutputIterator>
   OutputIterator copy(
      InputIterator _First, 
      InputIterator _Last, 
      OutputIterator _DestBeg
   );

copy 사용 방법

vector<int> vec1;
……..
vector<int> vec2;

copy( vec1.begin(), vec1.end(), vec2.begin() );

< 리스트 5. copy 알골리즘 사용 예>

#include <algorithm>
#include <vector>
#include <list>
#include <iostream>

using namespace std;

int main()
{
  vector<int> vec1(10);
  generate( vec1.begin(), vec1.end(), rand );

  cout << "vec1의 모든 데이터를 vec2에 copy" << endl;

  vector<int> vec2(10);
  copy( vec1.begin(), vec1.end(), vec2.begin() );
  for( vector<int>::iterator IterPos = vec2.begin();
    IterPos != vec2.end();
    ++IterPos )
  {
    cout << *IterPos << endl;
  }
  cout << endl;


  cout << "vec1의 모든 데이터를 list1에 copy" << endl;
  list<int> list1(10);
  copy( vec1.begin(), vec1.end(), list1.begin() );
  
  for( list<int>::iterator IterPos2 = list1.begin();
    IterPos2 != list1.end();
    ++IterPos2 )
  {
    cout << *IterPos2 << endl;
  }

  return 0;
}

< 결과 > 

5 

9.4.4. remove 

remove 알고리즘은 컨테이너에 있는 특정 값들을 삭제하고 싶은 때 사용합니다.
주의해야 될 점은 삭제 후 크기가 변하지 않는다는 것입니다. 삭제가 성공하면 삭제 대상이 아닌 데이터들을 앞으로 몰아 넣고 마지막 위치의(컨테이너의 end()가 아닌 삭제 후 빈 공간에 다른 데이터를 쓰기 시작한 위치) 반복자를 반환합니다. 리턴 값이 가리키는 부분부터 끝(end()) 사이의 데이터 순서는 정의되어 있지 않으면 진짜 삭제를 하기 위해서는 erase()를 사용해야 합니다. 

remove 원형

template<class ForwardIterator, class Type>
ForwardIterator remove( ForwardIterator _First, ForwardIterator _Last, const Type& _Val );

첫 번째 파라미터는 삭제 대상을 찾기 시작할 위치의 반복자, 두 번째 파라미터는 삭제 대상을 찾을 마지막 위치의 반복자, 세 번째 파라미터는 삭제를 할 값입니다. 

remove 사용 방법

vector<int> vec1;
…..
remove( vec1.begin(), vec1.end(), 20 );

< 리스트 6. remove 사용 예>

#include <algorithm>
#include <vector>
#include <list>
#include <iostream>

using namespace std;

int main()
{
  vector<int> vec1;
  vec1.push_back(10);  vec1.push_back(20);  vec1.push_back(20);
  vec1.push_back(40); vec1.push_back(50);  vec1.push_back(30);  

  vector<int>::iterator iterPos;

  cout << "vec1에 있는 모든 데이터 출력" << endl;
  for( iterPos = vec1.begin(); iterPos != vec1.end(); ++iterPos )
  {
    cout << *iterPos << "  " << endl;
  }
  cout << endl;

  cout << "vec1에서 20 remove" << endl;
  vector<int>::iterator RemovePos = remove( vec1.begin(), vec1.end(), 20 );

  for( iterPos = vec1.begin(); iterPos != vec1.end(); ++iterPos )
  {
    cout << *iterPos << "  " << endl;
  }
  cout << endl;

  cout << "vec1에서 20 remove 이후 사용 하지않는 영역 완전 제거" << endl;
  while( RemovePos != vec1.end() )
  {
    RemovePos = vec1.erase( RemovePos );
  }

  for( iterPos = vec1.begin(); iterPos != vec1.end(); ++iterPos )
  {
    cout << *iterPos << "  " << endl;
  }
}

< 결과 > 

6 

<리스트 6>에서는 remove 후 erase()로 완전하게 제거하는 예를 나타내고 있습니다.

vector<int>::iterator RemovePos = remove( vec1.begin(), vec1.end(), 20 );

로 삭제 후 남은 영역의 반복자 위치를 받은 후

while( RemovePos != vec1.end() )
{
  RemovePos = vec1.erase( RemovePos );
}

로 완전하게 제거하고 있습니다. 

9.4.5. replace 

컨테이너의 특정 값을 다른 값으로 바꾸고 싶을 때는 replace 알고리즘을 사용합니다. 

replace 원형

template<class ForwardIterator, class Type>
   void replace(
      ForwardIterator _First, 
      ForwardIterator _Last,
      const Type& _OldVal, 
      const Type& _NewVal
   );

replace 사용 방법

vector<int> vec1;
……
replace( vec1.begin(), vec1.end(), 20, 30 );

< 리스트 7. replace 사용 예>

#include <algorithm>
#include <vector>
#include <list>
#include <iostream>

using namespace std;

int main()
{
  vector<int> vec1;
  vec1.push_back(10);  vec1.push_back(20);  vec1.push_back(20);
  vec1.push_back(40); vec1.push_back(50);  vec1.push_back(30);

  vector<int>::iterator iterPos;

  cout << "vec1에 있는 모든 데이터 출력" << endl;
  for( iterPos = vec1.begin(); iterPos != vec1.end(); ++iterPos )
  {
    cout << *iterPos << "  " << endl;
  }
  cout << endl;

  cout << "vec1의 세 번째 요소부터 20을 200으로 변경" << endl;
  replace( vec1.begin() + 2, vec1.end(), 20, 200 );

  for( iterPos = vec1.begin(); iterPos != vec1.end(); ++iterPos )
  {
    cout << *iterPos << "  " << endl;
  }

  return 0;
}

< 결과 > 

7 

변경 가능 시퀀스 알고리즘에는 generate, copy, remove, replace 이외에도 fill, swap, reverse, transform 등이 있으니 제가 설명하지 않은 알고리즘들은 다음에 여유가 있을 때 공부하시기를 바랍니다. 

지금까지 설명한 알고리즘을 보면 어떤 규칙이 있다는 것을 알 수 있을 것입니다. 알고리즘에 넘기는 파라미터는 알고리즘을 적용할 컨테이너의 위치를 가리키는 반복자와 특정 값 or 조건자를 파라미터로 넘기고 있습니다. 그래서 각 알고리즘은 이름과 기능이 다를 뿐 사용 방법은 대체로 비슷합니다. 즉 사용 방법에 일괄성이 있습니다. 그래서 하나의 알고리즘만 사용할 줄 알게 되면 나머지 알고리즘은 쉽게 사용하는 방법을 배웁니다. 이런 것이 바로 STL의 장점이겠죠 

이번 회는 이것으로 설명을 마치고 남은 알고리즘은 다음 회에 이어서 계속 설명하겠습니다.


출처 : http://www.hanb.co.kr/

Trackback 0 And Comment 0

[펌] About STL : C++ STL 프로그래밍(8)-셋(Set)

|

제공 : 한빛 네트워크
저자 : 최흥배
이전기사 :

앞서 설명했던 map과 비슷하면서도 다른 set을 이번에 이야기 하려고 합니다. map 이전에는 hash_map을 설명했는데 이번에 이야기할 set과 여러 부분에서 중복되는 부분이 있고, 저는 현업에서 set을 사용하는 경우가 거의 없어서 이번에는 내용이 길지 않을 것 같습니다. 

8.1 set 이란 

set은 원하는 key를 신속하게 찾고, 또 이 key가 정렬되기를 원할 때 사용합니다.(여기서 말하는 key라는 것은 저장할 자료를 말합니다). map과 비슷하지만 다른 점은 map은 key와 값이 한 쌍으로 저장하지만 set은 key만 저장합니다. set도 map과 같이 key를 중복으로 저장할 수 없습니다. 만약 key를 중복으로 사용하고 싶다면 multiset을 사용해야 합니다. 사용방법은 set과 거의 같습니다. set은 map과 같이 이진 탐색 트리 자료구조를 사용합니다. 

8.2 set을 사용할 때 

앞서 이야기 했듯이 set은 자료를 저장할 때 내부에서 자동으로 정렬하고, map과 다르게 key만 저장합니다. 

set은 아래 조건일 때 사용하면 좋습니다.

  1. 정렬해야 한다.
  2. key가 있는지 없는지 알아야 할 때
  3. 많은 자료를 저장하고, 검색 속도가 빨라야 할 때

다른 컨테이너를 설명 할 때 꽤 많은 이야기를 했는데 그동안 했던 이야기와 중복되는 것이 많고 특히 앞의 map과 비슷한 부분이 많아서 이번에는 바로 사용 방법으로 들어가겠습니다. 

8.3 set 사용 방법 

set 컨테이너를 쓰려면 먼저 헤더 파일을 포함해야 합니다.

#include <set>

보통 set을 사용하는 방법은 아래와 같습니다.

set< key 자료 type > 변수 이름

set< int > set1;

map과 사용 방법이 비슷하죠? 다만, set은 위와 같이 key만 저장합니다. 위에서는 key로 int 타입을 사용했습니다. 

set은 map과 같이 기본적으로 오름차순으로 정렬을 합니다. 만약 이것을 내림 차순으로 바꾸고 싶거나 key 자료형이 기본형이 아니란 유저 정의형이라면 함수 객체로 정렬 방법을 제공해야 합니다. 

set의 key가 기본형이고 내림 차순으로 정렬하고 싶다면 STL의 greater 알고리즘을 사용하면 됩니다.

set< key 자료 type, 비교 함수 > 변수 이름

set< int, greater<int> > set1;

만약 key가 기본형이 아니고 Player 이라는 클래스를 사용하고 Player의 멤버 중 HP를 비교하여 정렬하고 싶다면 아래와 같이 하면 됩니다.

class Player
{
public:
   Player() {}
   ~Player() {}

   int m_HP;
};

template< typename T >
struct HP_COMPARE : public binary_function< T, T, bool >
{
   bool operator() (T& player1, T& player2) const 
   { 
      return player1.m_HP > player2.m_HP;
   }
};

int main()
{
   set< Player, HP_COMPARE<Player> > set1;
   return 0;
}

8.3.1 set의 주요 멤버들 

멤버설명
begin첫 번째 원소의 랜덤 접근 반복자를 반환
clear저장하고 있는 모든 원소를 삭제
empty저장 하고 있는 요소가 없으면 true 반환
end마지막 원소 다음의(미 사용 영역) 반복자를 반환
erase특정 위치의 원소나 지정 범위의 원소들을 삭제
findkey와 연관된 원소의 반복자 반환
insert원소 추가
lower_bound지정한 key의 요소를 가지고 있다면 해당 위치의 반복자를 반환
operator[]지정한 key 값으로 원소 추가 및 접근
rbegin역방향으로 첫 번째 원소의 반복자를 반환
rend역방향으로 마지막 원소 다음의 반복자를 반환
size원소의 개수를 반환
upper_bound지정한 key 요소를 가지고 있다면 해당 위치 다음 위치의 반복자 반환

[표 1]. map의 주요 멤버들 

8.3.2. 추가 

set 에서는 자료를 추가 할 때 insert를 사용합니다.

원형 : 
  pair <iterator, bool> insert( const value_type& _Val );
  iterator insert( iterator _Where, const value_type& _Val );
  template<class InputIterator> void insert( InputIterator _First, InputIterator _Last );

첫 번째가 자주 사용하는 방식입니다.

set< int > set1;

// key 1을 추가.
set1.insert( 1 );

// 추가했는지 조사 하고 싶을 때는
pair< set<int>::iterator, bool > Result;
Result = set1.insert( 1 );
if( Result.second )
{
   // 추가 성공
}
else
{
  // 추가 실패
}

두 번째 방식은 특정 위치에 추가할 수 있습니다.

// 첫 번째 위치에 key 1, value 35를 추가
set1.insert( set1.begin(), 10 );

세 번째 방식은 지정한 반복자 구간에 있는 것들을 추가합니다.

set< int > set2;
// set1의 모든 요소를 set2에 추가.
set2.insert( set1.begin(), set1.end() );

set은 이미 있는 key 값을 추가할 수 없습니다(복수의 key 값을 사용하기 위해서는 multiset을 사용해야 합니다). 

참고로 특정 위치를 지정하여 추가를 하여도 정렬되어 저장합니다. 

<리스트 1. 특정 위치에 추가했을 때의 정렬 여부>

#include <iostream>
#include <functional>
#include <set>

using namespace std;

int main()
{
   set< int > set1;
   set1.insert( 10 );
   set1.insert( 15 );
   set1.insert( 12 );
   set1.insert( 2 );
   set1.insert( 100 );

   for( set<int>::iterator IterPos = set1.begin();
      IterPos != set1.end(); ++IterPos )
   {
      cout << *IterPos << endl;
   }

   set<int>::iterator IterPos = set1.begin();
   ++IterPos;
   set1.insert( IterPos, 90 );
   
   cout << endl;
   cout << "90을추가후set1의모든요소출력" << endl;
   for( set<int>::iterator IterPos = set1.begin();
      IterPos != set1.end(); ++IterPos )
   {
      cout << *IterPos << endl;
   }
   
   return 0;
}

<결과> 

그림1 

8.3.3. 반복자 사용 

다른 컨테이너와 같이 정 방향 반복자 begin(), end()와 역 방향 반복자 rbegin(), rend()를 지원합니다. 사용 방법은 다음과 같습니다.

// 정 방향으로 set1의 모든 Key 출력
set< int >::iterator Iter_Pos;
for( Iter_Pos = set1.begin(); Iter_Pos != set1.end(); ++Iter_Pos)
{
   cout << *Iter_Pos << endl;
}

// 역 방향으로 set1의 모든 요소 Key 출력
set< int >::reverse_iterator Iter_rPos;
for( Iter_rPos = set1.rbegin(); Iter_rPos != set1.rend(); ++Iter_rPos)
{
   cout << *Iter_rPos << endl;
}

8.3.4. 검색 

set에서 검색은 key를 대상으로 합니다.
key와 같은 요소를 찾으면 그 요소의 반복자를 반환하고, 찾지 못한 경우에는 end()를 가리키는 반복자를 반환합니다.

원형 : 
  iterator find( const Key& _Key );
  const_iterator find( const Key& _Key ) const;

두 방식의 차이는 반환된 반복자가 const 여부입니다. 첫 번째 방식은 const가 아니므로 찾은 요소의 Key를 변경할 수 있습니다. 그러나 두 번째 방식은 Key를 변경할 수 없습니다.

// key가 10인 요소 찾기.
set< int >::Iterator FindIter = set1.find( 10 );

// 찾았다면 value를 1000으로 변경
if( FindIter != set1.end() )
{
   // Key를 찾았다!!!
}

set은 map과 다르게 Key만 저장하기에 Key의 변경이 가능하지만 find로 찾은 Key를 변경하면 정렬되지 않습니다. 

<리스트 2. find로 찾은 Key 변경>

int main()
{
   set< int > set1;
   set1.insert( 10 );
   set1.insert( 15 );
   set1.insert( 12 );

   for( set<int>::iterator IterPos = set1.begin();
      IterPos != set1.end(); ++IterPos )
   {
      cout << *IterPos << endl;
   }

   set<int>::iterator FindIter = set1.find( 15 );
   if( FindIter != set1.end() )
   {
      *FindIter = 11;
   }

   cout << endl;
   cout << "15를 검색 후11로 변경한 후 set1의 모든 요소 출력" << endl;
   for( set<int>::iterator IterPos = set1.begin();
      IterPos != set1.end(); ++IterPos )
   {
      cout << *IterPos << endl;
   }

   return 0;
}

<결과> 

그림2 

8.3.5. 삭제 

저장하고 있는 요소를 삭제할 때는 erase와 clear를 사용합니다.
erase는 특정 요소를 삭제할 때 사용하고, clear는 모든 요소를 삭제할 때 사용합니다. 

erase

원형 : 
  iterator erase( iterator _Where );
  iterator erase( iterator _First, iterator _Last );
  size_type erase( const key_type& _Key );

첫 번째 방식은 특정 위치에 있는 요소를 삭제합니다.

// 두 번째 위치의 요소 삭제.
set1.erase( ++set1.begin() );

두 번째 방식은 지정한 구역 에 있는 요소들을 삭제합니다.

// set1의 처음과 마지막에 있는 모든 요소 삭제
set1.erase( set1.begin(), set1.end() );

세 번째 방식은 지정한 Key와 같은 요소를 삭제합니다.

// key가 10인 요소 삭제.
set1.erase( 10 );

첫 번째와 두 번째 방식으로 삭제를 하면 삭제되는 요소의 다음을 가리키는 반복자를 반환합니다.
세 번째 방식은 삭제된 개수를 반환하는데 정말 삭제가 되었다면 1이 반환됩니다. 그러나 multiset에서는 1이 아닌 삭제된 개수를 반환합니다. 

clear 

set의 모든 요소를 삭제할 때는 clear를 사용합니다.

set1.clear();

이것으로 set에서 자주 사용하는 멤버들에 대한 설명은 끝났습니다. 
아래의 <리스트 3>은 set을 전반적으로 사용하는 예를 나타내고 있습니다. 

<리스트 3. set 사용 예>

#include <iostream>
#include <functional>
#include <set>

using namespace std;

class Player
{
public:
   Player() {}
   ~Player() {}

   int m_Level;
};

// 레벨이 높은 순으로 정렬
template< typename T > 
struct LEVEL_COMPARE : public binary_function< T, T, bool >
{
   bool operator() (const T& player1, const T& player2) const 
   { 
      return player1->m_Level > player2->m_Level;
   }
};

int main()
{
   set< Player*, LEVEL_COMPARE<Player*> > PlayerList;

   Player* pPlayer1 = new Player; pPlayer1->m_Level = 10;
   PlayerList.insert( pPlayer1 );
   Player* pPlayer2 = new Player; pPlayer2->m_Level = 45;
   PlayerList.insert( pPlayer2 );
   Player* pPlayer3 = new Player; pPlayer3->m_Level = 5;
   PlayerList.insert( pPlayer3 );
   Player* pPlayer4 = new Player; pPlayer4->m_Level = 15;
   PlayerList.insert( pPlayer4 );

   // 정 방향으로 출력( 레벨이 높은 순으로)
   for( set< Player*, LEVEL_COMPARE<Player*> >::iterator IterPos = PlayerList.begin();
      IterPos != PlayerList.end(); ++IterPos )
   {
      cout << (*IterPos)->m_Level << endl;
   }

   cout << endl;

   // 역 방향으로 출력( 레벨이 낮은 순으로)
   for( set< Player*, LEVEL_COMPARE<Player*> >::reverse_iterator IterPos = PlayerList.rbegin();
      IterPos != PlayerList.rend(); ++IterPos )
   {
      cout << (*IterPos)->m_Level << endl;
   }

   cout << endl;

   // pPlayer4를검색
   set< Player*, LEVEL_COMPARE<Player*> >::iterator FindPlayer = PlayerList.find( pPlayer4 );
   if( FindPlayer != PlayerList.end() )
   {
      cout << "pPlayer4를 찾았습니다" << endl;

      cout << "pPlayer4 삭제" << endl;
      PlayerList.erase( FindPlayer );
   }
   else
   {
      cout << "pPlayer4를 못찾았습니다" << endl;
   }

   cout << endl;
   cout << "Total Player Count : " << PlayerList.size() << endl;

   cout << endl;
   PlayerList.clear();
   if( PlayerList.empty() )
   {
      cout << "Player가 없습니다." << endl;
   }
   
   delete pPlayer1;
   delete pPlayer2;
   delete pPlayer3;
   delete pPlayer4;
   return 0;
}

<결과> 

그림3 

이전 회의 map과 거의 대부분 비슷하기 때문에 map을 아시는 분들은 아주 쉬웠을 것이라고 생각합니다. 

이전과 같이 set의 멤버 중 설명하지 않은 것들은 MSDN에 있는 set 설명을 참조하시기를 바랍니다. 

과제 

Sset은 Key를 중복으로 저장할 수 없습니다. 중복 Key를 저장하기 위해서는 multiset을 사용해야 합니다. multiset을 공부해 보세요 

참고 url : http://msdn.microsoft.com/ko-kr/library/w5txk7zc.aspx


출처 : http://www.hanb.co.kr/

Trackback 0 And Comment 0

[펌] About STL : C++ STL 프로그래밍(7)-맵(Map)

|

제공 : 한빛 네트워크
저자 : 최흥배
이전기사 :

이번에는 이전 회에 설명한 hash_map과 같은 연관 컨테이너 중의 하나인 map에 대해서 설명합니다. 사용법이 hash_map과 대부분 비슷해서 앞으로 할 이야기가 별로 어렵지 않을 것입니다. 

이전 회에서 설명했던 것은 가급적 또 설명하지 않을 테니 앞의 글들을 보지 않은 분들은 꼭 봐 주세요. 그럼 map에 대한 이야기를 시작 하겠습니다. 

7.1 map의 자료구조 

map의 자료구조는 '트리(tree)'입니다(정확하게 말하면 트리 자료구조 중의 하나인 '레드-블랙 트리(Red-Black tree)'입니다). 

트리는 한글로 '나무'입니다. 나무는 뿌리에서 시작하여 여러 갈래의 가지가 있고, 가지의 끝에는 나무 잎이 있습니다. 트리 자료구조도 이와 같은 형태를 가지고 있어서 루트(root), 리프(leaf)라는 용어를 사용합니다. 

그림1 
[그림 1] 실제 나무(왼쪽)와 트리 자료구조의 모습(오른쪽) 

오른쪽의 트리 자료구조에서 제일 최상 위의 '5'는 루트 노드(root node)라고 하며, 노드'5'와 노드'7'의 관계에서 노드'5'는 부모 노드(parent node), 노드'7'은 자식 노드(child node)라고 합니다. 또한 노드 '12'와 노드 '30'의 관계에서는 부모 노드는 노드'12'입니다. 자식이 없는 노드는 리프 노드(leaf node)라고 합니다. 그림 1에서는 '9', '30', '35', '20'이 리프 노드입니다. 

7.2 트리 자료구조의 특징 

트리는 노드를 균형 있게 가지는 것이 성능에 유리하기 때문에 기본 트리에서 변형된 B-트리, B+ 트리, R-트리, 레드 블랙 트리, AVL 트리 등 다양한 종류의 트리 자료구조가 있습니다. 

균형을 이룬 트리는 자료를 정해진 방식에 따라서 분류하여 저장하기 때문에 시퀸스(일렬로)하게 자료를 저장하는 연결 리스트에 비해서 검색이 빠릅니다. 그렇지만 정해진 규칙에 따라서 자료를 삽입, 삭제 해야 되기 때문에 삽입과 삭제가 간단하지 않으며 구현이 복잡합니다. 

7.3 map을 언제 사용해야 될까? 

map은 많은 자료를 정렬하여 저장하고 있고 빠른 검색을 필요로 할 때 자주 사용합니다. 많은 자료를 빠르게 검색한다고 하는 부분은 앞 회에서 설명한 hash_map과 비슷합니다. 그러나 hash_map과 크게 다른 부분이 있습니다. map은 자료를 저장할 때 내부에서 자동으로 정렬을 하고, hash_map은 정렬하지 않는다라는 것입니다. 정렬이 필요하지 않는 곳에서 map을 사용하는 것은 불 필요한 낭비입니다. 

map은 아래 조건일 때 사용하면 좋습니다. 

1. 정렬해야 한다.
2. 많은 자료를 저장하고, 검색이 빨라야 한다
3. 빈번하게 삽입, 삭제하지 않는다. 

7.4 map 사용 방법 

가장 먼저 map의 헤더파일을 포함합니다.

#include 

보통 map을 사용하는 방법은 아래와 같습니다.

map< key 자료 type, value 자료 type > 변수 이름

map< int, int > map1;

value는 저장할 자료이고, key는 value를 가리키는 것입니다. 위에서는 key의 자료형 int, value 자료형 int인 map을 생성합니다. 

앞에서 map은 자료를 저장할 때 정렬을 한다고 말했습니다. 정렬의 대상은 key를 대상으로 하며오름차순으로 정렬합니다. 그래서 내림차순으로 정렬하고 싶거나 key의 자료형이 기본형이 아닌 유저 정의형(class나 struct로 정의한 것)인 경우는 정렬 방법을 제공해야 합니다. 

위에 생성한 map1은 오름차순으로 정렬하는데 이것을 내림차순으로 정렬하고 싶다면 아래와 같이 하면 됩니다.

map< key 자료 type, value 자료 type, 비교 함수 > 변수 이름

map< int, int, greater<int> > map1;

위에서 사용한 비교 함수 greater는 제가 따로 만든 것이 아니고 STL에 있는 템플릿입니다. 

greater와 같은 것을 STL 알고리즘 이라고 하는데 이것들은 다음 시간에 자세하게 설명할 예정이니 여기서는 이런 것이 있다는 것만 아시면 됩니다. 

다른 컨테이너와 같이 map도 동적 할당을 할 수 있습니다. 사용 방법은 앞서 소개한 컨테이너들과 비슷합니다. 

앞 회의 hash_map과 비교를 하면 사용 방법이 거의 같다라는 것을 알 수 있습니다. 이후 소개하는 map의 멤버함수도 일부분만 제외하고는 hash_map과 같습니다. 이전에도 이야기 했지만 서로 다른 컨테이너가 사용방법이 서로 비슷하여 하나만 제대로 배우 나머지 것들도 배우기 쉽다라는 것이 STL의 장점 중의 하나입니다. 

7.4.1 map의 주요 멤버들 

멤버설명
begin첫 번째 원소의 랜덤 접근 반복자를 반환
clear저장하고 있는 모든 원소를 삭제
empty저장 하고 있는 요소가 없으면 true 반환
End마지막 원소 다음의(미 사용 영역) 반복자를 반환
erase특정 위치의 원소나 지정 범위의 원소들을 삭제
Findkey와 연관된 원소의 반복자 반환
insert원소 추가
lower_bound지정한 key의 요소를 가지고 있다면 해당 위치의 반복자를 반환
operator[]지정한 key 값으로 원소 추가 및 접근
rbegin역방향으로 첫 번째 원소의 반복자를 반환
rend역방향으로 마지막 원소 다음의 반복자를 반환
size원소의 개수를 반환
upper_bound지정한 key 요소를 가지고 있다면 해당 위치 다음 위치의 반복자 반환


[표 1] map의 주요 멤버들 

7.4.2. 추가 

map 에서는 자료를 추가 할 때 insert를 사용합니다.

원형 : 
pair <iterator, bool> insert( const value_type& _Val );
iterator insert( iterator _Where, const value_type& _Val );
template<class InputIterator> void insert( InputIterator _First, InputIterator _Last );

첫 번째 방식이 보통 가장 자주 사용하는 방식입니다.

map< int, int > map1;

// key는 1, value는 35를 추가.
map1.insert( map< int, int >::value_type(1, 35));

// 또는 STL의 pair를 사용하기도 합니다.
typedef pair < int, int > Itn_Pair;
map1.insert(  Int_Pair(2, 45) );

두 번째 방식으로는 특정 위치에 추가할 수 있습니다.

// 첫 번째 위치에 key 1, value 35를 추가
map1.insert( map1.begin(), map< int, int >::value_type(1, 35) );

// 또는
map1.insert( map1.begin(),  Int_Pair(2, 45) );

세 번째 방식으로는 지정한 반복자 구간에 있는 것들을 추가합니다.

map< int, int > map2;
// map1의 모든 요소를 map2에 추가.
map2.insert( map1.begin(), map1.end() );

map은 이미 있는 key 값을 추가할 수 없습니다(복수의 key 값을 사용하기 위해서는 multi_map을 사용해야 합니다). 가장 자주 사용하는 첫 번째 방식으로 추가하는 경우는 아래와 같은 방법으로 결과를 알 수 있습니다.

pair< map<int, int>::iterator, bool > Result;
Result = map1.insert( Int_Pair(1, 35));

만약 이미 key 값 1이 추가 되어 있었다면 insert 실패로 Result.second 는 false이며, 반대로 성공하였다면 true 입니다. 

operator[]를 사용하여 추가하기 

insert가 아닌 operator[]를 사용하여 추가할 수도 있습니다.

// key 10, value 80을 추가
map1[10] = 80;

7.4.3. 반복자 사용 

다른 컨테이너와 같이 정 방향 반복자 begin(), end()와 역 방향 반복자 rbegin(), rend()를 지원합니다. 

사용 방법은 다음과 같습니다.

// 정 방향으로 map1의 모든 요소의 value 출력
map< int, int >::iterator Iter_Pos;
for( Iter_Pos = map1.begin(); Iter_Pos != map1.end(); ++Iter_Pos)
{
   cout << Iter_Pos.second << endl;
}

// 역 방향으로 map1의 모든 요소의 value 출력
map< int, int >::reverse_iterator Iter_rPos;
for( Iter_rPos = map1.rbegin(); Iter_rPos != map1.rend(); ++Iter_rPos)
{
   cout << Iter_rPos.second << endl;
}

위에서 map을 정의할 때 비교함수를 사용할 수 있다고 했습니다. 만약 비교함수를 사용한 경우는 반복자를 정의할 때도 같은 비교함수를 사용해야 합니다.

map< int, int, greater<int> > map1;
map< int, int, greater<int> >::iterator Iter_Pos;

7.4.4. 검색 

map에서 검색은 key 값을 대상으로 합니다. key와 같은 요소를 찾으면 그 요소의 반복자를 반환하고, 찾지 못한 경우에는 end()를 가리키는 반복자를 반환합니다.

원형 : 
iterator find( const Key& _Key );
const_iterator find( const Key& _Key ) const;

두 방식의 차이는 반환된 반복자가 const냐 아니냐는 차이입니다. 첫 번째 방식은 const가 아니므로 찾은 요소의 value를 변경할 수 있습니다(참고로 절대 key는 변경 불가입니다). 그러나 두 번째 방식은 value를 변경할 수 없습니다.

// key가 10인 요소 찾기.
map< int, int >::Iterator FindIter = map1.find( 10 );

// 찾았다면 value를 1000으로 변경
if( FindIter != map1.end() )
{
   FindIter->second = 1000;
}

7.4.5. 삭제 

저장하고 있는 요소를 삭제할 때는 erase와 clear를 사용합니다. erase는 특정 요소를 삭제할 때 사용하고, clear는 모든 요소를 삭제할 때 사용합니다. 

erase

원형 : 
iterator erase( iterator _Where );
iterator erase( iterator _First, iterator _Last );
size_type erase( const key_type& _Key );

첫 번째 방식은 특정 위치에 있는 요소를 삭제합니다.

// 두 번째 위치의 요소 삭제.
map1.erase( ++map1.begin() );

두 번째 방식은 지정한 구역에 있는 요소들을 삭제합니다.

// map1의 처음과 마지막에 있는 모든 요소 삭제
map1.erase( map1.begin(), map1.end() );

세 번째 방식은 지정한 키와 같은 요소를 삭제합니다.

// key가 10인 요소 삭제.
map1.erase( 10 );

첫 번째와 두 번째 방식에서는 삭제하는 요소의 다음을 가리키는 반복자를 반환하고(C++ 표준에서는 반환하지 않습니다만 Microsoft의 Visual C++에서는 반환합니다), 세 번째 방식은 삭제된 개수를 반환합니다. map에서는 세 번째 방식으로 삭제를 하는 경우 정말 삭제가 되었다면 무조건 1이지만, multi_map에서는 삭제한 개수만큼의 숫자가 나옵니다. 

clear 

map의 모든 요소를 삭제할 때는 clear를 사용합니다.

map1.clear();

이것으로 map에서 자주 사용하는 멤버들에 대한 설명은 끝났습니다. [표 1]에 나와 있는 멤버들 중 사용 방법이 간단한 것은 따로 설명하지 않으니 [리스트 1]의 코드를 봐 주세요. 

[리스트 1] 정렬된 아이템 리스트 출력

#include <map>
#include <string>
#include <iostream>

using namespace std;

struct Item
{
  char Name[32];  // 이름
  char Kind;  // 종류
  int BuyMoney;  // 구입 가격
  int SkillCd;  // 스킬 코드
};

int main()
{
  map< char*, Item > Items;
  map< char*, Item >::iterator IterPos;
  typedef pair< char*, Item > ItemPair;

  Item Item1;
  strncpy( Item1.Name, "긴칼", 32 );
  Item1.Kind = 1;    Item1.BuyMoney = 200;  Item1.SkillCd = 0;

  Item Item2;
  strncpy( Item2.Name, "성스러운 방패", 32 );
  Item2.Kind = 2;    Item2.BuyMoney = 1000;  Item2.SkillCd = 4;

  Item Item3;
  strncpy( Item3.Name, "해머", 32 );
  Item3.Kind = 1;    Item3.BuyMoney = 500;  Item3.SkillCd = 0;

  // Items에 아이템 추가
  Items.insert( map< char*, Item >::value_type(Item2.Name, Item2) );
  Items.insert( ItemPair(Item1.Name, Item1) );

  // Items가 비어 있지않다면
  if( false == Items.empty() )
  {
    cout << "저장된 아이템 개수- " << Items.size() << endl;
  }

  for( IterPos = Items.begin(); IterPos != Items.end(); ++IterPos )
  {
    cout << "이름: " << IterPos->first << ", 가격: " << IterPos->second.BuyMoney << endl;
  }

  IterPos = Items.find("긴칼");
  if( IterPos == Items.end() )   {
    cout << "아이템'긴칼'이 없습니다." << endl;
  }
  cout << endl;

  cout << "올림차순으로 정렬되어있는 map(Key 자료형으로string 사용)" << endl;
  
  map< string, Item, less<string> > Items2;
  map< string, Item, less<string> >::iterator IterPos2;
  
  Items2.insert( map< string, Item >::value_type(Item2.Name, Item2) );
  Items2.insert( ItemPair(Item1.Name, Item1) );
  // operator[]를 사용하여 저장
  Items2[Item3.Name] = Item3;

   for( IterPos2 = Items2.begin(); IterPos2 != Items2.end(); ++IterPos2 )
  {
    cout << "이름: " << IterPos2->first << ", 가격: " << IterPos2->second.BuyMoney << endl;
  }
  cout << endl;

  cout << "해머의 가격은 얼마? ";
  IterPos2 = Items2.find("해머");
  if( IterPos2 != Items2.end() )   {
    cout << IterPos2->second.BuyMoney << endl;
  }
  else {
    cout << "해머는 없습니다" << endl;
  }
  cout << endl;

  // 아이템 "긴칼"을 삭제한다.
  IterPos2 = Items2.find("긴칼");
  if( IterPos2 != Items2.end() )   {
    Items2.erase( IterPos2 );
  }

  cout << "Items2에 있는 아이템 개수: " << Items2.size() << endl;

  return 0;
}

결과 

그림2 

[리스트 1]의 Items에서 '긴칼'을 검색을 하면 찾을 수가 없습니다. 이유는 key의 자료형으로 char*을 사용했기 때문입니다. 그래서 Items2에서는 STL의 문자열 라이브러리인 string을 사용하였습니다. String에 대해서는 다음 기회에 설명할 예정이니 문자열을 처리하는 라이브러리라고 알고 계시면 됩니다. 

이것으로 map에 대한 설명이 끝났습니다. 이전 회의 hash_map과 비슷한 부분이 많아서 hash_map에 대한 글을 보셨던 분들은 쉽게 따라왔으리라 생각합니다. 그리고 map과 hash_map에 대하여 잘못 알고 있어서 정렬이 필요하지 않은 곳에서 map을 사용하는 경우가 있는데 조심하시기 바랍니다. 제가 미쳐 설명하지 않은 부분에 대해서는 MSDN에 있는 map 설명을 참조하시기를 바랍니다(http://msdn.microsoft.com/ko-kr/library/xdayte4c.aspx). 

과제 

1. [리스트 1]은 아이템 이름을 key 값으로 사용하였습니다. 이번에는 아이템 가격을 Key 값으로 사용하여 아이템을 저장하고 내림차순으로 출력해 보세요. 

앞서 중복된 key를 사용할 수 있는 multi_map 이라는 것이 있다고 이야기 했습니다. 이번 과제는 multi_map을 사용해야 합니다. multi_map에 관한 설명은 아래 링크의 글을 참조하세요. http://msdn.microsoft.com/ko-kr/library/1y9w8dz4.aspx



출처 : http://www.hanb.co.kr/

Trackback 0 And Comment 0