泛型编程风格(Generic Programming)
泛型风格与泛型算法简述
Standard Template Library(STL),即标准模板库,主要由容器(container)和用以操作容器的泛型算法(generic algorithm) 两类组件构成。
泛型算法之所以 “泛型” 是因为与其要操作的元素类型无关。其主要通过 function template(函数模板) 以实现与操作对象的类型相互独立,而要使其实现“与容器无关”的窍门,就是不要直接在容器上进行操作,而是借由一对 iterator (first and last),即迭代器,以标示迭代的元素范围。
指针的算术运算
检查vector中是否有所要的值,存在则返回指向其的指针,反之返回0
处理多种类型的前提是该类型定义有equality(相等)运算符
template <typename elemType>
elemType* find(vector<elemType> &vec, elemType value) // 此处vector前不要按书加const
// 编译器提示不能将const elemType*的对象返回给elemType*,但反之可以
{
for (int i = 0; i < vec.size();i++)
{
if (vec[i] == value)
return &vec[i];
}
return 0;
}
接下来想让find()同时可以处理array和vector,可以通过函数重载,但本节有其他方法
几个注意事项:
1.当数组被传递给函数时,传递的是数组第一个元素的地址
2.通过传入数组最后一个元素的下个地址作为“标兵”以标示结束位置,以此使得原数组array从参数列表中消失
3.既可以使用指针也可以使用subscript(下标)对特定位置的元素进行访问
4.指针算术运算中,会考虑所指类型的大小
在考虑find()对vector类的实现中,要考虑到vector可能为空,因而引入begin,end两个函数
// 若非空则取回第一个元素的地址,否则返回0
template <typename elemType>
inline elemType* begin(vector<int> &vec)
{return vec.empty() ? 0 : &vec[0];}
Iterator(泛型指针)
实现对通用容器的iterator需要利用C++的类机制,其内部的各类运算符由iterator class内相关的inline函数提供,本节先看如何定义并使用标准容器的iterator
定义iterator应提供两份信息:迭代对象(容器)的类型,iterator所指的元素类型
vector<string> svec;
// 标准库中的iterator语法
// iter指向一个vector,后者元素类型为string
// iter一开始指向svec的第一个元素
vector<string>::iterator iter = svec.begin();
// 使用STL中的iterator的更为通用的display函数
template <typename elemType>
void display(const vector<elemType> &vec, ostream &os)
{
vector<elemType>::const_iterator iter = vec.begin();
vector<elemType>::const_iterator end_it = vec.end();
for (; iter != end_it;++iter)
os << *iter << ' ';
os << endl;
}
重新实现的find(),同时支持一对指针或一对iterator
template <typename IteratorType, typename elemType>
IteratorType find(IteratorType first, IteratorType last,
const elemType &value)
{
for (; first != last;++first)
if (value == *first)
return first;
}
其实现基于底部元素所属类型的equality(相等)运算符。若未提供此运算符,则传入一个函数指针或运用function object
使用顺序性容器
顺序性容器用来维护一组排列有序、类型相同的元素,典型的顺序性容器有vector,list
vector随机访问(random access) 效率高,但插入、删除效率较低,list则恰好相反
第三种容器是deque(双端队列),类似于vector都以连续内存储存元素,但其对最前端、最后端的插入与删除效率更高
容器的具体使用
首先要包含相关的头文件
#include <vector>
#include <list>
#include <deque>
定义顺序性容器对象有以下五种方式
// 1.产生空的容器
list<string> slist;
vector<string> ivec;
// 2.产生特定大小的容器,每个元素以其类型的默认值作为初值(注:c++内置算术类型默认值为0)
list<int> ilist(1024);
vector<string> svec(32);
// 3.特定大小的容器,为每个元素指定相同的初值
vector<int> ivec(10, -1);
list<string> slist(16, "unassigned");
// 4.通过一对iterator产生容器,这对iterator用来标示一整组作为初值的元素的范围
int ia[8] =
{1, 1, 2, 3, 5, 8, 13, 21};
vector<int> fib(ia, ia + 8);
// 5.根据某个容器产生新容器,复制原容器内的元素,作为新容器的初值
list<string> slist; // 当前为空,假设后续填充了该list
list<string> slist2(slist); // 进行复制操作
特殊的操作函数
push_back()末端插入 pop_back()末端删除
list和deque特有:push_front() pop_front()
注:两个pop函数不会返回被删除的元素值,故读前端使用front(),读末端使用back()
通用的insert及erase函数
insert()为通用函数,具有四种变形(在position前插入value,在position前插入类型默认值,在position前插入count个value,在position前插入iterator first~last(左闭右开)的各个元素)
erase()同上,具有两种变形(删一个,删一个范围)
使用泛型算法
使用泛型算法前,先要包含对应的algorithm头文件
#include <algorithm>
e.g:用vector储存的递增数列,构造is_elem()检索是否有elem
可采用的四种泛型搜索算法
- find()
- binary_search()
- count() 返回数值相符的元素个数
- search() 比对某个容器内是否存在某个子序列,存在则返回iterator指向子序列起始处,不存在则返回iterator指向容器末尾
设计泛型算法
泛型算法:与操作的元素类型无关,与操作的容器也无关
vector<int> less_than_10(const vector<int> &vec)
{
vector<int> nvec;
for (int ix = 0; ix < vec.size();++ix)
if (vec[ix]<10)
nvec.push_back(vec[ix]);
return nvec;
}
bool less_than(int v1, int v2)
{return v1<v2 ? true : false;}
bool greater_than(int v1, int v2)
{return v1>v2 ? true : false; }
// 自定义了比较函数,通过第三个参数pred以函数指针的形式,并要求pred只能返回bool
vector<int> filter(const vector<int> &vec,
int filter_value,
bool (*pred)(int, int))
{
vector<int> nvec;
for (int ix = 0; ix < vec.size();++ix)
{
// 调用pred所指函数
// 比较vec[ix]与filter_value
if (pred(vec[ix], filter_value))
nvec.push_back(vec[ix]);
}
return nvec;
}
从寻找vector中小于10的元素一步步扩展其弹性,最后使用函数指针来自定义其判断中的比较操作
接下来继续扩展弹性,通过find_if()替换循环,并用函数指针传入符合条件
Function Object
其本质是某种class的实例对象,由于对function call做了重载操作,其可以当做一般函数使用
引出其主要是为了代替函数指针方式,并提升效率
使用前要包含相关头文件
#include <functional>
e.g
// 使泛型算法sort()以升序排列
sort(vec.begin(), vec.end(), greater<int>());
// binary_search一行运行
binary_search(vec.begin(), vec.end(), elem, greater<int>());
Function Object Adapter
Function Object Adapter对Function Object进行操作
目的:通过adapter将二元运算转换为一元运算符(通过绑定参数的方式)
bind1st:指定值绑定到第一参数
bind2st:指定值绑定到第二参数
vector<int> filter(const vector<int> &vec,
int val, less<int> <)
{
vector<int> nvec;
vector<int>::const_iterator iter = vec.begin();
// bind2nd(less<int>, val);
// 会把val绑定与less<int>的第二个参数上
// 则,less<int>将每个元素拿来与val比较
while ((iter =
find_if(iter, vec.end(),
bind2nd(lt, val)))!=vec.end())
{
nvec.push_back(*iter);
iter++;
}
}
进行进一步的泛型化, 消除vector元素类型、vector容器类型的依赖关系,通过function template的方式
// 对函数中不同类型分别声明其名称以区分
template <typename InputIterator, typename OutputIterator,
typename Elemtype, typename Comp>
OutputIterator filter(InputIterator first, OutputIterator last,
OutputIterator at, const Elemtype &val, Comp pred)
{
while ((first =
find_if(first, last,
bind2nd(pred, val)))!=last)
{
cout << "found value: " << *first << endl;
*at++ = *first++;
}
return at;
}
另一种adapter
为所谓negator,对function object真伪取反,可实现通过function object中的less寻找最大值
使用Map
map定义为一对数值,key通常为字符串作为索引,另一数值为value
e.g 真正的字典
#include <map>
#include <string>
map<string, int> words;
// 输入方式
words["vermeer"] = 1;
words[tword] // 当tword不存在时,会被放于map内,并获得默认值0
// for循环打印整个map
map<string, int>::iterator it = words.begin();
for (; it != map.end();++it)
cout << "key: " << it->first
<< "valu: " << it->second << endl;
// map对象first member对应于key,second对应于value,iterator通过箭头运算符访问
查询map中是否存在某个key的三种方法
1.将key作为索引直接访问,其缺点是key不在map中时,其会被自动加入并设置为默认值
2.map的find()函数(非泛型算法find())
words.find("vermeer")
在其中则返回iterator,指向key/value形成的一个pair。反之返回其end()
3.map的count()函数
其返回某特定项在map内的个数,map中最多只有一个
使用Iterator Inserter
目的:灵活处理目的端容器的大小问题,使其能在需要时进行扩展
insertion adapter
首先要包含对应头文件
#include <iterator>
其可以避免使用容器的assignment(赋值)运算符
1.back_inserter()
以容器的push_back()取代赋值运算,参数为容器本身
vector<int> result_vec;
unique_copy(ivec.begin(), ivec.end(), back_inserter(result_vec));
2.inserter()
以容器的insert()函数取代assignment运算符,其两个参数分别为容器、指向插入操作起点的iterator
vector<string> svec_res;
unique_copy(sevc.begin(), sevc.end(), inserter(svec_res, svec_res.end()));
3.front_inserter()以容器的push_front()函数取代assignment,只适用于list和deque
list<int> ilist_clone;
copy(ilist.begin(), ilist.end(), front_inserter(ilist_clone));
使用iostream Iterator
通过iostream Iterator从标准输入读取,或想标准输出中写入
标准库中有供输入及输出使用的iosream iterator类,分别为istream_iterator和ostream_iterator,分别支持单一元素的读取和写入
使用前需包含iterator头文件
#include <iterator>
从输入读取:
需要一对iterator:first和last
// 绑定至标准输入,作为first
istream_iterator<string> is(cin);
// 不为其指定istream对象,即代表EOF(end of file)
istream_iterator<string> eof;
// 从标准输入写到vector中,并考虑到vector空间问题,使用back_inserter
copy(is, eof, back_inserter(text));
输出:
需要一个ostream_iterator标示字符串元素的输出位置
ostream_iterator<string> os(cout, " ");
第二个参数既可以为C-style字符串,也可以是字符串常量,表示各个元素被输出时的分隔符,默认情况下无分隔
copy(text.begin(), text.end(), os);
非标准输入输出: 将istream_iterator绑定至ifstream object,ostream_iterator绑定至ofstream object