org-page

static site generator

princples and practice using c++ ch21 reading note

1 Algorithms and Maps

1.1 C++ 11 features

  • lambda 表达式 (){}

1.2 Standard Library algorithms

介绍了一些通用算法: find, sort, accumulate, innerprodudt等. 介绍了函数对象.

1.3 The simplest algorithm: find()

最简单常用的算法莫过于find():

template<typename In, typename T>
In find(In first, In end, const T& val)
{
    while(first!=end && *first!=val) ++first;
    return first;
}

它支持任意STL风格的容器, 任意类型.

1.4 The general search:findif()

findif可以传入一个断言(下一节解释这玩意), findif查找满足这个断言的第一个值.

template<typename In, typename T>
In find_if(In first, In end, Pred pred)
{
    while(first!=end && *!pred(*first)) ++first;
    return first;
}

1.5 Function objects

上面说的pred是一个函数对象:一个跟函数的行为类似的对象. 对象可以储存数据, 比如储存被比较的值.

class Larger_than()
{
    int v;
public:
    Larger_than(int vv):v(vv){}
    bool operator()(int x) const {return x>v;}
}

1.5.1 An abstract view of function objects

把楼上Largerthan函数的对象名换成一个模棱两可的名字(比如F), 把int换成模板, 就是一个比较抽象的函数对象了. 因为携带了数据, 函数对象是"有状态"的.

函数对象是STL参数化的重要手段.

性能方面, 使用函数对象当做参数优于使用函数. 为了获取最佳性能, 函数对象要以引用的方式专递, 而且operator()最好实现在class内部. 这样可以为编译器提供足够多的信息进行优化.

比如: 上面的Largerthan可以被编译器优化成一个机器指令, 而不是进行函数调用. 进行一次函数调用的开销大概是运行比较指令的10-50倍. 另外为函数调用生成的代码也会比较多.

1.5.2 Predicates on class members

如果要使用函数对象来比较class member, 那么现有的武器就不太顺手了..我们可能要写许多类似以下的代码:

struct cmp_by_name {
   bool operator()(const Record& a, const Record& b) { return a.name < b.name; }
}

1.5.3 Lambda expressions

C++11 提供对了 lambda表达式.

auto p = find_if(v.begin(), v.end(), (double a){ return a > 31;});

可以理解为(double a){return a > 31;}定义函数对象的语法糖.

1.6 Numerical algorithms

数值运算算法在<numeric>中. (如果以后有机会写引擎的话大概会用到吧…

1.6.1 Accumulate

x = accumulate(b,e,i), 计算[b,e)所有元素与i的和. 返回值x的类型会被用于初始化accumulate, 所以必须明确的把计算结果赋值给一个变量. ( 还真不知道这个..

double s1 = 0;
s1 = accumulate(v.begin(),v.end(),s1); // ok
s2 = accumulate(v.begin(),v.end(),s2); // oops
float3 = 0;
accumulate(v.begin(),v.end(),s3);      // oops

1.6.2 Generalizing accumulate()

运算不限于加法. STL提供了一个4参数版本的accumulate, 可以自定义运算:

template<typename In, typename T, typename BinOp>
// requires Input_iterator<In>() && Number<T>() && Binary_operator<BinOp, Value_type<In>, T>()
T accumulate(In first, In last, T init, BinOp op)
{
    while (first != last) {
        init = op(init, *first);
  ++first;
    }
}

1.6.3 Inner product

内积…计算两个序列中每一对元素的和, 最后相加.

int sum = inner_product( v1.begin(), v1.end(), v2.begin, 0); // v2 maybe have more elements than v1, it's ok.

1.6.4 Generalizing innerproduct()

一个比上上节更加冗长的声明:

T inner_product(In first, In last, In2 first2, T init, BinOp op, BinOp2 op2)

(累爱…

1.7 Associative containers

最常用的关容器非map莫属. 与map类似的结构体还有:associative array, hash table, red-black trees … unorderedmap是为字符串key优化过的map.

这些容器在<map>,<set>,<unorderedmap>,<unorderedset>中.

1.7.1 map overview

STL采用了红黑树实现map – 左子树的key小于父节点, 右子树大于父节点.

map的interface:

class map {
    using value_type = pair<Key, Value>;

    using iterator = sometype1; // 可以理解为一个指向 tree node 的指针
    using const_iterator = sometype2;

    iterator begin();
    iterator end();
    Value& operator[](const Key& k);
    iterator find(const Key& k);
    void erase(iterator p);
    pair<iterator, bool> insert(const value_type&); // 插入一个键值对
}

map的iterator是一个类似于[指向树节点的指针]的东西.

insert方法的返回值要特别注意一下, 它是一个<迭代器, bool>键值对.如果插入成功了, bool是true, 并返回指向新元素的iterator. 如果插入失败bool是false. 这个返回值经常会被忽略.

map支持按照特定方法排序:

map<string, double, No_case> m;

Nocase的默认值是less<Key>.

1.7.2 unorderedmap

在vector中查找的复杂度是O(N), 在map中查找的复杂度是O(log2(N)), 在unorderedmap中是O(1).

STL的unorderedmap是用哈希表实现的: 把key哈希到一个不太长的vector中,用下标索引, 查找复杂度可以降低到接近O(1).

关于vector, map和unorderedmap的使用提示:

  • vector: 没什么事的话就用它吧
  • map: 需要根据key查询, 且key支持比较高效的<运算.
  • unorderedmap: 需要频繁的查找, 且不需要有序的遍历.

1.8 Set

set可以认为是一个只有key的map, 它也是棵红黑树.

  • set没有value, 它不支持operator[].
  • set不需要pair, 使用起来不需要写it->second这种代码, 比map简洁一些.

1.9 Copying

STL提供了3种copy操作: copy, uniquecopy, copyif

1.9.1 basic copy

template<typename In, typename Out>
Out copy(In first, In last, Out res)
{ 
    while ( first != last ) {
        *res = *first;
  ++res;
  ++first;
    }
    return res;
}

注意, res的size需要程序猿自己检查, STL为了性能, 一般不提供边界检查.

1.9.2 Stream iterator

可以利用copy把input stream的内容转移到output stream中.

string from, to;
cin >> from >> to; // source and target file name
ifstream is {from};
ofstream os {to};

istream_iterator<string> ii {is};
istream_iterator<string> eof;
ostream_iterator<string> oo {os, "\n"};

vector<string> b{ii, eos};
sort(b.begin(), b .end());
copy(b.begin(), b.end(), oo);

传入一对iterator给vector作为初始化参数的意思是"读取[a:b)". 上面vector b会读取输入文件的内容, 直到碰到eof为止. 据说如果试验一下这个case(回头再说吧..), 会发现input buffer比想象中的小, 很容易利用这个搞一些overflow什么的.

1.9.3 Uniquecopy

uniquecopy不会拷贝重复的元素

1.9.4 Using a set to keep order

修改一下上面copy例子的最后三行

set<string> b {istream_iterator<string>{is}, istream_iterator<string>{}};
copy(b.begin(), b.end(), ostream_iterator<string>{os, ""}};

这时利用了set的特性:自动去重, 自动排序.

1.9.5 copyif

跟前面的algorithms方法类似, STL也提供了可以使用函数对象的版本.

Out copy_if(In first, In last, Out res, Pred p);

1.10 Sorting and seraching

如果需要保持数据有序, 我们可以使用有序的容器map和set, 也可以使用sort方法. sort()默认使用<作为排序规则, 也可以自己定义排序函数.

当数据有序以后, find就高效多了, 可以用二分查找, binarysearch() 和 equalrange().

bool binary_search(Ran first, Ran last, const T&val);

binarysearch仅能告诉我们要查找的数据是否存在. 如果需要知道元素的位置, 可以用 lowerbound(), upperbound() 或者 equalrange().

1.11 Container algorithms

综上所述, 容器的算法大抵是接受一对iterator做参数, 然后返回一个iterator. 这个设定很好的保证了算法的通用性.

以上.

Comments

comments powered by Disqus