2014-07-07
3.1 指针的算术运算(The Arithmetic of Pointer)
Standard Template Library(STL)主要由两种组件构成:
容器概述:
vector和list这两个容器是所谓的序列式容器(sequential container)。序列式容器会依次维护第一个元素、第二个元素......直到最后一个元素。我们在序列式容器上主要进行所谓的迭代(iterate)操作。
map和set这两种容器属于关联式容器(associative container)。关联式容器可以让我们快速寻找容器中的元素。
所谓map乃是一对对的key/value组合。key用于搜索,value要存储或取出数据。
所谓set,其中仅含有key。我们对它进行查询操作,为的是要判断某值是否存在其中。例如:我们想要建立一组索引表,用来记录新闻、故事中出现的字,我们希望一些中性字眼,如the,an,but排除掉。我们可以把这些中性字放在exclude_word的一个set中,每次放入某个字到索引表之前,查询一下exclude_word。若有,忽略;反之,加入。
泛型算法概述:
所谓泛型算法,提供了许多可施行于容器及数组型别上的操作行为。这些算法之所以称为泛型(generic),因为它们和它们所要操作的元素型别无关。
泛型算法是通过function template技术,达成“与操作对象之型别相互独立”的目的。
假设我们需要一个函数find()要完成以下任务。给定一个存储整数的vector,以及一个整数值。如果此值存在于vector内,返回一个指针指向该值;反之,返回0。
1 int *find(const vector<int>&vec, int value) 2 { 3 for(int ix=0;ix<vec.size();++ix) 4 if(vec[ix]==value) 5 retrun &vec[ix]; 6 return 0; 7 }
新需求1:要求这个函数不仅可以处理整数,也可以处理任何型别。其实,这个任务其实要求我们将泛型算法find()以fuction template的形式呈现:
1 template <typename elemType> 2 elemType *find(const vector<elemType>&vec, const elemType &value) 3 { 4 for(int ix=0;ix<vec.size();++ix) 5 if(vec[ix]==value) 6 retrun &vec[ix]; 7 return 0; 8 }
新需求2:要求这个函数可以处理vector和array内任意型别的元素。
这个任务难度较大,我们将它分割一下:
(1)将array的元素传入find(),而不指明该array;
(2)将将vector的元素传入find(),而不指明该vector。理想情况下,这两个问题的解法会包含我们最初问题的共同解法。
我们先看任务(1):首先要了解array是如何传入函数的,看如下代码:
int min(int *arrary){};
当数组被传递给函数,或者由函数返回时,仅有第一个元素的地址会被传递。
由于函数find()需要知道何时停止对array的读取。
解法之一是:增加一个参数,用来表示array的容量,声明如下:
template <typename elemType> elemType *find(const elemType *arr, int size, const elemType &value);
解法之二是:传入另一个地址,指示array读取操作的终点。我们将此值成为“哨兵”。
template <typename elemType> elemType *find(const elemType *arr, const elemType *sentinel, const elemType &value);
解法二最让人感兴趣的地方是:array从参数表中彻底消失了---这形同解决了小问题(1)
解法一实现:
1 template <typename elemType> 2 elemType *find(const elemType *arr, int size, const elemType &value) 3 { 4 if(!array||size<1) 5 return 0; 6 for(int ix=0;ix<size;++ix) 7 if(arr[ix]==value) 8 retrun &arr[ix]; 9 return 0; 10 }
上述代码中,虽然array是以第一个元素的指针传入find()中,但我们可以看到,仍然可以通过subscript(下标)运算符存取array的每个元素,就如同此arr是个对象(而非指针形式)一般。why?
因为事实上所谓的下标操作就是将array的起始值加上索引值,产生出某个元素的地址,然后该地址在被提领以返回元素值。例如:
arr[2];
会返回array的第三个元素。
*(arr+2)
也返回第三个元素的值。 这里的2,在指针运算中,要把“指针所指之型别”的容量大小考虑进去。若arr第一个元素为1000,那么arr+2的地址为1000+2*4。
解法二实现:
1 template <typename elemType> 2 elemType *find(const elemType *first, const elemType *last, const elemType &value) 3 { 4 if(!first||!last) 5 return 0; 6 for(;first!=last;++first) 7 if(*first==value) 8 retrun first; 9 return 0; 10 }
调用解法二函数find()
string sa[4]={"when","where","who","what"}; string *ps=find(sa,sa+3,sa[3]);
再看任务(2):这个任务是说,无论vector元素类型为何,都能一一存取vector内的每个元素。因为vector和array相同,都是一一块连续内存存储所有元素,所以它们的处理方式相同。但是,切记:vector可以为空。
如果定义一个空的vector,执行是就会抛错,见如下代码:
vector<string> vec; find(&vec[0],&vec[vec.size()-1],search_value);
我们可以封装一下“取用第一个元素的地址”,“取用最后一个元素的地址”
1 template <typename elemType> 2 inline elemType * begin(const vector<elemType> &vec) 3 { 4 return vec.empty()?0:&vec[0]; 5 } 6 template <typename elemType> 7 inline elemType * end(const vector<elemType> &vec) 8 { 9 return vec.empty()?0:&vec[vec.size()-1]; 10 } 11 12 find(begin(vec),end(vec),search_value);
这又是一个难题。list也是一个容器。不同的是,list元素以一组指针相互链接(linked):向前(forward)指针用来寻址下一个(next)元素,回向(backward)指针用来寻址上一个(preceding)元素。
因此,指针的算术运算并不适用于list,因为指针的算术运算必须首先假设所有元素都存储在连续的空间里,然后才能根据当前指针,加上元素大小之后,指向下一个元素。
首先浮起的念头是,多写一份find()函数,使其有能力处理list对象。我们的理由是:array、vector、list的指针行为大相径庭,以至于无法以一个通用的语法来取得其下一个元素。
这样的说法错综复杂。对的部分是,他们的地层指针运行方式,就标准语法而言的确大不相同。错的部分是,我们不需要提供另一个find()函数来支持list。事实上,除了参数表之外,find()的实现内容一点也不需要改变。
解决这个问题的方法是,在底层指针的行为处理上提供一层抽象化机制,取代原来的“指针直接操作的“方式 。
那么,如何对底层指针的操作实现抽象化呢?
实现上述问题后,我们就可以得到方法find()代码:
1 template <typename IteratorType, typename elemType> 2 IteratorType find(IteratorType first, IteratorType last, const elemType &value) 3 { 4 if(!first||!last) 5 return 0; 6 for(;first!=last;++first) 7 if(*first==value) 8 retrun first; 9 return 0; 10 }
这样,array、vector和list就都能调用它了:
1 const int asize=8; 2 int ia[asize]={1,1,2,3,5,8,13,21}; 3 vector<int> ivec(ia,ia+asize); 4 list<int> ilist(ia,ia+asize); 5 6 int *pia=find(ia,ia+asize,1024); 7 8 vector<int>::iterator it; 9 it=find(ivec.begin(),ivec.end(),1024); 10 11 list<int>::iterator iter; 12 iter=find(ilist.begin(),ilist.end(),1024);
以下为容器类(以及string类)的共同操作:
每个容器还提供如下函数:
现有个函数,用户给一个整数vector,我们必须返回一个新的vector,其中内含原vector之中小于10的所有数值。它的代码如下:
1 vector<int> less_than(const vector<int> &vec,int less_tan_val) 2 { 3 vector<int> nvec; 4 for(int ix=0;ix<vec.size();++ix) 5 if(vec[ix]<less_tan_val) 6 nvec.push_back(vec[ix]); 7 return nvec; 8 }
新需求:这个函数允许用户指定不同的比较操作,如大于、小于等等。如何can能将“比较操作”参数化呢?
解法:以函数调用取代less-than运算符,加入第三个参数pred,用它来指定一个函数指针,下面是这个函数声明:
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;) bool greater_than(int v1,int v2){return v1>v2?true:false;)
函数filter第一个版本的定义:
1 vector<int> filter_ver1(const vector<int> &vec, int filter_value, bool (*pred)(int,int)) 2 { 3 vector<int> nvec; 4 for(int ix=0;ix<vec.size();++ix) 5 //调用pred所指函数比较vec[ix]和filter_value 6 if(pred(vec[ix],filter_value)) 7 nvec.push_back(vec[ix]); 8 return nvec; 9 }
调用filter_ver1:
1 vector<int> big_vec; 2 int value; 3 //...填充big_vec和value 4 vector<int> lt_10=filter_ver1(big_vec,value,less_than);
所谓function object,是某种class的实体对象,这类classes对function call运算符进行了重载操作,如此一来,可是function object被当成一般函数来使用。
function object实现出我们原本可能以独立函数加以定义的事物。但又何必如此呢?主要是为了效率。我们可以令call 运算符成为inline,因而消除“通过函数指针来调用函数”时需付出的额外代价。
标准程序库事先定义了一组function objects,分为算术运算(arithmetic)、关系(relational)、逻辑运算(logical)三大类。以下列表中的type会被替换为内建型别或class 型别:
算术运算:plus<type>,minus<type>,...
关系:less<type>,greater<type>,...
逻辑运算:分别对应与&&,||,!运算符:logical_and<type>,logical_or<type>,logical_not<type>
看如何使用function object:
1 //欲使用事先定义的function objects,首先得含入相关头文件 2 #include <functional> 3 4 //sort()会使用底部元素型别所供应的greater_than运算符,将匀速递减排序 5 sort(vec.begin(),vec.end(),greater<int>);
其中的: greate<int>{}会产生一个匿名的class template object,传给sort()。
fuction object less<type>期望外界传入两个值,如果第一个值小于第二个值就返回true。但在本例中,每个元素都必须和用户指定的数值进行比较。理想情况下,我们需要做的就是将less<type>转化为一个一元运算符。这可以通过“将其第二个参数绑定(bind)至用户指定的数值”而达成。这么一来,less<type>便会将每个元素拿出来一一与用户指定的数值比较。
标准程序库提供的adapter(配接器)便应此而生。
见使用了bind2nd adapter的函数filter_ver2:
1 vector<int> filter_ver2(const vector<int> &vec, int filter_value, less<int> <) 2 { 3 vector<int> nvec; 4 vector<int>::const_iterator iter=vec.begin(); 5 6 //bind2nd(lt,val)会把val绑定于less<int>的第二个参数上,这样,less<int>会将每个iter和val比较。 7 while((iter=find_if(iter,vec.end(),bind2nd(lt,val))))!=vec.end()) 8 { 9 nvec.push_back(vec[ix]); 10 iter++; 11 } 12 return nvec; 13 }
接下来如何消除filter()与vector元素型别的相互馆来,以及filter()与vector容器类型的相依关联,意识filter更加泛型化呢?
为了消除它和容器类型间的相依性,我们传入一对iterator[first,last],并将在参数表中增加另一个iterator,用以指定从何处开始复制元素。见如下代码:
1 template <typename InputIterator,typename OutputIterator, typename ElemType, typename Com> 2 OutputIterator filter_ver1(InputIterator first, InputIterator last, OutputIterator at, const ElemType &val, Com pred) 3 { 4 while((first=find_if(first,last,bind2nd(pred,val))))!=last) 5 { 6 cout<<"found value: "<<*first; 7 *at++=*first++; 8 } 9 return at; 10 }