5.3 迭代器?
- 前置式递增比后置式递增效率更高,因为后者需要一个额外的临时对象,因为他需要存储一个迭代器原本的位置并将其进行返还,因此最好使用++pos,而不是pos++;
5.3.1 关联式容器的运用实例
- 修改map默认的递增的方式,将内部排序修改为 递减方式
- ? ? std::map<int,int,std::greater<int>>map{};
- greater是一个预先定义的仿函数
- 关联式容器使用自己的默认规则进行元素位置的存放,因此相较于序列式容器取消了push_back()和push_front()函数
- std::greater - C++中文 - API参考文档
#include <list>
#include <iostream>
#include <set>
#include <map>
int main(){
std::map<int,int,std::greater<int>>map{};
map.insert(std::make_pair(1,1));
map.insert(std::make_pair(2,2));
map.insert(std::make_pair(3,3));
map.insert(std::make_pair(4,4));
for (auto temp=map.begin();temp!=map.end();++temp) {
std::cout << temp->first << " " << temp->second << std::endl;
}
}
- multimap包含在map头文件内部,允许key和value是1对多的关系
#include <list>
#include <iostream>
#include <set>
#include <map>
int main(){
std::multimap<int,int,std::greater<int>>map{};
map.insert(std::make_pair(1,1));
map.insert(std::make_pair(1,1));
map.insert(std::make_pair(1,1));
map.insert(std::make_pair(1,1));
map.insert(std::make_pair(2,2));
map.insert(std::make_pair(3,3));
map.insert(std::make_pair(4,4));
for (auto temp=map.begin();temp!=map.end();++temp) {
std::cout << temp->first << " " << temp->second << std::endl;
}
}
- 迭代器访问 元素的时候,可以使用 迭代器->first? 或者? (*迭代器).first 使用*解除引用,再对元素进行访问
- 类名 变量名;将创建一个对象将其存储在栈上,使用 变量名.成员? 进行使用
- 类名 变量名 = new 类名();??将创建一个对象将其存储在堆上,使用 变量名->成员? 进行使用
- map 键值/实值 所形成的群集中,如果所有的键值是唯一的,将其作为一个关联式数组;这里想表达的意思是我们可以像使用数组一样进行元素的访问,数组通过数组下标访问对应的数值,map可以通过指定key,访问对应的value。通过map[key]访问对应的数值
5.3.2 迭代器分类
- 双向迭代器 Bidirectional iterator:双向行进,采用递增进行前进运算,使用递减进行后退运算,list、set、multiset、map和undered_map 都使用这个迭代器
- 随机存储迭代器Random access iterator:在双向迭代器的基础上还具备了随机访问的特性,因此可以对迭代器增加或者减少一个偏移量,处理迭代器之间的距离,或者使用<和>之类的relational相对关系的操作符来比较两个迭代器。vector、deque和string均采用此迭代器
- 尽量使用 != 这个的适配性更好,但是如果pos位于了end的后面,发生错误很难发现
for(pos = coll.begin();pos != coll.end();++pos){}operator !=
for(pos = coll.begin();pos < coll.end();++pos){}//operator < Random access iterator
5.4 算法
- 算法不是容器类别的成员函数,而是搭配迭代器使用的全局函数,采用的是泛型函数编程的思维,数据和操作进行分离
5.4.2 处理多个区间
- copy将第一区间的数据拷贝到目标区域,第一区间的起点和第一区间的终点确定了第一区间的元素的数量,因此第二区间只需要提供一个起点即可。
- 但是copy函数使用的是覆写操作,不是插入操作,因此需要第二区间具备足够的内存空间,如下例子所示,会产生未定义的行为
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
int main(){
std::list<int>coll1;
std::vector<int>coll2;
for (int i = 1; i <= 9; ++i) {
coll1.push_back(i);
}
std::copy(coll1.begin(),coll1.end(),coll2.begin());
}
- 对上述错误代码的修改:1,确认目标区域具备足够的内存;2,采用insert iterator
std::list<int>coll1;
std::vector<int>coll2;
for (int i = 1; i <= 9; ++i) {
coll1.push_back(i);
}
coll2.resize(coll1.size());
std::copy(coll1.begin(),coll1.end(),coll2.begin());
5.5 迭代器配接器
- Insert iterator 安插型迭代器
- Stream iterator 流迭代器
- reverse iterator 逆向迭代器
Insert iterator 安插型迭代器
- 采用安插方式而不是覆写的方式运作,解决目标区域不足的问题? 3种方式
- Back inserters 安插在容器的最尾端:内部调用push_back() 采用追加的操作,只能适用于具备push_back()成员函数的容器中? 适用于vector list deque
- ? ? std::copy(coll1.begin(),coll1.end(),std::back_inserter(coll2));
- Front inserters 安插在容器最前端,内部调用的是push_front() ,这种操作会逆转被插元素的次序,比如插入1 再次插入 2,输出的时候2会在1 的前面打印输出? 这个适用于 list 和 deque
- ? ? std::copy(coll1.begin(),coll1.end(),std::insert_iterator<>(coll2));
- General inserters一般性安插器: 将元素插入到初始化时接受的第二个参数指向的前方。内部调用的是insert函数,并按照新值和新的位置作为参数。适用于关联式容器
?流迭代器
- stream iterator是一种用来读取stream流的迭代器。提供了必要的抽象性,使得键盘的输入像一个群集,使得程序员可以从中读取内容,也可以把算法的输出重新导向某个文件或者屏幕
- 代码没有跑通
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
int main(){
std::vector<std::string>coll{};
copy(std::istream_iterator<string>(cin),
istream_iterator<string>(),
back_inserter(coll));
sort(coll.begin(),coll.end());
unique_copy(coll.begin(),coll.end(),
ostream_iterator<string>(cout,"\n"));
}
5.5.3 reverse iterators 逆向迭代器
- coll.rbegin()指向的是逆向遍历的起点,也就是先前的end()-1 的位置?
5.6.1 移除元素
- ?remove移除元素,是使用删除元素位置之后的元素对删除位置上的元素进行替代,不会是将其所有的3全部删除
- 但是群集末尾未被覆盖的元素原封不动,但是从逻辑层面上讲,他们已经不属于这个群集了
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
int main(){
std::list<int>coll{};
for(int i=1;i<=6;i++){
coll.push_back(i);
coll.push_front(i);
}
std::cout << "pre: ";
copy(coll.begin(),coll.end(),ostream_iterator<int>(cout," "));
std::cout << std::endl;
std::remove(coll.begin(),coll.end(),3);
std::cout << "post: ";
copy(coll.begin(),coll.end(),ostream_iterator<int>(cout," "));
std::cout << std::endl;
}
- pre: 6 5 4 3 2 1 1 2 3 4 5 6
- post: 6 5 4 2 1 1 2 4 5 6 5 6?
- 实际上 算法会产生一个新的终点,需要更新新的终点的位置,获得新的区间,也就是缩减元素之后的容器的大小,亦或是删除元素的个数
?改进如下
- ? ? std::list<int>::iterator end = std::remove(coll.begin(),coll.end(),3);
- 这个end也就是经过删除之后逻辑上新的终点
- 或者获取徐亚删除的元素的个数,利用distance函数,返回两个迭代器之间的距离
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
int main(){
std::list<int>coll{};
for(int i=1;i<=6;i++){
coll.push_back(i);
coll.push_front(i);
}
std::cout << "pre: ";
copy(coll.begin(),coll.end(),ostream_iterator<int>(cout," "));
std::cout << std::endl;
std::list<int>::iterator end = std::remove(coll.begin(),coll.end(),3);
std::cout << "post: ";
copy(coll.begin(),end,ostream_iterator<int>(cout," "));
std::cout << std::endl;
std::cout << "number of removed elements: "<< std::distance(end,coll.end()) << std::endl;
coll.erase(end,coll.end());
copy(coll.begin(),coll.end(),ostream_iterator<int>(cout," "));
std::cout << std::endl;
}
5.6.2 更易型算法和关联式容器
- 更易型算法是指 移除、重排、修改元素的算法,这些算法不适用于关联型算法,因为这些操作会修改位置上的数值,破坏排序
- 因此,关联式容器的所有迭代器都被声明为指向常量
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
int main(){
set<int>coll{};
for (int i = 0; i <= 9; ++i) {
coll.insert(i);
}
copy(coll.begin(),coll.end(),ostream_iterator<int>(cout," "));
std::cout << std::endl;
int num = coll.erase(3);
cout << "number os removed elements:" << num << std::endl;
copy(coll.begin(),coll.end(),ostream_iterator<int>(cout," "));
}
5.6.3 算法 VS 成员函数
- 如果容器提供了功能相似但是性能更佳的成员函数,优先使用成员函数
- 成员函数 对其进行了优化,相较于函数功能相似的算法
- 成员函数?remove(coll.begin(),coll.end(),4)
- 算法:? ? coll.remove(4);
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
int main(){
list<int>coll{};
for (int i = 1; i <= 6; ++i) {
coll.push_front(i);
coll.push_back(i);
}
coll.erase(remove(coll.begin(),coll.end(),3),coll.end());
coll.remove(4);
}
5.8.1 以函数作为算法的参数
- 算法接收用户定义的辅助性函数 ,在算法的内部被调用
- for_each函数对coll的begin到end区间内的每一个元素都调用print函数进行输出
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
void print(int elem){
std::cout << elem << " ";
}
int main(){
std::vector<int>coll{};
for (int i = 1; i <= 9; ++i) {
coll.push_back(i);
}
std::for_each(coll.begin(),coll.end(),print);
}
- 算法以🌲种态度来面对辅助函数,1,将其视为可有可无;2,视为必要;
- 利用他们指定搜寻的规则、排序的规则或者定义某种操作,从而将某个容器内 的元素转换到另外一个容器
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
void print(int elem){
std::cout << elem << " ";
}
int square(int value){
return value*value;
}
int main(){
std::set<int>coll{};
std::vector<int>coll2{};
for (int i = 1; i <= 9; ++i) {
coll.insert(i);
}
std::transform(coll.begin(),coll.end(),std::back_inserter(coll2),square);
std::for_each(coll2.begin(),coll2.end(),print);
}
5.8.2 判断式
- 潘端师就是返还结果是布尔数值的函数,常用于指定排序准则和搜寻准则,可能有一个或者两个操作数
- 判断式 对于相同的输入返还相同的输出结果,且程序执行不可以改变内部状态?
一元判断式
?二元判断式
?
5.9 仿函数
- 泛型编程强大威力和存粹抽象的一个例证。仿函数就是行为像函数,比如定义一个对象,他具备类似函数的一些特性,就可以当做函数来使用;比如使用小括号传递参数,如果指望定义的对象也可以实现 使用小括号传递参数,比如定义operator()???
#include <list>
#include <iostream>
#include <set>
#include <map>
#include <string>
#include <vector>
using namespace std;
class X{
public:
int operator()(int v1,int v2)const;
};
int X::operator()(int v1, int v2) const {
std::cout << v1 << " "<< v2<< std::endl;
}
int main(){
X fo;
fo(4,5);
fo.operator()(4,5);
}
class PrintInt{
public:
void operator()(int elem)const{
std::cout << elem << std::endl;
}
};
int main(){
std::vector<int>coll{};
for (int i = 1; i <= 6; ++i) {
coll.push_back(i);
}
std::for_each(coll.begin(),coll.end(),PrintInt());
}
?
void add10(int & elem){
elem += 10;
}
int main(){
std::vector<int>coll{};
for (int i = 1; i <= 6; ++i) {
coll.push_back(i);
}
for_each(coll.begin(),coll.end(),add10);
std::for_each(coll.begin(),coll.end(),PrintInt());
}
- 如果需要数个不同的固定数值? 而且他们在编译期 都已经确认,可以使用template?
- 使用模板传参,需要在函数被调用之前将数值传递给函数?
- ?具体的代码如下面所示,? ? for_each(coll.begin(),coll.end(),AddValue(10));这种方式,使用AddValue(10)生成一个AddValue的组件,并且赋予了初值为10,构造函数就会把10保存在成员theValue中,在for_each之内,针对coll的每一个元素调用 () ,实际上就是对传入的那个AddValue对象 暂时调用 operator()操作,并以容器的元素作为参数
class AddValue{
private:
int theValue;
public:
AddValue(int v):theValue(v){};
void operator()(int& elem)const{
elem += theValue;
}
};
class PrintInt{
public:
void operator()(int elem)const{
std::cout << elem << " ";
}
};
int main(){
std::vector<int>coll{};
for (int i = 1; i <= 6; ++i) {
coll.push_back(i);
}
for_each(coll.begin(),coll.end(),AddValue(10));
std::for_each(coll.begin(),coll.end(),PrintInt());
std::cout << std::endl;
for_each(coll.begin(),coll.end(),AddValue(*coll.begin()));
std::for_each(coll.begin(),coll.end(),PrintInt());
}
- ?? ? for_each(coll.begin(),coll.end(),AddValue(*coll.begin()+5));这种方式,传入的参数
- 使用这个技术就可以实现先前所说的 一个函数,两种状态的问题,用两个不同的仿函数实现
class AddValue{
private:
int theValue;
public:
AddValue(int v):theValue(v){};
void operator()(int& elem)const{
elem += theValue;
}
};
class PrintInt{
public:
void operator()(int elem)const{
std::cout << elem << " ";
}
};
int main(){
std::vector<int>coll{};
for (int i = 1; i <= 6; ++i) {
coll.push_back(i);
}
AddValue addx(4);
for_each(coll.begin(),coll.end(),addx);
std::for_each(coll.begin(),coll.end(),PrintInt());
std::cout << std::endl;
AddValue addy(5);
for_each(coll.begin(),coll.end(),addy);
std::for_each(coll.begin(),coll.end(),PrintInt());
}
?5.9.2 预先定义的仿函数
- C++标准库 包含了一些预定义的仿函数 涵盖了部分的基础运算
- 比如排序默认的排序是从小到大,也就是operator < 的缺省排序准则就是 less<>
- 默认的 std::set<int>coll? ->? set<int,less<int>>coll
- 反向排列?? set<int,greater<int>>coll
- negate<int>()? 将传入的int数值设置为 负数
- transform()算法 将第一群集的所有元素处理之后转移到第二群集,如果第二群集的起始地址是自身,就相当于对第一群集的元素进行操作覆盖
- transform的另外一种形式,按照某种特定的运算,将两个群集内的元素处理之后将其结果写入到第三群集;如下所示,设定 第一群集、第二群集 将两个群集的元素进行计算,将结果写回到第三群集
?
class PrintInt{
public:
void operator()(int elem)const{
std::cout << elem << " ";
}
};
int main(){
std::set<int,std::greater<int>>coll1;
std::deque<int>coll2;
for (int i = 1; i <= 9; ++i) {
coll1.insert(i);
}
std::for_each(coll1.begin(),coll1.end(),PrintInt());
transform(coll1.begin(),coll1.end(), //source
back_inserter(coll2), //destination
bind2nd(multiplies<int>(),10)); //operation
std::cout << std::endl;
std::for_each(coll2.begin(),coll2.end(),PrintInt());
//replace value equals to 70 with 42
replace_if(coll2.begin(),coll2.end(), //range
bind2nd(equal_to<int>(),70), //replace criterion
42); //new value
std::cout << std::endl;
std::for_each(coll2.begin(),coll2.end(),PrintInt());
//remove all elements with values less than 50
coll2.erase(remove_if(coll2.begin(),coll2.end(), //range
bind2nd(less<int>(),50)), //remove criterion
coll2.end());
std::cout << std::endl;
std::for_each(coll2.begin(),coll2.end(),PrintInt());
- ? ? transform(coll1.begin(),coll1.end(), //source
? ? ? ? ? ? ? back_inserter(coll2), //destination ? ? ? ? ? ? ? bind2nd(multiplies<int>(),10)); //operation - bind2nd 就是? 进行multiplies<int>()运算的时候,将源群集的元素作为第一参数,将10 作为第二参数
- 所有的仿函数通常都声明为inline,一方面使用类似函数的表示法或者抽象性,一方面又可以获得出色的效能
- 还有一些仿函数可以调用群集内的每个元素的成员函数?
?
?
?
?
- 智能指针:是一种对象,有着类似指针的接口,但是内部实现了一些额外的检查和处理工作?
?
?
?
?
|