[C++] 트리(Tree), set과 map

2024. 3. 22. 21:54Programming Language/C++

아래 링크 클릭 시 해당 본문으로 이동

트리(Tree)

이진 트리

트리 순회 (Tree Traversal)

중위 후속자

중위 선행자

set과 map

make_pair()과 find()


그래프

• 리스트에서 노드를 그래프에서는 vertex(정점)라고 한다.

ex) 지하철 노선도


트리(Tree)

• 그래프의 일종으로, 순회가 불가능한 그래프이다.

순회(curcit) : 처음 시작 노드부터 다시 자기 자리로 돌아오는 것

• 계층 관계를 표현할 때 사용한다.

  ex) 가족 관계도, 조직도

• 부모 노드와 자식 노드를 연결한다.

  ex) 윈도우의 폴더 구조

트리 용어 정리

• 이진 트리 : 자식의 개수가 2개 이하인 트리

• level(레벨) : 부모로부터 자식까지의 깊이

부모를 0이나 1로 두는데, 보통은 0으로 둔다.

가장 높은 레벨 = 해당 트리의 높이

  ex) 3 level => 높이 = 3

• 루트 노드 : 트리의 노드들 중에서 제일 위에 있는 노드로, 부모가 없다.

• 리프 노드 (단말 노드) : 트리의 노드들 중에서 제일 끝에 있는 노드로, 자식이 없다.


이진 트리

 문제를 절반씩 줄여나가면서 찾는 방식의 탐색 행위

이진 탐색 자체는 이진 탐색 트리랑은 무관하다.

이진 탐색은 반드시 트리에만 적용되는 개념이 아니다.

정렬되어 있는 데이터에 적용할 수 있다.

1. 완전 이진 트리(Complete Binary Tree)

• 루트 노드부터 자식 개수를 항상 꽉 채운 형태의 이진 트리

• 트리의 연결관계를 배열 형태로 구현할 수 있고, 보통 배열로 구현한다.

▷ 루트 노드를 1번으로 하고, 각각의 자식들을 순서대로 2~7까지 번호를 매겼을 때

• k(인덱스)의 첫 번째 자식 ➜ (2k + 1)번 인덱스

1번 노드(배열로는 0번째)의 첫 번째 자식은 1번 인덱스에 있다. ➜ 2

• k의 두 번째 자식 ➜ (2k + 2)번 인덱스

1번 노드의 두 번째 자식(오른쪽 자식)은 2번 인덱스에 있다. ➜ 3

 => 3은 1번의 자식이고 1번의 인덱스는 0이으므로 2k+2 = 2 × 0 + 2 ➜ 2번 인덱스

 

3번 노드의 자식은 6, 7번이다.

3의 첫 번째 자식은 5번 인덱스에 있다. ➜ 6

  => 3번의 인덱스는 2이므로, (2k+1에 적용) 3번의 왼쪽 자식은 5번 인덱스에 있다.

3의 두 번째 자식은 (2k+2에 적용) 6번 인덱스에 있다. ➜ 7

 

거꾸로 생각하여 6의 부모인 3은 몇 번째 인덱스에 있을까?

6 - 1 = 5, 5 ÷ 2 = 2 (나머지는 버리면 된다.)

즉, 2번 인덱스에 부모인 3이 있다.

(완전 이진 트리는 힙이라는 자료구조를 만들 때 쓰인다.)


2. 이진 탐색 트리(Binary Search Tree, BST)

• 이진 탐색을 위해 만들어진 이진 트리

• 데이터 입력 시 효율 : O(logN)

균형이 깨졌는지 체크하고 회전하는 기능도 있기 때문에 실제로는 좀 더 오래 걸릴 수도 있다.

• 탐색 시 효율 : O(logN)

• 트리의 모양이 밸런스가 유지되지 않으면 제대로 된 탐색 효율이 나오지 않는다.

때문에 자가 균형 기능이 필요하다. (AVL, Red/Black)

▷ 1~1000까지 100명이 하나씩 뽑을 때 225번을 뽑은 사람을 찾아야 하는 경우 (탐색)

각자 뭘 뽑았는지는 내가 모르는 상태이다.

다시 말해, 배열은 인덱스로 접근할 뿐, 인덱스에 어떤 값이 들어있는지는 모른다.

300이 300번째 인덱스에 있을 가능성은 너무 낮다.

그래서 0번부터 차례대로 보면서 내가 원하는 값을 찾아야 한다.

이때 숫자의 범위는 1~1000이고, 뽑는 사람은 100명이니까 100번 찾아야 한다.

 

처음부터 찾으면 너무 오래 걸리기 때문에 절반씩 계속 잘라나가는 방식을 사용해서 탐색한다.

100명 중 50번째 사람은 512를 가지고 있다.

그럼 225는 512를 기준으로 왼쪽에 있을 것이다.

이렇게 되면 오른쪽에 49명은 비교 대상에서 제외된다.

 

25번째 사람은 191을 갖고 있다.

225는 25번째 사람을 기준으로 오른쪽에 있다.

왼쪽에 24명의 사람도 비교 대상에서 제외된다.

이런 식으로 절반씩 계속 잘라나가면서 찾으면 처음부터 찾는 것보다 덜 오래 걸린다.

이진 탐색 조건

1. 데이터가 정렬되어 있어야 한다.

2. 해결해야 되는 데이터의 크기(문제)를 "절반"으로 줄여나가면서 탐색해 나간다.

빅오 표기법(Big-O notation)

• 빅오 표기법에서 앞에 계수는 무시한다.

n의 개수가 커지면 커질수록 효율이 기하급수적으로 늘어난다.

O(3n), O((1/2)n), O(n+3) = O(n)

이진 탐색할 때 최악의 문제 횟수 : log2(n) (n : 데이터의 개수)

더보기

log(로그)

log2(1024) = 10 → 2가 1024가 되려면 10승을 해야 함.

log10(1000) = 3 → 10이 1000이 되려면 3승을 해야 함.

log2(1000) = 대략 3

log2(2^32) = 32 → 2^32 = 약 42억

원하는 값이 나올 때까지 n을 계속 2로 쪼개며, 이는 2를 몇 승해야 되는지를 의미한다.

O(log2(N)) => logx(N) / logx(2) => logx(N)의 입장에서는 1/logx(2)가 계수이다.

1/logx(2) × logx(N) = logx(N) => 밑인 x는 무시한다.

∴ O(logN)

알고리즘 효율 순서(빠른 순)

• O(1) → O(logN) → O(N) → O(nlogN) → O(n^2)

O(logN)과 O(N)의 차이는 크다.

데이터 입력할 때 비용

BST(이진 탐색 트리) ➜ O(logN)

• vector, list ➜ O(1)

데이터 탐색할 때 비용

BST ➜ O(logN)

BST에서 데이터를 추가하는 비용도 탐색할 때와 같이 계속 2로 나누기 때문에 O(logN)이다.

• vector, list ➜ O(N)


탐색은 효율을 위해서 데이터가 입력될 때 약간의 손해를 감수한다.

아무 위치에 넣는 게 아니라 정렬하는 형태로 구성하고 있다.

이 자체로도 속도 방면에서 비효율적이다.

재할당되지 않는다는 가정 하에 벡터같은 경우, 탐색하지 않고 데이터 입력 개수와 상관없이 넣는 데이터 그대로 쌓여간

다. ➜ O(1) (빅오표기법의 상수시간)

 

이진 탐색 트리는 데이터를 집어넣을 때부터 정렬된 형태를 얻기 위해서 일종의 손해를 감수한다.

이진 탐색의 전제 조건인 정렬되어 있다는 규칙을 얻기 위함이다.

이진 탐색을 시도했을 때 데이터가 정렬되어 있으면 데이터를 추가하는 속도는 느리지만 그만큼 탐색에서 압도적인 효율을 보인다.

  ex) 특정 조건에 부합하는 것을 빠르게 가져와야 하는 경우

데이터 넣는 속도가 오래 걸리는 것은 게임으로 치면 로딩시간이 좀 더 걸리는 것이다.

로딩할 때 조금 손해를 보더라도 필요할 때 원하는 것을 빨리 찾아올 수 있는 장점이 있다.


트리 순회 (Tree Traversal)

모든 탐색의 시작은 루트부터이다.

• 트리 순회 종류 : 트리 전위 순회(Pre Order), 트리 중위 순회(In Order) , 트리 후위 순회(Post Order)

1) 트리 전위 순회

• 부모 → 왼쪽 자식 → 오른쪽 자식

2) 트리 중위 순회

왼쪽 자식 → 부모 → 오른쪽 자식

(부모의 우선순위가 중간이 있어서 중위이다.)

3) 트리 후위 순회

• 왼쪽 자식 → 오른쪽 자식 → 부모

 

이진 탐색 트리에서 중요한 것은 트리 중위 순회이다.

이진 탐색 트리에서 iterator는 루트부터 접근하지만 iterator가 가리키고 있는 데이터의 `begin()`은 중위 순회에서 제일 첫 번째 데

이터이다.

iterator가 ++하면 중위 순회 기준으로 다음 데이터에 접근한다.

아래 빨간색 숫자는 우선순위이다.

출처 : 유튜브 어소트락 아카데미

1. 트리 전위 순회

90 → 50 → 20 → 5 → 25 → 75 → 66 → 80 → 150 → 95 → 92 → 111 → 175 → 166 → 200

 

2. 트리 중위 순회

5 → 20 → 25 → 50 → 66 → 75 → 80 → 90 → 92 → 95 → 111 → 150 → 166 → 175 → 200

이진 탐색 트리가 정렬된 순서대로 접근할 수 있다.

 

3. 트리 후위 순회

5 → 25 → 20 → 66 → 80 → 75 → 50 → 92 → 111 → 95 → 166 → 200 → 175 → 150 → 90

 

중위 선행자, 중위 후속자

중위 순회 기준으로 다음에 있는 것이 중위 후속자, 이전에 있는 것이 중위 선행자이다.
이진 트리 예시에서 루트 노드인 90의 중위 선행자는 80이고, 중위 후속자는 92이다.


중위 후속자

1. 오른쪽 자식 보유 여부
• 오른쪽 자식 있음. + 오른쪽 자식에게 자식이 없음. => 오른쪽 자식이 중위 후속자이다.

  ex) D → I, E → K, F → M, G → O

 

무조건 오른쪽 자식이 중위 후속자인 것은 아니다.
오른쪽 자식이 있는데 해당 자식이 더 이상 자식이 없다면 오른쪽 자식이 중위 후속자이다.


오른쪽 자식이 있는데 해당 자식에게 또 자식이 있다면?

• 오른쪽 자식 있음. + 오른쪽 자식에게 자식이 있음. => 오른쪽 자식으로 간 다음, 왼쪽 자식이 없을 때까지 왼쪽으로 내려간다.
왼쪽 자식이 없는 해당 자식이 중위 후속자이다.

  ex) B → J, A → L, C → N


• 오른쪽 자식 없음. => 부모로부터 왼쪽 자식일 때까지 위로 올라간다.

  ex) H → D, I → B, J → E, K → A, L → F, M → C, N → G


2. 왼쪽 자식인지 아닌지
• 왼쪽 자식이면 => 부모가 중위 후속자이다.

  ex) H → D, J → E, L → F, N → G
• 왼쪽 자식이 아니면 => 그 윗 부모를 생각해야 한다.

  ex) I → B, K → A, M → C


오른쪽 맨 아래이면서 오른쪽 자식은 본인이 왼쪽 자식일 때까지 찾기 위해 끝까지 올라가도 없었다.
즉, 중위 후속자가 없으므로 end로 본다.

  ex) O

중위 선행자

1. 왼쪽 자식 보유 여부

• 왼쪽 자식 있음. + 왼쪽 자식에게 자식이 없음. => 왼쪽 자식이 중위 선행자이다.

  ex) G → N, F → L, E → J, D → H

 

• 왼쪽 자식 있음. + 왼쪽 자식에게 자식이 있음. => 왼쪽 자식으로 간 다음, 오른쪽 자식이 없을 때까지 오른쪽으로 내려간다.

  ex) C → M, A → K, B → I

 

• 왼쪽 자식 없음. => 부모로부터 오른쪽 자식일 때까지 위로 올라간다.

  ex) I → D, J → B, K → E, L → A, M → F, N → C, O → G

 

2. 오른쪽 자식인지 아닌지

• 오른쪽 자식이면 => 부모가 중위 선행자이다.

  ex) I → D, K → E, M → F, O → G
• 오른쪽 자식이 아니면 => 그 윗 부모를 생각해야 한다.

  ex) J → B, L → A, N → C


왼쪽 맨 아래이면서 왼쪽 자식은 본인이 왼쪽 자식일 때까지 찾기 위해 끝까지 올라가도 없었다.
즉, 중위 선행자가 없으므로 end로 본다.

  ex) H


이진 탐색 트리의 문제점

▷ 데이터의 입력이 순차적으로 되는 경우

만약 1~1000까지의 데이터 중 1000을 찾고 싶을 때, 제거해 낼 절반이 없기 때문에 데이터가 전혀 줄어들지 않는다.

따라서 이진 탐색 트리의 모양이 밸런스가 잡혀있어야 한 쪽을 선택했을 때 절반을 제거할 수 있다.

이를 방지하기 위해 실제 프로그래밍에서는 이진 탐색 트리만으로 탐색 알고리즘을 구현하지 않는다.
self balanced(자가 균형) 기능도 추가한 self balanced BST를 사용한다.
AVL, Red/Black : 베이스는 이진 탐색 트리지만 자가 균형 기능이 구현되어 있으며, 이를 시각적으로 도식화시켜놓은 사이트가 있다.
Red/Black Tree 사이트에서 오른쪽으로 계속 10부터 값을 넣어도 알아서 균형을 찾아간다. (균형이 깨졌는지 체크하는 기능 + 회전하는 기능)

`set`과 `map`

  std::set std::map
저장 고유 key만 저장 key-value(키-값) 쌍 저장
접근 key로 직접 접근 불가능 [] 또는 at()으로 직접 접근
값 정렬 key만 정렬 key별로 정렬
언제 사용? key만 필요한 경우 key와 관련 value가 모두 필요한 경우

set

• 이진 탐색 트리(일반적으로 Red/Black Tree)를 사용하여 정렬된 순서로 동적으로 저장하고 구성하는 STL 컨테이너

요소의 자동 정렬 및 효율적인 삽입, 삭제, 조회 작업이 필요할 때 유용하다.

• set의 요소는 기본적으로 오름차순으로 자동 정렬된다.

• 중복 요소를 허용하지 않는다.

• 삽입, 삭제, 검색과 같은 작업 효율 : O(logN)

• 요소는 메모리에 동적으로 할당되어 필요시 메모리를 늘릴 수 있다.

set<int> setInt;  // int를 저장할 수 있는 BST기반의 알고리즘을 사용한 컨테이너
setInt.insert(100);  // 객체를 이용해서 insert()로 데이터를 계속해서 넣을 수 있다.
// 하지만 힙 메모리에 동적 할당으로 값을 넣을 것이기 때문에 무한정 넣을 수는 없다.
// 객체(SetInt)는 루트 노드만 알면 모두 접근할 수 있다.
setInt.insert(50);
setInt.insert(150);

set보다는 map을 더 많이 사용한다.

map

• key-value(키-값) 쌍으로 요소를 저장하는 STL 컨테이너

• `map`은 type을 2개 지정한다. ➜ key, 값

첫 번째 타입인 key는 이진 탐색 트리를 구성하는 비교 데이터로 사용하며, 각 키는 단일 값과 연결된다.

두 번째 타입은 거기에 딸려있는 추가적인 데이터 타입을 의미한다.

• 항상 오름차순으로 저장된다. (비교 연산자 `<`는 `map`의 오름차순(기본)을 정의한다.)

• 삽입, 삭제, 검색과 같은 작업 효율 : O(logN)

• 각 key는 고유해야 한다.

중복된 키를 삽입하려고 하면 삽입 방법에 따라 삽입되지 않거나 기존 키의 값이 변경된다.

• 메모리는 동적으로 할당되어 필요에 따라 메모리를 사용한다.

 

ex1) 노드를 방이라고 했을 때, 그 안에 거주하고 있는 사람을 말하는 것이다.
ex2) 문자열로 생각했을 때

학생 정보라는 struct(구조체)에 이름, 나이, 성별 등 저장하고 싶은 데이터를 넣는다.

그중 이름을 key값으로 한다.

∴ map<학생이름, 학생정보>

map<int, float> mapData;

int ➜ key값으로만 사용할 타입으로, 이진 탐색 트리가 구성되는 데 실질적인 데이터
float ➜ 연결되어 있는 실제 데이터


`make_pair()`

2개의 값(또는 객체)을 묶어서 리턴해주는 함수

`find()`

말 그대로 찾는 함수이다.

mapData.find(L"홍길동");   // 홍길동을 찾고 싶을 때

이진 탐색 트리가 `const wchar_t*`을 기준으로 되어있기 때문에 문자열을 기준으로 찾는다.

찾았을 때 얻어지는 것은 2개를 묶어놨던 pair이다.

`find()`는 데이터 자체를 바로 주는 것이 아니라 그곳을 가리키고 있는 iterator를 준다.

즉, iterator는 2개를 묶어놨던 pair를 가리키고 있다.

mapiter = mapData.find(L"홍길동");  // 문자열 "홍길동"의 주소 탐색

찾는 데이터가 없을 때

탐색할 데이터가 존재하지 않을 때 되돌아온 iterator는 사용할 수 없지만 넣어놓지도 않은 데이터 때문에 프로그램이 터질 필요는 없다.

따라서 없는 데이터를 찾을 때 iterator는 end iterator를 반환한다.

`find()`의 결괏값의 iterator가 end iterator 상태라면 찾는 데이터가 없다는 의미이다.


▷ BST를 활용한 `map`과 `set` 사용 예시

더보기
#include <iostream>
#include <map>  // Red/Black 이진 탐색 트리가 구현되어있는 자료구조
#include <set>

using std::wcout;
using std::endl;
using std::map;
using std::make_pair;
using std::set;

// 성별
#define MAN 1
#define WOMAN 2

// 학생 정보 구조체
struct tStdInfo
{
	wchar_t szName[20];  // 이름
	unsigned char age;  // 나이
	unsigned char gender;  // 성별

	// 기본 생성자
	tStdInfo()
		:szName{}
		, age(0)
		, gender(0)
	{

	}

	// 생성자 오버로딩
	tStdInfo(const wchar_t* _pName, unsigned char _age, unsigned char _gender)
		:szName{}
		// szName{_pName}라고 하면 오류가 발생한다.
			// 배열 초기화는 wchar_t szArr[100] = L"abcdef"; 형태로는 가능한데,
			// 구조체 멤버는 _pName을 받아와서, 주소가 가리키는 곳에 있는 문자열을 멤버 내부 배열로 하나하나 옮겨와야 한다.
		, age(_age)
		, gender(_gender)
	{
		// wcscpy_s() : 문자열 복사 함수
		wcscpy_s(szName, _pName);  // 목적지(저장할 곳(멤버)), 가져올 원본
	}
};

// pair 구조체
struct pair
{
	const wchar_t* first;
	tStdInfo second;
};

int main()
{
	set<int> setInt;  // int를 저장할 수 있는 BST기반의 알고리즘을 사용한 컨테이너

	setInt.insert(100);  // 객체를 이용해서 insert()로 데이터를 계속해서 넣을 수 있다.
	// 하지만 힙 메모리에 동적 할당으로 값을 넣을 것이기 때문에 무한정 넣을 수는 없다.
	// 객체(SetInt)는 루트 노드만 알면 모두 접근할 수 있다.
	setInt.insert(50);
	setInt.insert(150);

	const wchar_t* pStr = L"문자열";
	wchar_t szArr[100] = L"abcdef";

	map<const wchar_t*, tStdInfo> mapData;
	// const wchar_t* => 이름이기 때문에 문자열 타입으로

	// 학생 정보
	tStdInfo info(L"홍길동", 18, MAN);
	tStdInfo info2(L"이지혜", 25, WOMAN);

	// make_pair() : 2개의 값(또는 객체)를 묶어서 리턴해주는 함수
	mapData.insert( make_pair(L"홍길동", info) );  // 노드 안에는 2개를 묶어놨던 pair가 있다.
	mapData.insert( make_pair(L"이지혜", info2) );

	// iterator 객체 생성
	map<const wchar_t*, tStdInfo>::iterator mapiter;
    
    // 한글이 출력되지 않을 때
	_wsetlocale(LC_ALL, L"korean");
	
    // find()
	mapiter = mapData.find(L"홍길동");  // 문자열 "홍길동"의 주소 탐색

	// 찾지 못했다면
	if (mapiter == mapData.end())
	{
		wcout << L"데이터를 찾을 수 없습니다." << endl;
	}
	// 찾았다면
	else
	{
		wcout << L"이름 : " << mapiter->second.szName << endl;
		wcout << L"나이 : " << mapiter->second.age << endl;
		wcout << L"성별 : " << endl;
		if (MAN == mapiter->second.gender)
		{
			wcout << L"남자" << endl;
		}
		else if (WOMAN == mapiter->second.gender)
		{
			wcout << L"여자" << endl;
		}
		else
		{
			wcout << L"알 수 없음." << endl;
		}
	}

	// map 안에서 사용하고 있는 pair라는 구조체도 템플릿으로써 첫 번째 타입은 const wchar_t*이고, 두 번째 타입은 tStdInfo이다.
	// 우리가 만든 tStdInfo라는 구조체가 또 다른 pair의 멤버가 되며, 역참조로 각각 접근할 수 있다.
	//(*mapiter).first;
	//(*mapiter).second;
	// ->도 가능
	mapiter->first;
	mapiter->second;


	return 0;
}

▽ 출력 결과

이름 : 홍길동
나이 : 18
성별 : 남자

▷ `map`과 문자열 비교를 활용한 사용자 정의 클래스 및 학생 정보 관리 예시

더보기
#include <iostream>
#include <map>  // Red/Black 이진 탐색 트리가 구현되어있는 자료구조
#include <string>  // 문자열 클래스 사용

using std::wcout;
using std::endl;
using std::map;
using std::make_pair;
using std::wstring;

// 성별
#define MAN 1
#define WOMAN 2

// 학생 정보 구조체
struct tStdInfo
{
	wchar_t szName[20];  // 이름
	unsigned char age;  // 나이
	unsigned char gender;  // 성별

	// 기본 생성자
	tStdInfo()
		:szName{}
		, age(0)
		, gender(0)
	{

	}

	// 생성자 오버로딩
	tStdInfo(const wchar_t* _pName, unsigned char _age, unsigned char _gender)
		:szName{}
		// szName{_pName}라고 하면 오류가 발생한다.
			// 배열 초기화는 wchar_t szArr[100] = L"abcdef"; 형태로는 가능한데,
			// 구조체 멤버는 _pName을 받아와서, 주소가 가리키는 곳에 있는 문자열을 멤버 내부 배열로 하나하나 옮겨와야 한다.
		, age(_age)
		, gender(_gender)
	{
		// wcscpy_s() : 문자열 복사 함수
		wcscpy_s(szName, _pName);  // 목적지(저장할 곳(멤버)), 가져올 원본
	}
};

// pair 구조체
struct pair
{
	const wchar_t* first;
	tStdInfo second;
};

// 내가 만든 클래스
class MyClass
{
private:
	int a;

public:
	bool operator < (const MyClass& _other) const
	{
		if (a < _other.a)
			return true;
		else
			return false;
	}
};

int main()
{
	// const wchar_t* => (ROM(읽기 전용 메모리)) 주소값
	// 때문에 문자열 자체의 크기를 비교하는 것이 아니라 100번지, 200번지처럼 주소로 우열을 가리게 된다.
	// 문자가 동일해서가 아니라 주소값이 같은 것을 찾았기 때문에 원하는 것을 찾을 수 있었다.
	// 문자열이 있는 주소값이 아니라 문자열 자체를 찾으려면?
		// 첫 번째 타입에 const wchar_t*를 넣으면 안 된다.
		// 문자열로 비교하려면 첫 번째 타입에 문자열을 다루는 클래스를 넣어줘야 한다.
	
	// 첫 번째 타입에 문자열 클래스 타입을 넣어준다.
	//map<wstring, tStdInfo> mapStdInfo;

	// map은 템플릿이기 때문에 첫 번째 타입으로 지정해준 객체들 간의 비교를 통해서 루트의 왼쪽으로 갈지, 오른쪽으로 갈지를 정한다.
	// 첫 번째 타입이 wstring 객체 타입이라서 map이 내가 넣어준 wstring끼리 비교한다.
	// 자체적으로 비교 연산자가 구현되어있기 때문에 문자로써 비교를 하므로 문제가 발생하지 않는다.

	// map의 첫 번째 타입을 내가 만든 클래스인 MyClass로 해보자.
	map<MyClass, tStdInfo> mapStdInfo;
	MyClass a;
	mapStdInfo.insert(make_pair(a, info));
	// '<'가 구현되지 않았다는 오류가 발생한다.
		// map의 첫 번째 key타입으로 내가 만든 클래스를 넣어놨다.
		// 내가 만든 클래스 타입끼리 비교할 수 있는 비교 연산자를 구현하지 않았다.
	// MyClass에 public: 부분(operator >, <)을 구현하면 내가 만든 클래스도 첫 번째 타입으로 사용할 수 있게 된다.
	// 즉, wstring끼리의 비교 기준이 있어야 한다.

	wstring str;
	str = L"abcdef";
	// wstring에 operator =가 구현되어있다.
	// 문자열 객체인 str은 주소를 가리키는 것이 아니라 문자열을 따로 관리한다.
	// 그래서 자체적으로 가지고 있는 공간 안에 문자열을 가져온다.
	//
	// 문자열 뒤에 이어서 붙일 수도 있다.
	// +=도 wstring 클래스 안에 구현되어있는 operator이다.
	str += L"ghijk";
	str += L"lmnop";
	
	// wstring은 힙 메모리에서 동적 할당으로 관리한다.
	// wstring은 가변 배열과 제일 유사하다.
		// vector<wchar_t> 즉, vector의 wchar_t 버전과 제일 유사하다.
	// ROM이 아니고 자체적인 힙 메모리 영역에 문자를 저장하는 것이기 때문에 문자열 수정도 가능하다.
	str[1] = L'c';

	// 비교 연산자도 가능
	// 크다, 작다의 경우 문자열의 우열 비교
	wstring str2 = L"abcxyz";
    
    wcout << L"str: " << str << endl;
    wcout << L"str2: " << str2 << endl;
    
    if (str < str2) {
        wcout << L"str이 str2보다 작다." << endl;
    } else {
        wcout << L"str이 str2보다 작지 않다." << endl;
    }

	return 0;
}

 ▽ 출력 결과

str: accdefghijklmnop
str2: abcxyz
str이 str2보다 작지 않다.

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

[C++] 문자  (0) 2024.03.24
[C++] 상속(Inheritance)  (0) 2024.03.24
[C++] list iterator, inline  (0) 2024.03.22
[C++] erase() (+ clear() 문제점)  (0) 2024.03.21
[C++] iterator  (0) 2024.03.20