map

-> map의 원소는 key와 value를 한 쌍으로 가진다.

-> 각 원소들은 삽입 시에 key값에 따라 자동 정렬이 발생한다.(순회 가능)

-> 노드 기반 컨테이너들 중 유일하게 key값을 통한 임의 접근이 가능하다.

-> 리소스 탐색용으로 많이 사용된다.

-> 단, 중복된 key값은 허용하지 않는다.

 

map

->map은 key와 value를 한 쌍으로 가진 컨테이너이다.

-> key로 사용할 자료형과 value로 사용할 자료형을 <>안에 명시해주면 된다.

-> 명시한 순서대로 key와 value의 타입이 결정된다.

map<int, int> Mymap;

map의 원소 삽입

map은 원소 삽입 시 key 값에 따라 자동 정렬이 발생한다.

-> 자동 정렬이 발생하기 때문에 앞, 뒤 원소 삽입의 의미가 없으므로 push 함수를 지원하지 않는다.

 

map의 원소

pair 객체(구조체)

map은 원소로 객체를 가진다.

pair<int, int> Mypair(1, 100);

구조체이기 때문에 읽기/쓰기가 가능하다. 

Mypair.first  = 10;
Mypair.second = 100;

               (key)                   (value)
cout << Mypair.first << ", " << Mypair.second << endl;

map의 원소 삽입 방법

#1. pair 객체

pair<int, int> Mypair<1, 100>;
Mymap,insert(Mypair);

auto iter = Mymap.begin();

for(; iter != Mymap.end(); ++iter)
{
 iter->second += 10;
 cout << iter->first << ", " << iter->second << endl; 
}

pair<int, int>(1, 100);
Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(pair<int, int>(2, 200));
Mymap.insert(pair<int, int>(3, 300));

map의 key값(iter->first)은 상수이기때문에 대입이 불가능하다.

 

#2. make_pair()

인자로 2가지 정볼르 받아 첫 번째 인자는 key값으로, 두 번째 인자는 value값으로 만든 pair를 반환한다.

Mymap.insert(make_pair(1, 100));

Mymap.insert(make_pair(2, 200));

Mymap.insert(make_pair(3, 300));

 

#3. value_type

map 컨테이너 내부에 구현된 pair객체라고 생각하면 된다.

map<int, int>::value_type MyValuetype(1, 100);
cout << MyValuetype.first << ", " << MyValuetype.second << endl;
MyValuetype.first  = 10;		// 불가능
MyValuetype.second = 1000;

Mymap.insert(MyValuetype);
Mymap.insert(map<int, int>::value_type(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));

value_type의 key값(first) 또한 상수이기때문에 대입이 불가능하다.

 

#4. index 방식

-> []를 사용한다.

Mymap[1] = 100;

Mymap[2] = 200;

for(auto pair : Mymap)
{
  cout << pair.first << ", " << pair, second << endl;
}

insert 와 []의 차이점

-> insert : 중복된 key값이 있을 경우 무시한다.

-> [] : 중복된 key값이 있을 경우 value를 수정한다.

 

map은 중복되는 key값을 허용하지 않기 때문에 []을 통한 삽입은 자주 사용하지 않는다.

 

#5. emplace()

모든 컨테이너의 삽입 용어를 획일화 하기 위해 추가되었다.

vector : emplace_back()

list     : emplace_back() / emplace_front()

map   : emplace()

Mymap.emplace(1, 100);
Mymap.emplace(2, 200);
Mymap.emplace(3, 300);

단, key의 타입이 다를 경우 오류가 발생한다.

 

#6. 유니폼 초기화

-> {}를 사용한다.

Mymap.insert({1, 100});

-> 초기화가 필요한 것들에 대해서 모든 초기화가 가능하다.

-> 가독성이 떨어진다는 단점이 있다.

 

map의 삽입 정리

map<int, int>		Mymap;

    // index 방식
	Mymap[0] = 0;
    
    // pair 객체
	Mymap.insert(pair<int, int>(1, 100));
    
    // 첫 번째 인자는 key값으로, 두 번째 인자는 value값으로 만든 pair를 반환
	Mymap.insert(make_pair(2, 200));
    
    // map 컨테이너 내부에 구현된 pair 객체
	Mymap.insert(map<int, int>::value_type(3, 300)); 
    
    // 획일화된 삽입 용어(C++11 문법)
	Mymap.emplace(4, 400);
    
    // 유니폼 초기화(C++11 문법)
	Mymap.insert({ 5, 500 });
}

 

map의 반복자

map<int, int> Mymap;

map<int, int>::iterator iter = Mymap.begin();

++iter;
++iter;

cout << iter->first << ", " << iter->second << endl;

map은 노드기반 컨테이너이기 때문에 양방향접근 반복자를 사용한다.

 

 

map의 중간 삽입

Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(make_pair(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));
Mymap.emplace(4, 400);
Mymap.insert({ 5, 500 });

map<int, int>::iterator iter = Mymap.begin();
++iter;
++iter;

Mymap.insert(iter, {10, 1000});

for(auto pair : Mymap)
  cout << pair.first << ", " << pair.second << endl;

여기서 우리가 원하는 값은 1, 2, 10, 3, 4, 5 이다.

하지만 map은 key값에 따라 자동 정렬이 발생한다.

그렇기 때문에 결과는 1, 2, 3, 4, 5, 10이 나오게 된다.

-> 중간 삽입의 의미가 없게된다.

 

map의 중간 삭제

Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(make_pair(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));
Mymap.emplace(4, 400);
Mymap.insert({ 5, 500 });

map<int, int>::iterator		iter = Mymap.begin();
map<int, int>::iterator		iter_end = Mymap.end();
++iter;
++iter;

Mymap.erase(iter);
iter = Mymap.begin();

for(; iter != iter_end; ++iter)
{
   cout << iter->first << ", " << iter->second << endl;
}

map의 중간 삭제는 자동 정렬 발생과는 상관이 없으므로

정상적으로 원하는 값이 나오게 된다.(다른 컨테이너 방식과 같음)

 

map의 탐색_find()

cout << Mymap[10] << endl;

탐색을 하기 위해 []를 사용할 경우 key값이 있으면 value를 반환한다.

 

하지만, key값이 없을 경우 원소를 삽입하게된다.

-> 새로운 원소를 삽입 후에 그 원소에 있는 value 값을 반환

 

find()의 사용 방법은 다음과 같다.

map<int, int>		Mymap;

Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(make_pair(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));
Mymap.emplace(4, 400);
Mymap.insert({ 5, 500 });

auto iter = Mymap.find(4);

if(iter != Mymap.end())
{
   cout << iter->first << ", " << iter->second << endl;
}

find 함수의 문제점

-> find함수는 단순 비교이다.

map<const char*, int> Mymap;

Mymap.emplace("AAA", 10);
Mymap.emplace("BBB", 20);
Mymap.emplace("CCC", 30);

auto iter = Mymap.find("BBB");

char szBuff[16] = "AAA";
auto iter = Mymap.find(szBuff);

if(iter != Mymap.end())
{
   cout << iter->first << ", " << iter->second << endl;
}
else
{
   cout << "탐색 불가" << endl;
}

 

-> key값으로 저장하는 것은 문자열, Data영역의 주소이다.

-> 탐색하고자 하는 szBuff는 지역 변수 stack영역에 할당이 되므로 탐색 불가가 출력이 된다.

-> 즉, 문자열을 비교하는 것이 아니라 단순 주소를 비교하고 있는 것이다.

 

map 컨테이너의 key값이 포인터일 경우 탐색 방법

-> 알고리즘 함수의 find_if를 사용한다.

-> find_if는 단항 조건자여야 한다.

-> 즉, 함수 객체를 통해서 탐색을 하면 된다.

class CStringCmp
{
public:
   CStringCmp(const char* _pString) : m_pString(_pString) {}
   
public:
   template <typename T>
   bool operator()(T& _dst)
   {
     return !strcmp(m_pString, _Dst.first);
   }
   
private:
   const char* m_pString;
};

void main()
{
  map<const char*, int>		Mymap;
  Mymap.emplace("AAA", 10);
  Mymap.emplace("BBB", 20);
  Mymap.emplace("CCC", 30);

  char	szBuff[16] = "AAA";

	
  auto iter = find_if(Mymap.begin(), Mymap.end(), CStringCmp(szBuff));

  if (iter != Mymap.end())
  {
   	 cout << iter->first << ", " << iter->second << endl;
  }
  else
  {
     cout << "탐색 불가" << endl;
  }
}

'Programming > STL' 카테고리의 다른 글

C++(STL Container) 반복자(iterator)  (0) 2020.09.28
C++(STL Container) vector  (0) 2020.09.27
C++(STL Container) list  (0) 2020.09.27
C++ STL이란?  (0) 2020.09.27
STL map / unordered_map 과 map이 사용하는 자료구조  (0) 2020.05.07

list

-> 노드를 기반으로 하는 컨테이너 이다.

-> 더블 링크드 리스트로 구현되어 있다.

-> 앞, 뒤 노드의 삽입 및 삭제가 가능하다.

각 노드는 연속적인 메모리를 사용하는 것이 아니고,

비 연속적인 메모리에 여기저기 저장되지만 포인터로 각 노드를 연결하여

마치 연속된 메모리를 사용하는 것처럼 보인다.

-> 임의 접근이 불가능

-> 탐색하고자하는 원소가 나올 때까지 모든 노드를 순회해야함

-> 선형 시간 O(n)

삽입 삭제 시에는 단순 포인터의 해제 및 연결만 하기 때문에

메모리를 밀고 당길 필요가 없어 빠르다.(상수 시간 O(1))

메모리의 재할당 및 복사가 필요 없기 때문에 런타임 시에도 삽입 삭제가 용이하다.

 

list의 선언

list<int> listInt;

list의 원소 삽입

listInt.push_back(3);
listInt.push_back(4);
listInt.push_back(5);
listInt.push_front(1);
listInt.push_front(2);

list는 앞에서도 원소를 삽입할 수 있기에 push_front 함수를 지원한다.

 

여기서 원소를 출력해야하는데 list는 배열 기반 컨테이너가 아니므로 인덱스접근이 불가능하다.

list의 모든 원소를 순회하기 위해서는 반복자를 활용해야한다.

list<int>::iterator iter = listInt.begin();

for(; iter != listInt.end(); ++iter)
{
   cout << *iter << endl;
}

그런데 반복자가 아닌 list의 원소를 확인할 수 있는 방법이 있다.

그것은 '범위기반 for문' 과 'auto'이다.

 

auto

-> 자료형이다.

-> 대입 받는 데이터의 타입으로 자동 설정된다.

-> 단, 선언과 동시에 초기화를 진행해야만 한다.

auto a = 10    // int형
auto b = 3.14  // double형
auto c = 3.14f // float형


list<int>::iterator	iter = listInt.begin();
auto		        	iter = listInt.begin();

 

범위기반 for문

-> 배열 또는 컨테이너의 모든 원소를 순회할 때 사용한다.

-> 단, 범위 기반 for문을 사용 중 원소의 개수가 바뀌게 되면 오류가 발생한다.

-> 그러므로 단순 순회 용도로만 사용한다.

 

 

list의 원소 삭제

listInt.pop_back();
listInt.pop_front();

for(auto iNum : listInt)
{
  cout << iNum << endl;
}

 

list의 멤버함수

size(), empty(), clear(), front(), back(), begin(), end(), swap() 등

-> 메모리와 관련된 함수를 제외하고는 vector와 동일하다.

 

list의 반복자

-> 임의 접근이 불가능하기 때문에 임의 접근 반복자를 사용하지 않고,

-> 양 방향 접근 반복자를 사용한다.

list<int> listInt;

auto iter = listInt.begin();

++iter;

 

list의 중간 삽입

auto iter_begin = listInt.begin();
auto iter_end   = listInt.end();

++iter_begin;
++iter_begin;

listInt.insert(iter_begin, 999);

list는 vector와 달리 메모리를 밀고 당길 필요가 없다.

단순 빈 공간에 원소를 삽입 후 포인터 연결만 해제 및 재연결하면 되기 때문에

반복자(begin 과 end)의 위치가 변경되지 않아 무효화가 발생하지 않는다.

 

list의 중간 삭제

auto iter_begin = listInt.begin();
auto iter_end   = listInt.end();

++iter_begin;
++iter_begin;

iter_begin = listInt.erase(iter_begin);

 

list의 정렬

-> list는 멤버 함수로 sort를 지원한다.

template <typename T>
bool Less(T _Left, T _Right)
{
	return _Left < _Right;
}

void main()
{
   list<int> listInt;
   
   listInt.push_back(5);
   listInt.push_back(4);
   listInt.push_back(1);
   listInt.push_back(3);
   listInt.push_back(2);

   listInt.sort();
   listInt.sort(Less<int>);
   listInt.sort(less<int>());
   
   for(auto iNum : listInt)
   {
     cout << iNum << endl;
   }
}

list의 멤버 함수 sort는 아무런 조건도 명시하지 않을 경우 기본 오름차순 정렬이 발생한다.

하지만 functional 또는 템플릿 함수를 만들어서 사용해도 무방하다.

 

list의 멤버 함수안에 있는 sort와 reverse를 함께 사용하면 기본 오름차순에서 내림차순 정렬로 바꿀 수 있다.

listInt.sort();
listInt.reverse();

 

'Programming > STL' 카테고리의 다른 글

C++(STL Container) vector  (0) 2020.09.27
C++(STL Container) map  (0) 2020.09.27
C++ STL이란?  (0) 2020.09.27
STL map / unordered_map 과 map이 사용하는 자료구조  (0) 2020.05.07
STL vector에서 push_back과 emplace_back의 차이점  (0) 2019.12.04

STL

-> Standard Template Library의 약자

-> 표준에 등록된 클래스 템플릿의 집합

-> 자료구조 및 알고리즘 등 사용법이 다른 것을 획일화 시킨 것.

 

STL의 구성 요소

#1. 컨테이너

-> 메모리에 등록된 STL이다.

-> 여러 컨테이너 중 대표적인 것 3가지

 

컨테이너 구분 기준

메모리 등록 방법

#1. 표준 시퀀스 컨테이너(선형적)

-> vector, list

 

#2. 표준 연관 컨테이너(비 선형적)

-> 일렬로 줄 지을 수 없는 방법

-> 대표적으로 트리 구조가 있음.

-> map

 

원소 저장 방법

#1. 배열 기반

-> 배열에 원소를 저장

-> vector

 

#2. 노드 기반

-> 메모리 여기저기 저장하면서 포인터로 연결해 놓는 구조

-> list, map

-> C#에서는 list가 배열 기반 형태로 되어있고, C++에서는 노드 기반으로 되어있다.

 

#2. 알고리즘

-> 대부분의 알고리즘은 전역으로 구성되어 있다.

-> 정렬, 탐색 등 획일화 하기 위해 각 컨테이너의 멤버가 아닌 전역으로 정의되어있다.

 

#3. 반복자

-> 컨테이너를 순회하는데 동일한 방법으로 순회할 수 있도록 만들어 놓았다.

-> STL의 핵심이라고 할 수 있따.

-> 각 컨테이너의 멤버로 정의되어 있다.

 

#4. 함수 객체

-> 말 그대로 함수를 객체처럼 사용하는 것이다.

 

템플릿의 형태

1
2
3
4
5
template <typename T>
T 함수이름(T _a, T _b)
{
    
}
cs
 

#1. 함수와 템플릿

-> 템플릿 함수

1
2
3
4
5
template <typename T>
T Add(T _a, T _b)
{
    return _a + _b;
}
cs

-> 컴파일 중 템플릿을 호출하는 코드라인을 컴파일할 때

-> 컴파일러가 실제 함수의 코드를 생성한다.

-> <>안에 명시한 자료형이 함수 템플릿에서 정의한 T자리에 매칭이 된다.

-> 사용자가 <>를 명시하지 않을 경우 인자의 타입에 따라 템플릿 함수가 생성된다.

-> 템플릿 함수는 일반적으로 포인터간의 + 연산은 불가능하다.

-> 하지만 템플릿의 특수화(오버로딩)를 통해서 합치는 방법이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <>
char* Add(char* _a, char* _b)
{
    char* pStr = new char[strlen(_a) + strlen(_b) + 1];
 
    strcpy_s(pStr, strlen(_a) + strlen(_b) + 1, _a);
    strcat_s(pStr, strlen(_a) + strlen(_b) + 1, _b);
 
    return pStr;
}
 
 
void main()
{    
    char* pStr = Add<char*>("Hello""World");
 
    cout << pStr << endl;
 
    delete[] pStr;
}
cs

 

두 가지 이상의 자료형을 사용하는 경우

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T1, typename T2, typename T3>
T3 Add(T1 _a, T2 _b)
{
    return _a + _b;
}
 
 
void main()
{
    double dA = Add<intfloatdouble>(103.14f);
    cout << dA << endl;
}
cs

 

클래스 템플릿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
class CObj
{
public:
    CObj() {}
    CObj(T _a, T _b) : m_A(_a), m_B(_b) {}
 
private:
    T    m_A;
    T    m_B;
};
 
void main()
{    
    CObj<int>        ObjInt;
    CObj<float>        ObjFloat;
}
cs

 

템플릿의 파일 분할

-> 템플릿은 파일 분할을 하지않는다.

-> 파일 분할을 하게되면 다른 소스파일에서 선언 하였을 때,

-> 해당 템플릿의 정의부를 알 수 없기 때문에 컴파일러가 템플릿 클래스를 생성할 수가 없다.(링커 오류가 발생)

-> 템플릿 클래스는 선언한 코드라인을 컴파일 할 때 생성한다.

-> 그러므로 템플릿을 사용할 때는 헤더파일에 선언과 정의를 같이 해줘야 한다.

 

 

템플릿의 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename T>
class CObj
{
public:
    void Func()
    {
 
    }
};
 
template <typename T>
class CPlayer : public CObj<T>
{
 
};
cs

 

#1. 어떤 자료형을 사용하는 클래스인지 알려주어야 한다.

#2. T는 무엇인지 알려주어야 한다.

 

템플릿과 static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template <typename T>
class CObj
{
public:
    void Func()
    {
        m_Static += 1;
        cout << m_Static << endl;
    }
 
private:
    static T    m_Static;
};
 
template <typename T>
T CObj<T>::m_Static = 0;
 
void main()
{
    CObj<int>        objInt;
   objInt.Func();     // 1
   objInt.Func();     // 2
   objInt.Func();    // 3
 
    CObj<int>        objInt2;
   objInt2.Func();    // 4
   objInt2.Func();    // 5
   objInt2.Func();    // 6
 
    CObj<float>      objFloat;
   objFloat.Func();   // 1
}
cs

-> 같은 자료형을 사용하는 템플릿 클래스끼리만 static 변수를 공유한다.

-> 자료형이 달라질 경우 static 변수는 공유하지 않는다.(재 생성)

'Programming > C++ Basic' 카테고리의 다른 글

C++ 시간 복잡도  (0) 2020.09.27
C++ 함수 객체  (0) 2020.09.23
C++ 임시 객체  (0) 2020.09.23
C++ 연산자 오버로딩(operator)  (0) 2020.09.23
C++ 인라인  (0) 2020.09.23

+ Recent posts