zoukankan      html  css  js  c++  java
  • 《Essential C++》读书笔记 之 泛型编程风格

    《Essential C++》读书笔记 之 泛型编程风格

    2014-07-07

    3.1 指针的算术运算(The Arithmetic of Pointer)

      新需求1

      新需求2

      新需求3

    3.2 了解 Iterators(泛性指针)

    3.3 所有容器的共通操作

    3.6 如何设计一个泛性算法

      Function Objects

      Function Object Adapters

     

     

    Standard Template Library(STL)主要由两种组件构成:

    • 一是容器(container),包括vector,list,set,map等类;
    • 另一种是用以操作这些容器类的所谓泛型算法(generic algorithm),包括find(),sort(),replace(),merge()等等。

    容器概述

    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技术,达成“与操作对象之型别相互独立”的目的。

    3.1 指针的算术运算(The Arithmetic of Pointer)


     返回

    假设我们需要一个函数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 }
    View Code

    新需求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 }
    View Code

    新需求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 }
    View Code

    上述代码中,虽然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 }
    View Code

    调用解法二函数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);
    View Code

    新需求3:令它也支持list类。

    这又是一个难题。list也是一个容器。不同的是,list元素以一组指针相互链接(linked):向前(forward)指针用来寻址下一个(next)元素,回向(backward)指针用来寻址上一个(preceding)元素。

    因此,指针的算术运算并不适用于list,因为指针的算术运算必须首先假设所有元素都存储在连续的空间里,然后才能根据当前指针,加上元素大小之后,指向下一个元素。

    首先浮起的念头是,多写一份find()函数,使其有能力处理list对象。我们的理由是:array、vector、list的指针行为大相径庭,以至于无法以一个通用的语法来取得其下一个元素。

    这样的说法错综复杂。对的部分是,他们的地层指针运行方式,就标准语法而言的确大不相同。错的部分是,我们不需要提供另一个find()函数来支持list。事实上,除了参数表之外,find()的实现内容一点也不需要改变。

    解决这个问题的方法是,在底层指针的行为处理上提供一层抽象化机制,取代原来的“指针直接操作的“方式 。

    3.2 了解Iterators(泛型指针)


     返回

    那么,如何对底层指针的操作实现抽象化呢?

    • 第一:需要一组对象,提供有如内建运算符(++, *, ==, !=)一般运算符,并允许我们只为这些运算符提供一份代码实现。我们可以利用c++的类机制来完成。
    • 第二:要设计一组iterator classes,让我们得以使用“和指针相同的语法”进行程序的撰写。

    实现上述问题后,我们就可以得到方法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 }
    View Code

    这样,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);
    View Code

    3.3 所有容器的共通操作


     返回

    以下为容器类(以及string类)的共同操作:

    • equality(==)和inequality(!=)运算符,返回true或false。
    • assignment(=)运算符,将某个容器复制给另一个容器。
    • empty()会在容器无任何元素是返回true,否则返回false。
    • szie()传用容器内当前含有的元素数目。
    • clear()删除所有元素。

    每个容器还提供如下函数:

    • begin()返回一个iterator,指向容器第一个元素。
    • end()返回一个iterator,指向容器的最后一个元素的下一个位置。
    • insert()将一个或某个范围内的元素安插到容器内
    • erase()将容器内的单一元素或某个范围内的元素删除。 

    3.6 如何设计一个泛性算法


     返回

    现有个函数,用户给一个整数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 }
    View Code

    新需求:这个函数允许用户指定不同的比较操作,如大于、小于等等。如何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 }
    View Code

    调用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);
    View Code

    Function Objects


     返回 

    所谓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>);
    View Code

    其中的: greate<int>{}会产生一个匿名的class template object,传给sort()。

    Function Object Adapters


     返回

    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> &lt)
     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 }
    View Code

    接下来如何消除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 }
    View Code
  • 相关阅读:
    C# winform中 窗体缩放自适应的方法(不同电脑/不同分辨率)
    C# WinForm窗体及其控件自适应各种屏幕分辨率
    Socket网络编程
    C# Socket编程
    获取当前程序目录
    C# winfrom界面跳转闪烁问题解决方法
    C# 屏蔽windows功能键
    c#多线程调用有参数的方法
    C# winform Visual Studio Installer打包教程
    查询重复的数据
  • 原文地址:https://www.cnblogs.com/Ming8006/p/3830078.html
Copyright © 2011-2022 走看看