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(), //sourceback_inserter(coll2), //destinationbind2nd(multiplies<int>(),10)); //operationstd::cout << std::endl;std::for_each(coll2.begin(),coll2.end(),PrintInt());//replace value equals to 70 with 42replace_if(coll2.begin(),coll2.end(), //rangebind2nd(equal_to<int>(),70), //replace criterion42); //new valuestd::cout << std::endl;std::for_each(coll2.begin(),coll2.end(),PrintInt());//remove all elements with values less than 50coll2.erase(remove_if(coll2.begin(),coll2.end(), //rangebind2nd(less<int>(),50)), //remove criterioncoll2.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,一方面使用类似函数的表示法或者抽象性,一方面又可以获得出色的效能
- 还有一些仿函数可以调用群集内的每个元素的成员函数
- 智能指针:是一种对象,有着类似指针的接口,但是内部实现了一些额外的检查和处理工作