C++

[강의] 12월 27일 수업정리

k-codestudy 2024. 12. 27. 18:00

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 사용 시 [] 오퍼레이터는 아래와 같은 이유로 사용하지 말아야 한다.

  1. 성공/실패 여부 확인 불가
  2. 중복된 데이터 덮어쓰기
  3. 기본값 자동 삽입

 

이건 가장 기본적인 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