C++技术点积累(7)——STL(标准模板库):
一、STL分类
STL的从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),容器和算法通过迭代器可以进行无缝地连接。STL的一个重要特点是数据结构和算法的分离,这种分离使得STL变得非常通用。STL详细的说六大组件:容器(Container);算法(Algorithm);迭代器(Iterator)仿函数(Function object);适配器(Adaptor);空间配制器(allocator)
1)容器
容器部分主要由头文件<vector>,<list>,<deque>,<set>,<map>,<stack>和<queue>组成。
序列式容器(Sequence containers):每个元素都有固定位置——取决于插入时机和地点,和元素值无关——vector、deque、list;
关联式容器(Associated containers):元素位置取决于特定的排序准则,和插入顺序无关 ——set、multiset、map、multimap。
数据结构 | 描述 | 头文件 |
vector | 连续存储的元素 | <vector> |
列表(list) | 由节点组成的双向链表,每个结点包含着一个元素 | <list> |
双队列(deque) | 连续存储的指向不同元素的指针所组成的数组 | <deque> |
集合(set) | 由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列, | <set> |
多重集合(multiset) | 允许存在两个次序相等的元素的集合 | <set> |
栈(stack) | 后进先出的值的排列 | <stack> |
队列(queue) | 先进先出的执的排列 | <queue> |
优先队列(priority_queue) | 元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列 | <queue> |
映射(map) | 由{键,值}对组成的集合,以某种作用于键对上的谓词排列 | <map> |
多重映射(multimap) | 允许键对有相等的次序的映射 | <map> |
2)迭代器
迭代器在STL中用来将算法和容器联系起来,起着一种黏和剂的作用。几乎STL提供的所有算法都是通过迭代器存取元素序列进行工作的,每一个容器都定义了其本身所专有的迭代器,用以存取容器中的元素。
3)算法
<algorithm>,<numeric>和<functional> 组成。<algorithm>是所有STL头文件中最大的一个,它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。
4)STL容器、算法、迭代器的设计理念
A)STL的容器通过类模板技术,实现数据类型和容器模型的分离;
B)STL的迭代器技术实现了遍历容器的统一方法;也为STL的算法提供了统一性奠定了基础;
C)STL的算法,通过函数对象实现了自定义数据类型的算法运算;所以说:STL的算法也提供了统一性。
核心思想:其实函数对象本质就是回调函数,回调函数的思想:就是任务的编写者和任务的调用者有效解耦合。函数指针做函数参数。
D)具体例子:transform算法的输入,通过迭代器first和last指向的元算作为输入;通过result作为输出;通过函数对象来做自定义数据类型的运算。
template<class _InIt1,class _InIt2,class _OutIt,class _Fn2>
inline _OutIt transform(_InIt1 _First1, _InIt1 _Last1,_InIt2 _First2, _OutIt _Dest, _Fn2 _Func)
{ // transform [_First1, _Last1) and [_First2, ...) with _Func
_DEBUG_RANGE(_First1, _Last1);
_DEBUG_POINTER(_Dest);
_DEBUG_POINTER(_Func);
if (_First1 != _Last1)
return (_Transform2(_Unchecked(_First1), _Unchecked(_Last1),_First2, _Dest, _Func,_Is_checked(_Dest)));
return (_Dest);
}
二、string
1)string的构造函数:
默认构造函数:
string(); //构造一个空的字符串string s1。
拷贝构造函数:
string(const string &str); //构造一个与str一样的string。如string s1(s2)。
带参数的构造函数:
string(const char *s); //用字符串s初始化
string(int n,char c); //用n个字符c初始化
2)string的存取字符操作:
const char &operator[] (int n) const;
const char &at(int n) const;
char &operator[] (int n);
char &at(int n);
operator[]和at()均返回当前字符串中第n个字符,但二者是有区别的。
主要区别在于at()在越界时会抛出异常,[]在刚好越界时会返回(char)0,再继续越界时,编译器直接出错。如果你的程序希望可以通过try,catch捕获异常,建议采用at()。
3)从string取得const char*的操作:
const char *c_str() const; //返回一个以'\0'结尾的字符串的首地址
4)把string拷贝到char*指向的内存空间的操作:
int copy(char *s, int n, int pos=0) const; //把当前串中以pos开始的n个字符拷贝到以s为起始位置的字符数组中,返回实际拷贝的数目。注意要保证s所指向的空间足够大以容纳当前字符串,不然会越界。
5)string的长度:
int length() const; //返回当前字符串的长度。长度不包括字符串结尾的'\0'。
bool empty() const; //当前字符串是否为空
6)string的赋值:
string &operator=(const string &s); //把字符串s赋给当前的字符串
string &assign(const char *s); //把字符串s赋给当前的字符串
string &assign(const char *s, int n); //把字符串s的前n个字符赋给当前的字符串
string &assign(const string &s); //把字符串s赋给当前字符串
string &assign(int n,char c); //用n个字符c赋给当前字符串
string &assign(const string &s,int start, int n); //把字符串s中从start开始的n个字符赋给当前字符串
7)string字符串连接:
string &operator+=(const string &s); //把字符串s连接到当前字符串结尾
string &operator+=(const char *s); //把字符串s连接到当前字符串结尾
string &append(const char *s); //把字符串s连接到当前字符串结尾
string &append(const char *s,int n); //把字符串s的前n个字符连接到当前字符串结尾
string &append(const string &s); //同operator+=()
string &append(const string &s,int pos, int n); //把字符串s中从pos开始的n个字符连接到当前字符串结尾
string &append(int n, char c); //在当前字符串结尾添加n个字符c
8)string的比较:
int compare(const string &s) const; //与字符串s比较
int compare(const char *s) const; //与字符串s比较
compare函数在>时返回 1,<时返回 -1,==时返回 0。比较区分大小写,比较时参考字典顺序,排越前面的越小。大写的A比小写的a小。
9)string的子串:
string substr(int pos=0, int n=npos) const; //返回由pos开始的n个字符组成的子字符串
特注:n的默认值是str.size() - pos,即截取从pos位置开始的所有剩下字符
10)string的查找 和 替换:
查找:
int find(char c,int pos=0) const; //从pos开始查找 字符c 在当前字符串的位置(迭代器的位置); 没找到--返回string::npos
int find(const char *s, int pos=0) const; //从pos开始查找 字符串s 在当前字符串的位置(迭代器的位置)
int find(const string &s, int pos=0) const; //从pos开始查找 字符串s 在当前字符串中的位置(迭代器的位置)
find函数如果查找不到,就返回-1
int rfind(char c, int pos=npos) const; //从pos开始从后向前查找字符c在当前字符串中的位置
int rfind(const char *s, int pos=npos) const;
int rfind(const string &s, int pos=npos) const;
rfind是反向查找的意思,如果查找不到, 返回-1
替换
string &replace(int pos, int n, const char *s); //删除从pos开始的n个字符,然后在pos处插入串s
string &replace(int pos, int n, const string &s); //删除从pos开始的n个字符,然后在pos处插入串s
void swap(string &s2); //交换当前字符串与s2的值
11)string的区间删除和插入:
string &insert(int pos, const char *s);
string &insert(int pos, const string &s);
//前两个函数在pos位置插入字符串s
string &insert(int pos, int n, char c); //在pos位置 插入n个字符c
string &erase(int pos=0, int n=npos); //删除pos开始的n个字符,返回修改后的字符串
12) cctype (ctype.h)
isalnum(c):当c为字母或者数字时为真;
isalpha(c):当c为字母时为真;
当c为数字时为真;
当c为小写字母时为真;
当c为大写字母时为真;
当c为标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种);
当c为空白时为真(即c是空格、回车符、换行符等中的一种);
但是不改变c
但是不改变c
三、vector
1)vector对象的默认构造
vector<int> vecInt; //一个存放int的vector容器。
vector<float> vecFloat; //一个存放float的vector容器。
vector<string> vecString; //一个存放string的vector容器。
//尖括号内还可以设置指针类型或自定义类型。
Class CA{};
vector<CA*> vecpCA; //用于存放CA对象的指针的vector容器。
vector<CA> vecCA; //用于存放CA对象的vector容器。由于容器元素的存放是按值复制的方式进行的,所以此时CA必须提供CA的拷贝构造函数,以保证CA对象间拷贝正常。
2)vector对象的带参数构造
vector(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。
vector(n,elem); //构造函数将n个elem拷贝给本身。
vector(const vector &vec); //拷贝构造函数
int iArray[] = {0,1,2,3,4};
vector<int> vecIntA( iArray, iArray+5 );
vector<int> vecIntB ( vecIntA.begin() , vecIntA.end() ); //用构造函数初始化容器vecIntB
vector<int> vecIntB ( vecIntA.begin() , vecIntA.begin()+3 );
vector<int> vecIntC(3,9); //此代码运行后,容器vecIntB就存放3个元素,每个元素的值是9。
vector<int> vecIntD(vecIntA);
3)vector的赋值
vector.assign(beg,end); //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。
vector.assign(n,elem); //将n个elem拷贝赋值给本身。
vector& operator=(const vector &vec); //重载等号操作符
vector.swap(vec); // 将vec与本身的元素互换。
vector<int> vecIntA, vecIntB, vecIntC, vecIntD;
int iArray[] = {0,1,2,3,4};
vecIntA.assign(iArray,iArray+5);
vecIntB.assign( vecIntA.begin(), vecIntA.end() ); //用其它容器的迭代器作参数。
vecIntC.assign(3,9);
vector<int> vecIntD;
vecIntD = vecIntA;
vecIntA.swap(vecIntD);
4)vector的大小
vector.size(); //返回容器中元素的个数
vector.empty(); //判断容器是否为空
vector.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
vector.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
例如:vecInt是vector<int> 声明的容器,现已包含1,2,3元素。
int iSize = vecInt.size(); //iSize == 3;
bool bEmpty = vecInt.empty(); // bEmpty == false;
执行vecInt.resize(5); //此时里面包含1,2,3,0,0元素。
再执行vecInt.resize(8,3); //此时里面包含1,2,3,0,0,3,3,3元素。
再执行vecInt.resize(2); //此时里面包含1,2元素。
5)vector末尾的添加、移除操作
vector<int> vecInt;
vecInt.push_back(1); //在容器尾部加入一个元素
vecInt.pop_back();
6)vector的数据存取
vec.at(idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
vec[idx]; //返回索引idx所指的数据,越界时,运行直接报错
vector.front() = 11; //vecInt包含{11,3,8,7,9}
vector.back() = 19; //vecInt包含{11,3,8,7,19}
7)vector的插入
vector.insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
vector.insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。
vector.insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值
简单案例:
vector<int> vecA;
vector<int> vecB;
vecA.push_back(1);
vecA.push_back(3);
vecA.push_back(5);
vecA.push_back(7);
vecA.push_back(9);
vecB.push_back(2);
vecB.push_back(4);
vecB.push_back(6);
vecB.push_back(8);
vecA.insert(vecA.begin(), 11); //{11, 1, 3, 5, 7, 9}
vecA.insert(vecA.begin()+1,2,33); //{11,33,33,1,3,5,7,9}
vecA.insert(vecA.begin() , vecB.begin() , vecB.end() ); //{2,4,6,8,11,33,33,1,3,5,7,9}
8)vector的删除
vector.clear(); //移除容器的所有数据
vec.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。
vec.erase(pos); //删除pos位置的数据,返回下一个数据的位置。
简单案例:
删除区间内的元素
vecInt是用vector<int>声明的容器,现已包含按顺序的1,3,5,6,9元素。
vector<int>::iterator itBegin=vecInt.begin()+1;
vector<int>::iterator itEnd=vecInt.begin()+2;
vecInt.erase(itBegin,itEnd);
//此时容器vecInt包含按顺序的1,6,9三个元素。
假设 vecInt 包含1,3,2,3,3,3,4,3,5,3,删除容器中等于3的元素
for(vector<int>::iterator it=vecInt.being(); it!=vecInt.end(); ) //小括号里不需写 ++it
{
if(*it == 3)
{
it = vecInt.erase(it); //以迭代器为参数,删除元素3,并把数据删除后的下一个元素位置返回给迭代器。
//此时,不执行 ++it;erase操作已经让迭代器自动后移
}
else
{
++it;
}
}
//删除vecInt的所有元素
vecInt.clear(); //容器为空
四、deque队列
1)Deque简介
deque是“double-ended queue”的缩写,和vector一样都是STL的容器,deque是双端数组,而vector是单端的。deque在接口上和vector非常相似,在许多操作的地方可以直接替换。deque可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法)。deque头部和尾部添加或移除元素都非常快速。但是在中部安插元素或移除元素比较费时。#include <deque>
2)deque对象的默认构造
deque采用模板类实现,deque对象的默认构造形式:deque<T> deqT;
deque <int> deqInt; //一个存放int的deque容器。
deque <float> deqFloat; //一个存放float的deque容器。
deque <string> deqString; //一个存放string的deque容器。
//尖括号内还可以设置指针类型或自定义类型。
3)deque末尾的添加、移除操作
deque.push_back(elem);//在容器尾部添加一个数据
deque.push_front(elem); //在容器头部插入一个数据
deque.pop_back(); //删除容器最后一个数据
//删除容器第一个数据
4)deque的数据存取
deque.at(idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range。
deque[idx]; //返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。
deque.front(); //返回第一个数据。
deque.back(); //返回最后一个数据
5)deque与迭代器
deque.begin(); //返回容器中第一个元素的迭代器。
deque.end(); //返回容器中最后一个元素之后的迭代器。
deque.rbegin(); //返回容器中倒数第一个元素的迭代器。
deque.rend(); //返回容器中倒数最后一个元素之后的迭代器。
6)deque对象的带参数构造
deque(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。
deque(n,elem); //构造函数将n个elem拷贝给本身。
deque(const deque &deq); //拷贝构造函数。
7)deque的赋值
deque.assign(beg,end); //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。
deque.assign(n,elem); //将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
deque.swap(deq); // 将vec与本身的元素互换
deque的大小、deque的插入、deque的删除不再赘述
五、stack栈
1)stack简介
stack是堆栈容器,是一种“先进后出”的容器。stack是简单地装饰deque容器而成为另外的一种容器。#include <stack>
2)stack对象的默认构造
stack采用模板类实现, stack对象的默认构造形式: stack <T> stkT;
stack <int> stkInt; //一个存放int的stack容器。
stack <float> stkFloat; //一个存放float的stack容器。
stack <string> stkString; //一个存放string的stack容器。
//尖括号内还可以设置指针类型或自定义类型。
3)stack的push()与pop()方法
stack.push(elem); //往栈头添加元素
stack.pop(); //从栈头移除第一个元素
4)stack对象的拷贝构造与赋值
stack(const stack &stk); //拷贝构造函数
stack& operator=(const stack &stk); //重载等号操作符
5)stack的数据存取
stack.top(); //返回最后一个压入栈元素
6)stack的大小
stack.empty(); //判断堆栈是否为空
stack.size(); //返回堆栈的大小
六、Queue双端队列
1)Queue简介
先进先出”的容器。queue是简单地装饰deque容器而成为另外的一种容器。#include <queue>
2)queue对象的默认构造
queue采用模板类实现,queue对象的默认构造形式:queue<T> queT; 如:
queue<int> queInt; //一个存放int的queue容器。
queue<float> queFloat; //一个存放float的queue容器。
queue<string> queString; //一个存放string的queue容器。
//尖括号内还可以设置指针类型或自定义类型。
3)queue的push()与pop()方法
queue.push(elem); //往队尾添加元素
queue.pop(); //从队头移除第一个元素
4)queue对象的拷贝构造与赋值
queue(const queue &que); //拷贝构造函数
queue& operator=(const queue &que); //重载等号操作符
5)queue的数据存取
queue.back(); //返回最后一个元素
queue.front(); //返回第一个元素
6)queue的大小
queue.empty(); //判断队列是否为空
queue.size(); //返回队列的大小
七、List链表
1)List简介
list是一个双向链表容器,可高效地进行插入删除元素。list不可以随机存取元素,所以不支持at.(pos)函数与[]操作符。It++(ok)、it+5(err)。#include <list>
2)list对象的默认构造
list采用采用模板类实现,对象的默认构造形式:list<T> lstT; 如:
list<int> lstInt; //定义一个存放int的list容器。
list<float> lstFloat; //定义一个存放float的list容器。
list<string> lstString; //定义一个存放string的list容器。
//尖括号内还可以设置指针类型或自定义类型。
3)list头尾的添加、移除操作
list.push_back(elem); //在容器尾部加入一个元素
list.pop_back(); //删除容器中最后一个元素
list.push_front(elem); //在容器开头插入一个元素
list.pop_front(); //从容器开头移除第一个元素
4)list的数据存取
list.front(); //返回第一个元素。
list.back(); //返回最后一个元素。
5)list与迭代器
list.begin(); //返回容器中第一个元素的迭代器。
list.end(); //返回容器中最后一个元素之后的迭代器。
list.rbegin(); //返回容器中倒数第一个元素的迭代器。
list.rend(); //返回容器中倒数最后一个元素的后面的迭代器。
6)list对象的带参数构造
list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。
list(n,elem); //构造函数将n个elem拷贝给本身。
list(const list &lst); //拷贝构造函数。
7)list的赋值
list.assign(beg,end); //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。
list.assign(n,elem); //将n个elem拷贝赋值给本身。
list& operator=(const list &lst); //重载等号操作符
list.swap(lst); // 将lst的元素与list本身的元素互换。
8)list的插入
链表的结点index 序号是从0号位置开始,在3号位置插入元素, 让原来的3号位置变成4号位置 原来的4号位置变成5号位置
9)list的删除
list.clear(); //移除容器的所有数据
list.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。
list.erase(pos); //删除pos位置的数据,返回下一个数据的位置。
list.remove(elem); //删除容器中所有与elem值匹配的元素。
lstInt.clear(); //删除lstInt的所有元素 //容器为空
list.unique(); //删除数值相同的连续元素(注意:只有“连续而相同的元素”,才会被移除剩一个。“2,2,3,3,2,2”======》“2,3,2”)
假设 lstInt 包含1,3,2,3,3,3,4,3,5,3,删除容器中等于3的元素。
方法一:
for(list<int>::iterator it=lstInt.being(); it!=lstInt.end(); ) //小括号里不需写 ++it
{
if(*it == 3)
{
it = lstInt.erase(it); //以迭代器为参数,删除元素3,并把数据删除后的下一个元素位置返回给迭代器。
//此时,不执行 ++it;
}
else
{
++it;
}
}
方法二:
lstInt.remove(3);
10)list的反序排列
list.reverse(); //反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
八、priority_queue优先级队列
最大值优先级队列、最小值优先级队列,优先级队列适配器 STL priority_queue,用来开发一些特殊的应用。#include <queue>
pq;
priority_queue<int, vector<int>> pq;
pq.empty()
pq.size()
pq.top()
pq.pop()
pq.push(item)
#include <iostream>
#include <queue>
#include <functional>
using namespace std;
void main()
{
priority_queue<int> p1; //默认情况下 是 最大值优先级队列 ,等同于下面这行
priority_queue<int, vector<int>, less<int>> p2; //最大值优先队列,默认是用vector适配器来适配的,提前定义好的预定义函数 谓词
priority_queue<int, vector<int>, greater<int>> p3; //最小值优先级队列
cout << "测试 最大值优先级队列 " << endl;
p1.push(33);
p1.push(11);
p1.push(55);
p1.push(22);
cout << " 最大值优先级队列 队头元素:" << p1.top() << endl;
cout << " 最大值优先级队列 队列的大小:" << p1.size() << endl;
while (p1.size() > 0)
{
cout << p1.top() << " ";
p1.pop();
}
cout << endl;
cout << "测试 最小值优先级队列 " << endl;
p3.push(33);
p3.push(11);
p3.push(55);
p3.push(22);
cout << " 最小值优先级队列 队头元素:" << p3.top() << endl;
cout << " 最小值优先级队列 队列的大小:" << p3.size() << endl;
while (p3.size() > 0)
{
cout << p3.top() << " ";
p3.pop();
}
cout << endl;
}
——优先级队列的实例应用
九、set和multiset集合
集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。set不可以直接存取元素(不可以使用at.(pos)与[]操作符)。multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次。不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素。#include <set>
1)set/multiset对象的默认构造
set<int> setInt; //一个存放int的set容器。
set<float> setFloat; //一个存放float的set容器。
set<string> setString; //一个存放string的set容器。
multiset<int> mulsetInt; //一个存放int的multi set容器。
multiset<float> multisetFloat; //一个存放float的multi set容器。
multiset<string> multisetString; //一个存放string的multi set容器。
2)set的插入与迭代器
set.insert(elem); //在容器中插入元素。 ————————————默认从小到大!
set.begin(); //返回容器中第一个数据的迭代器。
set.end(); //返回容器中最后一个数据之后的迭代器。
set.rbegin(); //返回容器中倒数第一个元素的迭代器。
set.rend(); //返回容器中倒数最后一个元素的后面的迭代器。
3)Set集合的元素排序
set<int,less<int> > setIntA; //该容器是按升序方式排列元素。(默认) set<int> 相当于 set<int,less<int>>。
set<int,greater<int>> setIntB; //该容器是按降序方式排列元素。
less<int>与greater<int>中的int可以改成其它类型,该类型主要要跟set容纳的数据类型一致。
less<>与greater<>是什么?如果set<>不包含int类型,而是包含自定义类型,set容器如何排序?要解决如上两个问题,需要了解容器的函数对象,也叫伪函数,英文名叫functor。
4)仿函数
比如使用stl提供的函数对象——greater,set<int,greater<int>> setIntB;
函数对象functor的用法:
尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。
functor,翻译成函数对象,伪函数,算符,是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。
greater<>与less<>就是内置的函数对象。
题目:学生包含学号,姓名属性,现要求任意插入几个学生对象到set容器中,使得容器中的学生按学号的升序排序。
#define _CRT_SECURE_NO_WARNINGS
#include<string>
#include<iostream>
#include<set>
#include"functor.h"
using namespace std;
class student
{
public:
student(char *name,int age);
~student();
char m_name[64];
int age;
};
student::student(char *name, int age)
{
strcpy(m_name, name);
this->age = age;
}
//仿函数,提供一个函数入口地址
struct FuncStudent
{
bool operator()(const student &left, const student &right)
{
if (left.age < right.age) //从小到大按照年龄进行排序
{
return true;
}
else
return false;
}
};
void main()
{
student stu1("zhao", 33);
student stu2("qian", 35);
student stu3("sun", 26);
student stu4("li", 37);
student stu5("zhou", 32);
set<student,FuncStudent> set1; //调用仿函数
set1.insert(stu1);
set1.insert(stu2);
set1.insert(stu3);
set1.insert(stu4);
set1.insert(stu5);
for (set<student, FuncStudent>::iterator it = set1.begin(); it != set1.end(); it++)
{
cout << (*it).age << "\t" << it->m_name << endl;
}
cout << endl;
}
5)set对象的拷贝构造与赋值
set(const set &st); //拷贝构造函数
set& operator=(const set &st); //重载等号操作符
set.swap(st); //交换两个集合容器
6)set的大小
set.size(); //返回容器中元素的数目
set.empty();//判断容器是否为空
7)set的删除
set.clear(); //清除所有元素
set.erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
set.erase(beg,end); //删除区间[beg,end)的所有元素,返回下一个元素的迭代器。
set.erase(elem); //删除容器中值为elem的元素。
8)set的查找
set.find(elem); //查找elem元素,返回指向elem元素的迭代器位置。
set.count(elem); //返回容器中值为elem的元素个数。对set来说,要么是0,要么是1。对multiset来说,值可能大于1。
set.lower_bound(elem); //返回第一个>=elem元素的迭代器。
set.upper_bound(elem); //返回第一个>elem元素的迭代器。
set.equal_range(elem); //返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。 该函数返回两个迭代器,而这两个迭代器被封装在pair中。
pair的含义:
//find查找 equal_range
//返回结果是一个pair
void main()
{
set<int> set1;
for (int i = 0; i<10; i++)
{
set1.insert(i + 1);
}
//从大到 小
for (set<int>::iterator it = set1.begin(); it != set1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
set<int>::iterator it0 = set1.find(5);
cout << "it0:" << *it0 << endl; //5
int num1 = set1.count(5);
cout << "num1:" << num1 << endl; //1
set<int>::iterator it1 = set1.lower_bound(5); // 大于等于(>=)5的元素 的 迭代器的位置
cout << "it1:" << *it1 << endl; //5
set<int>::iterator it2 = set1.upper_bound(5); // 大于(>)5的元素 的 迭代器的位置
cout << "it2:" << *it2 << endl; //6
//equal_range:返回容器中与elem相等(set中元素是唯一的)的上下限的两个迭代器
//typedef pair<iterator, iterator> _Pairii;
pair<set<int>::iterator, set<int>::iterator> mypair = set1.equal_range(5);
set<int>::iterator it3 = mypair.first;
cout << "it3:" << *it3 << endl; //5
set<int>::iterator it4 = mypair.second;
cout << "it4:" << *it4 << endl; //6
//set1.erase(5);——把5元素删除掉
//*it3 = 6 //*it4 = 6
}
怎样判断set1.insert()是否插入成功?通过以下的跟踪,知道,可以使用pair数据类型来判断
_Pairib insert(const value_type& _Val)
{ // insert a key value
return (_Mybase::insert(_Val));
}
typedef pair<iterator, bool> _Pairib
pair的使用:
pair译为对组,可以将两个值视为一个单元。
pair<T1,T2>存放的两个值的类型,可以不一样,如T1为int,T2为float。T1,T2也可以是自定义类型。
pair.first是pair里面的第一个值,是T1类型。pair.second是pair里面的第二个值,是T2类型。
//如何判断 set1.insert函数的返回值
//typedef pair<iterator, bool> _Pairib;
//Pair的用法
void main()
{
Student s1("s1", 31);
Student s2("s2", 22);
Student s5("s5", 31);
set<Student, FuncStudent> set1;
pair<set<Student, FuncStudent>::iterator, bool> pair1 = set1.insert(s1);
if (pair1.second == true)
{
cout << "插入s1(31)成功" << endl; //s1插入成功
}
else
{
cout << "插入s1(31)失败" << endl;
}
set1.insert(s2);
//如何知道 插入 的结果
pair<set<Student, FuncStudent>::iterator, bool> pair5 = set1.insert(s5); //如果两个31岁 能插入成功
if (pair5.second == true)
{
cout << "插入s5(31)成功" << endl;
}
else
{
cout << "插入s5(31)失败" << endl; //s1插入失败,因为在set1中的FuncStudent中是按年龄排序的
}
//遍历
for (set<Student, FuncStudent>::iterator it=set1.begin(); it!=set1.end(); it++ )
{
cout << it->age << "\t" << it->name << endl;
}
//只会打印出 s2,s1
}
9)multiset
void main()
{
multiset<int> mset1;
int tmp = 0;
cout << "enter the value of multiset:" << endl;
while (cin >> tmp)
{
mset1.insert(tmp);
}
cout << "multiset中元素如下:" << endl;
for (multiset<int>::iterator it = mset1.begin(); it != mset1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
cout << "开始删除multiset中元素:" << endl;
while (!mset1.empty())
{
multiset<int>::iterator it = mset1.begin();
cout << *it << " ";
mset1.erase(it); //尝试一下mset1.erase(*it);
//set.erase(pos);——删除pos迭代器所指的元素,返回下一个元素的迭代器。
//set.erase(elem);——删除容器中值为elem的元素。
}
cout << endl;
}
小结:
一、容器set/multiset的使用方法;
红黑树的变体,查找效率高,插入不能指定位置,插入时自动排序。
二、functor的使用方法;
类似于函数的功能,可用来自定义一些规则,如元素比较规则。
三、pair的使用方法。
对组,一个整体的单元,存放两个类型(T1,T2,T1可与T2一样)的两个元素。
十、map和multimap键值对序列
map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对。它提供基于key的快速检索能力。map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。map的具体实现采用红黑树变体的平衡二叉树的数据结构。在插入操作和删除操作上比vector快。map可以直接存取key所对应的value,支持[]操作符,如map[key]=value。multimap与map的区别:map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符。#include <map>
map容器的底层机制以红黑树完成,也就是说map内部自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。
1)map/multimap对象的默认构造
map<T1,T2> mapTT;
multimap<T1,T2> multimapTT;
//其中T1,T2还可以用各种指针类型或自定义类型
2)map的插入与迭代器
map.insert(...); //往容器插入元素,返回pair<iterator,bool>,根据bool可以判断是否插入成功
mypair5 = map1.insert(map<int, string>::value_type(5, "teacher05") );
if (mypair5.second != true)
{
cout << "key 5 插入失败" << endl;
}
else
{
cout << mypair5.first->first << "\t" << mypair5.first->second <<endl; //注意mypair.first
}
在map中插入元素的三种方式:(假设 map<int, string> mapStu;)
一、通过pair的方式插入对象
mapStu.insert(pair<int,string>(3,"小张") );
二、通过make_pair的方式插入对象
mapStu.inset(make_pair(-1, “校长-1”));
三、通过value_type的方式插入对象
mapStu.insert(map<int,string>::value_type(1,"小李") );
四、通过数组的方式插入值,不存在则插入,存在则修改(覆盖)
mapStu[3] = “小刘"; //修改value
mapStu[5] = “小王";
前三种方法,采用的是insert()方法,该方法返回值为pair<iterator,bool>
第四种方法非常直观,但存在一个性能的问题。插入3时,先在mapStu中查找主键为3的项,若没发现,则将一个键为3,值为初始化值的对组插入到mapStu中,然后再将值修改成“小刘”。若发现已存在3这个键,则修改这个键对应的value。
string strName = mapStu[2]; //取操作或插入操作
只有当mapStu存在2这个键时才是正确的取操作,否则会自动插入一个实例,键为2,值为初始化值。
迭代器:
for (map<int, string>::iterator it = map1.begin(); it != map1.end(); ++it)
{
pair<int, string> pr = *it;
int iKey = pr.first;
string strValue = pr.second;
cout << iKey << " " << strValue << endl;
}
map<T1,T2,less<T1> > mapA; //该容器是按键的升序方式排列元素。未指定函数对象,默认采用less<T1>函数对象。
map<T1,T2,greater<T1>> mapB; //该容器是按键的降序方式排列元素。
less<T1>与greater<T1> 可以替换成其它的函数对象functor。可编写自定义函数对象以进行自定义类型的比较,使用方法与set构造时所用的函数对象一样。
map.begin(); //返回容器中第一个数据的迭代器。
map.end(); //返回容器中最后一个数据之后的迭代器。
map.rbegin(); //返回容器中倒数第一个元素的迭代器。
map.rend(); //返回容器中倒数最后一个元素的后面的迭代器。
3)map对象的拷贝构造与赋值
map(const map &mp); //拷贝构造函数
map& operator=(const map &mp); //重载等号操作符
map.swap(mp); //交换两个集合容器
例如:
map<int, string> mapA;
mapA.insert(pair<int,string>(3,"小张"));
map<int ,string> mapB(mapA);//拷贝构造
map<int, string> mapC;
mapC = mapA;//赋值
mapC[3] = "老张";
mapC.swap(mapA);//交换
4)map的大小
map.size(); //返回容器中元素的数目
map.empty();//判断容器是否为空
5)map的删除
map.clear(); //删除所有元素
map.erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
map.erase(beg,end); //删除区间[beg,end)的所有元素,返回下一个元素的迭代器。
map.erase(keyElem); //删除容器中key为keyElem的对组。
6)map的查找
map.find(key); //查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
map.count(keyElem); //返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。
map.lower_bound(keyElem); //返回第一个key>=keyElem元素的迭代器。
map.upper_bound(keyElem); //返回第一个key>keyElem元素的迭代器。
例如:mapStu是用map<int,string>声明的容器,已包含{1,"小李"}{3,"小张"}{5,"小王"}{7,"小赵"}{9,"小陈"}元素。
map<int,string>::iterator it;
it = mapStu.lower_bound(5); //it->first==5 it->second=="小王"
it = mapStu.upper_bound(5); //it->first==7 it->second=="小赵"
map.equal_range(keyElem); //返回容器中key与keyElem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。该函数返回两个迭代器,而这两个迭代器被封装在pair中。
例如:map<int,string> map1;
.....//往map1容器插入元素{1,"小李"}{3,"小张"}{5,"小王"}{7,"小赵"}{9,"小陈"}
pair<map<int, string>::iterator, map<int, string>::iterator> mypair = map1.equal_range(5);
cout << mypair.first->first << " " << mypair.first->second << endl;
cout << mypair.second->first << " " << mypair.second->second << endl;
7)Multimap 案例:
1个key值可以对应多个valude =分组 ;公司有销售部 sale (员工2名)、技术研发部 development (1人)、财务部 Financial (2人);人员信息有:姓名,年龄,电话、工资等组成;通过 multimap进行 信息的插入、保存、显示;分部门显示员工信息。
#include<iostream>
#include<string>
#include<map>
using namespace std;
//1个key值可以对应多个valude——=分组
//公司有销售部 sale (员工2名)、技术研发部 development (1人)、财务部 Financial (2人)
//人员信息有:姓名,年龄,电话、工资等组成
//通过 multimap进行 信息的插入、保存、显示
//分部门显示员工信息
class Person
{
public:
string name;
int age;
string tel;
double saly;
};
void main01()
{
Person p1, p2, p3, p4, p5;
p1.name = "wang1";
p1.age = 30;
p2.name = "wang2";
p2.age = 34;
p3.name = "zhang3";
p3.age = 35;
p4.name = "li4";
p4.age = 36;
p5.name = "zhou5";
p5.age = 33;
multimap<string, Person> map2;
//sale部门
map2.insert(make_pair("sale", p1));
map2.insert(make_pair("sale", p2));
//development部门
map2.insert(make_pair("development", p3));
//Financial部门
map2.insert(make_pair("Financial", p4));
map2.insert(make_pair("Financial", p5));
for (multimap<string, Person>::iterator it = map2.begin(); it != map2.end(); it++)
{
cout << it->first << "\t" << it->second.name << endl;
}
cout << "遍历结束!" << endl << endl;
int num = map2.count("development");
cout << "development 部门人数:" << map2.count("development") << endl;
multimap<string, Person>::iterator it2 = map2.find("development"); //查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
cout << "development 部门员工信息:" << endl;
int count = 0;
while (it2 != map2.end() && count < num) //如果不加count控制,it2会一直循环到end,从而打印多余信息
{
cout << it2->first << "\t" << it2->second.name << endl;
it2++;
count++; //控制只打印development 部门的员工信息
}
cout << endl;
}
void main()
{
Person p1, p2, p3, p4, p5;
p1.name = "wang1";
p1.age = 30;
p2.name = "wang2";
p2.age = 34;
p3.name = "zhang3";
p3.age = 35;
p4.name = "li4";
p4.age = 36;
p5.name = "zhou5";
p5.age = 33;
multimap<string, Person> map2;
//sale部门
map2.insert(make_pair("sale", p1));
map2.insert(make_pair("sale", p2));
//development部门
map2.insert(make_pair("development", p3));
//Financial部门
map2.insert(make_pair("Financial", p4));
map2.insert(make_pair("Financial", p5));
cout << "按照条件 检索数据 进行修改:" << endl;
for (multimap<string, Person>::iterator it = map2.begin(); it != map2.end(); it++)
{
if (it->second.age == 33)
{
it->second.name = "name3333";
}
}
for (multimap<string, Person>::iterator it = map2.begin(); it != map2.end(); it++)
{
cout << it->first << "\t" << it->second.name << endl;
}
cout << "遍历结束!" << endl << endl;
}
十一、无序容器unordered_set、unordered_map
以下这四个容器不使用“比较运算符”来自动组织元素,而是使用一个“哈希函数”和“关键字类型的==运算符”。也就是遍历unordered_,得到的序列式无序的。
(1)unordered_set; //无序,无重复(set:有序,无重复)
(2)unordered_multiset; //无序,可重复
无序,无重复(map:有序,无重复)
无序,可重复
Unordered Map
键值快速检索各个元素。
在unordered_map中,键值一般用来唯一标识元素,而对应的值是一个对象关联到这个键的内容。键映射值的类型可能会有所不同。
unordered_map的元素不以键值或映射的元素作任何特定的顺序排序,其存储位置取决于哈希值允许直接通过其键值为快速访问单个元素(具有恒定平均的平均时间复杂度)。
更快地通过键值访问他们的单个元素,虽然unordered_map一般都是比map通过其元素的一个子集范围迭代效率低。
哈希map允许使用操作运算符(运算符[])以其键值作为参数直接访问元素。
map是按照operator<比较判断元素是否相同,以及比较元素的大小,然后选择合适的位置插入到树中。所以,如果对map进行遍历(中序遍历)的话,输出的结果是有序的。顺序就是按照operator< 定义的大小排序。
而boost::unordered_map是计算元素的Hash值,根据Hash值判断元素是否相同。所以,对unordered_map进行遍历,结果是无序的。
容器属性
关联
在关联容器的元素通过键值引用,而不是由他们在容器中的绝对位置。
无序
通过哈希表组织其元素的使用,允许通过键值快速访问其对应元素。
映射
键值用于识别元素,其主要内容是键对应的值。
唯一键
键。
分配器的唤醒
容器使用一个分配器对象动态地处理其存储需求。
模板参数
Key
关键值的类型。一个unordered_map中的每个元素通过键值被唯一标识。
T
映射值的类型。 一个unordered_map中的每个元素是用来存储一些数据作为其映射值。别名为成员类型unordered_map:: mapped_type。请注意,这是和unordered_map:: value_type不同的(见下文)。
十二、STL各大容器比较总结
deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
vector与deque的比较:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。
list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。