人把自己置身于忙碌中, 有一种麻木的踏实, 但丧失了真实.–<无问西东>
6. STL简介
6.1 定义
- STL(standard template libraray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架.
6.2 STL版本
6.2.1 原始版本
- Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意 运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使 用。 HP 版本–所有STL实现版本的始祖;
6.2.2 P.J版本
- 由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低, 符号命名比较怪异。
6.2.3 RW版本
- 由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
6.2.4 SGI版本
- 由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好、可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码, 主要参考的就是这个版本。
6.3 STL 六大组件
6.3.1 仿函数
一个行为类似函数的对象,它可以没有参数,也可以带有若干参数。任何重载了调用运算符operator()的类的对象都满足函数对象的特征函数对象可以把它称之为smart function。
6.3.2 算法
算法Algorithms,用来处理群集内的元素。它们可以出于不同的目的而搜寻、排序、修改、使用那些元素。通过迭代器的协助,我们可以只需编写一次算法,就可以将它应用于任意容器,这是因为所有的容器迭代器都提供一致的接口。
6.3.3 迭代器
- 迭代器 Iterators,用来在一个对象群集(collection of objects)的元素上进行遍历。这个对象群集或许是个容器,或许是容器的一部分。
- 迭代器的主要好处是,为所有容器提供了一组很小的公共接口。
- 迭代器以++进行累进,以*进行提领,因而它类似于指针,我们可以把它视为一种smart pointer。
6.3.4 空间配置器
6.3.5 容器
6.3.5.1 定义
6.3.5.2七种基本容器
- 向量(vector)、双端队列(deque)、列表(list)、集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap)
6.3.5.3 容器的分类
序列式容器
说明:底层结构为线性序列的数据结构,里面存储的是元素本身。
关联系容器(树形结构和哈希结构)
(1)定义
- 底层结构为非线性序列的数据结构,里面存储的是 <key, value> 的键值对,在数据检索时比序列式容器效率更高。
树形结构的关联系容器
特点
- 使用平衡搜索树(即红黑树)作为其底层结构,容器中的元素是一个有序的序列。
哈希结构的关联式容器
6.3.2 适配器
- 适配器是一种类,为已有的类提供新的接口,目的是简化、约束、使之安全、隐藏或者改变被修改类提供的服务集合。
6.3.2.1 常见的容器适配器
- stack、queue、priority_queue
template <class T, class Containter = deque<T> > class stack;
template <class T, class Container = dequer<T> > class queue;
template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> > class priority_queue;
6.4 STL的缺陷
- 1.STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出 来已经相隔了13年,STL才进一步更新。
- 2.STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
- 3.STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
- 4.STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语 法本身导致的。
6.5 String 类
6.5.1 文档介绍
- 1.字符串是表示字符序列的类;
- 2.标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,再添加了一些专门用来操作 string 的常规操作;
- 3.string 在底层实际是:basic_string 模板类的别名,typedef basic_string<char, char, char_traits, allocator> string;
- 4.不能操作多字节或者变长字符的序列;
- 5.底层存储结构:动态的顺序表。
6.5.2 string 类的常用接口说明
6.5.2.1 string 类对象的常见构造
函数名称 | 功能说明 |
---|
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) | 拷贝构造函数 |
string(const string&s, size_t n) | 用s中的前n个字符构造新的string类对象 |
6.5.2.2 string 类对象的容量操作
函数名称 | 功能说明 |
---|
size_t size() const | 返回字符串有效字符长度 |
size_t length() const | |
size_t capacity ( ) | const 返回空间总大小 |
bool empty ( ) | const 检测字符串释放为空串,是返回true,否则返回false |
void clear() | 清空有效字符 |
void resize ( size_t n, char c) | 将有效字符的个数改成n个,多出的空间用0填充 |
void reserve ( size_t res_arg=0 ) | 为字符串预留空间,只改变底层空间大小(当空间变大时增大,变小时不变) |
6.5.2.3 string 对象的访问操作
函数名称 | 功能说明 |
---|
char& operator[] ( size_t pos ) | 返回pos位置的字符,const string类对象调用 |
const char& operator[] ( size_t pos )const | 返回pos位置的字符,非const string类对象调用 |
6.5.2.4 string 对象的修改
函数名称 | 功能说明 |
---|
void push_back(char c) | 在字符串后尾插字符c |
string& append (const char* s) | 在字符串后追加一个字符串 |
string& operator+=(const string& str) | 在字符串后追加字符串str |
string& operator+=(const char* s) | 在字符串后追加C个数字符串 |
string& operator+=(char c) | 在字符串后追加字符c |
const char* c_str( )const | 返回C格式字符串 |
size_t find (char c, size_t pos = 0)const | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
size_t rfind(char c, size_t pos = npos) | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
string substr(size_t pos = 0, size_t n = npos)const | 在str中从pos位置开始,截取n个字符,然后将其返回 |
6.5.2.5 string 非成员函数
函数 | 功能说明 |
---|
operator+ | 尽量少用,因为效率低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串 |
relational operators | 大小比较 |
6.5.2.6 代码示意
#include <iostream>
using namespace std;
#include <string>
#if 0
int main()
{
string s1;
string s2("hello world");
string s3(10, '$');
string s4(s3);
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
system("pause");
return 0;
}
#endif
#if 0
int main()
{
string s("hello ");
cout << s << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s.length() << endl;
if (s.empty())
{
cout << "empty string" << endl;
}
else{
cout << "not empty string" << endl;
}
s.clear();
if (s.empty())
{
cout << "empty string" << endl;
}
else{
cout << "not empty string" << endl;
}
cout << s.capacity() << endl;
system("pause");
return 0;
}
#endif
#if 0
int main()
{
string s("hello");
cout <<"s.size() "<< s.size() << endl;
cout <<"s.capacity()" <<s.capacity() << endl;
s.resize(10, '$');
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
s.resize(30, '8');
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
s.resize(24);
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
s.resize(12);
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
system("pause");
return 0;
}
#endif
#if 0
int main()
{
string s("hello");
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
s.reserve(10);
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
s.reserve(20);
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
s.reserve(16);
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
s.reserve(3);
cout << "s.size() " << s.size() << endl;
cout << "s.capacity()" << s.capacity() << endl;
system("pause");
return 0;
}
#endif
#if 0
int main()
{
string s("hello");
cout << s[3] << endl;
cout << s.at(4) << endl;
auto it = s.begin();
while (it != s.end())
{
cout << *it;
++it;
}
cout << endl;
for (auto e : s)
{
cout << e;
}
cout << endl;
for (size_t i = 0; i < s.size(); ++i)
{
cout << s[i];
}
cout << endl;
auto rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit;
++rit;
}
cout << endl;
system("pause");
return 0;
}
#endif
#if 0
void TestPushBack()
{
string s;
s.reserve(100);
size_t sz = s.capacity();
cout << "making s grow:\n";
cout << "capacity changed: " << sz << '\n';
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
int main()
{
TestPushBack();
string s1("hello");
string s2("world");
if (s1 > s2)
{
cout << "s1 > s2" << endl;
}
else
{
cout << "s1 <= s2" << endl;
}
string s3;
s3 = s1 + s2;
cout <<"s3: "<< s3 << endl;
system("pause");
return 0;
}
6.5.3 string 类的模拟实现
6.5.3.1 浅拷贝
定义
- 也称位拷贝,编译器只是将对象中的值拷贝过来。
- 如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对该资源进行操作时,就会发生了访问违规。
导致原因
6.5.3.2 深拷贝
定义
- 给每个对象独立分配资源,保证多个对象之间不会因为共享资源而造成多次程序奔溃问题。
6.5.3.3 写时拷贝技术
6.5.3.4 string 类的模拟实现
6.6 Vector
6.6.1 文档介绍
- 1.vector 是表示可变大小数组的序列式容器。
- 2.就像数组一样,vector 也采用连续的存储空间来存储元素(下标访问+大小动态改变);
- 3.vector 使用动态分配数组来存储它的元素;
- 4.vector 分配空间策略:vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间大;
- 5.vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长;
- 6.与其他动态序列容器相比(deque,list and forward_list),vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。
6.6.2 vector 常用接口说明
6.6.2.1 vector 的定义
构造函数声明 | 接口说明 |
---|
vector() | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化 n 个 val |
vector (const vector& x) | 拷贝构造 |
vector (InputIterator first, InputIterator last) | 使用迭代器进行初始化构造 |
6.6.2.2 vector iterator 的使用
iterator的使用 | 接口说明 |
---|
begin() | 获取第一个数据位置的iterator |
end() | 获取最后一个数据的下一个位置的iterator |
rbegin() | 获取最后一个数据位置的reverse_iterator |
rend() | 获取第一个数据前一个位置的reverse_iterator |
cbegin() | 获取第一个数据位置的const_iterator |
cend() | 获取最后一个数据的下一个位置的const_iterator |
6.6.2.3 vector 的空间增长
容量空间 | 接口说明 |
---|
size() | 获取数据个数 |
capacity() | 获取容量大小 |
void resize (size_type n, value_type val = value_type()) | 改变vector的size |
void reserve (size_type n) | 改变vector放入capacity |
6.6.2.4 vector增删改查
vector 增删改查 | 接口说明 |
---|
void push_back (const value_type& val) | 尾插 |
void pop_back() | 尾删 |
InputIterator find (InputIterator first, InputIterator last, const T& val) | 查找 |
iterator insert (iterator position, const value_type& val) | 在position之前插入val |
iterator erase (iterator position) | 删除position位置的数据 |
void swap (vector& x) | 交换两个vector的数据空间 |
reference operator[] (size_type n) | 像数组一样访问 |
6.6.2.5 代码示意
#if 0
int main()
{
vector<int> v1;
vector<int> v2{ 10, 5 };
vector<int> v3(v2.begin(), v2.end());
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
vector<int> v4(array, array + sizeof(array) / sizeof(array[0]));
vector<int> v5(v4);
for (size_t i = 0; i < v2.size(); ++i)
{
cout << v2[i] << " ";
}
cout << endl;
auto it = v3.begin();
while (it != v3.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
auto rit = v5.rbegin();
while (rit != v5.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
system("pause");
return 0;
}
#endif
#if 0
int main()
{
size_t sz;
std::vector<int> foo;
foo.reserve(100);
sz = foo.capacity();
std::cout << "making foo grow:\n";
for (int i = 0; i < 100; ++i)
{
foo.push_back(i);
if (sz != foo.capacity())
{
sz = foo.capacity();
std::cout << "capacity changed: " << sz << '\n';
}
}
cout << sz << endl;
system("pause");
return 0;
}
#endif
#if 0
int main()
{
vector<int> v;
v.resize(10);
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
auto it = find(v.begin(), v.end(), 5);
if (it != v.end())
{
cout << "5 is in vector!" << endl;
}
else{
cout << "5 is not in vector!" << endl;
}
system("pause");
return 0;
}
#endif
#if 0
int main()
{
vector<int> v{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
auto it = v.begin();
v.push_back(0);
v.push_back(1);
v.push_back(2);
v.push_back(3);
cout <<"v.size(): "<< v.size() << endl;
it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
vector<int> v1{ 1, 2, 3 };
vector<int> v2{ 1, 2, 3, 4, 5 };
auto it1 = v1.begin();
while (it1 != v.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
vector<int> v3{ 1, 2, 3 };
auto it2 = v3.begin();
v3.assign(10, 5);
it2 = v3.begin();
cout << *it2 << endl;
system("pause");
return 0;
}
#endif
#if 1
vector<vector<int>> PascalTriangle(size_t nRows)
{
vector<vector<int>> vRet;
if (0 == nRows)
return vRet;
vRet.resize(nRows);
for (size_t i = 0; i < nRows; ++i)
{
vRet[i].resize(i + 1);
vRet[i][0] = vRet[i][i] = 1;
}
for (size_t i = 2; i < nRows; ++i)
{
for (size_t j = 1; j < i; ++j)
vRet[i][j] = vRet[i - 1][j] + vRet[i - 1][j - 1];
}
return vRet;
}
int main()
{
vector<vector<int>> vRet = PascalTriangle(5);
for (size_t i = 0; i < vRet.size(); ++i)
{
for (size_t j = 0; j < vRet[i].size(); ++j)
{
cout << vRet[i][j] << " ";
}
cout << endl;
}
system("pause");
return 0;
}
#endif
6.6.3 vector 迭代器失效问题
6.6.3.1 说明
6.6.3.2 解决方式
6.6.3.3 迭代器失效场景
- resize, reserve, push_back,assign, insert, swap, erase (导致底层空间发生改变)。
6.7 List
6.7.1 文档介绍
- 1.list 是可以在常数范围内,在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代;
- 2.list 的底层结构:带头结点的双向循环链表结构,双向链表中每个元素存储在互不相关的独立结点中,在节点中通过指针指向前一个元素和后一个元素。
- 3.list 与 forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
- 4.与其他的序列式容器相比(array,vector,deque),list 通常在任意位置进行插入、移除元素的执行效率更好。
- 5.与其序列式容器相比,list 和 forward_list 最大的缺陷是不支持任意位置的随机访问。
6.7.2 List 的使用
6.7.2.1 list 的构造
构造函数 | 接口说明 |
---|
list() | 构造空的list |
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list (const list& x) | 拷贝构造函数 |
list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
6.7.2.2 list iterator 的使用
函数声明 | 接口说明 |
---|
begin() | 返回第一个元素的迭代器 |
end() | 返回最后一个元素下一个位置的迭代器 |
rbegin() | 返回第一个元素的reverse_iterator,即end位置 |
rend() | 返回最后一个元素下一个位置的reverse_iterator,即begin位置 |
cbegin() (C++11) | 返回第一个元素的cosnt_iterator |
cend() (C++11) | 返回最后一个元素下一个位置的const_iterator |
crbegin() (C++11) | 即crend()位置 |
crend() (C++11) | 即crbegin()位置 |
注意
- 1.begin() 与 end() 为正向迭代器,对迭代器执行 ++操作,迭代器向后移动;
- 2.rbegin(end) 与 rend(begin) 为反向迭代器,对迭代器执行 ++操作,迭代器向前移动;
- 3.cbegin与cend为const的正向迭代器,与begin和end不同的是:该迭代器指向节点中的元素值不能修改;
- 4.crbegin与crend为const的反向得带器,与rbegin和rend不同的是:该迭代器指向节点中的元素值不能修改;
6.7.2.3 list capacity
函数声明 | 接口说明 |
---|
bool empty() const | 检测list是否为空,是返回true,否则返回false |
size_t size() const | 返回list中有效节点的个数 |
6.7.2.4 list element access
函数说明 | 接口说明 |
---|
reference front() | 返回list的第一个节点中值的引用 |
const_reference front() const | 返回list的第一个节点中值的const引用 |
reference back() | 返回list的最后一个节点中值的引用 |
const_reference back() const | 返回list的最后一个节点中值的const引用 |
6.7.2.5 list modifiers
函数声明 | 接口说明 |
---|
void push_front (const value_type& val) | 在list首元素前插入值为val的元素 |
void pop_front() | 删除list中第一个元素 |
void push_back (const value_type& val) | 在list尾部插入值为val的元素 |
void pop_back() | 删除list中最后一个元素 |
iterator insert (iterator position, const value_type& val) | 在list position 位置中插入值为val的元素 |
void insert (iterator position, size_type n, const value_type&val) | 在list position位置插入n个值为val的元素 |
void insert (iterator position, InputIterator first, InputIterator last) | 在list position位置插入[first, last)区间中元素 |
iterator erase (iterator position) | 删除list position位置的元素 |
iterator erase (iterator first, iterator last) | 删除list中[first, last)区间中的元素 |
void swap (list& x) | 交换两个list中的元素 |
void resize (size_type n, value_type val = value_type()) | 将list中有效元素个数改变到n个,多出的元素用val填充 |
void clear() | 清空list中的有效元素 |
6.7.2.6 代码示意
#if 0
int main()
{
list<int> L1;
list<int> L2(10, 5);
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> L3(array, array + sizeof(array) / sizeof(array[0]));
vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> L4(v.begin(), v.end());
list<int> L5(L3);
list<int>::iterator it = L2.begin();
while (it != L2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
list<int>::reverse_iterator rit = L3.rbegin();
while (rit != L3.rend())
{
cout << *it << " ";
}
cout << endl;
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
system("pause");
return 0;
}
#endif
#if 0
class Data
{
public:
Data(int year = 1900, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
list<int> L1(10, 5);
cout << "L1: " << endl;
auto it1 = L1.begin();
while (it1 != L1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
list<int> L2(10);
auto it2 = L2.begin();
cout << "L2: " << endl;
while (it2 != L2.end())
{
cout << *it2 << " ";
++it2;
}
cout << endl;
list<Data> L3(10, Data(2020, 3, 17));
list<Data> L4(10);
system("pause");
return 0;
}
#endif
#if 0
int main()
{
list<int> L{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
cout << L.size() << endl;
L.resize(20, 8);
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
L.resize(5);
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
cout << L.front() << endl;
cout << L.back() << endl;
const list<int> CL{ 1, 2, 3 };
cout << L.front() << endl;
cout << L.back() << endl;
system("pause");
return 0;
}
#endif
#if 0
int main()
{
vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
cout << v[4] << endl;
auto vit = find(v.begin(), v.end(), 5);
if (vit != v.end())
{
cout << "5 is in the vector!";
}
else
{
cout << "5 is not in the vector!";
}
cout << endl;
list<int> L{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
auto it = find(L.begin(), L.end(), 5);
if (it != L.end())
{
cout << "5 is in the list!";
}
else
{
cout << "5 is not in the list!";
}
system("pause");
return 0;
}
#endif
#if 0
int main()
{
list<int> L{ 1, 2, 3 };
L.push_back(4);
L.push_back(0);
L.sort();
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
L.pop_front();
L.pop_back();
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
auto it = L.insert(find(L.begin(), L.end(), 2), 6);
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
it = L.erase(find(L.begin(), L.end(), 6));
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
L.clear();
cout << "L.size(): "<<L.size() << endl;
system("pause");
return 0;
}
#endif
int main()
{
list<int> L{ 4, 4, 1, 1, 2, 2, 3, 3, 1 };
L.sort();
L.unique();
auto it = L.begin();
while (it != L.end())
{
cout << *it << " ";
++it;
}
cout << endl;
L.erase(it);
it = L.begin();
L.resize(20);
while (it != L.end())
{
cout << *it << " ";
++it;
}
cout << endl;
L.assign(10, 5);
while (it != L.end())
{
cout << *it << " ";
++it;
}
cout << endl;
system("pause");
return 0;
}
6.7.3 List 的迭代器失效
- 迭代器失效即迭代器所指向的节点无效,即该节点被删除了。
- list 的底层结构是带头结点的双向循环链表,因为在 list 中进行插入时是不会导致 list 的迭代器失效,只有在删除时才会失效,并且失效的只是指向被删除结点的迭代器,其他迭代器不会受到影响。
6.7.4 vector 和 list 的对比
| vector | list |
---|
底层结构 | 动态顺序表,一段连续的空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率为O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容 | 任意位置插入和删除元素效率较高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层结点动态开辟,小结点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(结点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入和删除效率 | 大量插入和删除操作,不关心随机访问 |
6.8 Stack && Queue && priority_queue
6.8.1 stack 的介绍和使用
6.8.1.1 文档介绍
- 1.stack 是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
- 2.stack 是作为容器适配器被实现的,容器适配器即使对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
- 3.stack 的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:empty、back、push_back、push_front。
- 4.标准容器 vector、deque、list 均符合这些需求,默认情况下,如果没有 stack 指定的底层容器,默认情况下使用 deque。
6.8.1.2 stack 的使用
函数说明 | 接口说明 |
---|
stack(const container_type& ctnr = container_type()) | 构造空的栈 |
bool empty() const | 检测stack是否为空 |
size_type size() const | 返回stack中元素的个数 |
value_type& top() | 返回栈顶元素的引用 |
const value_type& top() const | 返回栈顶元素的const引用 |
void push (const value_type& val) | 将元素val压入stack中 |
void pop() | 将stack中尾部的元素弹出 |
template <class… Args> void emplace (Args&&… args) (C++11) | 在stack的栈顶构造元素 |
void swap (stack& x) (C++11) | 交换两个栈中的元素 |
6.8.2 queue 的介绍和使用
6.8.2.1 文档介绍
- 1.队列是一种容器适配器,专门用在FIFO(先进先出)中操作,其中从容器一段插入元素,另一端提取元素。
- 2.队列作为容器适配器实现,容器适配器即将特定容器封装作为其底层容器类,queue 提供一组特定的成员函数来访问其元素。元素从队尾入列,从对头出列。
- 3.底层容器可以是标准容器模板之一,也可以是其他专门设计的容器类。该容器应至少支持以下操作:empty、size、front、back、push_front、push_back。
- 4.标准容器类 deque 和 list 满足了这些要求,默认情况下,如果没有 queue 实例化指定容器类,则使用标准容器 deque。
6.8.2.2 queue 的使用
函数声明 | 接口说明 |
---|
queue (const container_type& ctnr = | |
container_type()) | 构造空的队列 |
bool empty() const | 检测队列是否为空,是返回true,否则返回false |
size_type size() const | 返回队列中有效元素的个数 |
value_type& front() | 返回队头元素的引用 |
const value_type& front() const | 返回队头元素的const引用 |
value_type& back() 返回队尾元素的引用 | 返回队尾元素的引用 |
const value_type& back() const | 返回队尾元素的cosnt引用 |
void push(value_type& val) | 在队尾将元素val入队列 |
void pop() | 将队头元素出队列 |
template <class… Args> void emplace (Args&&… | |
args) (C++11) | 在队尾构造元素 |
void swap(queue& x) | 交换两个队列中的元素 |
6.8.3 priority_queue 的介绍和使用
6.8.3.1 文档介绍
- 1.优先级队列是一种容器适配器,根据严格的排序标注,它的第一个元素总是它所包含的元素中最大的。
- 2.类似于大堆,在堆中可以随时插入元素,并且只能检索最大元素(优先级队列中位于顶部的元素)。
- 3.优先级队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue 提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先级队列的顶部。
- 4.底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:empty、size()、front()、push_front()、pop_back()。
- 5.标准容器类 vector 和 deque 满足这些需求,默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用 vector。
- 6.需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap 和 pop_heap 来自动完成此操作。
6.8.3.2 priority_queue 的使用
- 优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将 vector 中元素构造成堆的结构,因此 priority_queue就是堆,默认情况下 priority_queue 是大堆。
函数声明 | 接口说明 |
---|
priority_queue(const Compare& x = Compare(), const Container& y = Container() ); | 构造一个空的优先级队列 |
template priority_queue(InputIterator first, InputIterator last, const Compare& comp = Compare(), const Container& ctnr = Container()); | 用[first, last)区间中的元素构造优先级队列 |
bool empty( ) const | 检测优先级队列是否为空,是返回 true;否返回 false |
const value_type& top ( ) const | 返回优先级队列中最大(最小元素),即堆顶元素 |
void push ( const T& x ) | 在优先级队列中插入元素 x |
void pop ( ) | 删除优先级队列中最大(最小)元素,即堆顶元素 |
6.8.3.3. 代码示意
#if 0
#include <queue>
#include <functional>
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return _day < d._day;
}
private:
int _year;
int _month;
int _day;
};
typedef bool(*Compare)(const Date* pLeft, const Date* pRight);
bool Less(const Date* pLeft, const Date* pRight)
{
return *pLeft < *pRight;
}
int main()
{
Date d0(2019, 6, 25);
Date d1(2019, 6, 27);
Date d2(2019, 6, 28);
Date d3(2019, 6, 26);
priority_queue <Date*, vector<Date*>, Compare> q(Less);
q.push(&d1);
q.push(&d2);
q.push(&d3);
q.push(&d0);
system("pause");
return 0;
}
#endif
#if 0
#include <queue>
#include <functional>
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return _day < d._day;
}
private:
int _year;
int _month;
int _day;
};
class Compare
{
public:
bool operator()(const Date* pLeft, const Date* pRgiht)
{
return *pLeft < *pRgiht;
}
};
void TestFunc(const Date* pLeft, const Date* pRgiht)
{}
int main()
{
Date d0(2019, 6, 25);
Date d1(2019, 6, 27);
Date d2(2019, 6, 28);
Date d3(2019, 6, 26);
Compare com;
com.operator()(&d1, &d2);
com(&d1, &d2);
TestFunc(&d1, &d2);
priority_queue <Date*, vector<Date*>, Compare> q;
q.push(&d1);
q.push(&d2);
q.push(&d3);
q.push(&d0);
system("pause");
return 0;
}
#endif
7. map
7.1 文档介绍
- 1.map 是关联式容器,它按照特定的次序(按照 key 来比较)存储由 key 和 value 组合而成的元素。
- 2.在 map 中,键值 k 通常用于排序和唯一地标识元素,而值 value 中存储与此键值 key 关联的内容。键值 key 和 value 的类型可能不同,并且在 map 的内部,key 与 value 通过成员类型 value_type 绑定在一起,为其取名称为 pair: typedef pair value_type;
- 3.在内部,map 中的元素总是按照键值 key 进行比较排序的;
- 4.map 中通过键值访问单个元素的速度通常比 unordered_map 容器慢,但 map 允许根据顺序对元素直接进行迭代(即对 map 中的元素进行迭代时,可以得到一个有序的序列 );
- 5.map 支持下标访问,即在 [] 中放入 key,就可以找到与 key 对应的 value;
- 6.map 通常被实现为二叉搜索树(更准备的说:平衡二叉搜索树)。
7.2 map 的使用
7.2.1 map 的模板参数说明
class template
std::map
template < class Key,
class T,
class Compared = less<Key>,
class Alloc = alloctor<pair<const Key, T>>
> class map;
- key:键值对中 key 的类型;
- T:键值对中 value 的类型;
- Compare:比较器的类型,map 中的元素是按照 key 来比较的,缺省情况下按照小于的方式来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显示传递比较规则(一般情况下按照函数指针或者仿函数来传递);
- Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用v标准库提供的空间配置器。
- 注意:使用 map 时,需要包含头文件。
7.2.2 map 的构造
函数声明 | 功能介绍 |
---|
map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) | 构造一个空的map |
template map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) | 用[first, last)区间中的元素构造map |
map (const map& x) | map的拷贝构造 |
7.2.3 map 的迭代器
函数声明 | 功能说明 |
---|
iterator begin () | 返回第一个元素的位置 |
iterator end () | 返回最后一个元素的下一个位置 |
const_iterator begin () const | 返回第一个元素的const迭代器 |
const_iterator end () const | 返回最后一个元素下一个位置的const迭代器 |
reverse_iterator rbegin() | 返回第一个元素位置的反向迭代器即rend |
reverse_iterator rend() | 返回最后一个元素下一个位置的反向迭代器即 |
rbegin | |
const_reverse_iterator rbegin() const | 返回第一个元素位置的const反向迭代器即rend |
const_reverse_iterator rend() const | 返回最后一元素下一个位置的反向迭代器即rbegin |
7.2.4 map 的容量与元素访问
函数声明 | 功能简介 |
---|
bool empty ( ) const | 检测map中的元素是否为空,是返回true,否则返回false |
size_type size() const | 返回map中有效元素的个数 |
mapped_type& operator[] (const key_type& k) | 返回去key对应的value |
7.2.5 map 中元素的修改
函数声明 | 功能说明 |
---|
pair<iterator,bool> insert ( const value_type& x ) | 在map中插入键值对x,注意x是一个键值对,返回值也是键值对:iterator代表新插入元素的位置,bool代表释放插入成功 |
iterator insert ( iterator position, const value_type& x ) | 在position位置插入值为x的键值对,返回该键值对在map中的位置,注意:元素不一定必须插在position位置,该位置只是一个参考 |
template void insert ( InputIterator first, InputIterator last ) | 在map中插入[first, last)区间中的元素 |
void erase ( iterator position ) | 删除position位置上的元素 |
size_type erase ( const key_type& x ) | 删除键值为x的元素 |
void erase ( iterator first, iterator last ) | 删除[first, last)区间中的元素 |
void swap ( map<Key,T,Compare,Allocator>& mp ) | 交换两个 map 中元素 |
void clear ( ) | 将map中的元素清空 |
iterator find ( const key_type& x ) | 在map中插入key为x的元素,找到返回该元素的位置的迭代器,否则返回end |
const_iterator find ( const key_type& x ) const | 在map中插入key为x的元素,找到返回该元素的位置的const迭代器,否则返回cend |
size_type count ( const key_type& x ) const | 返回key为x的键值在map中的个数,注意map中key是唯一的,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中 |
7.2.6 代码示意
#include <string>
#include <map>
void TestMap(){
map<string, string> m;
m.insert(pair<string, string>("peach", "桃子"));
m.insert(make_pair("banan", "香蕉"));
m["apple"] = "苹果";
cout << m.size() << endl;
for (auto& e : m)
cout << e.first << "--->" << e.second << endl;
cout << endl;
auto ret = m.insert(make_pair("peach", "桃色"));
if (ret.second)
cout << "<peach, 桃色>不在map中, 已经插入" << endl;
else
cout << "键值为peach的元素已经存在:" << ret.first->first << "--->" <<
ret.first->second <<" 插入失败"<< endl;
m.erase("apple");
if (1 == m.count("apple"))
cout << "apple还在" << endl;
else
cout << "apple被吃了" << endl;
}
7.3 总结
- 1.map 中的元素是键值对;
- 2.map 中的 key 是唯一的,并且不能修改;
- 3.默认按照小于的方式对 key 进行比较;
- 4.map 中的元素如果采用迭代器遍历,可以得到一个有序的序列;
- 5.map 的底层为平衡搜索树(红黑树),查找效率比较高 O(logN);
- 6.支持 []访问操作符,operator [] 中实际进行插入查找。
8. multimap
8.1 multimap 的文档介绍
-
- Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key, value>,其中多个键值对之间的key是可以重复的。
-
- 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对: typedef pair<const Key, T> value_type;
-
- 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。
-
- multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代器直接遍历multimap中的元素可以得到关于key有序的序列。
-
- multimap在底层用二叉搜索树(红黑树)来实现。
注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。
8.2 multimap 的使用
multimap 的接口可以参考 map,功能都是类似的。
- 1.multimap 中的 key 是可以重复的;
- 2.multimap 中的元素默认将 key 按照小于方式来比较;
- 3.multimap 中没有重载 operator[] 操作;
- 4.使用时与 map 包含的头文件相同。
9. Set
9.1 set 的文档介绍
- 1.set 是按照一定次序存储元素的容器;
- 2.在 set 中,元素的 value 也标识它(value 就是 key,类型为 T),并且每个 value 必须是唯一的。set 中的元素不能在容器中修改(元素总是 const),但是可以从容器中插入和删除它们。
- 3.在内部,set 中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- 4.set 容器通过 key 访问单个元素的速度通常比 unordered_set 容器慢,但它们允许根据顺序对子集进行直接迭代。
- 5.set 在底层是用二叉搜索树(红黑树)实现的。
9.2 set的使用
9.2.1 set 的模板参数列表
class template
std::set
template < class T,
class Compare = less<T>,
class Alloc = allocator<T>
> class set;
- T:set 中存放元素的类型,实际在底层存储 <value,value>的键值对;
- Compare:set 元素默认按照小于的方式来比较;
- Alloc:set 中元素空间的管理方式,使用 STL 提供的空间配置器管理。
9.2.2 set 的构造
函数声明 | 功能介绍 |
---|
set (const Compare& comp = Compare(), const Allocator& = Allocator() ); | 构造空的set |
set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() ); | 用[first, last)区间中的元素构造set |
set ( const set<Key,Compare,Allocator>& x); | set的拷贝构造 |
9.2.3 set 的迭代器
函数声明 | 功能介绍 |
---|
iterator begin() | 返回set中起始位置元素的迭代器 |
iterator end() | 返回set中最后一个元素后面的迭代器 |
const_iterator cbegin() const | 返回set中起始位置元素的const迭代器 |
const_iterator cend() const | 返回set中最后一个元素后面的const迭代器 |
reverse_iterator rbegin() | 返回set第一个元素的反向迭代器,即end |
reverse_iterator rend() | 返回set最后一个元素下一个位置的反向迭代器,即 rbegin |
const_reverse_iterator crbegin() const | 返回set第一个元素的反向const迭代器,即cend |
const_reverse_iterator crend() const | 返回set最后一个元素下一个位置的反向const迭代器,即crbegin |
9.2.4 set 的容量
函数声明 | 功能介绍 |
---|
bool empty ( ) const | 检测set是否为空,空返回true,否则返回true |
size_type size() const | 返回set中有效元素的个数 |
9.2.5 set 修改操作
函数声明 | 功能介绍 |
---|
pair<iterator,bool> insert ( const value_type& x ) | 在set中插入元素x,实际插入的是<x, x>构成的键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经存在,返回<x在set中的位置,false> |
iterator insert ( iterator position, const value_type& x ) | 在set的position位置插入x,实际插入的是<x, x>构成的键值对,注意:position位置只是参考,x最终不一定在该位置 |
template void insert ( InputIterator first, InputIterator last ); | 在set中插入[first, last)区间中的元素 |
void erase ( iterator position ) | 删除set中position位置上的元素 |
size_type erase ( const key_type& x ) | 删除set中值为x的元素,返回删除的元素的个数 |
void erase ( iterator first, iterator last ) | 删除set中[first, last)区间中的元素 |
void swap ( set<Key,Compare,Allocator>& st ); | 交换set中的元素 |
void clear ( ) | 将set中的元素清空 |
iterator find ( const key_type& x ) const | 返回set中值为x的元素的位置 |
size_type count ( const key_type& x ) const | 返回set中值为x的元素的个数 |
9.2.6 代码示意
#include <set>
void TestSet(){
int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
set<int> s(array, array+sizeof(array)/sizeof(array));
cout << s.size() << endl;
for (auto& e : s)
cout << e << " ";
cout << endl;
for (auto it = s.rbegin(); it != s.rend(); ++it)
cout << *it << " ";
cout << endl;
cout << s.count(3) << endl;
}
9.2.7 注意
-
- 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
-
- set中插入元素时,只需要插入value即可,不需要构造键值对;
-
- set中的元素不可以重复(因此可以使用set进行去重);
- 4.使用set的迭代器遍历set中的元素,可以得到有序序列;
- 5.set中的元素默认按照小于来比较;
- 6.set中查找某个元素,时间复杂度为:log2n;
- 7.set中的元素不允许修改;
- 8.set中的底层使用二叉搜索树(红黑树)来实现。
10. Multiset
10.1 文档介绍
- 1.multiset 是按照特定顺序存储元素的容器,其中元素是可以重复的。
- 2.在 multiset 中,元素的 value 也会识别它(因为 multiset 中本身存储的就是 <value, value> 组成的键值对,value 本身就是 key,key 就是 value,类型为 T)。multiset 元素的值不能在容器中进行修改(因为元素总是 const 的,),但是可以从容器中插入或删除。
- 3.在内部,multiset 中的元素是按照其内部比较规则(类型比较)所指示的特定严格若排序进行排序。
- 4.multiset 容器通过 key 访问单个元素的速度通常比 unordered_multiset 容器慢,但当使用迭代器遍历时,会得到一个有序序列。
- 5.multiset 底层结构为二叉搜索树(红黑树)。
10.2 multiset 的使用
- 此处只简单展示了 set 与 multiset 的不同,其他接口与 set 相同。
#include <set>
void TestSet(){
int array[] = { 2, 1, 3, 9, 6, 0, 5, 8, 4, 7 };
multiset<int> s(array, array + sizeof(array)/sizeof(array[0]));
for (auto& e : s)
cout << e << " ";
cout << endl;
return 0;
}
10.3 注意
- 1.multiset 底层中存储的是 <value, value> 的键值对;
- 2.multiset 的插入接口只需要插入即可;
- 3.与 set 的区别是,multiset 中的元素可以重复,set 中 value 是唯一的;
- 4.使用迭代器对 multiset 中的元素进行遍历,可以得到有序的序列;
- 5.multiset 中的元素不能修改;
- 6.在 multiset 中找某个元素,时间复杂度为 O(log2N);
- 7.multiset 的作用:可以对元素进行排序。
11. Unordered_map
11.1 文档介绍
- 1.unordered_map 是存储 <key, value> 键值对的关联式容器,其允许通过 key 快速索引到与其对应的 value。
- 2.在 unordered_map 中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联,键和映射值的类型可能不同。
- 3,在内部,unordered_map 没有对 <key, value> 按照任何特定的顺序排序,为了能在常数范围内找到 key 所对应的 value,unordered_map 将相同哈希值的键值对放在相同的桶中。
- 4.unordered_map 容器通过 key 访问单个元素要比 map 快,但它通常在遍历元素子集的范围迭代方面效率较低。
- 5.unordered_map 实现了直接访问操作(operator[]),它允许使用 key 作为参数直接访问
11.2 unordered_map的使用
11.2.1 unordered_map 的构造
11.2.2 unordered_map 的容量
11.2.3 unordered_map 的迭代器
11.2.4 unordered_map 的元素访问
11.2.5 unordered_map 的查询
11.2.6 unordered_map 的修改操作
11.2.7 unordered_map 的桶操作