STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。
组件名 | 作用 |
---|---|
容器(Container) | 是一种数据结构,如list,vector,和deques ,以模板类的方法提供。 |
迭代器(Iterator) | 提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。 |
算法(Algorithm) | 是用来操作容器中的数据的模板函数。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象。 |
仿函数(Functor) | |
适配器(Adaptor) | |
分配器(allocator) |
序列式容器(Sequence containers)
每个元素都有固定位置
关联式容器(Associated containers)
元素位置取决于特定的排序准则
Iterator(迭代器)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
迭代器的作用:能够让迭代器与算法不干扰的相互发展,最后又能无间隙的粘合起来,重载了*,++,==,!=,=运算符。用以操作复杂的数据结构,容器提供迭代器,算法使用迭代器;常见的一些迭代器类型:iterator、const_iterator、reverse_iterator和const_reverse_iterator.
查找算法(13个):判断容器中是否包含某个值
函数 | 作用 |
---|---|
adjacent_find | 在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的 ForwardIterator。否则返回last。 |
binary_search | 在有序序列中查找value,找到返回true。 |
count | 利用等于操作符,把标志范围内的元素与输入值比较,返回相等元素个数。 |
count_if | 利用输入的操作符,对标志范围内的元素进行操作,返回结果为true的个数。 |
equal_range | 功能类似equal,返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。 |
find | 利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较。当匹配时,结束搜索,返回该元素的一个InputIterator |
find_end | 在指定范围内查找"由输入的另外一对iterator标志的第二个序列"的最后一次出现。找到则返回最后一对的第一 个ForwardIterator,否则返回输入的"另外一对"的第一个ForwardIterator。 |
find_first_of | 在指定范围内查找"由输入的另外一对iterator标志的第二个序列"中任意一个元素的第一次出现。 |
find_if | 使用输入的函数代替等于操作符执行find。 |
lower_bound | 返回一个ForwardIterator,指向在有序序列范围内的可以插入指定值而不破坏容器顺序的第一个位置。 |
upper_bound | 返回一个ForwardIterator,指向在有序序列范围内插入value而不破坏容器顺序的最后一个位置,该位置标志 一个大于value的值。 |
search | 给出两个范围,返回一个ForwardIterator,查找成功指向第一个范围内第一次出现子序列(第二个范围)的位 置,查找失败指向last1。 |
search_n | 在指定范围内查找val出现n次的子序列。 |
排序和通用算法
函数 | 作用 |
---|---|
inplace_merge | 合并两个有序序列,结果序列覆盖两端范围。 |
merge | 合并两个有序序列,存放到另一个序列。 |
nth_element | 将范围内的序列重新排序,使所有小于第n个元素的元素都出现在它前面,而大于它的都出现在后面。 |
partial_sort | 对序列做部分排序,被排序元素个数正好可以被放到范围内。 |
partial_sort_copy | 与partial_sort类似,不过将经过排序的序列复制到另一个容器。 |
partition | 对指定范围内元素重新排序,使用输入的函数,把结果为true的元素放在结果为false的元素之前。 |
random_shuffle | 对指定范围内的元素随机调整次序。重载版本输入一个随机数产生操作。 |
reverse | 将指定范围内元素重新反序排序。 |
reverse_copy | 与reverse类似,不过将结果写入另一个容器。 |
rotate | 将指定范围内元素移到容器末尾,由middle指向的元素成为容器第一个元素。 |
rotate_copy | 与rotate类似,不过将结果写入另一个容器。 |
sort | 以升序重新排列指定范围内的元素。重载版本使用自定义的比较操作。 |
stable_sort | sort类似,不过保留相等元素之间的顺序关系。 |
stable_partition | 与partition类似,不过不保证保留容器中的相对顺序。 |
删除和替换算法
函数 | 作用 |
---|---|
copy | 复制序列 |
copy_backward | 与copy相同,不过元素是以相反顺序被拷贝。 |
iter_swap | 交换两个ForwardIterator的值。 |
remove | 删除指定范围内所有等于指定元素的元素。注意,该函数不是真正删除函数。内置函数不适合使用remove和remove_if函数。 |
remove_copy | 将所有不匹配元素复制到一个制定容器,返回OutputIterator指向被拷贝的末元素的下一个位置。 |
remove_if | 删除指定范围内输入操作结果为true的所有元素。 |
remove_copy_if | 将所有不匹配元素拷贝到一个指定容器。 |
replace | 将指定范围内所有等于vold的元素都用vnew代替。 |
replace_copy | 与replace类似,不过将结果写入另一个容器。 |
replace_if | 将指定范围内所有操作结果为true的元素用新值代替。 |
replace_copy_if | 与replace_if,不过将结果写入另一个容器。 |
swap | 交换存储在两个对象中的值。 |
swap_range | 将指定范围内的元素与另一个序列元素值进行交换。 |
unique | 清除序列中重复元素,和remove类似,它也不能真正删除元素。 |
unique_copy | 与unique类似,不过把结果输出到另一个容器。 |
排列组合算法
函数 | 作用 |
---|---|
next_permutation | 取出当前范围内的排列,并重新排序为下一个排列。 |
prev_permutation | 取出指定范围内的序列并将它重新排序为上一个序列。如果不存在上一个序列则返回false。 |
算术算法
函数 | 作用 |
---|---|
accumulate | iterator对标识的序列段元素之和,加到一个由val指定的初始值上。 |
partial_sum | 创建一个新序列,其中每个元素值代表指定范围内该位置前所有元素之和。 |
inner_product | 对两个序列做内积(对应元素相乘,再求和)并将内积加到一个输入的初始值上。 |
adjacent_difference | 创建一个新序列,新序列中每个新值代表当前元素与上一个元素的差。 |
生成和异变算法
函数 | 作用 |
---|---|
fill | 将输入值赋给标志范围内的所有元素。 |
fill_n | 将输入值赋给first到first+n范围内的所有元素。 |
for_each | 用指定函数依次对指定范围内所有元素进行迭代访问,返回所指定的函数类型。该函数不得修改序列中的元素。 |
generate | 连续调用输入的函数来填充指定的范围。 |
generate_n | 与generate函数类似,填充从指定iterator开始的n个元素。 |
transform | 将输入的操作作用与指定范围内的每个元素,并产生一个新的序列。 |
关系算法
函数 | 作用 |
---|---|
equal | 如果两个序列在标志范围内元素都相等,返回true。 |
includes | 判断第一个指定范围内的所有元素是否都被第二个范围包含,使用底层元素的<操作符,成功返回true。 |
lexicographical_compare | 比较两个序列 |
max | 返回两个元素较大的一个 |
max_element | 返回一个ForwardIterator,指出序列中最大的元素。 |
min | 返回两个元素中较小一个。 |
min_element | 返回一个ForwardIterator,指出序列中最小的元素。 |
mismatch | 并行比较两个序列,指出第一个不匹配的位置,返回一对iterator,标志第一个不匹配元素位置。如果都匹配,返回每个容器的last。 |
集合算法
函数 | 作用 |
---|---|
set_union | 构造一个有序序列,包含两个序列中所有的不重复元素。 |
set_intersection | 构造一个有序序列,其中元素在两个序列中都存在。 |
set_difference | 构造一个有序序列,该序列仅保留第一个序列中存在的而第二个中不存在的元素。 |
set_symmetric_difference | 构造一个有序序列,该序列取两个序列的对称差集(并集-交集)。 |
堆算法
函数 | 作用 |
---|---|
make_heap | 把指定范围内的元素生成一个堆 |
pop_heap | 并不真正把最大元素从堆中弹出,而是重新排序堆。它把first和last-1交换,然后重新生成一个堆。 |
push_heap | 假设first到last-1是一个有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向该函数前,必须先把元素插入容器后。 |
sort_heap | 对指定范围内的序列重新排序,它假设该序列是个有序堆。 |
仿函数(functor),就是使一个类的使用看上去象一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
主要用于有些功能的的代码,会在不同的成员函数中用到,想复用这些代码。有两种方式:
1、采用公共的函数,不过函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。
2、仿函数,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。
C语言使用函数指针和回调函数来实现仿函数
#include
#include
//int sort_function( const void *a, const void *b);
int sort_function( const void *a, const void *b)
{
return *(int*)a-*(int*)b;
}
int main()
{
int list[5] = { 54, 21, 11, 67, 22 };
qsort((void *)list, 5, sizeof(list[0]), sort_function);//起始地址,个数,元素大小,回调函数
int x;
for (x = 0; x < 5; x++)
printf("%i\n", list[x]);
return 0;
}
在C++里,我们通过在一个类中重载括号运算符的方法使用一个函数对象而不是一个普通函数。
#include
#include
using namespace std;
template<typename T>
class display
{
public:
void operator()(const T &x)
{
cout << x << " ";
}
};
int main()
{
int ia[] = { 1,2,3,4,5 };
for_each(ia, ia + 5, display<int>());
system("pause");
return 0;
}
要使用STL内建的仿函数,必须包含
#include
#include
#include
#include
using namespace std;
int main()
{
int ia[] = { 1,2,3,4,5 };
vector<int> iv(ia, ia + 5);
//120
cout << accumulate(iv.begin(), iv.end(), 1, multiplies<int>()) << endl;
//15
cout << multiplies<int>()(3, 5) << endl;
modulus<int> modulusObj;
cout << modulusObj(3, 5) << endl; // 3
system("pause");
return 0;
}
#include
#include
#include
#include
using namespace std;
template <class T>
class display
{
public:
void operator()(const T &x)
{
cout << x << " ";
}
};
int main()
{
int ia[] = { 1,5,4,3,2 };
vector<int> iv(ia, ia + 5);
sort(iv.begin(), iv.end(), greater<int>());
for_each(iv.begin(), iv.end(), display<int>());
system("pause");
return 0;
}
标准库提供了三种顺序容器适配器:queue(FIFO队列)、priority_queue(优先级队列)、stack(栈)。
种类 | 默认顺序容器 | 可用顺序容器 | 说明 |
---|---|---|---|
stack | deque | vector、list、deque | |
queue | deque | list、deque | 基础容器必须提供push_front()运算 |
priority_queue | vector | vector、deque | 基础容器必须提供随机访问功能 |
定义适配器
1、初始化
stack
2、覆盖默认容器类型
stack
使用适配器
stack<int> s;
stack< int, vector<int> > stk; //覆盖基础容器类型,使用vector实现stk
s.empty(); //判断stack是否为空,为空返回true,否则返回false
s.size(); //返回stack中元素的个数
s.pop(); //删除栈顶元素,但不返回其值
s.top(); //返回栈顶元素的值,但不删除此元素
s.push(item); //在栈顶压入新元素item
queue<int> q; //priority_queue q;
q.empty(); //判断队列是否为空
q.size(); //返回队列长度
q.push(item); //对于queue,在队尾压入一个新元素
//对于priority_queue,在基于优先级的适当位置插入新元素
//queue only:
q.front(); //返回队首元素的值,但不删除该元素
q.back(); //返回队尾元素的值,但不删除该元素
//priority_queue only:
q.top(); //返回具有最高优先级的元素值,但不删除该元素
1.构造函数
函数 | 作用 |
---|---|
vector() | 创建一个空vector |
vector(int nSize) | 创建一个vector,元素个数为nSize |
vector(int nSize,const t& t) | 创建一个vector,元素个数为nSize,且值均为t |
vector(const vector&) | 复制构造函数 |
vector(begin,end) | 复制[begin,end)区间内另一个数组的元素到vector中 |
2.增加函数
函数 | 作用 |
---|---|
void push_back(const T& x) | 向量尾部增加一个元素X |
iterator insert(iterator it,const T& x) | 向量中迭代器指向元素前增加一个元素x |
iterator insert(iterator it,int n,const T& x) | 向量中迭代器指向元素前增加n个相同的元素x |
iterator insert(iterator it,const_iterator first, const_iterator last) | 向量中迭代器指向元素前插入另一个相同类型向量的[first,last)间的数据 |
3.删除函数
函数 | 作用 |
---|---|
iterator erase(iterator it) | 删除向量中迭代器指向元素 |
iterator erase(iterator first,iterator last) | 删除向量中[first,last)中元素 |
void pop_back() | 删除向量中最后一个元素 |
void clear() | 清空向量中所有元素 |
4.遍历函数
函数 | 作用 |
---|---|
reference at(int pos) | 返回pos位置元素的引用 |
reference front() | 返回首元素的引用 |
reference back() | 返回尾元素的引用 |
iterator begin() | 返回向量头指针,指向第一个元素 |
iterator end() | 返回向量尾指针,指向向量最后一个元素的下一个位置 |
reverse_iterator rbegin() | 反向迭代器,指向最后一个元素 |
reverse_iterator rend() | 反向迭代器,指向第一个元素之前的位置 |
5.判断函数
函数 | 作用 |
---|---|
bool empty() const | 判断向量是否为空,若为空,则向量中无元素 |
6.大小函数
函数 | 作用 |
---|---|
int size() const | 返回向量中元素的个数 |
int capacity() const | 返回当前向量张红所能容纳的最大元素值 |
int max_size() const | 返回最大可允许的vector元素数量值 |
7.其他函数
函数 | 作用 |
---|---|
void swap(vector&) | 交换两个同类型向量的数据 |
void assign(int n,const T& x) | 设置向量中第n个元素的值为x |
void assign(const_iterator first,const_iterator last) | 向量中[first,last)中元素设置成当前向量元素 |
#include < vector>
using namespace std;
Vector<类型>标识符
Vector<类型>标识符(最大容量)
Vector<类型>标识符(最大容量,初始所有值)
Int i[5]={1,2,3,4,5}
Vector<类型>vi(I,i+2);//得到i索引值为3以后的值
Vector< vector< int> >v; 二维向量//这里最外的<>要有空格。否则在比较旧的编译器下无法通过
#include
#include
// push_back(elem)在数组最后添加数据
obj.push_back(i);
//去掉数组最后一个数据
obj.pop_back();
obj.clear();//清除容器中所以数据
sort(obj.begin(),obj.end());//从小到大
reverse(obj.begin(),obj.end());//从大到小
bool compare(int a,int b)
{
return a< b; //升序排列,如果改为return a>b,则为降序
}
sort(a,a+20,compare);
//访问(直接数组访问&迭代器访问)
obj[i];
vector<int>::iterator it;//声明一个迭代器,来访问vector容器,作用:遍历或者指向vector容器的元素
for(it=obj.begin();it!=obj.end();it++)
{
cout<<*it<<" ";
}
//创建二维
int N=5, M=6;
vector<vector<int> > obj(N); //定义二维动态数组大小5行
for(int i =0; i< obj.size(); i++)//动态二维数组为5行6列,值全为0
{
obj[i].resize(M);
}
int N=5, M=6;
vector<vector<int> > obj(N, vector<int>(M)); //定义二维动态数组5行6列
所谓的deque是”double ended queue”的缩写,双端队列不论在尾部或头部插入元素,都十分迅速。而在中间插入元素则会比较费时,因为必须移动中间其他的元素。双端队列是一种随机访问的数据类型,提供了在序列两端快速插入和删除操作的功能,它可以在需要的时候改变自身大小,完成了标准的C++数据结构中队列的所有功能。
Vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。deque对象在队列的两端放置元素和删除元素是高效的,而向量vector只是在插入序列的末尾时操作才是高效的。deque和vector的最大差异,一在于deque允许于常数时间内对头端进行元素的插入或移除操作,二在于deque没有所谓的capacity观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。换句话说,像vector那样“因旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque中是不会发生的。也因此,deque没有必要提供所谓的空间预留(reserved)功能。
虽然deque也提供Random Access Iterator,但它的迭代器并不是普通指针,其复杂度和vector不可同日而语,这当然涉及到各个运算层面。因此,除非必要,我们应尽可能选择使用vector而非deque。对deque进行的排序操作,为了最高效率,可将deque先完整复制到一个vector身上,将vector排序后(利用STL的sort算法),再复制回deque。
deque是一种优化了的对序列两端元素进行添加和删除操作的基本序列容器。通常由一些独立的区块组成,第一区块朝某方向扩展,最后一个区块朝另一方向扩展。它允许较为快速地随机访问但它不像vector一样把所有对象保存在一个连续的内存块,而是多个连续的内存块。并且在一个映射结构中保存对这些块以及顺序的跟踪。
#include // 头文件
deque<type> deq; // 声明一个元素类型为type的双端队列que
deque<type> deq(size); // 声明一个类型为type、含有size个默认值初始化元素的的双端队列que
deque<type> deq(size, value); // 声明一个元素类型为type、含有size个value元素的双端队列que
deque<type> deq(mydeque); // deq是mydeque的一个副本
deque<type> deq(first, last); // 使用迭代器first、last范围内的元素初始化deq
函数 | 作用 |
---|---|
deq[ ] | 用来访问双向队列中单个的元素。 |
deq.front() | 返回第一个元素的引用。 |
deq.back() | 返回最后一个元素的引用。 |
deq.push_front(x) | 把元素x插入到双向队列的头部。 |
deq.pop_front() | 弹出双向队列的第一个元素。 |
deq.push_back(x) | 把元素x插入到双向队列的尾部。 |
deq.pop_back() | 弹出双向队列的最后一个元素。 |
#include
deque<int> q;
q.push_front(a[i]);
q.push_back(a[i]);
q.pop_front();
printf("%d\n", q.front()); /*清除第一个元素后输出第一个(6)*/
q.pop_back();
printf("%d\n", q.back()); /*清除最后一个元素后输出最后一个(7)*/
deque<int>::iterator it;
for (it = q.begin(); it != q.end(); it++) {
cout << *it << '\t';
}
List是stl实现的双向链表,与向量(vectors)相比, 它允许快速的插入和删除,但是随机访问却比较慢。使用时需要添加头文件
#include
list<int>lst1; //创建空list
list<int> lst2(5); //创建含有5个元素的list
list<int>lst3(3,2); //创建含有3个元素的list
list<int>lst4(lst2); //使用lst2初始化lst4
list<int>lst5(lst2.begin(),lst2.end()); //同lst4
函数 | 作用 |
---|---|
Lst1.assign() | 给list赋值 |
Lst1.back() | 返回最后一个元素 |
Lst1.begin() | 返回指向第一个元素的迭代器 |
Lst1.clear() | 删除所有元素 |
Lst1.empty() | 如果list是空的则返回true |
Lst1.end() | 返回末尾的迭代器 |
Lst1.erase() | 删除一个元素 |
Lst1.front() | 返回第一个元素 |
Lst1.get_allocator() | 返回list的配置器 |
Lst1.insert() | 插入一个元素到list中 |
Lst1.max_size() | 返回list能容纳的最大元素数量 |
Lst1.merge() | 合并两个list |
Lst1.pop_back() | 删除最后一个元素 |
Lst1.pop_front() | 删除第一个元素 |
Lst1.push_back() | 在list的末尾添加一个元素 |
Lst1.push_front() | 在list的头部添加一个元素 |
Lst1.rbegin() | 返回指向第一个元素的逆向迭代器 |
Lst1.remove() | 从list删除元素 |
Lst1.remove_if() | 按指定条件删除元素 |
Lst1.rend() | 指向list末尾的逆向迭代器 |
Lst1.resize() | 改变list的大小 |
Lst1.reverse() | 把list的元素倒转 |
Lst1.size() | 返回list中的元素个数 |
Lst1.sort() | 给list排序 |
Lst1.splice() | 合并两个list |
Lst1.swap() | 交换两个list |
Lst1.unique() | 删除list中相邻重复的元素 |
for(list<int>::const_iteratoriter = lst1.begin();iter != lst1.end();iter++)
{
cout<<*iter;
}
cout<<endl;
map和multimap都需要#include,唯一的不同是,map的键值key不可重复,而multimap可以,也正是由于这种区别,map支持[ ]运算符,multimap不支持[ ]运算符。在用法上没什么区别。
map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。
函数 | 作用 |
---|---|
begin() | 返回指向map头部的迭代器 |
clear() | 删除所有元素 |
count() | 返回指定元素出现的次数 |
empty() | 如果map为空则返回true |
end() | 返回指向map末尾的迭代器 |
equal_range() | 返回特殊条目的迭代器对 |
erase() | 删除一个元素 |
find() | 查找一个元素 |
get_allocator() | 返回map的配置器 |
insert() | 插入元素 |
key_comp() | 返回比较元素key的函数 |
lower_bound() | 返回键值>=给定元素的第一个位置 |
max_size() | 返回可以容纳的最大元素个数 |
rbegin() | 返回一个指向map尾部的逆向迭代器 |
rend() | 返回一个指向map头部的逆向迭代器 |
size() | 返回map中元素的个数 |
swap() | 交换两个map |
upper_bound() | 返回键值>给定元素的第一个位置 |
value_comp() | 返回比较元素value的函数 |
//头文件
#include
map<int, string> ID_Name;
// 使用{}赋值是从c++11开始的,因此编译器版本过低时会报错,如visual studio 2012
map<int, string> ID_Name = {
{ 2015, "Jim" },
{ 2016, "Tom" },
{ 2017, "Bob" } };
共有八个获取迭代器的函数:* begin, end, rbegin,rend* 以及对应的 * cbegin, cend, crbegin,crend*。
二者的区别在于,后者一定返回 const_iterator,而前者则根据map的类型返回iterator 或者 const_iterator。const情况下,不允许对值进行修改。
map<int,int>::iterator it;
map<int,int> mmap;
const map<int,int> const_mmap;
it = mmap.begin(); //iterator
mmap.cbegin(); //const_iterator
const_mmap.begin(); //const_iterator
const_mmap.cbegin(); //const_iterator
用insert插入pair数据
//数据的插入--第一种:用insert函数插入pair数据
#include
#include
#include
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
用insert函数插入value_type数据
//第二种:用insert函数插入value_type数据,下面举例说明
#include
#include
#include
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(map<int, string>::value_type (1, "student_one"));
mapStudent.insert(map<int, string>::value_type (2, "student_two"));
mapStudent.insert(map<int, string>::value_type (3, "student_three"));
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
用insert函数进行多个插入
四个重载
// 插入单个键值对,并返回插入位置和成功标志,插入位置已经存在值时,插入失败
pair<iterator,bool> insert (const value_type& val);
//在指定位置插入,在不同位置插入效率是不一样的,因为涉及到重排
iterator insert (const_iterator position, const value_type& val);
// 插入多个
void insert (InputIterator first, InputIterator last);
//c++11开始支持,使用列表插入多个
void insert (initializer_list<value_type> il);
#include
#include
int main()
{
std::map<char, int> mymap;
// 插入单个值
mymap.insert(std::pair<char, int>('a', 100));
mymap.insert(std::pair<char, int>('z', 200));
//返回插入位置以及是否插入成功
std::pair<std::map<char, int>::iterator, bool> ret;
ret = mymap.insert(std::pair<char, int>('z', 500));
if (ret.second == false) {
std::cout << "element 'z' already existed";
std::cout << " with a value of " << ret.first->second << '\n';
}
//指定位置插入
std::map<char, int>::iterator it = mymap.begin();
mymap.insert(it, std::pair<char, int>('b', 300)); //效率更高
mymap.insert(it, std::pair<char, int>('c', 400)); //效率非最高
//范围多值插入
std::map<char, int> anothermap;
anothermap.insert(mymap.begin(), mymap.find('c'));
// 列表形式插入
anothermap.insert({ { 'd', 100 }, {'e', 200} });
return 0;
}
用数组方式插入数据
//第三种:用数组方式插入数据,下面举例说明
#include
#include
#include
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent[1] = "student_one";
mapStudent[2] = "student_two";
mapStudent[3] = "student_three";
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
以上三种用法,虽然都可以实现数据的插入,但是它们是有区别的,当然了第一种和第二种在效果上是完成一样的,用insert函数插入数据,在数据的 插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,但是用数组方式就不同了,它可以覆盖以前该关键字对 应的值,用程序说明
mapStudent.insert(map
mapStudent.insert(map
上面这两条语句执行后,map中1这个关键字对应的值是“student_one”,第二条语句并没有生效,那么这就涉及到我们怎么知道insert语句是否插入成功的问题了,可以用pair来获得是否插入成功,程序如下
pair
Insert_Pair = mapStudent.insert(map
我们通过pair的第二个变量来知道是否插入成功,它的第一个变量返回的是一个map的迭代器,如果插入成功的话Insert_Pair.second应该是true的,否则为false。
下面给出完成代码,演示插入成功与否问题
//验证插入函数的作用效果
#include
#include
#include
using namespace std;
int main()
{
map<int, string> mapStudent;
pair<map<int, string>::iterator, bool> Insert_Pair;
Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_one"));
if(Insert_Pair.second == true)
cout<<"Insert Successfully"<<endl;
else
cout<<"Insert Failure"<<endl;
Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_two"));
if(Insert_Pair.second == true)
cout<<"Insert Successfully"<<endl;
else
cout<<"Insert Failure"<<endl;
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
查找
// 关键字查询,找到则返回指向该关键字的迭代器,否则返回指向end的迭代器
// 根据map的类型,返回的迭代器为 iterator 或者 const_iterator
iterator find (const key_type& k);
const_iterator find (const key_type& k) const;
删除
// 删除迭代器指向位置的键值对,并返回一个指向下一元素的迭代器
iterator erase( iterator pos )
// 删除一定范围内的元素,并返回一个指向下一元素的迭代器
iterator erase( const_iterator first, const_iterator last );
// 根据Key来进行删除, 返回删除的元素数量,在map里结果非0即1
size_t erase( const key_type& key );
// 清空map,清空后的size为0
void clear();
交换
// 就是两个map的内容互换
void swap( map& other );
// 查询map是否为空
bool empty();
// 查询map中键值对的数量
size_t size();
// 查询map所能包含的最大键值对数量,和系统和应用库有关。
// 此外,这并不意味着用户一定可以存这么多,很可能还没达到就已经开辟内存失败了
size_t max_size();
// 查询关键字为key的元素的个数,在map里结果非0即1
size_t count( const Key& key ) const; //
map中的元素是自动按Key升序排序,所以不能对map用sort函数;
这里要讲的是一点比较高深的用法了,排序问题,STL中默认是采用小于号来排序的,以上代码在排序上是不存在任何问题的,因为上面的关键字是int 型,它本身支持小于号运算,在一些特殊情况,比如关键字是一个结构体或者自定义类,涉及到排序就会出现问题,因为它没有小于号操作,insert等函数在编译的时候过 不去,下面给出两个方法解决这个问题。
小于号 < 重载
#include
#include
#include
using namespace std;
typedef struct tagStudentinfo
{
int niD;
string strName;
bool operator < (tagStudentinfo const& _A) const
{ //这个函数指定排序策略,按niD排序,如果niD相等的话,按strName排序
if (niD < _A.niD) return true;
if (niD == _A.niD)
return strName.compare(_A.strName) < 0;
return false;
}
}Studentinfo, *PStudentinfo; //学生信息
int main()
{
int nSize; //用学生信息映射分数
map<Studentinfo, int>mapStudent;
map<Studentinfo, int>::iterator iter;
Studentinfo studentinfo;
studentinfo.niD = 1;
studentinfo.strName = "student_one";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 90));
studentinfo.niD = 2;
studentinfo.strName = "student_two";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 80));
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first.niD << ' ' << iter->first.strName << ' ' << iter->second << endl;
return 0;
}
仿函数的应用,这个时候结构体中没有直接的小于号重载
//第二种:仿函数的应用,这个时候结构体中没有直接的小于号重载,程序说明
#include
#include
#include
using namespace std;
typedef struct tagStudentinfo
{
int niD;
string strName;
}Studentinfo, *PStudentinfo; //学生信息
class sort
{
public:
bool operator() (Studentinfo const &_A, Studentinfo const &_B) const
{
if (_A.niD < _B.niD)
return true;
if (_A.niD == _B.niD)
return _A.strName.compare(_B.strName) < 0;
return false;
}
};
int main()
{
//用学生信息映射分数
map<Studentinfo, int, sort>mapStudent;
map<Studentinfo, int>::iterator iter;
Studentinfo studentinfo;
studentinfo.niD = 1;
studentinfo.strName = "student_one";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 90));
studentinfo.niD = 2;
studentinfo.strName = "student_two";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 80));
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first.niD << ' ' << iter->first.strName << ' ' << iter->second << endl;
system("pause");
}
在c++11标准前,c++标准库中只有一种map,但是它的底层实现并不是适合所有的场景,如果我们需要其他适合的map实现就不得不使用比如boost库等三方的实现,在c++11中加了一种map unordered_map,unordered_set,他们的实现有什么不同呢?
map底层采用的是红黑树的实现查询的时间复杂度为O(logn),看起来并没有unordered_map快,但是也要看实际的数据量,虽然unordered_map的查询从算法上分析比map快,但是它有一些其它的消耗,比如哈希函数的构造和分析,还有如果出现哈希冲突解决哈希冲突等等都有一定的消耗,因此unordered_map的效率在很大的程度上由它的hash函数算法决定,而红黑树的效率是一个稳定值。
unordered_map的底层采用哈希表的实现,查询的时间复杂度为是O(1)。所以unordered_map内部就是无序的,数据是按散列函数插入到槽里面去的,数据之间无顺序可言,如果我们不需要内部有序,这种实现是没有问题的。unordered_map属于关联式容器,采用std::pair保存key-value形式的数据。用法与map一致。特别的是,STL中的map因为是有序的二叉树存储,所以对key值需要有大小的判断,当使用内置类型时,无需重载operator < ;但是用用户自定义类型的话,就需要重载operator < 。 unoredered_map全程使用不需要比较元素的key值的大小,但是,对于元素的==要有判断,又因为需要使用hash映射,所以,对于非内部类型,需要程序员为其定义这二者的内容,对于内部类型,就不需要了. unordered库使用“桶”来存储元素,散列值相同的被存储在一个桶里。当散列容器中有大量数据时,同一个桶里的数据也会增多,造成访问冲突,降低性能。为了提高散列容器的性能,unordered库会在插入元素是自动增加桶的数量,不需要用户指定。但是,用户也可以在构造函数或者rehash()函数中,指定最小的桶的数量。
还有另外一点从占用内存上来说因为unordered_map才用hash结构会有一定的内存损失,它的内存占用回高于map。
set容器内的元素会被自动排序,set与map不同,set中的元素即是键值又是实值,set不允许两个元素有相同的键值。不能通过set的迭代器去修改set元素,原因是修改元素会破坏set组织。当对容器中的元素进行插入或者删除时,操作之前的所有迭代器在操作之后依然有效。
由于set元素是排好序的,且默认为升序,因此当set集合中的元素为结构体或自定义类时,该结构体或自定义类必须实现运算符‘<’的重载。
multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。
set和multiset的底层实现是一种高效的平衡二叉树,即红黑树(Red-Black Tree)。
函数 | 作用 |
---|---|
begin() | 返回指向第一个元素的迭代器 |
clear() | 清除所有元素 |
count() | 返回某个值元素的个数 |
empty() | 如果集合为空,返回true |
end() | 返回指向最后一个元素的迭代器 |
equal_range() | 返回集合中与给定值相等的上下限的两个迭代器 |
erase() | 删除集合中的元素 |
find() | 返回一个指向被查找到元素的迭代器 |
get_allocator() | 返回集合的分配器 |
insert() | 在集合中插入元素 |
lower_bound() | 返回指向大于(或等于)某值的第一个元素的迭代器 |
key_comp() | 返回一个用于元素间值比较的函数 |
max_size() | 返回集合能容纳的元素的最大限值 |
rbegin() | 返回指向集合中最后一个元素的反向迭代器 |
rend() | 返回指向集合中第一个元素的反向迭代器 |
size() | 集合中元素的数目 |
swap() | 交换两个集合变量 |
upper_bound() | 返回大于某个值元素的迭代器 |
value_comp() | 返回一个用于比较元素间的值的函数 |
以下代码涉及的内容:
1、set容器中,元素类型为基本类型,如何让set按照用户意愿来排序?
2、set容器中,如何让元素类型为自定义类型?
3、set容器的insert函数的返回值为什么类型?
#include
#include
#include
using namespace std;
/* 仿函数CompareSet,在test02使用 */
class CompareSet
{
public:
//从大到小排序
bool operator()(int v1, int v2)
{
return v1 > v2;
}
//从小到大排序
//bool operator()(int v1, int v2)
//{
// return v1 < v2;
//}
};
/* Person类,用于test03 */
class Person
{
friend ostream &operator<<(ostream &out, const Person &person);
public:
Person(string name, int age)
{
mName = name;
mAge = age;
}
public:
string mName;
int mAge;
};
ostream &operator<<(ostream &out, const Person &person)
{
out << "name:" << person.mName << " age:" << person.mAge << endl;
return out;
}
/* 仿函数ComparePerson,用于test03 */
class ComparePerson
{
public:
//名字大的在前面,如果名字相同,年龄大的排前面
bool operator()(const Person &p1, const Person &p2)
{
if (p1.mName == p2.mName)
{
return p1.mAge > p2.mAge;
}
return p1.mName > p2.mName;
}
};
/* 打印set类型的函数模板 */
template<typename T>
void PrintSet(T &s)
{
for (T::iterator iter = s.begin(); iter != s.end(); ++iter)
cout << *iter << " ";
cout << endl;
}
void test01()
{
//set容器默认从小到大排序
set<int> s;
s.insert(10);
s.insert(20);
s.insert(30);
//输出set
PrintSet(s);
//结果为:10 20 30
/* set的insert函数返回值为一个对组(pair)。
对组的第一个值first为set类型的迭代器:
1、若插入成功,迭代器指向该元素。
2、若插入失败,迭代器指向之前已经存在的元素
对组的第二个值seconde为bool类型:
1、若插入成功,bool值为true
2、若插入失败,bool值为false
*/
pair<set<int>::iterator, bool> ret = s.insert(40);
if (true == ret.second)
cout << *ret.first << " 插入成功" << endl;
else
cout << *ret.first << " 插入失败" << endl;
}
void test02()
{
/* 如果想让set容器从大到小排序,需要给set容
器提供一个仿函数,本例的仿函数为CompareSet
*/
set<int, CompareSet> s;
s.insert(10);
s.insert(20);
s.insert(30);
//打印set
PrintSet(s);
//结果为:30,20,10
}
void test03()
{
/* set元素类型为Person,当set元素类型为自定义类型的时候
必须给set提供一个仿函数,用于比较自定义类型的大小,
否则无法通过编译
*/
set<Person,ComparePerson> s;
s.insert(Person("John", 22));
s.insert(Person("Peter", 25));
s.insert(Person("Marry", 18));
s.insert(Person("Peter", 36));
//打印set
PrintSet(s);
}
int main(void)
{
//test01();
//test02();
//test03();
return 0;
}
#include
#include
using namespace std;
/* 打印set类型的函数模板 */
template<typename T>
void PrintSet(T &s)
{
for (T::iterator iter = s.begin(); iter != s.end(); ++iter)
cout << *iter << " ";
cout << endl;
}
void test(void)
{
multiset<int> s;
s.insert(10);
s.insert(20);
s.insert(30);
//打印multiset
PrintSet(s);
/* multiset的insert函数返回值为multiset类型的迭代器,
指向新插入的元素。multiset允许插入相同的值,因此
插入一定成功,因此不需要返回bool类型。
*/
multiset<int>::iterator iter = s.insert(10);
cout << *iter << endl;
}
int main(void)
{
test();
return 0;
}
unordered_set是基于哈希表,因此要了解unordered_set,就必须了解哈希表的机制。哈希表是根据关键码值而进行直接访问的数据结构,通过相应的哈希函数(也称散列函数)处理关键字得到相应的关键码值,关键码值对应着一个特定位置,用该位置来存取相应的信息,这样就能以较快的速度获取关键字的信息。比如:现有公司员工的个人信息(包括年龄),需要查询某个年龄的员工个数。由于人的年龄范围大约在[0,200],所以可以开一个200大小的数组,然后通过哈希函数得到key对应的key-value,这样就能完成统计某个年龄的员工个数。而在这个例子中,也存在这样一个问题,两个员工的年龄相同,但其他信息(如:名字、身份证)不同,通过前面说的哈希函数,会发现其都位于数组的相同位置,这里,就涉及到“冲突”。准确来说,冲突是不可避免的,而解决冲突的方法常见的有:开发地址法、再散列法、链地址法(也称拉链法)。而unordered_set内部解决冲突采用的是----链地址法,当用冲突发生时把具有同一关键码的数据组成一个链表。下图展示了链地址法的使用:
使用unordered_set需要包含#include
除此之外unordered_multiset也是一种可选的容器。
(1)auto_ptr
(2)迭代器使用过程中优先选用前置式递增操作符(++iter)而不是选择后置式递增操作符(iter++)。
++iter 前向表达式返回引用,而iter++返回的却是一临时对象,因此++iter更高效。
(3)迭代器三个辅助函数:advance(), distance(), iter_swap():
advance()可令迭代器前进
distance()可处理迭代器之间的距离。
iter_swap()可交换两个迭代器所指内容。
// vector
++iter
_Myiter& operator++()
{ // preincrement
++*(_Mybase *)this;
return (*this);
}
iter++
_Myiter operator++(int)
{ // postincrement
_Myiter _Tmp = *this;
++*this;
return (_Tmp);
}
(4)/0’在string之中并不具有特殊意义,但是在一般C形式的string中却用来标记字符串结束。在string中,字符 ‘/0’和其他字符的地位完全相同。string中有三个函数可以将字符串内容转换成字符数组或C形式的string。
(5)容器中用empty来代替检查size是否为0;当使用new得到指针的容器时,切记在容器销毁前delete那些指针;千万不要把auto_ptr放入容器中。
(6)尽量使用vector和string来代替动态申请的数组;避免使用vector,vector有两个问题.第一,它不是一个真正STL容器,第二,它并不保存bool类型, 是STL的一个类。原因是:bool是bit,容器只对字节byte做操作
vector<bool> c{ false, true, false, true, false };
&tmp = c[0]; //错误,不能编译,对于引用来说,因为c[0]不是一个左值
bool *p = &c[0]; //错误,不能编译,因为无法将一个临时量地址给绑定到指针
(7)迭代器使用过程中,尽量使用iterator代替const_iterator,reverse_iterator和const_reverse_iterator;使用distance和advance把const_iterators转化成iterators。
(8)避免对set和multiset的键值进行修改。
(9)排序选择
int i=0;
string temp;
stringstream s;
//string转换为数字
temp = “1234”;
s<<temp;
s>>i;
cout<<i<<endl;
//数字转换为string
i=256;
s<<i;
temp = s.str();
cout<<temp<<end;
(16)对于自定义的结构体,放入容器中,最好不要对容器进行内存初始化(不要调用memset,zeromemory函数),否则如果结构体中有指针类型的变量时,就会出现问题。
(17)Vector的函数泄漏问题
定义了一个
struct temp
{
char name[256];
int i;
}
Vector<temp> vect;
当对这个vect执行pushback一些temp的结构体后,执行clear这样是否会内存泄露?可以释放掉temp结构体中的name内存吗?
解决方法:
不行,clear只是把那些元素全部删除掉,并不是释放内存。再者,你这样的定义容器是不需要释放内存的,如果你这样定义,std::vector
。就需要了。先pVec->clear()
再 pVec->swap( (std::vector
。就能实现内存的释放。
(18)stl之map erase方法的正确使用
STL的map表里有一个erase方法用来从一个map中删除掉指令的一个节点,不存在任何问题。
如果删除多一个节点时,需要使用正确的调用方法。比如下面的方法是有问题:
for(ITER iter=mapTest.begin();iter!=mapTest.end();++iter)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter);
}
这是一种错误的写法,会导致程序行为不可知.究其原因是map 是关联容器,对于关联容器来说,如果某一个元素已经被删除,那么其对应的迭代器就失效了,不应该再被使用;否则会导致程序无定义的行为。
正确的使用方法:
1).使用删除之前的迭代器定位下一个元素。STL建议的使用方式
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter++);
}
或者
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
ITER iterTmp = iter;
iter++;
cout<<iterTmp->first<<":"<<iterTmp->second<<endl;
mapTest.erase(iterTmp);
}
2). erase() 成员函数返回下一个元素的迭代器
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
cout<<iter->first<<":"<<iter->second<<endl;
iter=mapTest.erase(iter);
}
(19)std::bind
bind 是一组重载的函数模板.用来向一个函数(或函数对象)绑定某些参数. bind的返回值是一个函数对象.
性质:
不是函数,是一个class,是一个多元仿函数
模板参数:
带模板参数,但不需要,会自动推导!
构造函数参数:
格式:_需要绑定类型,_参数1,_参数2,_参数3,_参数4…
_需要绑定类型:可以是普通函数,类成员函数,成员变量
_参数N:可以是一个占位符,或者实际参数。
如果绑定的类型是一个类成员函数或变量,那么第一个参数必须是对象或者对象指针。
仿函数参数:
任意仿函数返回值
如果绑定的是函数,返回绑定函数的返回值。
如果绑定是成员变量,返回成员变量值
占位符:
_1,_2,_3,_4……_9
占位符的数字表示仿函数时对应参数的位置。
一个bind里可以嵌入多个bind,但占位符是相对于这一块的bind是共享。
注意事项
a)如果绑定的是类函数,传入对象时,最好使用对象指针,如果使用对象实例会产生多次对象复制。如果非要传对象而不想多次被复制传在在使用ref或cref(ref的const版)
b) 跟lambda混用时一定要特别小心
第一、 会与lambda的占位符有冲突
第二、 lambda库里有跟同样名字的bind,功能类似,但没有此功能强大
总结
无模板参数,构函数对绑定函数负责,仿函数是任意的。
举例说明
例一:
void nine_arguments(
int i1,int i2,int i3,int i4,
int i5,int i6,int i7,int i8, int i9) {
std::cout << i1 << i2 << i3 << i4 << i5 << i6 << i7 << i8 << i9 << '/n';
}
int main() {
int i1=1,i2=2,i3=3,i4=4,i5=5,i6=6,i7=7,i8=8,i9=9;
(boost::bind(&nine_arguments,_9,_2,_1,_6,_3,_8,_4,_5,_7))
(i1,i2,i3,i4,i5,i6,i7,i8,i9);
}
输出结果921638457
(20)平时我们用的最多的是 二元函数对象
template <class Arg1, class Arg2, class Result>
struct binary_function
{
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
如果为了更方便的使用,以后的仿函数(函数对象)最好都继承binary_function
template <class T> struct less_equal: binary_function <T,T,bool> {
bool operator() (const T& x, const T& y) const
{
return x<=y;
}
};
关于谓语函数对象都继承binary_function
(21)容易忽略的 C++11/17 可用但 C++98/14 不可用的特性:
(22)std::multiset 的 count() 函数的复杂度为 (log+),因此多多考虑用 std::map 实现 std::multiset 的功能。
(23)直接对 std::set/std::multiset/std::map/std::multimap 进行
(24)std::priority_queue 默认为大根堆,小根堆可以取负、重载运算符或 std::greater。
(25)std::multimap 不支持 [] 下标访问。
(26)在使用 std::vector 时可以尝试用 reserve() 预测数组的大小以提高效率。实测 push_back() 次数较多时 reserve() 后能快一倍。
(27)许多 STL 容器都特化了 swap() 函数,可以实现 (1) 交换。
(28)std::nth_element 会改变元素在容器中的位置。
(29)对 std::vector 等序列容器进行下标访问是最好将 size() 函数的返回值转为 int。一个成功避免大问题的一个例子:for (int i = (int)v.size() - 2; i >= 0; i–) v[i] += v[i + 1]。
(30)使用 std::map
(31)STL 容器进行修改操作时需要注意迭代器被非法化的情况,被非法化的迭代器不能再使用。如使用 std::set 的 erase(it) 后 it 就被非法化了。std::vector 等也需要注意这种问题。
(32)判断容器是否为空尽量使用 empty() 而不是 size() == 0,因为某些容器的 size() 函数的复杂度并不是 (1)(如一些 list 实现)。
(33)对迭代器 it 做 + a - b 的操作尽量写成 it + (a - b) 而不是 it + a - b,因为 it + a 可能出现越界情况。
(34)__gnu_cxx::rope 中的 erase(size_t __p) 是错(编写者锅了)的!它的注释中写的是 “Erase, single character”,但实际上写的是 erase(__p, __p + 1),那么你实际上会把 __p 开始的 __p + 1 个位置全部删除。
(35)一个小 trick:代码里出现一大坨 begin/end 是不是很烦?你可以 #define all(x) (x).begin(), (x).end()。要注意 (x) 会算两边,如果是临时算出来的不应该这么做。
参考:参考博客