容器与算法
顺序容器
标准库定义了三种容器(差别是访问元素方式),和三种容器适配器(操作接口不同)。
顺序容器 | 特点 |
---|---|
vector | 快速随机访问 |
list | 快速插入删除 |
deque | 双端队列 |
顺序容器适配器 | 特点 |
---|---|
stack | LIFO栈 |
queue | FIFO队列 |
priority_queue | 优先级队列 |
定义
头文件
#include <vector>
#include <list>
#include <deque>
构造函数
C<T> c; //创建名字为c点空容器,T是元素类型。是用所有容器
C c(c2); //创建c2的副本,适用所有容器
C c(b,e); //b和e是迭代器,创建b到e之间的副本,适用所有容器
C c(n,t); //创建n个值为t点容器c,只适用顺序容器
C c(n); //创建有n个值初始化元素点容器c,只适用顺序容器
//例如:
list<int> a={1,2,3};
list<int> b(a);
deque<int> c(b.begin(),b.end());
list<int>::iterator mid=b.begin()+b.size()/2;
list<int> d(mid,b.end());
//指针也是迭代器,可以通过内置数组的指针初始化容器
char *words[]={"ab","cd","ef"};
vector<string> words1(words,words+sizeof(words)/sizeof(char*);
vector<string> slist(100,"eh");
迭代器
迭代器为所有的标准库容器提供的操作
C<T> iter;
*iter;//返回iter指向的引用
*iter->mem;//等效与(*iter).mem;
++iter; iter++; //自加和自减
iter1 == iter2;
iter1 != iter2;是否相等
vector和deque定义额外的操作
iter + n;
iter - n;
iter+=n;
iter-=n;//加减n表示前面或者后面的底n个位置
iter1<iter2;//> < >= <=比较大小
关系操作符只适用于vector和deque。因为这两种提供了快速的随机访问。也正是因此才能有效的实现算术和关系运算。(list不提供关系运算符和算术运算,但是提供自增和自减。)
两个迭代器可以组成一个迭代区间,这个区间是左闭合区间。例如c.begin() , c.end()
表示的是[c.begin() , c.end())
被erase掉的迭代器都会失效,但是无法通过测试发现迭代器是否失效,而且使用这些迭代器会导致程序不可控行为。我们需要避免这种情况。
顺序容器操作
- 添加元素
- 删除元素
- 设置容器大小
容器类型别名
别名 | 解释 |
---|---|
size_type | 无符号整型,足以储存容器类型最大可能长度 |
iterator | 容器迭代器类型 |
const_iterator | 只读迭代器类型 |
reverse_iterator | 逆序寻址元素点迭代器 |
const_reverse_iterator | 只读逆序寻址迭代器 |
difference_type | 足以储存两个迭代器差值的有符号整型 |
value_type | 元素类型 |
reference | 元素左值类型,也就是value_type& |
const_reference | 元素常量左值类型,等消const value_type& |
begin和end
函数 | 说明 |
---|---|
c.begin() | 返回指向c第一个元素的迭代器 |
c.end() | 返回指向c最后一个元素下一个位置的迭代器 |
c.rbegin() | 返回指向最右一个元素的逆序迭代器 |
c.rend() | 返回指向c第一个元素前一个位置的逆序迭代器 |
添加元素
函数 | 说明 |
---|---|
c.push_back(t) | c尾部添加元素t,返回void |
c.push_front(t) | c前端添加元素t,返回void(vector没有该函数) |
c.insert(p,t) | 迭代器p所指向元素前面插入元素t,返回指向新添加元素的迭代器 |
c.insert(p,n,t) | 迭代器p指向元素前面插入n个值为t的元素,返回void |
c.insert(p,b,e) | 迭代器p所指向元素前面插入由迭代器b,e标记范围内所有元素。返回void |
记住,任何insert和push操作都可能导致迭代器失效。编写循环将元素插入vector或者deque当中时,程序必须保证迭代器每次循环后都得到更新。
容器关系操作符
所以热熔器类型都支持关系操作符来实现容器比较。比较容器必须有相同的容器类型和元素类型。而且容器的元素必须具有相应的关系操作符。下面以string为例:
- 如果容器长度相同切所有元素相同,他们就相等,否则不相等。
- 如果长度不同,较短容器所有元素等于较长容器对应位置元素,则较短的<较长的
- 如果两个相互都不是对方子序列,则比较结果取决于第一个不相同的元素
容器大小的操作
函数 | 操作 |
---|---|
c.size() | 返回c中元素个数,类型是c::size_type |
c.max_size() | 返回c可容纳最多元素个数,c::size_type |
c.empty() | 容器大小是否为0 |
c.resize(n) | 调整容器大小,让它容纳n个元素。如果超出就删除,不够就用初始值补充 |
c.resize(n,t) | 同上,新元素用t补充 |
访问元素
函数 | 操作 |
---|---|
c.back() | 返回最后一个元素的引用(如果c为空回导致致命错误 |
c.front() | 返回第一个元素引用(如果c为空同上) |
c[n];c.at(n); | 返回下标为n对应元素的引用。只适用vector和deque |
删除元素
函数 | 操作 |
---|---|
c.erase(p) | 删除迭代器p指向元素,返回删除元素后的下一个元素的迭代器 |
c.erase(b,e) | 删除b和e迭代器范围内所有元素,返回e后面一个元素的迭代器。如果e==c.end(),则返回删除后容器的c.end() |
c.pop_back() | 删除最后一个元素,返回void |
c.pop_front() | 删除第一个元素,返回void(只适用list和deque) |
赋值与swap
函数 | 操作 |
---|---|
c1 = c2 | 等效c1.erase(c1.begin(),c1.end());c1.insert(c1.begin(),c2.begin(),c2.end()); |
c1.swap(c2) | 交换c1,c2内容。两个容器类型必须相同。这个操作比赋值快 |
c.assign(b,e) | 清空c,然后把迭代器b,e范围内元素复制奥c中。b,e必须不是c中的迭代器 |
c.assign(n,t) | 清空c,设置为储存n个值为t的元素 |
容器特有算法
list容器迭代器是双向的。而不是随机访问。因此list不支持很多随机访问的算法(例如sort函数)。但是有些算法利用list自身特点实现在list当中,可以高效执行:
//函数具体用途不细说
lst.merge(lst2);
lst.remove(val)
lst.reverse();
lst.sort();
lst.splice(iter,lst2);
lst.unique()
vector自增长
为了实现快速的随机访问,vector容器的元素以连续方式存放,每一个元素都紧挨着前一个元素。如果添加一个元素,但是原地址已经被占用,就需要重新分配空间,然后把原来的元素复制过去,插入新元素,释放旧空间。为了使vector容器空间快速高效增长,标准库使用不同的分配策略(一般是2^n速度增加)。所以在实际应用当中,vector总是比list更加高效。
capacity和reserve成员:
capacity()返回容器需要分配更多储存空间之前可以储存的元素个数,reserve(n)可以告诉vector容器应该预留多少个元素的储存空间。在容器使用之初通过reserve(n)改变容器点内存分配策略,可以优化程序的运行。
函数 | 功能 |
---|---|
c.shrink_to_fit() | 将capacity()减小到和size()相同大小 |
c.capacity() | 不重新分配内存空间时候,c可以保存多少元素 |
c.reserve(n) | 分配至少容纳n个元素的内存空间 |
其中capacity和reserve只适用vector和string。shrink_to_fit只是用vector,string,deque
容器的选用
vector和deque容器提供了对元素的快速随机访问,但是在任意位置插入或者删除元素比在容器尾部插入和删除元素开销更大。list提供快速的任意位置的插入删除,但是付出的代价是对元素随机访问开销较大。
-
插入如何影响容器选择
list容器内部实现是双向循环链表,可以在任何位置高效的insert和erase。对于vector,除了容器尾部,其他位置insert或者erase都会消耗很多时间。对于deque,双端队列的数据被表示为一个分段数组,容器中的元素分段存放在一个个大小固定的数组中,此外容器还需要维护一个存放这些数组首地址的索引数组。所以deque可以很快的在首尾插入元素,而且push_back元素时候及时扩充空间也不需要移动元素,但是对于元素的访问慢于vector,deque中间插入元素效率更加低下。
-
元素的访问
vector和deque都支持对元素高效快速随机访问(vector效率更高)。对于list访问任意位置需要便利所有元素。
-
容器选择提示
- 如果需要随机访问元素,使用vector或者deque
- 如果需要在中间插入元素,使用list
- 如果程序不是在中间插入元素,而是首部或者尾部插入元素,应该是deque
- 如果只需要在读取输入时在容器中间位置插入元素,然后就需要随机访问元素,则可以考虑在输出时将元素读入到一个list当中,然后对容器重新排序,让它适合顺序访问,然后把排序后list复制到一个vector当中。
容器适配器
标准库提供三种容器适配器queue,priority,stack。适配器本质是让一个事物类似于另一个事物。
适配器通用操作和类型 | 说明 |
---|---|
size_type | 足以储存适配器最大对象长度的类型 |
value_type | 元素类型 |
A a; | 创建一个新的空适配器 |
A a©; | 创建给一个容器a初始化为c副本 |
关系操作符 | ==,!=,<,<=,>,>== |
对应头文件:
#include<stack> //stack adaptor
#include<queue> //queue and priority adaptor
覆盖基础容器类
默认stack和queue都是通过deque实现,priority是vector实现。创建适配器时候可以通过指定第二个参数实现覆盖默认关联容器类型。
stack<string,vector<string> > str_stk;
stack可以建立在vector,list,deque之上,queue只能建立在list和deque之上(因为要求有push_front)。priority_queue只能建立在vector或者deque之上(可以随机访问)。
栈适配器
操作 | 说明 |
---|---|
s.empty() | 如果为空,返回true,否则false |
s.size() | 返回元素个数 |
s.pop() | 删除栈顶,但是不返回值 |
s.top() | 返回栈顶,但是不删除 |
s.push(item) | 栈顶压入新元素 |
队列和优先级队列
其中优先队列的实现是通过大顶堆里面。
队列和优先级队列操作 | 说明 |
---|---|
q.empty() | 同上 |
q.size() | 同上 |
q.pop() | 删除队首,不返回 |
q.front() | 返回队首值(只适用队列) |
q.back() | 返回队尾,不删除(只适用队列) |
q.top() | 返回最高优先级元素值(只适用优先级队列) |
q.push(item) | 对于queue,队尾压入元素。对于priority_queue,基于优先级适当位置压入 |
关联容器
关联容器支持通过键来高效的查找和读取元素。
关联容器类型 | 说明 |
---|---|
map | 关联数组,通过键来储存和读取 |
set | 大小可变集合,通过键实现快速读取 |
multimap | 支持一个键出现多次的map |
multiset | 支持一个键出现多次的set |
pair
#include<utility>
操作 | 说明 |
---|---|
pair<T1,T2> p1(v1,v2); | 创建一个pair,first初始化为v1,second初始化为v2 |
make_pair(v1,v2) | 以v1,v2为值创建一个pair |
p1<p2 | 如果p1.first<p2.first || (!(p2.first<p1.first) && p1.second < p2.second) 返回true |
p1 == p2 | first和second都相同 |
p.first | p的first成员 |
p.second | p的second成员 |
关联容器和顺序容器共享的操作
- 三种构造函数
C<T> c;//empty C<T> c1(c2); //c2 is same type as c1 C<T> c(b,e); //iterator b to e
- 关系运算
- begin,end,rbegin,rend
- 类型别名,但是value_type是pair类型
- swap和赋值,但是没有assign
- clear和erase,erase返回void
- 除了resize以外的容器大小操作
map类型
map<k,v> m;//包含上述三种构造函数
- 键类型约束
使用关联容器时,键需要有一个“小于”比较函数。也就是键类型必须具备 < 操作符
map类型
类型 | 说明 |
---|---|
map<K,V>::key_type | 键类型 |
map<K,V>::mapped_type | 值类型 |
map<K,V>::value_type | pair类型,其中first是const map<K,V>::key_type |
使用下标访问:
map<string,int> wc;
wc["Anna"] = 1;
这个操作将回导致:
- wc中查找键为Anna的元素,没有找到
- 将一个新的键值对插入wc,键为const string类型(“Anna”),值是一个初始化默认值(int中默认为0)
- 读取新插入元素,将它赋值为1
map容器的insert操作:
insert | 操作 |
---|---|
m.insert(e) | e是一个value_type,如果e.first存在,则保持m不变,否则插入e.second。这个函数返回值是一个pair,first是指向e.first键的迭代器,second是bool表示是否插入了元素 |
m.insert(beg,end) | beg和end标记元素范围,元素必须是m.value_type。对于这个范围所有元素,如果e.first不存在,就插入这个pair。返回void |
m.emplace(first,second) | 只有元素关键字不在c中是插入元素(first和second)构成。返回一个pair,包含一个迭代器指向具有指定关键字的元素,和一个只是是否成功的bool。对于multi类型,总会插入。 |
map当中查找元素
- m.count(k) 返回m中k出现个数
- m.find(k) 如果存在k引索元素,返回指向该元素迭代器,否则返回m.end()
map当中删除元素
- m.erase(k) 删除键为k的元素,返回size_type表示删除元素个数
- m.erase§ 从m中删除迭代器p指向元素。p必须是不为m.end()的存在的元素。返回void
- m.erase(b,e) 删除一段元素,b指向元素必须是e之前,返回void
set类型
set支持大部分map操作,包括:
- 所有之前列出的共享操作
- 所有map的insert操作
- count和find
- erase
set不支持下标引索,没有定义mapped_type,value_type和key_type相同。set容器键是唯一的,不可修改
multimap和multiset
multimap和mulitset操作基本和map和set操作相同,只不过multimap不支持下标运算。
带有一个键参数的erase函数会删除所有的对应键-值。
在multi中查找元素的方式:
-
使用find和count操作:
string search_item("a"); typedef multimap<string,string>::size_type sz_type; sz_type entries=m.count(search_item); multimap<string,string>::iterator iter=m.find(search_item); for(sz_type cnt=0;cnt!=entries;++cnt,++iter) cout<<iter->second<<endl;
-
面向迭代器的解决方案
函数 说明 m.lower_bound(k) 返回迭代器指向键不小于k的第一个元素 m.upper_bound(k) 返回键大于k的第一个元素 m.equal_range(k) 返回一个pair对象,first等于m.lower_bound(k)。second等于m.upper_bound(k) 如果该键不在容器当中,lower_bound返回保持容器元素顺序前提下该键被插入的第一个位置。(upper_bound也等于lower_bound)
C++11无序关联容器
无序关联容器(unordered_set,unorder_multiset,unordered_map和unordered_multimap)使用键和哈希表快速存取数据。
c++11库提供了hash<key>
无序关联容器默认使用这个模板进行hash运算
关联容器上述介绍的函数基本适用于无序关联容器,但是有这些例外:不需要方法lower_bound()和upper_bound(),因为关联容器通常都是经过排序的,所以他们有小于的概念,无序关联容器只有等于的概念。
类型
类型 | 说明 |
---|---|
X::key_type | 键类型 |
X::key_equal | 函数类,检查两个类型Key是否相同 |
X::hasher | hash函数类型 |
X::local_iterator | 一个类型和X::iterator相同的迭代器,但是只能用于一个桶 |
X::const_local_iterator | const同上 |
X::mapped_type | 仅限于map和multimap |
操作
类型 | 说明 |
---|---|
X a(n,hf,eq) | 创建一个名为a的空容器,至少包含n个桶,并将hf作为哈希函数,将eq用作键值相等的谓词。如果省略了eq,将key_equal()作为键值相等的谓词。如果省略了hf,将hasher()作为哈希函数。 |
X a(i,j,n,hf,eq) | 创建一个名为a的空容器,n,hf,eq含义同上,插入迭代器i,j之间的元素 |
b.hash_function() | 返回b使用的哈希函数 |
b.key_eq() | 返回创建b时使用的键值相等谓词 |
b.bucket_count() | 返回b包含的桶数量 |
b.max_bucket_count() | 返回b最多可以包含多少个桶 |
b.bucket(k) | 返回键值为k的元素所属桶引索 |
b.bucket_size(n) | 返回引索为n的桶包含的元素数。 |
b.begin(n) | 指向引索为n的桶的第一个元素 |
b.end(n) | 同上,最后一个元素 |
b.load_factor() | 返回每个桶包含的平均元素个数 |
b.max_load_factor() | 负载系数最大可能值,超过这个值将增加桶 |
b.max_load_factor(z) | 可能修改最大负载系数,设为z |
b.rehash(n) | 将桶数调整为不小于n,确保a.bucket_count()>a.size()/a.max_load_factor() |
a.reserve(n) | 等效a.rehash(ceil(n/a.max_load_factor())) |
string
#include<string>
定义和初始化
string s1;
string s1(s2);
string s2=s1;
string s3("value"); //s3是"value"的副本,处理最后那个空字符
string s3="value";
string s4(n,'c'); //n个字符c
string s(cp,n); //s是cp指向数组前n个字符的拷贝
string s(s2,pos2); s是string s2从pos2开始字符拷贝
string s(s2,pos2,pos3);
操作
os<<s; //s写入os流当中,返回os
is>>s; //is中读取字符串给s。字符串以空白分割。返回is
getline(is,s); //从is中读取一行赋值给s返回is
s.empty();
s.size(); //返回string::size_type类型
s[n];
s1+s2;
s1=s2;
s1==s2;
s1!=s2;//大小写敏感
//字符串可以根据字典顺序比较
s.substr(n,m); //从序号n开始之后的m个字符
s.substr(n); //序号n之后的字符
s.erase(pos,len);删除从位置pos开始的len个字符。如果len被省略,删除从pos到s结尾所有字符。返回s引用
//说明:str表示字符串,cp表示字符数组,c表示字符,b e表示迭代器范围。init_list表示用花括号包围的逗号分割的字符列表
//在pos前插入args指定字符。pos可以是下标或者迭代器。如果是下标,返回指向s引用,args可以使str或者str,pos,len或者n,c。如果是迭代器返回指向第一个插入字符的迭代器,args可以是n,c或者b,e或者init_list
s.insert(pos,args);
//assign的args包括insert中适用的所有args
s.assign(args);//替换s中字符为args指定字符。返回s引用
s.append(args);//追加args到s,返回s引用。args同assign
//range可以是一个下标和一个长度替换为args指定字符,这时候args可以是str或者str,pos,len或者cp,len或者cp或者n,c。range可以使一个迭代器范围,这时候args可以是str或者cp,len或者cp或者n,c或者b,e或者init_list
s.replace(range,args);
//以下寻找操作args必须是以下几种:1. c,pos 从pos开始找c,c可以是字符,字符串或者字符数组 2. c 含义同上。
//如果找不到返回npos
s.find(args); //查找args第一次出现位置
s.rfind(args); //查找args最有一次出现位置
s.find_first_of(args); //在s中查找args中任何一个字符第一次出现的位置
s.find_last_of(args); //在s中查找args中任何一次字符出现最后一个位置
s.find_first_not_of(args); //同find_first_of,只不过是不在args中的
s.find_last_not_of(args); //同上,最后一个位置
//比较。相等返回0,小于返回-1,大于返回正数
s.compare(s1);//比较s和s1
s.compare(a,b,s1);//s从a开始长度为b字符串与s1比较
s.compare(a,b,s1,a1,b1);
cmopare函数
处理关系运算符,string也提供了compare函数根据s等于,大于还是小于,返回0,正数,负数
s.compare参数形式 | 说明 |
---|---|
s2 | 比较s与s2(s2可以是C语言字符数组) |
pos1,n1,s2 | s从pos1开始n1字符与s2比较(s2可以是C语言字符数组) |
pos1,n1,s2,pos2,n2 | 含义同上 |
pos1,n1,cp,n2 | cp是C语言字符数组 |
数值转化
函数 | 说明 |
---|---|
to_string(val) | val可以是浮点或者整数等任何算术类型 |
stoi(s,p,b);stol(s,p,b);stoul(s,p,b);stoll(s,p,b);stoull(s,p,b); | 返回s起始子串的数值。返回类型对应i表示int,u表示unsigned,l表示long。b表示基数,默认是10,p是size_t指针,保存s中第一个非数值字符下标。p默认是0,也就是不保存 |
stof(s,p);stod(s,p);stold(s,p) | 同上,分别是float,double,long double。 |
处理字符
#include<cctype>
isalnum(c);//c是字母或者数字
isalpha(c);//c是字母
isdigit(c);//c是数字为真
iscntrl(c);//c是控制字符为真
isgraph(c);//c不是空格但是可以打印时候为真
islower(c);//小写字母
isprint(c);//c可打印(空格或者可视
ispunct(c);//c是标点符号(c不是控制,数字字母和可打印空白)
isspace(c);//c空白为真(空格,横向纵向制表符,回车符,换行符,进纸符的一种
isupper(c);//大写字母
isxdigit(c);//c是16进制数字
tolower(c);//大写变小写
toupper(c);
遍历
for(auto i:s);
for(auto &i:s);
泛型算法
泛型算法依赖于元素类型的操作,但是不依赖与容器本身。大多数算法都在algorithm中。numeric中也定义了一组数值泛型算法
泛型的写算法(例如fill,copy)不检查写操作。所以如果范围超过指定返回会导致产生一场灾难(不报错的BUG)
使用堆
堆是一种很常见的算法。我们在C++当中通常使用vector来模拟堆的实现
//使用make_heap创建一个堆。make_heap没有返回值,传入一个迭代器范围,可选的比较函数(默认是小于,也就是大顶堆)
make_heap(v.begin(),v.end(),compare);
//添加元素。首先使用push_back加入一个元素在堆的尾部,然后调用push_heap来使得元素加入堆中。没有返回值,有一个可选的比较函数
v.push_back(val);
push_heap(v.begin(),v.end(),comp);
//取出堆顶。首先调用pop_heap函数把堆顶移动到vector的尾部。然后pop_back出来。没有返回值,有一个可选的比较函数
pop_heap(v.begin(),v.end(),comp);
auto val=v.back();
v.pop_back();
//堆排序。sort_heap函数会逐一取出堆顶元素放在vector末尾。也就是如果是一个小顶堆,最终vector会变成一个由大到小的数组。
sort_heap(v.begin(),v.end(),comp);
//判断是不是堆。返回范围是否构成堆
is_heap(v.begin(),v.end(),comp);
常用泛型算法
//数组容器初始化函数,前两个参数可以是迭代器或者指针。value是初始化的值。
fill(begin(),end(),value);
fill_n(begin(),n,val);//作用同上,n表示要fill的数量。n<=size()
//copy接受三个迭代器。前两个表示一个范围。第三个表示目的序列起始位置。目的序列大小要大于范围大小。返回目的位置迭代后的值。
copy(begin(),end(),dest);
//replace三个参数,前两个迭代器表示范围。后两个是一个要搜索的值另一个是新值。把等于第一个的替换为第二个
replace(begin(),end(),a,b);
//如果保持原序列不变,要额外提供一个目的迭代器
replace_copy(begin(),end(),dest,a,b);
//返回val第一次出现位置迭代器
find(begin(),end(),value);
find_if(begin(),end(),comp); //comp是一个谓词。返回第一个使谓词非0的迭代器。否则返回end()
//范围求和,初始值是init_value
accumulate(begin(),end(),init_value);
//排序,comp是一个谓词,可以忽略也可以使用functional提供的基于模板的比较函数equal_to<T>,not_equal_to<T>,greater<T>,greater_equal<T>,less<T>,less_equal<T>
sort(begin(),end(),comp);
stable_sort(begin(),end(),comp); //稳定排序算法,保证了相等元素的原始顺序
//for_each遍历所有元素,并对每一个元素调用一个一元谓词(可以是lambda)
for_each(begin(),end(),func);
//unique重排数组,把重复的元素放在容器尾部,返回指向第一个重复元素的迭代器(unique使用的前提是数组已经排序)
auto end_unique = unique(begin(),end());
//transform接受三个迭代器和一个可调用对象。前两个表示输入范围,第三个表示一个输出目的位置(可以与第一个迭代器相同,必须大小够用)。最后是一个可调用对象,输入元素,返回一个新值
transform(begin(),end(),dest,func);
//reverse反序
reserve(begin(),end());
reserve_copy(begin(),end(),dest);
//remove元素
remove(begin(),end(),val);//移除val
remove_if(begin(),end(),comp) //如果comp谓词输出true,移除元素
remove_copy_if(begin(),end(),back_inserter(v2),comp);
//最大值最小值算法。返回指向最大值最小值的迭代器
max_element(begin(),end());
min_element(begin(),end());
//遍历全排列。生成begin(),end()的下一个字典序。如果是最后一个字典序,返回false,否则返回true。comp是比较谓词
next_permutation(begin(),end(),comp);
//实例:
int a[]={2,3,5,1},n=4;
sort(a.begin(),a.end());
do{ /*whatever*/ }while(next_permutation(a,a+n));
//二分搜索法.comp可选,默认是升序,可以改为降序(greater<int>()注意,对应关系是左闭右开
//第一个大于或等于
lower_bound(begin(),end(),key,comp);
//第一个大于
upper_bound(begin(),end(),key,comp);
//计算一个排序数组中元素个数:
upper_bound(b,e,key)-lower_bound(b,e,key);
//寻找数组中是否存在某个元素,返回bool
binary_search(b,e,val);
//合并有序集合
//合并b1,e1集合和b2,e2集合到db迭代器位置,可选比较谓词comp。返回合并的最后一个元素的后一个位置的迭代器
merge(b1,e1,b2,e2,db,comp);
//就地合并。first是第一个排序范围的开始。middle第一个排序范围的结束以及第二个排序范围的开始。last是第二个排序范围的结尾。comp是可选的比较谓词
inplace_merge(first,middle,last,comp);
//如果有序序列b2,e2范围是b1,e1的子序列,返回true,否则返回false
includes(b1,e1,b2,e2,comp);
//差集:复制来自有序序列b1,e1并且不在b2,e2范围内的元素到始于db的范围。结构仍然有序
set_difference(b1,e1,b2,e2,comp);
//交集:复制从b1,e1和b2,e2中都存在的元素(注意,相当于A+B-(A∩B))。返回构造后的尾后迭代器
set_union(b1,e1,b2,e2,db,comp);
//对称差:(A∪B)-(A∩B)
set_symmetric_difference(b1,e1,b2,e2,comp);
-
一些算法使用重载形式传递一个谓词来代替对象自身的比较算法
-
_if版本算法
接受一个元素值的算法通常还有一个不同版本,使用谓词代替元素值,附加了_if前缀。例如find和find_if
-
默认的各种重排算法都将重排元素写回输入序列。这些算法还提供了带有_copy的算法,并且需要给定一个目的的迭代器。例如reverse,reverse_copy,remove_if和remove_copy_if
lambda表达式
基本语法
在泛型算法中谓词就是所谓的可调用对象。C++中可调用对象包括函数,函数指针,重载了函数调用运算符的类和lambda表达式
一个lambda表达式表示一个可调用代码单元。可以理解为一个未命名的内联函数。lambda有一个返回类型,参数列表和一个函数体。
[capture list](parameter list) -> return type {function body}
/*
capture list: 一个lambda 所在函数 中定义的局部变量列表(通常为空)。
return type: 返回类型
parameter list: 参数列表
function body: 函数体
lambda必须是尾置范围
*/
//最简单实例:
auto f=[]{return 42;};
cout<<f()<<endl;
//忽略括号和参数列表等价于空参数列表。如果忽略返回类型,lambda根据函数体推断出返回类型。如果函数体只是一个return。则返回类型从返回表达式推断出来。否则返回类型是void
//sort实例:按照字符串长度排序
stable_sort(words.begin(),words.end(),
[](const string &a,const string &b)
{ return a.size()<b.size(); } );
//find_if:使用捕获列表
int sz=4;
auto wc = find_if(words.begin(),words.end(),
[sz](const string &a)
{ return a.size() >= sz; });
注意:捕获的capture list在lambda创建时被拷贝。所以不会因为下位改变capture list的值就改变函数调用结果。
如果想要capture list捕获对象的值随着局部变量值变化,可以使用引用捕获(但是这个lambda对象只能在局部变量存在时调用,也就是我们不能返回一个捕获引用的lambda表达式):
void fcn2()
{
size_t v1=42;
auto f2=[&v1] { return v1;};
v1=0;
auto j=f2(); //j=0
}
捕获列表
格式 | 描述 |
---|---|
[] | 空捕获列表。不能使用所在函数变量 |
[names] | names是逗号分割的名字列表。都是lambda所在函数的局部变量。默认拷贝变量,可以名字前你使用&采用引用捕获方式 |
[&] | 隐式捕获。采用引用捕获方式。根据函数实体推断捕获对象 |
[=] | 同上,但是是值捕获 |
[&, identifier_list] | identifier_list是一个逗号分割的列表,包含0个或者多个来自所在函数的变量。这些变量采用值捕获方式。任何隐式捕获变量都采用引用方式捕获。identifier_list中名字前面不能使用& |
[=, identifier_list] | identifier_list中变量采用引用方式捕获,任何隐式捕获都是值方式捕获。identifier_list中名字不能包括this,且这些名字中必须使用& |
可变lambda
默认情况下,lambda不允许改变值拷贝的变量(也就是捕获的值拷贝变量是只读的)。如果想在lambda里面修改值拷贝变量的值,可以在参数表后面添加mutable属性。
int v1=42;
auto f= [v1]()mutable {return ++v1;}; //如果没有mutable,会提示v1是只读的
v1=0;
cout<<f()<<endl; //43
lambda返回类型
如果lambda只有一个return语句时候,返回类型就是return表达式的类型。但是一旦lambda包含return意外的任何语句,编译器都会假定lambda返回void。例如如下语句:
transform(vi.begin(),vi.end(),vi.begin(),
[](int i) { if(i<0)return -i;else return i;}); //编译错误。因为默认类型变成void
这时候我们就要使用尾置返回类型了
transform(vi.begin(),vi.end(),vi.begin(),
[](int i) -> int { if(i<0)return -i;else return i;});
参数绑定
标准库bind函数
头文件functional中有一个bind函数作为一个通用函数适配器。接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
auto newCallable = bind(callable,arg_list);
其中newCallable是一个可调用对象,arg_list是一个逗号分割的参数列表,对象给定的callable参数。当我们调用newCallable时候,newCallable会调用callable,并且传递给他arg_list里面的参数。
arg_list中参数包含形如_n
的名字。其中n是一个整数。这些参数是占位符(placeholder)。数值n表示生成的可调用对象中参数的位置。_1是newCallable第一个参数,_2是第二个。以此类推
#include<functional>
using namespace std;
using namespace std::placeholders;
bool check_size(const string &s,string::size_type sz)
{
return s.size()>=sz;
}
auto check6=bind(check_size,_1,6);
string s = "hello"
bool b=check6(s);
我们可以推断出,我们可以使用bind重排一个函数的参数顺序(通过改变placeholder顺序)
bind使用引用
bind默认都是值传递。但是如果你想向内部函数传递一个引用的话,一个办法就是使用标准库ref函数。
ostream &print(ostream &os,const string &s,char c)
{
return os<<s<<c;
}
for_each(words.begin(),words.end(),bind(print,os,_1,' '));//错误,应为os无法拷贝
for_each(words.begin(),words.end(),bind(print,ref(os),_1,' '));
ref返回一个reference_wrapper来代替原本被识别的值类型,而reference_wrapper可以隐式的转换为被引用的值的引用类型。标准库还有cref函数生成一个保存const引用的类。都定义在functional头文件里面
特殊迭代器
插入迭代器
绑定到一个容器,用来插入元素。
创建
back_inserter(vec);
创建一个使用push_back的迭代器(vec必须支持push_back)front_inserter(vec);
同上,使用push_frontinserter(vec,iter);
vec支持insert操作,iter指向给定c。元素被插入给点迭代器元素之前
操作
操作 | 解释 |
---|---|
it = t | 在it指定位置插入t。假定c是t绑定的容器,依赖于插入迭代器的种类,分别调用push_back,push_front,insert函数。 |
*it,++it,it++ | 存在操作,但是不会对it做任何事情。每个操作都返回it |
应用
list<int> lst={1,2,3,4};
list<int> lst2;//空lst
copy(lst.cbegin(),lst.end(),front_inserter(lst2));//lst2包含4,3,2,1
流迭代器
- istream_iterator
读取输入流 - ostream_iterator
读取输出流
因为暂时用不上,跳过介绍
反向迭代器
除了forward_list之外,其他容器都支持反向迭代器。我们可以通过rbegin(),rend(),crbegin(),crend()成员函数获取反向迭代器。
内置位运算函数
//判断n的二进制中有多少个1
__builtin_popcount(unsigned int n);
//判断n二进制1个数奇偶性。偶数返回0,奇数返回1
——builtin_parity(unsigned int n);
//判断n二进制末尾最后以1的位置。如果n是0,行为未定义
__builtin_ffs(unsigned int n);
//判断n二进制末尾的0个数,n为0时返回int长度
__builtin_ctz(unsigned int n);
//返回前导0个数
__builtin_clz(unsigned int n);
元组tuple
tuple是一个固定大小的不同类型的集合。是泛华的std::pair。可以作为通用结构体使用。是c++11的新标准。
#include<tuple>
tuple创建和初始化
//使用默认构造函数创建一个tuple
tuple<T1,T2,T3> t1;
//初始化
tuple<T1,T2,T3> t2(v1,v2,v3);
//tuple元素类型可以是引用
tuple<T1&> t2(ref&);
auto t=make_tuple(v1,v2);
tuple相关操作
//获取tuple元素个数
tuple<int,char,double> t(10,'a',2.0);
cout<<"size is"<<tuple_size<decltype(t)::value;
//获取元素值
//使用get方法可以通过get<lth>(obj)获取
cout<<get<0>(t)<<get<1>(t)<<get<2>(t);
//tuple不支持迭代,get的lth引索必须是编译时指定的。
//获取元素类型
tuple_element<1,decltype(t)>::type xxx;
xxx=get<1>(t);
//利用tie或者[]解包
#include<utility>
int a;char b;double c;
tie(a,b,c)=t;
auto [x,y,z] = t;
//解包时候可以使用ignore
tie(a,std::ignore,std::ignore) = t;
//enjoy tuple