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性能探索
Learning C++ Functional Programming by Wisnu Anggoro English | 10 Aug. 2017 | ISBN: 1787281973 | ASIN: B06WVD7CVT | 304 Pages | AZW3 | 2.4 MB Key Features Modularize your applications and make them highly reusable and testable Get familiar with complex concepts such as metaprogramming, concurrency, and immutability A highly practical guide to building functional code in C++ filled with lots of examples and real-world use cases Book Description Functional programming allows developers to divide programs into smaller, reusable components that ease the creation, testing, and maintenance of software as a whole. Combined with the power of C++, you can develop robust and scalable applications that fulfill modern day software requirements. This book will help you discover all the C++ 17 features that can be applied to build software in a functional way. The book is divided into three modules—the first introduces the fundamentals of functional programming and how it is supported by modern C++. The second module explains how to efficiently implement C++ features such as pure functions and immutable states to build robust applications. The last module describes how to achieve concurrency and apply design patterns to enhance your application's performance. Here, you will also learn to optimize code using metaprogramming in a functional way. By the end of the book, you will be familiar with the functional approach of programming and will be able to use these techniques on a daily basis. What you will learn Get to know the difference between imperative and functional approaches See the use of first-class functions and pure functions in a functional style Discover various techniques to apply immutable state to avoid side effects Design a recursive algorithm effectively Create faster programs using lazy evaluation Structure code using design patterns to make the design process easier Use concurrency techniques to develop responsive software Learn how to use the C++ Standard Template Library and metaprogramming in a functional way to improve code optimization About the Author Wisnu Anggoro is a Microsoft Certified Professional in C# programming and an experienced C/C++ developer. He has also authored the books Boost.Asio C++ Network Programming - Second Edition and Functional C# by Packt. He has been programming since he was in junior high school, which was about 20 years ago, and started developing computer applications using the BASIC programming language in the MS-DOS environment. He has solid experience in smart card programming, as well as desktop and web application programming, including designing, developing, and supporting the use of applications for SIM Card Operating System Porting, personalization, PC/SC communication, and other smart card applications that require the use of C# and C/C++. He is currently a senior smart card software engineer at CIPTA, an Indonesian company that specializes in innovation and technology for smart cards. He can be reached through his email at wisnu@anggoro.net. Table of Contents Diving into Modern C++ Manipulating functions in functional programming Applying immutable state to the function Recurring method invocation using recursive algorithm Procrastinating the execution process using Lazy Evaluation Optimizing code with Metaprogramming Running parallel execution using Concurrency Creating and debugging application in functional approach
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值