STL复习
1. 常见的容器
如何介绍这些容器,分别从常见接口,迭代器类型,底层实现
序列式容器
string
string严格来说不属于stl,它是属于C++标准库
**底层实现:**string本质是char类型的顺序表,因为不同编译器下的具体实现不同,这里只提供一个我的简答框架
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
private:
char* _str; // 堆上开辟的顺序表空间
size_t _size; // 有效字符个数
size_t _capacity; // _str的空间大小
static const size_t npos; // 最大字符串大小
};
const size_t string::npos = -1;
实际在VS系列下,string是包含一个指针,一个联合体(一个数组和一个指针,如果字符串长度小于16就会使用提前开辟好的数组,如果大于16字节再去堆上申请空间使用指针指向),size和capacity
在g++下string只包含一个指针,指向堆上的一段空间,包含一个指针指向为字符串开辟的空间,引用计数,size和capacity,这个引用计数可以让赋值拷贝这些对象只需要浅拷贝增加引用计数即可
迭代器类型: 随机访问迭代器
常用接口:
函数名 | 功能 |
---|---|
size / length | 返回字符串有效字符个数 |
clear / reserver / resize | 清空有效字符 / 预留空间 / 将有效字符的个数该成n个,多出的空间用字符c填充 |
operator[] | 返回pos位置的字符 |
push_back / append / operator+= | 在字符串后尾插字符c / 字符串 / 字符串 |
c_str | 返回C格式字符串 |
find / rfind + npos | 从字符串pos位置开始往(后 / 前)找字符c,返回该字符在字符串中的位置,没有返回npos |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
operator<< / operator>> / getline | 从iostream中写 / 读 (空格/回车),getline可自定义分隔符 |
// 剪去报头 length\r\n*******\r\nlength\r
std::string Decode(std::string& str)
{
// 判断条件是否满足,不满足返回空串
size_t pos = str.find(SEP);
if (pos == std::string::npos)
return "";
size_t lenght = atoi(str.substr(0, pos).c_str());
if (lenght > str.size() - pos - SEP_LEN * 2)
return "";
str.erase(0, pos + SEP_LEN);
std::string ret = str.substr(0, lenght);
str.erase(0, lenght + SEP_LEN);
return ret;
}
// 简单实现
class string
{
friend std::ostream& operator<<(std::ostream& out, const string& str);
//friend istream& operator>>(istream& in, const string& str);
public:
string() : _str(nullptr), _size(0), _capacity(0)
{}
string(const char* str) :string()
{
size_t sz = strlen(str);
_str = new char[sz + 1] {0};
strcpy(_str, str);
_size = sz;
_capacity = sz + 1;
}
string(const string& str)
: string(str.c_str())
{}
string& operator==(string str)
{
if(this != &str)
swap(str);
return *this;
}
~string()
{
delete[] _str;
}
string& operator+=(const string& str)
{
size_t sz = str._size;
if (sz + _size + 1 > _capacity)
{
reserve(sz > _capacity ? _capacity + sz : 2 * _capacity);
}
strcpy(_str + _size, str.c_str());
_size += sz;
return *this;
}
char& operator[](size_t pos)
{
return *(_str + pos);
}
void reserve(size_t newsize)
{
if (newsize > _capacity)
{
char* str = new char[newsize] {0};
strcpy(str, _str);
delete[] _str;
_str = str;
_capacity = newsize;
}
}
void swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str;
size_t _size;
size_t _capacity;
static size_t npos;
};
size_t string::npos = -1;
std::ostream& operator << (std::ostream& out, const string& d)
{
out << d.c_str();
return out;
}
vector
底层实现:
vector本质是模板类型的顺序表,对于底层数据结构是顺序表类型的容器在每次增容都需要极大的代价,需要开辟更大空间,并对之前的数据进行拷贝,,vs下capacity是按1.5倍增长的,g++是按2倍增长的
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解string和vector增容的代价缺陷问题
与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
迭代器类型: 随机访问迭代器
常用接口:
函数名 | 功能 |
---|---|
size / capacity / empty | 数据个数 / 容量大小 / 是否为空 |
reserve / resize / clear | 改变vector的capacity / size / 清空 |
push_back / pop_back | 尾插 / 尾删 |
find + v.end() (不常用) | 查找,注意这个是算法模块实现,不是vector的成员接口 |
insert / erase | 在pos位置插入删除 |
emplace / empalce_back | 构造和插入 |
operator[] | 返回pos位置的对象 |
深拷贝问题,在拷贝构造时如果对像是自定义类型,就会调用自定义类型的拷贝构造
// 简单实现
template<class T>
class vector
{
typedef T* iterator;
public:
iterator begain() { return _start; }
iterator end() { return _finish; }
vector():_start(nullptr), _finish(nullptr), _end(nullptr)
{}
vector(size_t n, T val = T()) :vector()
{
reserve(n);
for (size_t i = 0; i < n; ++i)
{
_start[i] = val;
}
_finish = _start + n;
}
vector(const vector& v) :vector()
{
reserve(v.size());
_finish = _start + v.size();
for (size_t i = 0; i < v.size(); ++i)
{
operator[](i) = v[i];
}
}
~vector(){delete[] _start; }
void push_back(T val) {insert(_finish, val); }
void pop_back() {erase(_finish - 1); }
void insert(iterator pos, T val = T())
{
assert(pos >= _start && pos <= _finish);
if (_finish + 1 > _end)
{
size_t pos_size = pos - _start;
reserve(size() + 1);
pos = _start + pos_size;
}
iterator cur = _finish;
while (cur != pos)
{
*cur = *(cur - 1);
--cur;
}
*pos = val;
_finish += 1;
}
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator cur = pos;
while (cur != _finish)
{
*cur = *(cur + 1);
++cur;
}
_finish -= 1;
return pos;
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
void reserve(size_t newcapacity)
{
size_t capacity = _end - _start;
size_t size = _finish - _start;
if (capacity < newcapacity)
{
newcapacity = newcapacity < 2 * capacity ? 2 * capacity : newcapacity;
T* newv = new T[newcapacity];
if(_start)
memcpy(newv, _start, size * sizeof(T));
delete[] _start;
_start = newv;
_end = newv + newcapacity;
_finish = _start + size;
}
}
size_t size() const { return _finish - _start; }
size_t capacity() const { return _end - _start; }
private:
iterator _start;
iterator _finish;
iterator _end;
};
list
底层实现:
list的底层是带头双向循环链表结构,链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素
与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好
最大的缺陷是不支持任意位置的随机访问,list还需要一些额外的空间,以保存每个节点的相关联信息
template<class T>
struct list_node
{
typedef list_node<T> node;
node* _next;
node* _prev;
T _date;
};
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> node;
typedef __list_iterator<T, Ref, Ptr> self;
node* _it;
};
template<class T>
class list
{
typedef list_node<T> node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
private:
node* _head;
};
迭代器类型: 双向访问迭代器
常用接口:
函数名 | 功能 |
---|---|
empty / size | 判空 / 返回节点个数 |
front / back | 返回最后 / 最前节点值的引用 |
push_front / pop_front | 头插 / 头删 |
push_front / pop_back | 尾插 / 尾删 |
insert / erase | 插入 / 删除 |
emplace / emplace_front / emplace_back | 构造 + 插入 |
sort | 链表的排序 |
template<class T>
struct list_node
{
list_node* _prev;
list_node* _next;
T _val;
list_node(T val = T()) :_prev(nullptr), _next(nullptr), _val(val)
{}
};
template<class T, class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> node;
typedef list_iterator<T, Ref, Ptr> Self;
node* _it;
list_iterator(node* it) :_it(it)
{}
list_iterator(const list_iterator<T, T&, T*>& iterator) :_it(iterator._it)
{}
Ptr operator->() {return &(_it->_val);}
Ref operator*() {return _it->_val; }
Self operator++() {return _it = _it->_next; }
Self operator--() {return _it = _it->_prev; }
bool operator!=(const Self& iterator) const {return _it != iterator._it; }
};
template<class T>
struct list
{
typedef list_node<T> node;
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
public:
iterator begin() { return iterator(_head->_next); }
const_iterator begin() const { return const_iterator(_head->_next); }
iterator end() { return iterator(_head); }
const_iterator end() const { return const_iterator(_head); }
list():_head(new node)
{
_head->_next = _head;
_head->_prev = _head;
}
~list()
{
while (!empty())
{
pop_front();
}
delete _head;
}
void insert(iterator pos, const T& val)
{
node* next = pos._it;
node* prev = next->_prev;
node* newnode = new node(val);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = next;
next->_prev = newnode;
_size++;
}
void erase(iterator pos)
{
assert(pos._it != _head);
node* next = pos._it->_next;
node* prev = pos._it->_prev;
delete pos._it;
next->_prev = prev;
prev->_next = next;
_size--;
}
void push_back(const T& val) { insert(end(), val); }
void push_front(const T& val) { insert(begin(), val); }
void pop_back(){erase(_head->_prev); }
void pop_front(){erase(begin()); }
bool empty() const
{
return _size == 0;
}
private:
list_node<T>* _head;
size_t _size = 0;
};
deque
底层实现:
双端队列由一个指针数组和多个相同长度的定长数组组成
双端队列,头插尾插,头删尾删效率高,这点比vector要好,但是不如list在任意位置都可以O(1)时间复杂度插入删除(增删)
通过迭代器支持operator[]随机访问,在随机访问上比list要好,但是不如vector(查改)
因为头尾的插入删除效率高,所以他就非常适合stack和queue
迭代器类型: 随机访问迭代器
常用接口:
函数名 | 功能 |
---|---|
size / resize / empty | 大小 / 改变大小 / 判空 |
operator[] / front / back | 读数据 |
push_front / pop_front | 头插 / 头删 |
push_front / pop_back | 尾插 / 尾删 |
insert / erase | 插入 / 删除 |
关联式容器
map/set
unordered_map/unordered_set
bitset
容器适配器
stack
stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
- empty:判空操作
- back:获取尾部元素操作
- push_back:尾部插入元素操作
- pop_back:尾部删除元素操作
标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况下使用deque
函数名 | 功能 |
---|---|
stack() | 构造空的栈 |
empty | 检测stack是否为空 |
size | 返回stack中元素的个数 |
top | 返回栈顶元素的引用 |
push | 将元素val压入stack中 |
pop | 将stack中尾部的元素弹出 |
queue
队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
- empty:检测队列是否为空
- size:返回队列中有效元素的个数
- front:返回队头元素的引用
- back:返回队尾元素的引用
- push_back:在队列尾部入队列
- pop_front:在队列头部出队列
标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque
函数名 | 功能 |
---|---|
queue() | 构造空的队列 |
empty | 检测queue是否为空 |
size | 返回queue中元素的个数 |
front | 返回queue顶元素的引用 |
back | 返回queue尾元素的引用 |
push | 将元素val压入queue中 |
pop | 将queue中尾部的元素弹出 |
priority_queue
优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)
优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
- empty():检测容器是否为空
- size():返回容器中有效元素个数
- front():返回容器中第一个元素的引用
- push_back():在容器尾部插入元素
- pop_back():删除容器尾部元素
标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指 定容器类,则使用vector(模板参数第二位)
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap和pop_heap 来自动完成此操作
默认情况下priority_queue是大堆,使用less(模板参数第三位)
一句话,少的点挪动的次数多 时间复杂度约等于 T(N)
多的点挪动次数多 时间复杂度 T(N*LogN)
若是pop n次用来排序,时间复杂度 T(N*LogN)
优先级队列,使用迭代器建堆,比遍历push,要好
函数名 | 功能 |
---|---|
priority_queue() | 构造一个空的优先级队列 |
priority_queue(first,last) | 根据迭代器区间构造优先级队列 |
empty() | 检测优先级队列是否为空 |
top() | 返回优先级队列中最大(最小元素),即堆顶元素 |
size() | 返回优先级队列里的元素个数 |
push(x) | 在优先级队列中插入元素x |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
杂度 T(N*LogN)
优先级队列,使用迭代器建堆,比遍历push,要好
函数名 | 功能 |
---|---|
priority_queue() | 构造一个空的优先级队列 |
priority_queue(first,last) | 根据迭代器区间构造优先级队列 |
empty() | 检测优先级队列是否为空 |
top() | 返回优先级队列中最大(最小元素),即堆顶元素 |
size() | 返回优先级队列里的元素个数 |
push(x) | 在优先级队列中插入元素x |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |