map에 대한 수업을 들었다
1. map
- 고유 아이디로 동적할당해서 집어넣을 예정
1.1 data.h / cpp
#pragma once
class C_DATA
{
private:
int m_nData;
public:
C_DATA() = default;
void setData(int nData);
int getData();
};
#include "data.h"
void C_DATA::setData(int nData)
{
m_nData = nData;
}
int C_DATA::getData()
{
return m_nData;
}
1.2 dataMgr.h / cpp
#pragma once
#include <map>
#include <string>
#include "data.h"
class C_MGR
{
private:
std::map<std::string, C_DATA*> m_mapData;
private:
C_DATA* createData(int nData);
public:
C_MGR() = default;
void insert(const char* str, int nData);
void print();
void earse(const char* str);
};
#include "dataMgr.h"
C_DATA* C_MGR::createData(int nData)
{
C_DATA* pData = new C_DATA{};
pData->setData(nData);
return pData;
}
void C_MGR::insert(const char* str, int nData)
{
std::pair<std::map<std::string, C_DATA*>::iterator, bool> pairResult{};
pairResult = m_mapData.insert({ str, nullptr });
if (pairResult.second)
pairResult.first->second = createData(nData);
else
printf("중복\n");
}
void C_MGR::print()
{
printf("\n");
std::map<std::string, C_DATA*>::iterator iter = m_mapData.begin();
while (iter != m_mapData.end())
{
printf("%s, %d\n", iter->first.c_str(), iter->second->getData());
iter++;
}
}
void C_MGR::earse(const char* str)
{
std::map<std::string, C_DATA*>::iterator iter = m_mapData.find(str);
if (iter == m_mapData.end())
return;
delete iter->second;
m_mapData.erase(iter);
}
1.2 1 insert()
1.2.1.1 insert 방식
- insert를 하는 방법이 몇 가지 존재한다.
1. 변수 생성 후 삽입
std::pair<std::string, C_DATA*> pair(str, createData(nData));
m_mapData.insert(pair);
- std::pair 변수를 만들어 키와 데이터를 저장한 뒤 삽입
2. 클래스 상수 사용
C_DATA* pData = createData(nData);
m_mapData.insert(std::pair<std::string, C_DATA*>(str, pData));
- std::pair를 직접 생성해 삽입
3. make_pair
C_DATA* pData = createData(nData);
m_mapData.insert(std::make_pair(str, pData));
- std::make_pair로 키와 데이터를 삽입.
- 메모리를 직접 잡지 않아도 되는 방식으로 간결하지만, 실제로 잘 사용하지 않음. ( 굳이 메모리를 잡을 이유가 없음 )
4. value_type
C_DATA* pData = createData(nData);
m_mapData.insert(std::map<std::string, C_DATA*>::value_type(str, pData));
- 맵의 value_type을 활용해 데이터 삽입.
5. [] 오퍼레이터 ( 사용하지 말 것)
C_DATA* pData = createData(nData);
m_mapData[str] = pData;
- 없는 키에 대해 메모리를 할당해 잘못된 동작을 유발할 수 있으므로 사용하지 않음.
6. 유니폼 초기화
C_DATA* pData = createData(nData);
m_mapData.insert({ str, pData });
- 유니폼 초기화가 클래스 상수를 밀어 넣어줘서 가능함
- 우리는 이 방식으로 사용할 것임
1.2.1.2 insert에서 find 사용법
- insert 전 find를 사용하는 경우, 추가 검색을 유발하지 않도록 주의해야 함.
1. 2번 찾는 경우
std::map<std::string, C_DATA*>::iterator iter = m_mapData.find(str);
if (iter != m_mapData.end())
return;
m_mapData.insert({ str, createData(nData)});
- find에서 한 번 검색한 뒤, insert가 다시 검색을 수행해 비효율적.
2. 트리 구조를 망치는 경우
std::map<std::string, C_DATA*>::iterator iter = m_mapData.find(str);
if (iter != m_mapData.end())
return;
m_mapData.insert(iter, { str, createData(nData) });
- iter를 사용하면 트리의 균형이 깨져 삽입 효율성이 저하됨. ( 한쪽으로 치우쳐진 트리 )
3. 올바른 경우
std::pair<std::map<std::string, C_DATA*>::iterator, bool> pairResult{};
pairResult = m_mapData.insert({ str, nullptr });
if (pairResult.second)
pairResult.first->second = createData(nData);
else
printf("중복\n");
- insert가 내부적으로 키를 검색하고, 중복 여부를 반환하므로 검색을 중복하지 않음.
- bool은 insert의 성공 실패 유무를 판단하는 것이며, iterator는 성공하면 그 자리에 생성 후 저장하며, 실패를 하게 되면 그 자리를 가리키기에 무조건 존재를 하게 된다
- pairResult.second 즉 값이 nullptr이라면 pairResult.first->second 즉 포인터 (값) 부분에 저장을 하는 것이고 아니면 중복이라고 알려주면 끝
1.2.2 print()
void C_MGR::print()
{
printf("\n");
std::map<std::string, C_DATA*>::iterator iter = m_mapData.begin();
while (iter != m_mapData.end())
{
printf("%s, %d\n", iter->first.c_str(), iter->second->getData());
iter++;
}
}
auto iter = m_mapData.begin();
- auto로 반복자 선언 가능, 이렇게도 가능하지만은 익숙해지면 하자
printf("%s, %d\n", iter->first.c_str(), iter->second->getData());
- 한 다리 걸쳐있어서 헷갈릴 수 있으니 잘 확인하면서 사용할 것
1.2.3 earse()
void C_MGR::earse(const char* str)
{
std::map<std::string, C_DATA*>::iterator iter = m_mapData.find(str);
if (iter == m_mapData.end())
return;
delete iter->second;
m_mapData.erase(iter);
}
- find로 삭제할 키를 검색.
- 키가 없으면 바로 반환(end() 검사).
- 키가 있으면 데이터를 삭제한 뒤, 맵에서 키-값 쌍을 제거
1.3 main.cpp
#include <iostream>
#include "dataMgr.h"
int main()
{
C_MGR cMgr{};
cMgr.insert("철수", 10);
cMgr.insert("영희", 20);
cMgr.insert("민수", 30);
cMgr.insert("영희", 50);
cMgr.print();
cMgr.earse("철수");
cMgr.print();
}
2. str에서 [ ] 오퍼레이터 주의점
#include <iostream>
#include <map>
void print(std::map<int, float>& map);
int main()
{
std::map<int, float> map{};
map.insert({ 1, 0.5f });
map[2] = 9.9f;
print(map);
std::map<int, float>::iterator iter = map.find(3);
if (iter != map.end())
printf("find : %d, %f", iter->first, iter->second);
else
printf("없음\n");
printf("find : %d, %f", 3, map[3]);
}
void print(std::map<int, float>& map)
{
printf("--------------\n");
std::map<int, float>::iterator iter = map.begin();
while (iter != map.end())
{
printf("%d, %f\n", iter->first, iter->second);
iter++;
}
}
[] 오퍼레이터는 절대 사용하지 말라고 이야기를 하셨는데 이유가 무엇일까?
2.1 [] 오퍼레이터는 성공/실패 여부를 확인할 수 없다.
map[2] = 9.9f;
- map [key] = value는 삽입이 성공했는지 실패했는지 반환하지 않으며, 키가 존재하는 경우에도 값을 무조건 덮어씌운다.
- 프로그래머가 의도하지 않은 행동을 해버리는 결과를 초례한다.
2.2 중복된 데이터가 있어도 기존 데이터를 덮어쓴다.
- 이미 존재하는 키에 새로운 값을 할당하면 기존 데이터가 사라지므로, 데이터 무결성을 해칠 수 있다.
2.3 없는 키에 기본값을 생성한다.
printf("find : %d, %f", 3, map[3]);
- map [key]를 호출하면 키가 없더라도 기본값(예: float의 경우 0.0)을 삽입하여 의도하지 않은 동작이 발생할 수 있다.
- 위 코드는 3이라는 키가 존재하지 않더라도 기본값 0.0을 가진 엔트리를 삽입하므로, 출력 결과는 find: 3, 0.0000이 된다.
즉, std::map 사용 시 [] 오퍼레이터는 아래와 같은 이유로 사용하지 말아야 한다.
- 성공/실패 여부 확인 불가
- 중복된 데이터 덮어쓰기
- 기본값 자동 삽입
이건 가장 기본적인 find 구문이므로 알아두자
std::map<int, float>::iterator iter = map.find(3);
if (iter != map.end())
printf("find : %d, %f", iter->first, iter->second);
else
printf("없음\n");
'C++' 카테고리의 다른 글
[강의] 12월 31일 수업정리 (0) | 2024.12.31 |
---|---|
[강의] 12월 30일 수업정리 (0) | 2024.12.30 |
[강의] 12월 26일 수업정리 (1) | 2024.12.26 |
[강의] 12월 24일 수업정리 (0) | 2024.12.25 |
[강의] 12월 23일 수업정리 (1) | 2024.12.23 |