Essential C++读书笔记- 泛型编程

3.1 指针的算术运算

容器
Vector - 连续的存储块
List - 双向链表构成的存储块

考虑要实现一种通用的查找容器的方法find

3.1.1 Step 1 我们要实现一个vector容器的元素查找

很容易实现,find 函数中轮询vector里面所有元素,找到就返回

int* find(const vector &vec, int value)
{
    for( int ix = 0 ; ix < vec.size(); ++ix)
    {
        if (vec[ ix ] == value)
        {
            return &vex[ ix ];
        }
    }

    return std::nullptr;
}

3.1.2 Step 2 实现任意类型的vector元素的查找

这个时候我们引入了模板函数

template
elemType* find(const vector &vec, 
                const elemType& value)
{
    for( int ix = 0 ; ix < vec.size() ; ++ix)
    {
        if ( vec[ix] == value)
        {
            return &vec[ix];
        }
    }

    return std::nullptr
}

你可以用这个函数查找任何诸如vector之类的容器了

3.1.3 Step 3 我要用同一个函数来实现数组的查找,可行么?

这个时候vector要在find函数里面不可见,所以我们想到了指针

template
elemType* find(const elemType *array, int size,
                const elemType& value)
{
    if ( ! array || size < 1 )
    {
        return 0;
    }

    for ( int ix = 0 ; ix < size ; ++ix )
    {
        if ( vec[ix] == value )
        {
            return &vec[ix]
        }
    }

    return std::nullptr;
}

如果是数组,我们只要传递数组的首地址即可;如果是vector,只要传递容器中数据的首地址也能做到

3.1.4 Step 4 如果我现在还要是先list的查找呢

那就有点困难了,因为list底层是一个链表结构,不是连续内存,find函数这个时候就失效了。

3.2 了解Iterator

简单来说Iterator也是一组类,但是却可以实现和指针相同的语法,也提供内置的运算符操作(*, !=, ++ )。
那么我们怎么样来定义这样的一个迭代器类型呢?我们可能有vector,有list,怎么来进行区分?
标准库中是这么来定义的,

std::vector svec;
std::vector::iterator iter = svec.begin();

这里的iter指向vector容器的第一个元素,我们前面说了你可以把它当成指针来用,比如说取第一个string的内容:*iter;指向下一个元素;iter++
这样,看来我们就能解决前一节中的最后一个问题了

template
IteratorType find(IteratorType first, IteratorType last, 
                const elemtType &value)
{
    for (; first != last; ++first)
    {
        if(*first == value)
        {
            return first;
        }
    }
    return last;
}

这个时候,如果你要查找的是一个数组你可以这么写

    const inst asize = 8;
    int ia[ asize ] = { 1, 1, 2, 3, 5, 8, 13, 21};

    int* pia = find( ia, ia+asize, 1024);

vector可以这样

    vector ivec;
    vector::iterator it(ia, ia + asize); 
    it = find(ivec.begin, ivec.end(), 1024);

list可以这样

    list ilist;
    list::iterator iter(ia, ia + asize);
    iter = find(ilist.begin(), ilist.end(), 1024);

看样子大功告成了。

常用的泛型算法

  • 搜索算法: find(), count(), etc;
  • 排序及次序整理算法:merge(), sort(), reverse(), etc;
  • 赋值、删除、替换算法:copy(), remove(), swap(), etc;
  • 关系算法:equal(), include();
  • 生成(generation)与质变(mutation)算法: fill(), for_each(), generate(), transform();
  • 数值算法:accumulate(), partial_sum();
  • 集合算法:set_union(), set_difference()

3.3 顺序型容器

容器 实现机制 优点 缺点 适用场景
vector 连续存储结构,类似于数组 随机访问效率高 随机位置插入或删除元素效率低 高效的随机存取,没有大量插入删除操作的场景
list 双向链表结构 高效的随机插入删除操作 随机访问效率低下,需要维护额外指针 有大量的插入和删除,不关心随机存取
deque 连续存储结构;不同于vector的是提供了两级数组结构,一级和vector一样,另一级维护容器首地址 随机访问方便;方便的插入和删除操作;两端进行push和pop 占用内存多 即需要随机存取,又关心两端数据的插入删除

3.5 设计一个泛型算法(Generic Algorithm)

3.5.1 从问题开始

Step1, 我需要设计一个算法,输入是一个vector,输出是一个新的vector,包含了原vector中所有小于10的值。
很容易做到,

std::vector less_than(std::vector& vec, int level)
{
    std::vector nvec;
    for( int ix = 0; ix < vec.size(); ++ix)
    {
        if(vec[ix] < level)
        {
            nvec.push_back(vec[ix]);
        }
    }
    return nvec;
}

Step 2, 现在我们增加一些难度,允许用户指定不同的操作,比如说过滤大于一个值的元素,或者过滤等于一个值的元素。
一种解决的办法就是,增加第三个参数,一个函数指针,这第三个入参就是用户可以指定的操作,

vector filter_ver1(const vector& vec, 
                    int filter_value,
                    bool (*pred)( int, int))
{
    vector nvec;

    for( int ix = 0 ; ix < vec.size(); ix++)
    {
        if(pred(vec[ix], filter_value))
        {
            nvec.push_back(vec[ix]);
        }
    }

    return nvec;
}

这个时候用户可以自己定义小于操作

bool less_than(int a, int b)
{
  return a < b ? true : false;
}

3.5.2 函数对象

函数对象实际上是某种类的实例对象,这种对象对函数调用运算符()进行了重载操作,而这个时候运算符()重载函数通常是inline的,从效率上来说要优于函数指针。
标准库定义了很多函数对象

对象分类 具体运算
算术运算 plus, minus, negate, multiplies, divides, modules
关系运算 less, less_equal, greater, greater_equal, equal_to, not_equal_to
逻辑运算 分别对应||, &&, !操作,logical_and, logical_or, logical_not

我们来看一个使用这些运算符的例子,加入说我要对一个vector里的元素以降序排序,我们可以这么来写

#include 
sort( vec.begin(), vec.end(), greater());
  • 前两个参数标记范围;
  • 第三个参数告知操作;

3.5.3 函数对象适配器(Function Object Adapter)

我们再回到我们要实现这个泛型算法filter,我们可以使用标准库中使用的这些函数对象了,
Step 3, 使用标准库提供的函数对象less来作为filter的第三个入参,
除此之外我们还希望用函数find_if来替代原来用for循环遍历整个vector的方式,但是我们知道find_if的第三个入参(用来作为查找条件的)应该是一个一元操作函数对象,只接受一个输入参数;但我们这里的sort函数是一个二元操作函数对象,那我们该怎么实现?


std::vector filter(std::std::vector& vec, int val, less& lt)
{
    std::vector nvec;
    
    std::vector::const_iterator iter = vec.begin();
    while(( iter = 
                find_if(iter, vec.end(), 
                    bind2nd(lt, value)) != vec.end())
    {
        nvec.push_back(*iter);
        iter++;
    }
    return nvec;
}

这个时候就引出了本节所要介绍的一个新东西了,叫函数对象适配器,这个适配器会对普通的函数对象进行修改,来满足用户所需。
比如说这个例子里用到的绑定适配器(binder adapter)可以将函数对象的参数绑定到一些特定值,这样我们的二元操作符sort就可以变成我们所需的一元的了。

NOTE: C++11以后引入了std::bind函数,使得适配器操作起来更加灵活

Step4, 消除filter()与vector的元素类型,让我们的泛型算法更加通用
使用模板函数,

template
OutputIterator filter(InputIterator first, InputIterator last, 
                OutputIterator at, ElemType& val, Comp pred)
{
    while((first = find_if(first, last, bind2nd(pred,val))) != last)
    {
        *at++ = *first++;
    }
    return at;
}
  • 首先,要消除filter的返回值类型vector,如前所述的,我们只能返回迭代器;这里定义了返回值迭代器类型OutputIterator;
  • 然后,消除第一个入参的类型vector,也使用迭代器来标记过滤的范围;
  • 之后,加一个参数at,标记filter的结果保存的目的地址;
  • 接着是查找对象以及区别算法的定义。

NOTE: 后面章节中有另一种解决方法,叫做插入迭代器适配器(insert iterator adapter)

这里还要介绍另外一种迭代器,交租哦negator,它会对函数对象的true/false值取反。

3.9 如何使用Iterator Inserter

3.9.1 笨一点的调用泛型算法filter的方法

我们再回到3.5节中最后实现的filter函数,用户使用这个函数的时候,可以这么来做,

void main()
{
    int ia[5] = {1, 4, 2, 0, 9, 8};
    std::vector ivec( ia, ia+5 );

    std::vector ovec(5);

    filter(ivec.begin(), ivec.end(), ovec.begin(), 3, greater());
}

上述的程序中,由于filter函数内部是直接给目标容器的迭代器进行赋值操作,所以调用的时候,需要满足如下的这些条件

  • 预先定义目标容器必须足够大,这样才足以填充filter函数中的每个过滤出来的结果;
    也就是说我们可能需要定义一个和源容器一样大的目的容器,预留足够的空间,这个显然是会带来内存空间的浪费的。

那么我们可不可以定义一个空的容器呢?我们知道,进行赋值操作(assignment)的时候,容器并不会自动扩容,也就是空的容器会一直保持为空,这样调用的时候必然会报错;
那么我们可不可以使用类似push_back这样的操作来替换赋值操作呢?这样的话,我们就不需要定义一个固定大小的目标容器了,答案是有的,这就是所谓的迭代器插入适配器(insertion adapter),

Insertion adapter 目的 入参 使用方法
back_inserter() 以容器的push_back替换赋值操作 目标容器 unique_copy ( ivec.begin, ivec.end, back_inserter(result_vec) )
inserter() 以容器的inserter函数替换赋值操作函数 一个是目标容器,一个是迭代器指向要插入的起点 unique_copy ( ivec.begin, ivec.end, inserter(vec, vec.end()) )
front_inserter() 以容器的push_front()函数替换赋值操作 目标容器 只适用list和deque,copy(ilist.begin(), ilist.end(), front_inserter( ilist_clone ))

3.9.2 借助Iterator Inserter之后的聪明方法

借助back_inserter,

filter(ivec.begin(), ivec.end(), back_inserter(ovec), 3, greater());

3.10 使用iostream iterator

这节首先我们考虑要实现一个新的问题,

  • 从标准输入读取字符,保存到一个vector容器中;
  • 然后进行排序;
  • 然后通过标准输出到设备上;

3.10.1 首先还是笨点的办法

很容易理解,标准输入都进来,写入text,然后排序,然后写出去,

int main()
{
    std::string word;
    std::vector text;

    while (std::cin >> word)
    {
        text.push_back(word);
    }

    std::sort(text.begin(), text.end());

    for (int ix = 0; ix < text.size(); ix++)
    {
        std::cout << text[ix] << std::endl;
    }
}

3.10.2 借助标准库的iostream迭代器的聪明方法

这里用到了iostream迭代器

  • 首先我们考虑使用copy算法,源是cin,目的是text;
  • copy算法也有三个入参,前两个是源的范围(类似指针的范围);最后一个是text,这里我们借助back_inserter来把copy内部的赋值操作转换成了push_back操作;
  • 然后是输出,定义一个ostream_iterator,用于作为copy的目的;
int main()
{
    std::istream_iterator is(std::cin);
    std::istream_iterator eof;

    std::vector text;
    std::copy(is, eof, std::back_inserter(text));

    std::sort(text.begin(), text.end());

    std::ostream_iterator os(std::cout, " ");
    std::copy(text.begin(), text.end(), os);
}

3.10.3 来看看上述第二种方法带来的妙用吧

假如说这个时候我又有新的需求,不再从标准输入读入,从标准输出写出;而是要从文件读入,文件写出,该怎么做?
很简单,只要做到如下两点

  • istream_iterator绑定到ifstream对象
  • ostream_iterator绑定到ofstream对象
    这就大功告成了
int main()
{
    std::ifstream in_file("in.txt");           // 新建ifstream和ofstream
    std::ofstream out_file("out.txt");

    std::istream_iterator is(in_file);   // in_file替换std::cin
    std::istream_iterator eof;

    std::vector text;
    std::copy(is, eof, std::back_inserter(text));

    std::sort(text.begin(), text.end());

    std::ostream_iterator os(out_file, " ");  // out_file替换std::cout
    std::copy(text.begin(), text.end(), os);
}

这就是泛型带来的优势,一套代码,满足你各种需求。

你可能感兴趣的:(Essential C++读书笔记- 泛型编程)