第三章 泛型编程风格
STL主要由两种组件构成:1、容器:vector、list、set、map等类;2、是用以操作这些容器的所谓泛型算法,包括find(),sort(),replace(),merge()等
vector和list这两种容器是所谓的顺序性容器,会依次维护第一个、第二个等等元素,主要采用迭代操作
map和set这两种容器属于关联容器,关联容器可以让我们快速查找容器中的元素值。 key/value
3.1 指针的算数运算
主要任务:给定一个储存整数的vector,以及一个整数值。如果该值存在于vector内,就返回一个指针指向该值,反之返回0;
int *find(const vector<int> &vec,int value)
{
for(int ix=0;ix<vec.size();++ix){
if(vec[ix]==value)
return &vec[ix];
}
return 0;
}
1、实现函数不仅可以处理整数,又可以处理任意类型?
使用模板函数
template <typename elemType>
int *find(const vector<elemType> &vec,const elemType value)
{
for(int ix=0;ix<vec.size();++ix){
if(vec[ix]==value)
return &vec[ix];
}
return 0;
}
2、怎么实现同一个函数能同时处理vector和array内的任意类型元素?
答:可以使用重载;
怎么编写一个函数,就解决该问题?主要是采用了指针的地址,使用指针地址的运算
分解为两个小问题?1、将array的元素传入find(),而非指明array;2、将vecotr的元素传入find()而不指明该vector。
1、当数组被传给函数,或是由函数中返回,仅有第一个元素的地址会被传递。int main(int *array){…}
可以使用下标的自增来对数组进行操作;
也可以使用地址的移动来操作数组
这样就可以将array替换出
template <typeneme elemtype>
elemType* find(const elemType *first,const elemType *last,const elemType &value)
2、将vecotr的元素传入find()而不指明该vector
可以和处理array方式一样,将一组用来表示“起始位置/结束地址”的参数传入find函数
但vector可以为空,所以得判断vector是否为空。可以定义函数
template <typename T>
inline elemType *begin(const vector<elemType> &vec)
{
return vec.empty() ? 0 : &vec[0];
}
template <typename T>
inline elemType *end(const vector<elemType> &vec)
{
return vec.empty() ? 0 : &vec[vec.size()];
}
3、怎么实现支持list类型?
list类是以一组指针相互链接:前向指针指向下一个元素,后向指针指向上一个元素。
实现方法是在底层的行为之上提供一层抽象,取代程序原本的“指针直接操作”。
使用标准容器
3.2泛型指针 iterator
抽象出来,这样不论是面对list还是vector,使用内置的运算符都是iterator内相关inline函数提供,可以实现基本移动功能。
使用标准容器
每一个标准容器都提供一个名为begin()的操作函数,可返回一个iterator(迭代器),指向第一个元素。
提领操作:取得“位于该指针所指内存地址上”的对象,在指针之前使用*号,便可以达到这个目的。
vector<string> svec;
//iterator语法
vector<string>::iterator iter = svec.begin();
此处iter被定义为一个iterator,指向一个vector,后者的元素类型为string。其初始值指向svec的第一个元素。::表示此itertor是位于string vector定义内的嵌套类型。
则改进后的find的泛型算法如下:
template<typename IteratorType, typename elemType>
IteratorType find(IteratorType first, IteratorType last, const elemType &value)
{
for (; first != last; ++first) {
if (value == *first) {
return first;
}
}
return last;
}
3.3 所有容器的共通操作
equality(==)和inequality(!=)运算符,返回true和false。
empty(): 判断容器是否为空
size(): 返回容器的大小
clear(): 删除所有元素
begin(): 返回一个iterator,指向容器的第一个元素
end(): 返回一个iterator,指向容器的最后一个元素的下一个位置
insert(): 将单个或某个范围内的元素插到容器内
erase(): 将容器内的单一元素或某个范围内的元素删除
3.4 使用顺序性容器
用来维护一组排列有序,类型相同的元素,vector和list是两个最主要的顺序容器。
vector以一块连续内存来存放元素,每个元素位置固定,插入操作效率低。适合数列,查询
list是以双向链接而非连续内存存储内容,可以执行前进或后退操作。list中每个元素都包含三个字段:value、back、front指针。适合插入
deque容器:和vector类似,但对于最前端元素的插入和删除,效率更高
定义容器,必须包含相应的头文件
定义容器的五种方式
1、空的容器 list slist; vector ivec;
2、产生特定大小的容器。每个元素都以默认值作为初值。list ilist(1024); vector svec(32);
3、产生特定大小的容器,并为每个元素指定初值 list ilist(10,,1); vector svec(32,“adsadas”);
4、通过一对iterator产生容器。 int ia[8] = {1,2,5,4}; vector fib(ia,ia+8);
5、根据某个容器产生新容器,复制原容器内的元素,作为新容器的初值。 list slist; list slist2(slist); //将slist复制给slist2
push_back()插入和pop_back()删除 末尾操作
insert()有四种变形:
1、iterator insert(iterator position,elemType value) 可将value插入position之前,返回一个iterator,指向被插入的元素。
2、void insert(iterator position,int count,elemType value) 可在position之前插入count个元素,这些元素都和value相同。
3、void insert(iterator position,iterator2 first,iterator2 last) 可在position之前插入first last所标示的各个元素。
4、iterator insert(iterator position) 可在position之前插入元素,初始值为默认值
3.5 泛型算法
头文件 #include
find() 无序集合,搜索范围由iterator[first,last]标出
binary_search() 用于有序集合的搜索。
count() 返回数值相符的元素数目
search() 比对某个容器内是否存在某个子序列。
max_element() 返回该范围的最大值 int max_value = max_element(vec.begin(),vec.end());
copy() 接收两个iterator。标出复制范围。 vector temp(vec.size()); copy(vec.begin(),vec.end(),temp.begin());
sort() 排序 sort(temp.begin(),temp.end());
3.6 设计泛型算法
任务
用户给一个整数,返回一个新vector,其中内含原vector之中小于10的所有数字。就是将小于10的数拿出来。
常用方法:
vector<int> less_than_10(const vecotr<int> &vec)
{
vector<int> nvec;
for(int ix = 0;ix<vec.size();++ix)
{
if(vec[ix] < 10)
nvec.push_back(vec[ix]);
}
}
如果是小于11:让用户指定某个上限值
vector less_than(const vector &vec,int less_than_val);
允许用户指定不同的比较操作,大于、小于、等于
解法1:以函数调用来取代less_than运算符。
vector<int> filter(const vector<int> &vec,int filter_value,bool (*pred)(int,int));
这样就可以定义许多关系。bool less_than(int v1,int v2){return v1<v2?true:false;}
完整解法
vector<int> big_vec;
int value;
vector<int> it_10 = filse(big_vec,value,less_than);
vector<int> filter_ver1(const vector<int> &vec,int filter_value,bool (*pred)(int,int))
{
vector<int> nvec;
for(int ix=0;ix<vec.size();++ix)
{
if(pred(vec[ix],filter_value))
nvec.push_back(vec[ix]);
}
return nvec;
}
那么怎么去掉for循环呢?
使用泛型算法 find() 查找有几个等于val的值
int count_occurs(const vector<int> &vec,int val)
{
vector<int>::const_iterator iter = vec.begin();
int occurs_count = 0;
while((iter = find(iter,vec.end(),val)) != vec.end()) //迭代查找vec中等于val的值
{
++occurs_count;
++iter;
}
return occurs_count;
}
function object
是某种class的实例化对象,这类class对function call运算符做了重载操作。主要是为了效率。
标准库实现定义的function obj,分为算数运算、关系运算、逻辑运算
六个算数运算:plus 加、minus 减、negate 非、multiplies 乘、divides 除、modules
六个关系运算:less 小于、 less_equal 小于等于、 greater 大于、 greater_equal 大于等于、 equal_to 等于、 not_equal_to 不等于
三个逻辑运算:logical_and &&、 logical_or || 、 logical_not !
要想使用function object,要包含相关头文件
#include
function object adapter
想要比较某个vector中的数与一个固定数的大小,问题:怎么将参数绑定至用户指定的数值
标准库提供的adapter就可以
所谓binder adapter(绑定适配器)会将function object的参数绑定至某特定值,使biary(二元)function object转化为一元。
提供了两个:bindlst:将指定值绑定至第一操作数;bind2nd:将指定值绑定至第二操作数
vector<int> filter(const vector<int> &vec,int val,less<int> <)
{
vector<int> nvec;
vector<int>::const_iterator iter = vec.begin();
//less<int> 会将每个元素拿来和val比较
while((iter = find_if(iter,vec.end(),bind2nd(lt,val))) != vec.end()) //迭代查找vec中小于val的值
{
nvec.push_back(*iter);
iter++;
}
return nvec;
}
怎么使函数更泛型
使用template
template<typename InputIterator,typename OutputIterator,typename Elemtype,typename Comp>
OutputIterator
filter(InputIterator first,InputIterator last,OutputIterator at,const Elemtype &val,Comp pred)
{
while((first = find_if(first,last,bind2bd(pred,val))) != last)
{
cout <<"found value:" << *first <<endl;
//执行assign操作
*at++ = *first++;
}
return at;
}
另外一种adapter是所谓的negator,它会对function object的真伪值取反。 not1 可对unary(一元) function object的真伪取反;not2 对binary(二元)取反
这一节的思路
1、由一个找出在vector内小于10的所有元素出发。 太死板,只能确定的小于10
vector<int> less_than_10(const vecotr<int> &vec)
{
vector<int> nvec;
for(int ix = 0;ix<vec.size();++ix)
{
if(vec[ix] < 10)
nvec.push_back(vec[ix]);
}
}
2、加入一个val,让用户来指定某个数字。 只能是小于的比较方式*/
vector<int> less_than(const vector<int> &vec,int less_than_val)
{
vector<int> nvec;
for(int ix = 0;ix<vec.size();++ix)
{
if(vec[ix] < less_than_val)
nvec.push_back(vec[ix]);
}
}
3、想要修改比较方式,加一个新参数:一个函数指针,让用户指定比较方式
vector<int> big_vec;
int value;
vector<int> it_10 = filse(big_vec,value,less_than);
vector<int> filter_ver1(const vector<int> &vec,int filter_value,bool (*pred)(int,int))
{
vector<int> nvec;
for(int ix=0;ix<vec.size();++ix)
{
if(pred(vec[ix],filter_value))
nvec.push_back(vec[ix]);
}
return nvec;
}
函数指针定义着各种比较方式
4、引入function object概念,使我们得以将某组行为传给函数,
5、最后将函数以function template的方式重新实现。
template<typename InputIterator,typename OutputIterator,typename Elemtype,typename Comp>
OutputIterator
filter(InputIterator first,InputIterator last,OutputIterator at,const Elemtype &val,Comp pred)
{
while((first = find_if(first,last,bind2bd(pred,val))) != last)
{
cout <<"found value:" << *first <<endl;
//执行assign操作
*at++ = *first++;
}
return at;
}
3.7 map使用
map 被定义为 一对数字,key-value
#include <map>
#include <string>
map<string,int> words;
输入key/value的最简单方式: words[“vermeer”] = 1;
**想要查询map是否存在某个key。**有三种方法:
1、把key当成索引使用:
int count = 0;
if(!(count = words["vermeer"]))
//vermeer并不存在于words map中
缺点:假设"vermeer"不在words map中,这样的搜索方式会将它放在map中,而其value将是0。
2、map查询法:利用map的find()函数(不是泛型算法的find()函数),将key传入find()并调用。
words.find("vermeer");
如果key已放入其中,find()会返回一个iterator,指向key/value形成的一个pair,反之返回end();
int count = 0;
map<string,int>::iterator it;
it = words.find("vermeer");
if(it ! words.end())
count = it -> second;
3、利用map的count()函数,count()函数会返回某特定项在map内的个数
int count = 0;
string search_word("vermeer");
if(! words.count(search_word))
count = words[search_word];
3.8 set
set是由一群key组成,如果我们想知道某值是否存在与某个集合内,就可以使用set。
#include <set>
#include <string>
set<string> word_exclusion;
比较:word_exclusion.count(tword) 检测tword是否在其中
插入:iset.insert(ival);
插入某一个范围的值:iset.insert(vec.begin(),vec.end());
3.9 使用Iterator Inserter
在3.6中while((first = find_if(first,last,bind2bd(pred,val))) != last)
{
cout <<"found value:" << *first <<endl;
//执行assign操作
*at++ = *first++;
}
必须保证目标容器足够大。都是提前定义好大小,不能随意增加
怎么才能避免使用容器的assignment运算符?
1、back_inserter() 会以容器的push_back()函数取代assignment运算符。
2、inserter() 会以容器的insert() 函数取代assignment运算符。接收两个参数,一个容器,一个iterator,指向容器内的插入操作起点。
3、front_inserter() 会以容器push_front()函数取代assignment运算符。只适用于list和deque。
#include <iterator>
3.10 使用iostream Iterator
从标准输入设备读取一串string,将其存在vector内,排序,最后输出。
使用istream_iterator
#include<iterator>
first和last表示元素范围
first: istream_iterator<string> is(cin);
last : istream_iterator<string> eof;
传递: copy(is,eof,back_inserter(text));
输出: ostream_iterator<string> os(cout," ");