IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++_3——库(functional)与函数对象 -> 正文阅读

[C++知识库]C++_3——库(functional)与函数对象


??这一系列文章的目的是在学习了C++基础后,继续补充一些C++基础和进阶的知识点,包括C++11的相关内容。
以C++11标准为基础。

C++网站:http://www.cplusplus.com/reference/

1 函数对象

任何定义了函数调用操作符的对象都是函数对象(也叫仿函数),C++ 支持创建、操作新的函数对象,同时也提供了许多内置的函数对象,可以像调用函数一样使用。可以自定义函数类如下:

// func.h
class MyType{

    public:
        void operator()(){
            std::cout<<"mmmm\n";
        }
        void operator()(int bbb){
            std::cout<<bbb+10<<"\n";
        }
        int operator()(char a){
            if(isdigit(a))	// isdigit在lacale.h 里面还有很多实用的判断函数
                return atoi(&a);
            else
                return -1;
        }
};
// a.cpp
#include "func.h"
int main(){
    MyType val;
    val();  	// print mmmm
    val(123); 	// print 133
    std::cout<<val('a')<<std::endl; // print -1
}
2 lambda表达式

利用lambda表达式可以编写内嵌的匿名函数,替换独立函数或者函数对象,不用写一个额外的函数或类或结构体,并且使代码更可读。

  1. 基本使用 [ 捕获 ] ( 形参 ) -> 返回类型 { 函数体 }

  2. 捕获指明内部可以访问的外部变量,可以是值捕获,也可以是引用捕获

  3. 返回类型可以省略,由lambda表达式自动推导结果

  4. 编译器会将lambda表达式生成为一个定义了函数调用操作符的类/结构体,这个类/结构体是一个闭包类型,因为捕获了封装作用域内的变量。

  5. 举几个例子

    #include <iostream>
    #include <vector>
    #include <string>
    #include <algorithm>
    
    int main(){
        auto print = [](int s) {std::cout << "value is " << s << std::endl;};
        auto lambAdd = [](int aa, int bb) ->int { return aa + bb;};
        int iSum = lambAdd(10, 11);
        print(iSum);  // print value is 21
    
        int count = 0;
        std::vector<std::string> words{ "An", "ancient", "Pond" };
        std::for_each(words.cbegin(), words.cend(),			// for_each在algorithm.h, 原型为Function for_each (InputIterator first, InputIterator last, Function fn);
                     [&count](const std::string& word)		// 引用捕获外部变量count
                      {
                          if(isupper(word[0])) {			// isupper在lacale.h 里面还有很多实用的判断函数
                              std::cout << word << " " << count << std::endl;
                              count++;
                          }
                      });
    }
    
  6. 能不能修改捕获的变量?
    引用捕获,可以修改。
    值捕获时,不允许修改变量,这与函数的值传递不一样。若想修改,需要关键字mutabe,修改上面的例子如下。要注意所有修改在lambda表达式之外无效,这就跟函数传递值一样了。

    #include <iostream>
    #include <vector>
    #include <string>
    #include <algorithm>
    
    int main(){
        int count = 0;
        std::vector<std::string> words{ "An", "ancient", "Pond" };
        std::for_each(words.cbegin(), words.cend(),
                     [count](const std::string& word) mutable
                      {
                          if(isupper(word[0])) {
                              std::cout << word << " " << count << std::endl;
                              count++;
                          }
                      });
        std::cout<<count<<"\n";
    }
    
  7. 捕获还是直接传参?
    考虑传参是否会生成临时变量存储传参值
    捕获不会生成临时变量,例外情况如 值捕获使用mutable

  8. lambda表达式不能=赋值,可以()初始化拷贝

3 functional
  1. 内置函数对象

    #include <algorithm>
    #include <functional>
    
    // std::plus 加法函数对象类,返回两个值求和的结果
    	int first[]={1,2,3,4,5};
    	int second[]={10,20,30,40,50};
    	int results[5];
    	// transform 将一个/两个容器范围内的元素,根据运算方式计算,将结果存到result
    	std::transform (first, first+5, second, results, std::plus<int>()); 
    	// result : 11 22 33 44 55
    
    // std::not_equal_to 判断不等函数对象类
    	int numbers[]={10,10,10,20,20};
    	// adjacent_find 依据运算方式,判断范围内相邻元素
        int* pt = std::adjacent_find (numbers, numbers+5, std::not_equal_to<int>()) + 1;
        // *pt : 20
    
    // ...
    
  2. std::function

    Class that can wrap any kind of callable element (such as functions and function objects) into a copyable object, and whose type depends solely on its call signature (and not on the callable element type itself).

    1. std::function对象可以包装多种可调用实体,如普通函数、函数指针、函数对象、lambda表达式、指向成员函数的指针、指向成员变量的指针等,std::function对象与可调用实体的类型无关,与可调用实体的签名(输入和返回类型)有关,官网举例如下。
    	// function example
    	#include <iostream>     // std::cout
    	#include <functional>   // std::function, std::negate
    	
    	// a function:
    	int half(int x) {return x/2;}
    	
    	// a function object class:
    	struct third_t {
    	  int operator()(int x) {return x/3;}
    	};
    	
    	// a class with data members:
    	struct MyValue {
    	  int value;
    	  int fifth() {return value/5;}
    	};
    	
    	int main () {
    	  std::function<int(int)> fn1 = half;                    // function
    	  std::function<int(int)> fn2 = &half;                   // function pointer
    	  std::function<int(int)> fn3 = third_t();               // function object
    	  std::function<int(int)> fn4 = [](int x){return x/4;};  // lambda expression
    	  std::function<int(int)> fn5 = std::negate<int>();      // standard function object
    	
    	  std::cout << "fn1(60): " << fn1(60) << '\n';			 // print fn1(60): 30
    	  std::cout << "fn2(60): " << fn2(60) << '\n';			 // print fn1(60): 30
    	  std::cout << "fn3(60): " << fn3(60) << '\n';			 // print fn1(60): 20
    	  std::cout << "fn4(60): " << fn4(60) << '\n';			 // print fn1(60): 15
    	  std::cout << "fn5(60): " << fn5(60) << '\n';			 // print fn1(60): -60
    	
    	  // stuff with members:
    	  std::function<int(MyValue&)> value = &MyValue::value;  // pointer to data member
    	  std::function<int(MyValue&)> fifth = &MyValue::fifth;  // pointer to member function
    	
    	  MyValue sixty {60};
    	
    	  std::cout << "value(sixty): " << value(sixty) << '\n'; // print value(sixty): 60
    	  std::cout << "fifth(sixty): " << fifth(sixty) << '\n'; // print fifth(sixty): 12
    	
    	  return 0;
    	}
    
    1. std::function对象可以swap。std::function对象可以用nullptr判断是否为空,为空时调用将抛出std::bad_function_call异常。
    	#include <iostream>     // std::cout
    	#include <functional>   // std::function, std::plus
    	
    	int main () {
    	  std::function<int(int,int)> foo,bar;
    	  foo = std::plus<int>();
    	
    	  foo.swap(bar);
    	
    	  std::cout << "foo is " << (foo ? "callable" : "not callable") << ".\n";
    	  std::cout << "bar is " << (bar!=nullptr ? "callable" : "not callable") << ".\n";
    	
    	  return 0;
    	}
    
    1. 为什么使用std::function?统一封装,简化调用,类型擦除
    2. std::function对象内部是否存有可调用实体的地址?不同类型的可调用实体如何判断?各种内部原理?这篇博客…可以看看
  3. bind & placeholder

    1. bind绑定一个可调用实体(函数对象、函数指针、成员指针),返回基于该实体的函数对象。
    2. 可调用实体的每个输入参数可以绑定一个值或占位符(placeholder)。绑定值时,返回函数对象调用时,将使用该值。绑定占位符时,返回函数对象调用时,将根据占位符顺序依次传递。
    3. 可以通过模板指定返回类型。
    4. 绑定成员指针时,绑定/传入的第一个参数要求是成员所在类的一个实例化对象,如果是成员函数还有其他参数,那么就继续绑定第二个、第三个…参数。
    5. 简单用法:
    	// bind example
    	#include <iostream>     // std::cout
    	#include <functional>   // std::bind
    	// a function: (also works with function object: std::divides<double> my_divide;)
    	double my_divide (double x, double y) {return x/y;}
        struct MyPair {
    	  double a,b;
    	  double multiply() {return a*b;}
    	};
    
    	int main () {
    	  using namespace std::placeholders;    // adds visibility of _1, _2, _3,...
    	
    	  // binding functions:
    	  auto fn_five = std::bind (my_divide,10,2);               // returns 10/2
    	  std::cout << fn_five() << '\n';                          // 5
    	  // 占位符
    	  auto fn_half = std::bind (my_divide,_1,2);               // returns x/2 
    	  std::cout << fn_half(10) << '\n';                        // 5
    	  // 占位符顺序不同
    	  auto fn_invert = std::bind (my_divide,_2,_1);            // returns y/x 
    	  std::cout << fn_invert(10,2) << '\n';                    // 0.2
    	  // 指定返回类型
    	  auto fn_rounding = std::bind<int> (my_divide,_1,_2);     // returns int(x/y) 
    	  std::cout << fn_rounding(10,3) << '\n';                  // 3
    	  
    	  MyPair ten_two {10,2};
    	  // binding members:
    	  auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
    	  std::cout << bound_member_fn(ten_two) << '\n';           // 20
    	  auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
    	  std::cout << bound_member_data() << '\n';                // 10
    	  return 0;
    	}
    
    1. placeholder为占位符,如std::placeholders::_1std::placeholders::_2std::placeholders::_3等等,理论上数字可以一直增加,需要注意的是这些是变量,或者叫对象,是object。判断一个变量是不是占位符可以用std::is_placeholder<decltype(std::placeholders::_1)>::value
4 一些用于理解的引用

既然用函数对象与调用普通函数有相同的效果,为什么还有搞这么麻烦定义一个类来使用函数对象?主要在于函数对象有以下的优势:

  1. 函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。
  2. 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则。

来自 C++中的函数对象 - 端木月岛的文章 - 知乎

传递函数指针参数时,使用函数对象替代一些函数指针有利于代码复用和扩展
来自 C++ 仿函数

附录
lambda表达式捕获类型
[]默认不捕获任何变量
[=]默认以复制捕获所有变量,少用[&]默认以引用捕获所有变量,少用
[x]仅以复制捕获x,其它变量不捕获[x…]以包展开方式复制捕获参数包变量,也就是可变参数
[&x]仅以引用捕获x,其它变量不捕获[&x…]以包展开方式引用捕获参数包变量,也就是可变参数
[=, &x]默认以复制捕获所有变量,但是x是例外,通过引用捕获[&, x]默认以引用捕获所有变量,但是x是例外,通过复制捕获
[this]通过引用捕获当前对象(其实是复制指针)[*this]通过复制方式捕获当前对象
更多内置函数对象类型
内置函数对象类操作符重载类似其他内置函数对象类
四则std::plusT operator() (const T& x, const T& y) const {return x+y;}std::minus
std::multiplies
std::divides
大小std::not_equal_tobool operator() (const T& x, const T& y) const {return x!=y;}std::less_equal
std::less
std::greater_equal
std::greater
std::equal_to
取反std::negateT operator() (const T& x) const {return -x;}
取余std::modulusT operator() (const T& x, const T& y) const {return x%y;}
逻辑std::logical_orbool operator() (const T& x, const T& y) const {return x||y;}std::logical_not
std::logical_and
位运算std::bit_andT operator() (const T& x, const T& y) const {return x&y;}std::bit_xor
std::bit_or
参考
  1. C++中的函数对象
  2. https://www.apiref.com/cpp-zh/cpp/utility/functional.html
  3. C++ 仿函数
  4. c++中lambda表达式用法
  5. 剖析STD::FUNCTION接口与实现
  6. C++类型擦除与std::function性能探索
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 17:22:53  更:2022-04-18 17:23:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:48:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码