오늘의 TIL 목차 (22. 09. 21)
- 트리
- 해시
- map
- unordered map
- mutable
map
[ map ]
: map은 각 노드가 key와 value 쌍으로 이루어진 트리로 노드 기반의 연관 컨테이너이다.
#include <map> // map 사용 시 포함해야 하는 헤더파일
map<key 자료형, value 자료형> 객체명;
Ex.
map<int, string> MapStr; // 키는 int형, value는 string형인 map 객체를 생성
-
[ map 함수 ]
@. insert() 는 map의 원소 삽입 함수 ( vector와 list의 push, pop 같은 것 )
- insert(값) 시 하나의 노드가 추가되는데, 이때 값은 노드에 담을 key와 value가 둘 다 있어야 한다.
@. find(key값);
find(키값); // 반환타입 iterator
=> 만약 찾는 값이 ㅓㅇㅄ다면 end를 반환
그래서 하앙 조건식으로
if (iter == mapChar.end() )
cout < "찾느 ㄴ원소 없음 " << endl;
만약 find("BBB"); 라고 하면 당연히 얘도 컴파일러는 주소로 읽어들이기 대문에
원소의 주소값( 문자열 )과 BBB의 주소값이 동일하면 해당 반복자 반환
그러나 원소의 문자열 주소와 찾으려는 find "BBB" 가 메모리 주소가 다르게 할당되는 경우가 더러 잇음
그래ㅓㅅ find_if(begin, end, 조건자); 로 해주면 문제 해결?
참고로 find_if는 조건자로 함수 포인터 불가능, 함수 객체를 넣어줘라 (외부 데이터 참조를 원할 떈 함수포인터 불가라서)
=> 외부 와 내부 껄로 자유롭게 하기 위해서 함수 객체 사용한대 조건자로.
■ map의 원소 추가 방식
[ map의 원소 추가 방식 ]
- map은 노드 기반 컨테이너이므로 push_back(), push_pop()을 사용하지 않는다.
[1] 외부 컨테이너 pair(key와 value를 담는 템플릿)를 사용하여 insert()로 값 넣기
* pair객체는 선언만 한다면 자동으로 0 초기화
-
pair<int, string> InsertPair;
InsertPair.first = 1; // .first 는 key 값을 가리킴
InsertPair.second = "다인"; // .second는 value 값을 가리킴
MapStr.insert(InsertPair); // insertPair에 저장된 key, value값이 insert에 의해 MapStr에 노드로써 삽입
=> 외부 컨테이너인 pair 객체를 꼭 생성해야 한다는 단점, 비추
[2] pair 객체를 임시객체로 생성하여 원소를 추가하는 방법
-
MapStr.insert(pair<int, string>(2, "시우"));
[3] make_pair 함수 템플릿을 이용한 원소를 추가하는 방법
* make_pair은 std에 포함된 pair컨테이너를 만들어주는 함수 템플릿
MapInt.insert(make_pair<int, string>(3, "엄마"));
[4] value_type 정규 문법을 이용한 원소를 추가하는 방법
* value_type은 map 컨테이너 안의 멤버로, pair의 기능을 함 (first, second 사용)
-
map<int, int>::value_type MyValuePair(4, 400);
MyValuePair.first = 4;
MyValuePair.second = "아빠";
MapStr.insert(MyValuePair);
=> value_type은 일반 pair와 달리 key값 자료형에 const가 삽입되어 있어 중복 상수 정의가 불가능
[5] value_type 을 임시 객체 생성을 하여 원소를 추가하는 방법
-
MapStr.insert(map<int, string>::value_type(5, 500));
=> 외부 컨테이너 pair보다 pair의 기능을 하는 멤버 value_type 사용을 권장
=> 객체 생성 시 메모리 공간을 소모하므로 임시 객체를 사용하는 해당 방법을 권장
[6] []연산자를 통하여 원소를 추가하는 방법
* [key값] = value값; 형태
-
MapStr[6] = 600;
MapStr.insert(map<int, string>::value_type(6, 600));
MapStr[6] = 800;
MapStr.insert(map<int, string>::value_type(6, 800));
=> map은 노드 기반 연관 컨테이너 중 유일하게 임의 접근(인덱스 접근)이 가능
[ c++ 11이후에 등장한 원소 추가 방식 ] * 주로 사용 *
[1] emplace(key, value) 함수 사용
MapInt.emplace(7, 700);
[2] 유니폼 초기화를 이용하여 insert
MapInt.insert({ 8, 800 });
■ 기능 / 주의
- map은 노드 기반 컨테이너로, 한 노드에 key와 value를 가지고 있다.
- map은 원소 삽입(insert, emplace) 시 key값과 value값을 같이 넣어줘야 한다.
- map은 대괄호 연산자를 사용하여 임의 접근이 가능하나 반복자는 양방향 접근자이다.
- 대괄호 연산자 오버로딩이 정의되어 있음, 임의 접근을 통한 값 변경 가능
- 양방향 반복자는+=, -= 불가능 ( 임의접근 반복자는 +=, -= 가능 ) - key와 value는 first로 key, second로 value 값을 가리킨다.
- 반복자로 key와 value 접근 시 화살표 연산자 ->first, ->second를 이용하여 접근
- 반복자는 포인터가 아닌 객체이지만, 포인터와 동작 원리는 같기 때문 - map은 연관 컨테이너이므로 key값에 의해 자동정렬됨
- map은 중복된 key 값을 가질 수 없다. ( multimap은 key 중복 값 가질 수 있음 )
- map은 중간 삽입 시 자동정렬되기 때문에 중간 삽입의 의미가 없다.
- key 값이 char, string 타입일 경우 char은 아스키코드, string은 대소 비교 함수 오버로딩이 있어 자동 정렬된다.
- 문자열은 컴파일러 입장에서 상수 주소로 인식된다. - key 값이 char*일 경우 문자열의 시작주소를 대소 비교하여 정렬, 간혹 주소 할당 위치가 다르게 되어 의도치 않은 순서로 정렬이 될 수 있다.
- 조건자를 이용하여 정렬할 수 있지만 비추천, string으로 key값을 줄 것을 권장
▶ 똑같은 key의 노드는 삽입 불가능, [] 연산자를 통해 해당 키 위치의 value 값은 변경 가능
Map.insert(pair<char, string>('c', "다인")); // 임시 객체 pair 방식의 원소 추가 방식
Map.insert(map<char, string>::value_type('a', "시우")); // 임시 객체 value_type 원소 추가 방식
Map['b'] = " 엄마"; // [] 연산자를 이용한 원소 추가
Map['c'] = "아빠"; // [] 연산자를 이용한 해당 key 위치의 value값 변경
Map['f']; // value값이 없는 key값 f인 노드가 삽입되므로 주의
Map.emplace('b', "아빠"); // 중복된 key값 'b' 삽입 불가
Map.insert({ 'a', "고고" }); // 중복된 key값 'a' 삽입 불가
출력 결과 :
a 시우 // 원소 삽입 순서와 상관없이 key에 따라 정렬됨
b 엄마 // 대괄호 연산자로 원소 추가됨
c 아빠 // 대괄호 연산자로 인해 "다인" 에서 "아빠"로 값 변경
f
▶ key 값을 string, char*로 할 경우 ( char은 아스키코드에 의해 정렬됨 )
map<string, int> MapStr;
MapStr.emplace("Banana", 3); // emplace로 추가
MapStr.insert({ "Apple", 1 }); // 유니폼초기화로 추가
for (auto iter = UnMap.begin(); UnMap.end() != iter; ++iter)
cout << iter->first << "\t" << (*iter).second << endl;
출력 결과
Banana 3
Apple 1
=> string 컨테이너가 포함하는 대소 비교 오버로딩에 의해 정렬됨
* key값에 char*이 들어가면 오류가 뜸, 이유를 모르겠음
■ 예시
#include <map>
void main(void){
map<char, string> Map;
// value_type을 임시객체로 생성하여 원소 추가한 방식
Map.insert(map<char, string>::value_type('b', "다인"));
Map.insert(map<char, string>::value_type('a', "시우"));
Map['b'] = " 엄마"; // key 값 'b'위치에 value 값 "엄마"로 대입
for (auto& i : Map) // auto는 pair<const char, string> 타입으로 결정 (원소 자료형)
cout << i.first << "\t" << i.second << endl;
}
출력 결과:
a 시우
b 엄마 // "다인" 이었는데, 대입으로 인해 값 "엄마"로 바뀜
※ 원소 삽입 순서와 상관없이 key값에 따라 자동 정렬됨 ( 연관 컨테이너 특성 )
[ unordered map ]
: 자동 정렬을 하지 않는 map, 기존의 map과 달리 이진 탐색트리가 아닌 해시 테이블로 구성
#include <unordered_map> // unordered_map 사용 시 포함
unordered_map<char, int> Map;
Map.emplace('b', 3); // emplace로 추가
Map.insert( { 'a', 1 } ); // 유니폼초기화로 추가
for(auto iter = Map.begin(); Map.end() != iter; ++iter)
cout << iter->first << "\t" << (*iter).second << endl;
출력 결과 :
b 3
a 1
※ (*iter)는 Map의 pair이기 때문에 (*iter).first, (*iter).second로 key와 value에 접근
※ iter는 객체이지만 포인터처럼 동작하므로 iter->first, iter->second로 해당 pair의 key와 value에 접근
[ multimap & unordered_multimap ]
: 기존 map & multimap과 동일하나 key 값이 동일해도 원소를 추가할 수 있다. ( 기존에는 key 중복 불가 )
#include <unordered_map> // unordered_map 사용 시 포함
unordered_multimap<char, int> Map;
Map.emplace('a', 3); // emplace로 추가
Map.insert( { 'a', 1 } ); // 유니폼초기화로 추가
for(auto iter = Map.begin(); Map.end() != iter; ++iter)
cout << iter->first << "\t" << (*iter).second << endl;
출력 결과 :
a 3
a 1
int iA = 10;
int& r = iA;
char s = 'a';
cout << r << "\t" << &r << "\t" << &iA << endl;
cout << [&, iA, s]() mutable ->int { iA = 100; s = 'b'; return r + static_cast<int>(s); }() << endl;
cout << [&, r, s]() mutable ->int { iA = 100; s = 'b'; return r + static_cast<int>(s); }() << endl;
cout << iA << "\t" << s << endl;
auto& ramda = [&, s]() mutable ->char { return s = 'c'; };
//[&]()->char { return s = 'c'; }();
cout << ramda() << "\t";
cout << s << endl;
mutable
[ mutable ]
: 상수 객체나 상수 함수 (const) 안에서 멤버 변수의 값을 변경할 수 있게 해주는 키워드
: const를 무시하고 '쓰기'를 가능하게 해주는 키워드
[ 클래스 멤버 변수에 사용 ]
class cDainn {
private:
mutable int iA;
}
=> mutable된 iA는 상수 객체와 상수 함수에 상관없이 값 변경 가능
[ 람다식에 사용 ]
[캡처절] (매개변수) mutable -> 반환형식 { 함수내용 }
=> 람다절은 값으로 캡처한 것들은 자동 const화하기 때문에 mutable로 const 풀어줌 ( 사본만 변경됨 )
■ 기능 / 주의
- mutable은 멤버 변수와 람다식에만 사용 가능한 듯하다.
- mutable은 const를 무시하는 문법으로 최대한 사용을 지양할 것
- 람다식에서 mutable은 값 캡처절만 가능, const를 풀고 사본의 값 변경만 가능 (원본 값 변경 불가능)
■ 예시
int iA = 10;
int& r = iA;
char s = 'a';
cout << r << "\t" << &r << "\t" << &iA << endl; // r은 iA를 참조하므로 둘의 주소값은 동일
cout << [&, iA, s]() mutable ->int { iA = 100; s = 'b'; return r + static_cast<int>(s); }() << endl;
// 사본 iA는 100으로 변경, 사본 s는 'b'로 변경, r은 원본 iA 참조(10), r + s는 10 + 98
cout << [&, r]() mutable ->int { iA = 100; return r + static_cast<int>(++(++s)); }() << endl;
// 원본 iA(10)는 100으로 변경, 사본 r은 기존의 iA인 10인 상태, 원본 s는 'a'(97), 10 + (++(++97))은 199
// 끝에 ()로 호출해줘야함
cout << iA << "\t" << s << endl; // 원본 iA 100, 원본 s 는 'c' (97)
auto& ramda = [&, s]() mutable ->char { return s = 'z'; };
// 함수 자체를 저장하는 것이므로 호출 없이 람다식(함수 이름)만 저장
cout << ramda() << "\t"; // z 출력
cout << s << endl; // 원본은 변경 안됨, c 출력
출력 결과:
10 0053F8E8 0053F8E8
198
199
100 c
z c'C++' 카테고리의 다른 글
| [TIL 33장] R-value 레퍼런스( feat. move() ), 이동 생성자 (0) | 2022.09.26 |
|---|---|
| [TIL 32장] C++ 11 주요 함수 - 유니폼 초기화, auto, 범위 지정 for문, 람다식, try-catch문 (0) | 2022.09.22 |
| [TIL 31장] 리스트(feat. 벡터&리스트 시간 복잡도 및 응용), 임시 객체 (0) | 2022.09.22 |
| [TIL 30장] 벡터 함수(feat. 2차원벡터), 알고리즘(feat. 조건자) (1) | 2022.09.19 |
| [TIL 29장] 컨테이너 함수(feat. deque), 반복자 (0) | 2022.09.15 |