A successful book is not made of what is in it, but what is left out of it.
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ ㅤㅤㅤ— Mark Twain

接触C++ 2.0已经有段时间了,简单总结一下C++中认为是函数的东西,或者说类似于函数的东西,我们从标准的C和C++函数到函数对象和lambda表达式慢慢讲起。

1. 常规函数

C++中定义函数的方式有很多,下面我们举例说明,对于一些耳熟能详的概念就一带而过。

  • 标准C函数
bool greater(int arg1, int arg2) { return arg1 > arg2; }
  • 类成员函数
struct number {
  bool greater(int arg1, int arg2) { return arg1 > arg2; }
};

说明一下,在C++中我一般只使用struct来定义类,而不是使用传统的class,后面我所有的文字都会如此,这纯粹是个人的编程风格,读者只要保持自己的习惯,并一如既往坚持下去就行。

  • 类静态函数
struct number {
  static bool greater(int arg1, int arg2) { return arg1 > arg2; }
};

前面关于普通的函数,类的成员函数以及类的静态方法,相信读者已经耳熟能详了,下面介绍一下C++ 2.0的新式函数定义。

  • C++ 2.0的新式函数

c++11提供了一种新式函数的写法,在函数的末尾说明函数的返回类型,函数开头使用auto关键字,这一用法主要用于编写函数模板。

// C++ 11
auto greater(int arg1, int arg2) -> bool {    /* 尾置返回类型 */
    return arg1 > arg2;
}

上面的用法并不常用,在c++14以后可以完全忽略返回值类型,而由编译器根据return语句自动推断,这里的牵涉内容比较多,就不详细展开了,简单举两个例子。

// C++ 14
int value = 3;
auto answer()  { return value ; }     /* 返回类型 int */
const auto& answer()  { return value ; }     /* 返回类型 const int& */

// 还可以使用 decltype 关键字
decltype(auto) answer()  { return value ; }

2. 函数指针

函数指针(function pointer)它是一个存放函数地址的变量,可以通过这个变量调用该函数。在c++11之前一般使用typedef 关键字去定义函数指针类型,在c++11之后可以使用更具表现力的using来替代。

bool greater(int arg1, int arg2) { return arg1 > arg2; }

// C++11以前
typedef bool (*old_cmp)(int, int);
old_cmp cmp = greater;

// C++11以后
using new_cmp = bool (*)(int, int);
new_cmp  cmp = greater;

3. 仿函数

其实,在C++中一直可以定义和使用像函数一样的对象,它们被称为仿函数(Functor)。本质上,就是一个重载的调用操作符的类(call operator),即定义了operator()的类,可以有任意个数和任意类型的参数。

struct functor { 
    return_type operator()(args...) const { ... }
};

另外,值得提一下的是,根据operator()包含的0个、1个或2个参数,这种Functor分别被称为生成器、一元仿函数或二元仿函数,下面分别举例说明。

  • 生成器
struct increase_generator { 
  int operator()() noexcept { return num++; }
private:
  int num = 0;
};

工作原理非常简单,每次调用increase_generator::operator()时,将成员变量num的值返回,并将num的值增加1。

int main() {
  increase_generator num_generator;
  for (int i = 0; i < 3; ++i) {
    std::cout << num_generator() << std::endl;
  }
}
// output: 0 1 2
  • 一元仿函数
struct cube { 
  constexpr int operator()(const int value) const noexcept { return value * value * value; }
};

顾名思义,这个仿函数对它传递的值做了立方运算,并且这个operator()被声明为const,它的行为类似于数学上的纯函数,即无副作用。这里constexpr的作用,有兴趣的读者可以自行去研究一下。

  • 谓词

一元仿函数一个常用的用途就是当做谓词(predict),即只有一个参数且返回值为bool类型的仿函数。如下:

struct is_even { 
  constexpr bool operator()(const int value) const noexcept { return (value % 2) == 0; }
};

举个使用的例子

int main() {
  std::vector<int> numbers{1, 2, 3, 4, 5};
  numbers.erase(std::remove_if(std::begin(numbers), std::end(numbers), is_even()), 
                std::end(numbers));

  std::copy(std::begin(numbers), std::end(numbers), std::ostream_iterator<int>{std::cout, " "});
  return 0;
}
// output: 1 3 5 

上面的示例使用了Erase-remove惯用法,结合我们定义的is_even仿函数,实现了对vector中偶数元素的删除。

  • 二元仿函数
struct greater { 
  bool operator()(const auto& v1, const auto& v2) const noexcept { return v1 > v2; }
};

4. lambda

  • 看个例子

我们把上面实现的仿函数is_even,同样的用lambda去实现,如下

auto is_even = [] (auto item) { return (item % 2) == 0; };

is_even(2);  // 返回true

可以看到,使用lambda的实现更加简短,表现力也更丰富,不过通常lambda表达式使用都会内联实现,即在应用时实现。
注:上面的语法需要支持C++14及以上的编译器才可以编译成功。

  • 语法
[capture list] (param list) -> return_type { lambda body;}

说明

  • [capture list] 捕获列表,用于捕获外层变量

[] 不捕获任何变量
[&] 捕获外部作用域中所有变量,并作为引用在匿名函数体中使用
[=] 捕获外部作用域中所有变量,并拷贝一份在匿名函数体中使用
[x, &y] x按值捕获, y按引用捕获
[&, x] x按值捕获. 其它变量按引用捕获
[=, &y] y按引用捕获. 其它变量按值捕获
[this] 捕获当前类中的this指针,如果已经使用了&或者=就默认添加此选项

  • (param list) 参数列表

当匿名函数没有参数时,可以省略(param list)部分

  • -> return_type 返回值类型

C++14以后可以省略

  • { lambda body;} 函数实现

5. std::function包装函数对象

std::function 是一个可调用对象包装器,是一个类模板,可以容纳上述所有的可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。

  • 作用

对函数指针(包括普通函数,类成员函数,类静态成员函数),仿函数,lambda表达式做类型消除,也就是说可以将这些可调用实体都转换成std::function类型

  • 示例

定义格式std::function<函数类型>

bool greater_global(int arg1, int arg2) { return arg1 > arg2; }   // 普通函数

struct number {
  bool greater_member(int arg1, int arg2) { return arg1 > arg2; }  // 类成员函数
  static bool greater_static(int arg1, int arg2) { return arg1 > arg2; } // 类静态函数
};

// 仿函数
struct greater_functor { 
  bool operator()(int arg1, int  arg2) const noexcept { return arg1> arg2; }
};

auto greater_lambda = [] (int arg1, int arg2) { return arg1 > arg2; };  // lambda
auto greater_lambda2 = [] (auto arg1, auto arg2) { return arg1 > arg2; };  // 通用 lambda

int main() {
  std::function<bool(int, int)> test_function;
  test_function = greater_global;

  number object;
  test_function = std::bind(&number::greater_member, &object, 
                            std::placeholders::_1, std::placeholders::_2);
  test_function = std::bind(&number::greater_static, 
                            std::placeholders::_1, std::placeholders::_2);

  test_function = greater_lambda ;
  test_function = greater_lambda2;
  test_function = greater_functor();
  
  test_function(3, 2);
}

6. End

持续学习...


已注销
26 声望1 粉丝

下一篇 »
浅析内存分配