STL MAP 예제로 공부하기

by digipine posted Oct 29, 2017
?

Shortcut

PrevPrev Article

NextNext Article

ESCClose

Larger Font Smaller Font Up Down Go comment Print

1. 바보 전화번호부?


항목 데이타타입 Key
전화번호 string O
이름 string X
주소 string X

열라 간단하게 정했다. (전화번호가 문자열인 것에는 의문을 제기하는 사람이 있을 수도 있겠지만, 여기서는 내맘대로 하겠다. 어쨌든 겹치지만 않으면 되니깐.^^) 여기서, 한가지! 전화번호는 어떠한 집의 것을 대조해봐도 겹치지 않는 값이란 것을 알 수 있다. 그러므로 이 전화번호부를 입력하거나 수정할 때에는 다음과 같은 점을 고려해야 할 것이다.


같은 전화번호를 가진 사람정보를 두 개 이상 적을 수 없다. 그러므로 추가할 때 매번 목록을 검색해야한다.
이미 적은 전화번호를 변경할 때에도 다른 전화번호와 겹치면 안된다. 가장 좋은 방법은 수정을 못하게 하는 것이다. (지우고 다시 적는다. ^^)
추가시 전화번호는 값이 반드시 존재해야한다. 왜냐면 겹치지 않는다고 전제하려면 값이 반드시 있어야하니까.


자, 일단 한장의 전화번호 정보를 선언해보자. 다음과 같이 적을 수 있겠다.

#include <string>

using namespace std;

typedef struct _phoneinfo {
    string Name;
    string Address;
} _PHONEINFO;


이제 이것을 그냥 일반 list나 vector로 선언한다면...? 그러면 위에 언급한 3가지 조건들에 대한 부가적인 코딩이 분명 필요할 것이다.

그러므로 이것들 대신 우리의 map을 가지고 선언해보도록 하겠다. 자, 다음과 같이 선언하면 된다.

map<string, _PHONEINFO> PhoneMap;

끝났다. 열라 간단하지 않은가?
 이로써 겹치는 것도 검사해주고, 고맙게도 전화번호 순으로 정렬도 해주는 '인덱스 붙은 배열'이 선언되었다. 자, 이제 여러가지 작업을 수행해보자.


2. 추가작업 : 새로운 전화번호 등록
자, 이제 전화번호부라면 반드시 존재하는 목록 추가 기능을 구현해보자. 목록 추가는 다음과 같은 순서대로 구현할 수 있겠다.


우선 주어진 번호가 전화번호부상에 있는지 찾는다. 있으면 경고를 출력하고 종료.
없으면 목록에 추가한다.


다음의 소스는 전화번호를 입력받고, 이미 등록되었는지 검사한 후, 없으면 또다시 정보를 입력받아 등록하는 코드이다. // 알맞은 질문을 한 후 한명의 전화번호 정보를 입력받아 저장.

void AddInfo()
{
    string TelNo, Name, Address;
    cout << "전화번호 : "; cin >> TelNo;

    // PhoneMap에서 전화번호가 존재하는지 검사
    if (PhoneMap.count(TelNo) != 0)
    {
        cout << "이미 존재하는 전화번호입니다!n";
        return;
    }

    cout << "이름 : "; cin >> Name;
    cout << "주소 : "; cin >> Address;

    // 전화번호 구조체를 채운다.
    _PHONEINFO tmpInfo;
    tmpInfo.Name = Name;
    tmpInfo.Address = Address;

    // 추가하기 위해 pair를 선언한다.
    pair<string, _PHONEINFO> NewItem(TelNo, tmpInfo);

    // 이제 추가한다.
    PhoneMap.insert(NewItem);
}


우선 count() 메소드 부터 보도록 한다. 이것은 주어진 key값이 map상에 몇 개나 존재하는지 세어보고 그 수를 반환하는 역할을 한다. 앞서서 map은 키에 해당되는 값이 배열전반에 걸쳐서 겹치지 않는다고 말했다. 그럼 만일 존재한다면 count() 메소드는 몇 개가 반환할까? 1이다. (이것은 우연하게도 true에 해당되는 값이다.) 그러므로 위에서의 if문에서는 0이 아니면 찾았다고 간주한 것이다.


다음 라인 이후에서는 이름과 주소를 입력받고 _PHONEINFO라고 하는 구조체를 채운 다음, pair라고 하는 템플릿 클래스 인스턴스가 생성될 때의 인자로 넘겨주는 것을 볼 수 있다. pair는 STL에서 한 쌍의 데이타 형태를 지정하기 위해 만들어 놓은 자료구조라고 볼 수 있다. 앞서 설명한 전화번호부 목록 하나의 구조는 전화번호 + 이름/주소의 구조로 나눌 수 있다. (여기서 전화번호는 반복되지 않는 키 역할을 한다.) 그러므로 pair 자료구조는 키 + 기타부속데이타의 배열인 map에는 필수불가결한 요소가 된다. 즉, 맨 아래 줄 소스의 insert메소드를 실행할 때 인자가 pair 타입인 것을 기억해두자. 여기서는 전화번호는 키로 지정하고, 이름과 주소는 부속데이타로 처리한 것을 볼 수 있다.


insert메소드는 원래 pair형을 반환값으로 가진다. (첫번째는 삽입된 결과 iterator이고, 두번째는 추가한 pair의 키가 존재했는지 여부를 나타내는 bool값이다) 어짜피, 위와 같이 처리하면 절대 겹칠 일은 없으므로 그냥 반환값은 처리하지 않았다.


3. 조회작업 : 전화번호 목록 출력
우선 소스를 보도록 하자.

void ListInfo()
{
    cout << "=== 전화번호 전체 목록 ===n";
    // 전체를 훑어야 하므로 for문을 사용한다.  iterator에 주의.
    for (map<string, _PHONEINFO>::iterator k = PhoneMap.begin(); k != PhoneMap.end(); advance(k, 1))
    {
        // k가 pair 타입이므로 실제 저장된 구조체를 가져오려면 second를 참조해야한다.
        cout << "전화번호 : " << k->first << "n";
        cout << "이름 : " << k->second.Name << "n";
        cout << "주소 : " << k->second.Address << "n";
    }
    cout << "==========================n";
}


map은 내부적으로는 트리의 형태를 가지고 있다. 하지만, list와 같이 배열과 같은 형식으로 처리가 가능한데, 이렇게 처리하면 정렬된 순서대로 값을 얻을 수 있게 되는 효과가 있다. (물론 그 기준은 키값이다. 여기서는 전화번호순으로 출력된다.) iterator의 타입이 pair라는데 주의하자! k->first는 string 타입이고 이것은 전화번호를 나타낸다. k->second는 _PHONEINFO 타입이므로 구조체 인스턴스와 같이 취급하는 것을 볼 수 있다. pair는 내부적으로 first, second라는 맴버변수를 가지고 있는데 각각 첫번째 값, 두번째 값을 나타낸다. (템플릿이므로 정의한 타입 그대로 반환된다)


4. 검색작업 : 주어진 전화번호에 따른 정보 검색

검색은 find()메소드를 사용하여 처리한다. 사용방법은 매우 쉽다. (소스의 주석을 참조해라.) 인자로 키값을 넘겨주면 iterator 타입을 반환하는데, 이것이 end()값이면 찾지 못했다는 뜻이다. (자동으로 begin()부터 찾는다는 것을 알 수 있다.) 아니라면 조회작업때와 마찬가지로 처리하면 된다. (pair 값이라는 것을 명심하자.)

// 주어진 전화번호로 정보를 찾아서 출력한다.
void FindInfo()
{
    string TelNo;
    cout << "전화번호 : "; cin >> TelNo;
    // 주어진 키 값으로 map상에서의 포인터를 얻는다.
    map<string, _PHONEINFO>::iterator k = PhoneMap.find(TelNo);
    // 만일 end()값이 넘어오면 찾지 못한 것임.
    if (k == PhoneMap.end())
    {
        cout << "해당 전화번호의 사용자가 없습니다!!!n";
    }
    else
    {
        cout << "=== 전화번호 전체 목록 ===n";
        cout << "이름 : " << k->second.Name << "n";
        cout << "주소 : " << k->second.Address << "n";
        cout << "=== 검색결과 ===n";
    }
}


좀더 쉽게 검색할 수 있는 방법이 있다. 바로 [ ] 연산자를 사용하는 방법인데, 주의해야 할 점은 만일 이 연산자를 사용했을때, 키값이 존재하지 않는다면 텅빈 노드를 추가한다는 점이다. 그러므로 검색만하고 별도의 원하지않는 자동추가를 하고 싶지 않을때에는 번거롭더라도 위와 같은 방법으로 하는 것이 좋다. 위 예를 다시 고쳐서 적어보면 다음과 같다.

// 주어진 전화번호로 정보를 찾아서 출력한다.
void FindInfo()
{
    string TelNo;
    cout << "전화번호 : "; cin >> TelNo;
    
     // 주어진 키값으로 전화번호구조체값들을 찾아낸다.
     _PHONEINFO tmpPhoneInfo = PhoneMap[TelNo];

     // 이 경우에는 만일 해당 전화번호가 없으면 추가하고 전화번호정보인
    // _PHONEINFO에는 생성자에 따라 초기화된 값이 들어가버린다. (즉, ""이나 0과 같은 값으로 채워진다.)
    cout << "=== 전화번호 전체 목록 ===n";
    cout << "이름 : " << tmpPhoneInfo.second.Name << "n";
    cout << "주소 : " << tmpPhoneInfo.second.Address << "n";
    cout << "=== 검색결과 ===n";
}


5. 삭제작업 : 주어진 전화번호에 해당하는 정보 삭제하기
삭제작업은 erase() 메소드를 사용해서 처리한다. 사용법은 find()만큼이나 쉬운데, 키값을 넘겨주면 알아서 검색해서 지운후 그 결과로 지워진 개수를 반환한다. 0이면 지울 것이 없다는 것이고 1이면 지워졌다는 뜻이다. (1 이상 나올수 없다. 그 이유를 모르겠다면 처음부터 다시 읽어주기 바란다. ^^)

// 입력된 전화번호를 가진 정보를 삭제한다.
void DeleteInfo()
{
    string TelNo;
    cout << "전화번호 : "; cin >> TelNo;
    // 주어진 키 값에 해당하는 노드를 지운다.
    if (PhoneMap.erase(TelNo) > 0)
        cout << "성공적으로 삭제되었습니다!!n";
    else
        cout << "해당 전화번호의 사용자가 없습니다!!!n";
}


6. 조금 어렵게 : 맵에 포인터 변수를 넣어보자!

#include <list>
#include <string>
#include <iostream>

using namespace std;

typedef struct {
    int value3;
   } _testType;

int main(int argc, char* argv[])
{
    list<_testType> original;
    list<_testType*> ptrlist;

    _testType tmp = {3};
 
    original.push_back(tmp);
    ptrlist.push_back(&original.front());

    cout << original.front().value3 << " ";

    ptrlist.front()->value3 = 4;

    cout << original.front().value3;

    cout << endl;
    return 0;
}

// 출력 : 3 4

 

TAG •