C++11 函数式编程

C++11 也支持了好多新特性,表达力增强了许多。其实我看主要是人家别的语言都原生或更新支持了,再不支持就太不好意思了,再加上最近 Java 更新速度都快赶上 Chrome 了,7、8、9接踵而至。相较好像 C++ 居委会好像天天无所事事似的,幸好 C++ 是以年份编号,03 之后就是 11 了 :)

lambda

不得不说 lamdba 实在是把利器, 从 《黑客与画家》 上盗个题目,高阶函数加法:

add(a)(b)

function<int(int)> add(int a)
{
  return [a](int b){ return a + b; };
}

int main()
{
  cout << add(3)(4) << endl;

  return 0;
}

这代码好像比其他语言的要长点~,索性改成纯 lambda 的

auto add = [](int a)->function<int(int)> {
  return [a](int b) {
    return a + b;
  };
};

cout << add(3)(4) << endl;

在 Visual Studio 2013 里返回值声明 ->function<int(int)> 不能省略,傻傻的 VS 编译器表示不能自动推导返回值类型。 而试了 g++ 4.82 就不用写明这个返回类型。

这里我们也看到了 auto 的好处,再也不用写乱七八糟好长好恶心的声明,直接 auto 了事,比如这里:

auto add = [](int a)->function<int(int)>{
  return [a](int b){
    return a + b;
  };
};

vector<function<int(int)>> adds(10);

for (int i = 0; i < adds.size(); ++i)
  adds[i] = add(i);

for (auto _add : adds)
  cout << _add(1) << endl;

上面几个例子有点像是奇技淫巧,感觉实际中用处不大的样子。还是来个实际的,大家会遇到的:排序。

struct Student
{
  string name;
  int age;
  Student(string name, int age) : name(name), age(age){}
};

int main()
{
  vector<Student*> students;

  students.push_back(new Student("aaa", 12));
  students.push_back(new Student("bbb", 10));

  sort(students.begin(), students.end(), [](Student *s1, Student *s2){
    return s1->age < s2->age;
  });

  for (auto s : students)
    cout << s->name << " " << s->age << endl;

  return 0;
}

这么小的函数,单独再写一个实在麻烦,而 lambda 函数就小的刚刚好,如果输入也能自动推断就更好了,直接就写 [](auto s1, auto s2)

函数式编程

Python 中函数式编程核心的三个方法是 map reduce filter,而 Ruby 中是 map inject select。大概的表达的意思都是映射、规约和筛选。 在 C++ 中,我找到了几个类似函数,结合 lambda 写起来杠杠的~

transform
accumulate
for_each

Map

将一组数据统一进行某一操作, 如下求平方

vector<int> nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

transform(nums.begin(), nums.end(), nums.begin(), [](int n){
  return n*n;
});

for (auto n : nums)
  cout << n << " ";

Reduce

这个看着比较靠谱的就是 accumulate

auto sum = accumulate(students.begin(), students.end(), 0, [](int acc, Student* s){
  return acc + s->age;
});

// 别被名字骗啦,不止是累加~
// 据说这种 begin / end 的写法是新推荐的,统一数组和 vector
// vector<int> nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int nums[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto muti = accumulate(begin(nums), end(nums), 1, [](int i, int j){
  return i * j;
});

Filter

选择元素 找了半天没有找到对应 filter 的,所幸有 for_each 可以自己写

vector<int> result;
for_each(nums.begin(), nums.end(), [&result](int n){
  if (n % 3 == 1)
    result.push_back(n);
});

for (auto n : result)
  cout << n << " ";

其实吧这些都是可以用 for_each 或者 for(auto s : students) 改写的。而且看着也都并不麻烦。 不过要吐槽的一点就是写着越来越像 js 了。

One More Thing

还有一些函数也是经常配合使用的。

iota
all_of
any_of
copy_if
count_if
remove_if
...

PS

还有个不错的东东也挺不错的,元组 tuple。

看到元组的时候,首先想到的应用是函数返回多个值。 比如返回最大值及坐标,还能通过 idx == -1 判断是否是有效值。

tuple<int, int> max(vector<int> &vec)
{
  int value=0, idx=-1;
  for ( auto it=begin(vec); it!=end(vec); ++it)
  {
    if ( idx=-1 || *it>value )
      tie(value, idx) = make_tuple(*it, it-begin(vec));
  }
  return make_tuple(value, idx);
}

// main()
int value, idx;
// tie(value, idx) = max(nums);
tie(value, ignore) = max(nums);

auto max_idx = max(nums);
get<0>(max_idx);  // value
get<1>(max_idx);  // idx

然后想到的就是鸭子类型,tuple 作为能容纳不同类型的容器,如果加上容器遍历,这样就能调用具有相同方法的对象。

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

很可惜,我没有找到这样的遍历方法,有用函数模板写的遍历方式,不够通用普遍。再想想要实现这个,貌似还需要动态类型和反射,估计很难办到~

不过总体来说 tuple 还是很有意思的~