double-ended queue

 

vector와는 다르게 데이터가 꽉 차면 새로운 추가 공간을 할당하여 추가 사용한다.

vector와 list의 혼합 형태같은 모양이다.

 

배열과 같은 임의 접근을 지원한다.

vector와 마찬가지로 배열 기반으로 동작하지만 메모리 할당 정책이 다르다.

마치 아파트의 동과 호수로 구분된 듯한 모양이다.

 

데이터의 처음/끝의 삽입 및 삭제는 빠르게 동작한다.

중간 삽입 및 삭제는 vector와 마찬가지로 값을 땡기거나 밀어내기 때문에 성능이 좋지 않다.

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

[STL] set, multimap, multiset  (0) 2022.08.31
[STL] map  (0) 2022.08.31
[STL] list  (0) 2022.08.31
[STL] vector  (0) 2022.08.30
[콜백 함수] 콜백 함수  (0) 2022.08.30

노드 방식으로 작동. 이중 연결 리스트로 구현되어있다.

vector와 다르게 임의 접근을 허용하지 않는다.

삭제 자체는 O(1)이지만 원소에 접근하는 시간은 O(N)이다. 탐색과 삭제는 분리해서 봐야한다.

 

연속된 공간이 아닌 노드 단위로 관리되기 때문에 삽입 및 삭제는 큰 오버헤드 없이 수행할 수 있지만 이전, 다음 노드의 정보들만 가지고 있으므로 임의 접근은 매우 느리기 때문에 공식적으로 지원하는 메소드는 없다.

 

begin은 맨 처음 값이라서 prev가 없어야 정상이겠지만 실제로는 end를 가리키는 더미 노드 주소값이 들어가있다.

[1] <-> [2] <-> .. [5] <-> [_Myhead : end()] <-> [1] <-> ... 이런 구성으로 되어있다.

하지만 연결이 되어있다고 해서 실제로 begin에서 prev로 접근하면 크래시가 발생한다. 잘못된 코드를 잡아주기 위해 연결한 것이지 실제로 접근해도 되는 것은 아니다.

 

iterator의 증감 연산자는 사용이 가능하지만 iterator + 1 같은 형식은 사용이 불가능하다.

 

custom list 간단하게 구현

더보기
#include <iostream>
#include <list>
using namespace std;

// list (연결 리스트)
// - list의 동작 원리
// - 중간 삽입/삭제
// - 처음/끝 삽입/삭제
// - 임의 접근

template<typename T>
class Node
{
public:
	Node() : _next(nullptr), _prev(nullptr), _data(T()) {}
	Node(const T& value) : _next(nullptr), _prev(nullptr), _data(value) {}
public:
	Node*	_next;
	Node*	_prev;
	T		_data;
};

template<typename T>
class Iterator
{
public:
	Iterator() : _node(nullptr)
	{

	}

	Iterator(Node<T>* node) : _node(node)
	{

	}

	Iterator& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	Iterator operator++(int)
	{
		Iterator<T> temp = *this;
		_node = _node->_next;
		return temp;
	}
	
	Iterator& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Iterator operator--(int)
	{
		Iterator<T> temp = *this;
		_node = _node->_prev;
		return temp;
	}

	T& operator*()
	{
		return _node->_data;
	}

	bool operator==(const Iterator& right)
	{
		return _node == right._node;
	}

	bool operator!=(const Iterator& right)
	{
		return _node != right._node;
	}

public:
	Node<T>* _node;
};

template<typename T>
class List
{
public:
	List() : _size(0)
	{
		_header = new Node<T>();
		_header->_next = _header;
		_header->_prev = _header;
	}

	~List()
	{
		while (_size > 0)
			pop_back();

		delete _header;
	}

	void push_back(const T& value)
	{
		AddNode(_header, value);
	}

	void pop_back()
	{
		RemoveNode(_header->_prev);
	}

	int size() const { return _size; }

private:
	Node<T>* AddNode(Node<T>* before, const T& value)
	{
		Node<T>* node = new Node<T>(value);

		Node<T>* prevNode = before->_prev;
		prevNode->_next = node;
		node->_prev = prevNode;

		node->_next = before;
		before->_prev = node;
		
		_size++;

		return node;
	}

	Node<T>* RemoveNode(Node<T>* node)
	{
		Node<T>* prevNode = node->_prev;
		Node<T>* nextNode = node->_next;
		prevNode->_next = nextNode;
		nextNode->_prev = prevNode;

		delete node;

		_size--;

		return nextNode;
	}

public:
	typedef Iterator<T> iterator;
	iterator begin() { return iterator(_header->_next); }
	iterator end() { return iterator(_header); }
	iterator insert(iterator it, const T& value)
	{
		Node<T>* node = AddNode(it._node, value);
		return iterator(node);
	}
	iterator erase(iterator it)
	{
		Node<T>* node = RemoveNode(it._node);
		return iterator(node);
	}


private:
	Node<T>* _header;
	int _size;
};

int main()
{
	List<int> li;
	List<int>::iterator eraseIt;

	for (int i = 0; i < 10; i++)
	{
		if (i == 5)
			eraseIt = li.insert(li.end(), i);
		else
			li.push_back(i);
	}

	li.pop_back();

	li.erase(eraseIt);

	for (List<int>::iterator it = li.begin(); it != li.end(); ++it)
		cout << *it << endl;

	return 0;
}

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

[STL] map  (0) 2022.08.31
[STL] deque  (0) 2022.08.31
[STL] vector  (0) 2022.08.30
[콜백 함수] 콜백 함수  (0) 2022.08.30
[콜백 함수] 템플릿 기초  (0) 2022.08.30

STL (Standard Template Library)

프로그래밍할 때 필요한 자료구조/알고리즘들을 템플릿으로 제공하는 라이브러리

 

컨테이너 : 데이터를 저장하는 하나의 객체 (자료구조)

 

'어떤 원리로 작동하길래 배열을 유동적으로 사용할 수 있는 것인가?'

 

1) 여유분을 두고 메모리를 할당한다.

-> Q1. 여유분은 얼만큼이 적당한가?

 

2) 여유분까지 꽉 차면 메모리를 증설한다.

-> Q2. 얼마나 증설해야할까?

-> Q3. 증설 시, 기존의 데이터를 어떻게 처리할까?

 

size() : 현재 저장된 데이터의 개수

capacity() : 현재 동적 배열의 최대 크기

reserve() : capacity를 임의의 크기로 지정. (면접 질문: reserve 사용 이유? -> capacity가 증가할 때마다 복사에 의한 오버헤드가 일어나기 때문에 미리 넉넉하게 할당)

resize() : size를 임의의 크기로 지정. capacity도 자동으로 늘어난다. resize로 지정한 경우 push_back이 아닌 배열로 접근해서 데이터를 조작해야 한다. 생성자에서 지정해도 동일하게 작동한다.

 

컴파일러마다 다르지만 size가 capacity를 초과하면 capacity를 1.5배씩 증설시키며 크기를 동적으로 확장시킨다.

capacity는 늘어날지언정 줄어들지는 않는다. clear를 사용해도 줄어들지 않는다.

vector<int>().swap(v);

하지만 이런식으로 임시 vector와 교환 해서 capacity가 0인 것으로 돌려받는 방법은 있다.

보통은 이렇게까지 할 이유는 없어서 잘 이용되지 않는다.

 

반복자 (Iterator)

내부적으로는 포인터를 들고 있다.

vector뿐 아니라 다른 컨테이너에도 공통적으로 있는 개념이다.

const_iterator, reverse_iterator 등이 있다.

 

원소의 중간 삽입 및 삭제 : vector는 반드시 메모리 블록에 연속되어 있어야 하기 때문에 중간 삽입/삭제는 매우 비효율 적으로 동작한다. (오버헤드가 크게 발생)

insert와 erase로 삽입 및 삭제를 할 수 있다.

insert와 erase는 삽입/삭제한 원소의 위치 반복자를 반환해준다.

erase의 경우 end 범위는 삭제에 포함되지 않는다.

 

erase로 삭제된 iterator는 더 이상 해당 컨테이너에 속하지 않는다. (모든 정보가 NULL로 기록된다)

 

원소의 처음/끝 삽입 및 삭제 : 처음 원소의 삽입 및 삭제는 위와 같은 이유이지만 끝 원소의 삽입 및 삭제는 괜찮다.

 

원소의 임의 접근 : 가능

 

custom vector 간단하게 구현

더보기
#include <iostream>
#include <vector>
using namespace std;

template<typename T>
class Iterator
{
public:
	Iterator() : _ptr(nullptr) {}
	Iterator(T* ptr) : _ptr(ptr) {}

	Iterator& operator++()
	{
		_ptr++;
		return *this;
	}

	Iterator operator++(int)
	{
		Iterator temp = *this;
		_ptr++;
		return temp;
	}

	Iterator& operator--()
	{
		_ptr--;
		return *this;
	}

	Iterator operator--(int)
	{
		Iterator temp = *this;
		_ptr--;
		return temp;
	}

	Iterator operator+(const int count)
	{
		Iterator temp = *this;
		temp._ptr += count;
		return temp;
	}

	Iterator operator-(const int count)
	{
		Iterator temp = *this;
		temp._ptr -= count;
		return temp;
	}

	bool operator==(const Iterator& right) { return _ptr == right._ptr;  }
	bool operator!=(const Iterator& right) { return _ptr != right._ptr;  }

	T& operator*() { return *_ptr; }

public:
	T* _ptr;
};

template<typename T>
class Vector
{
public:
	Vector() : _data(nullptr), _size(0), _capacity(0) {}
	~Vector()
	{
		if (_data != nullptr)
			delete[] _data;
	}

	void push_back(const T& val)
	{
		if (_size == _capacity)
		{
			int newCapacity = static_cast<int>(_capacity * 1.5f);
			if (newCapacity == _capacity) newCapacity++;

			reserve(newCapacity);
		}

		_data[_size] = val;
		_size++;
	}

	void reserve(const int capacity)
	{
		_capacity = capacity;
		T* newData = new T[_capacity];

		for (int i = 0; i < _size; i++)
			newData[i] = _data[i];

		if (_data != nullptr)
			delete[] _data;

		_data = newData;
	}

	T& operator[](const int pos)
	{
		return _data[pos];
	}

	int size() const { return _size; }
	int capacity() const { return _capacity; }

public:
	typedef Iterator<T> iterator;

	iterator begin() { return iterator(&_data[0]); }
	iterator end() { return begin() + _size; }

private:
	T* _data;
	int _size;
	int _capacity;
};

// vector (동적 배열)
// - vecotr의 동작 원리
// - 중간 삽입/삭제
// - 처음/끝 삽입/삭제
// - 임의 접근

int main()
{
	Vector<int> v;

	v.reserve(100);

	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		cout << v.size() << " " << v.capacity() << endl;
	}

	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << endl;
	}
	
	cout << "------------------" << endl;

	for (Vector<int>::iterator it = v.begin(); it != v.end(); ++it)
	{
		cout << *it << endl;
	}

	return 0;
}

 

clear를 구현하는 경우 데이터를 지울 필요 없이 그냥 size만 0으로 재설정 하면 된다.

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

[STL] deque  (0) 2022.08.31
[STL] list  (0) 2022.08.31
[콜백 함수] 콜백 함수  (0) 2022.08.30
[콜백 함수] 템플릿 기초  (0) 2022.08.30
[콜백 함수] 함수 객체  (0) 2022.08.30

함수 포인터 + 함수 객체 + 템플릿을 모두 활용한다.

 

class Item
{
public:

public:
	int _itemId = 0;
	int _rarity = 0;
	int _ownerId = 0;
};

class FindByOwnerId
{
public:
	bool operator()(const Item* item)
	{
		return item->_ownerId == _ownerId;
	}

public:
	int _ownerId;
};

class FindByRarity
{
public:
	bool operator()(const Item* item)
	{
		return item->_rarity >= _rarity;
	}

public:
	int _rarity;
};

Item* FindItem(Item items[], int itemCount, FindByOwnerId selector) // 확장성이 떨어짐
{
	for (int i = 0; i < itemCount; i++)
	{
		Item* item = &items[i];

		if (selector(item)) return item;
	}
	return nullptr;
}

 

위와같은 경우 FindItem의 세번째 인자로 함수 객체를 넘겨주는데, 같은 형식의 함수 객체를 커버하지 못해서 확장성이 떨어진다.

그렇다고 부모 클래스를 만들어서 업캐스팅을 하는 것 보다는 템플릿을 이용하면 더 깔끔하게 처리가 된다.

 

template<typename T>
Item* FindItem(Item items[], int itemCount, T selector)
{
	for (int i = 0; i < itemCount; i++)
	{
		Item* item = &items[i];

		if (selector(item)) return item;
	}
	return nullptr;
}

 

 

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

[STL] list  (0) 2022.08.31
[STL] vector  (0) 2022.08.30
[콜백 함수] 템플릿 기초  (0) 2022.08.30
[콜백 함수] 함수 객체  (0) 2022.08.30
[콜백 함수] 함수 포인터  (0) 2022.08.30

함수나 클래스를 찍어내는 틀. 함수 템플릿과 클래스 템플릿이 있다.

 

template<typename T>
template<class T> // 둘다 사용 가능함

 

함수 템플릿

 

template<typename T>
void Print(T a) { // do something }

Print(50); // 암시적
Print<int>(50); // 명시적

 

인자가 2개인 경우도 사용 가능하다.

template<typename T1, typename T2>
void Print(T1 a, T2 b)
{
	// do something
}

 

템플릿 특수화

특정 타입에 대해서만 다른 규칙을 따르도록 하고 싶을 때 사용한다 (예외 적용)

template<typename T>
void Print(T a)
{
	// do something
}

template<>
void Print(Knight a)
{
	// do something
}

 

클래스 템플릿

 

template<typename T>
class RandomBox
{
public:
	T GetRandomData()
	{
		int idx = rand() % 10;
		return _data[idx];
	}

public:

	T _data[10];
};

 

무조건 typename만 붙여줘야 하는 것은 아니다.

template<typename T, int SIZE = 10> // 기본값 설정 가능
class RandomBox
{
public:
	T GetRandomData()
	{
		int idx = rand() % SIZE;
		return _data[idx];
	}

public:

	T _data[SIZE];
};

 

단, 템플릿 인자가 다른 경우 서로 별개의 클래스로 보기때문에 클래스 이름이 같아보여도 다형성이 성립하지 않는다.

인자까지 같아야만 같은 클래스로 인식한다.

RandomBox<int, 10> rb1;
RandomBox<int, 20> rb2;
rb1 = rb2 // 불가능!

 

템플릿 특수화

template<int SIZE = 10>
class RandomBox<double, SIZE>
{
public:
	double GetRandomData()
	{
		int idx = rand() % SIZE;
		return _data[idx];
	}

public:

	double _data[SIZE];
};

 

함수는 오버로딩이 지원되기 때문에 이름이 같아도 상관 없었지만 클래스는 그렇지 않기때문에 템플릿 특수화 라는것을 명시하기 위해 클래스명에 추가로 명시를 한다.

 

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

[STL] vector  (0) 2022.08.30
[콜백 함수] 콜백 함수  (0) 2022.08.30
[콜백 함수] 함수 객체  (0) 2022.08.30
[콜백 함수] 함수 포인터  (0) 2022.08.30
[디버깅]  (0) 2022.08.30

+ Recent posts