从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(中)

简介: 从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题

从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(上):https://developer.aliyun.com/article/1522407

2.2 lambda表达式语法

lambda表达式书写格式:

[capture-list] (parameters) mutable -> return-type { statement }
即:
[捕捉列表](参数列表)mutable->(返回值类型){函数体}

[捕捉列表]

捕捉列表是编译器判断lambda表达式的依据,所以必须写[ ],[ ]内可以有参数,后面会讲解。


(参数列表)

参数列表和普通函数的参数列表一样,如果不需要参数传递,可以连同( )一起省略。


mutable

       默认情况下,lambda表达式的形参都是const类型,形参不可以被修改,使用mutable可以取消形参的常量属性。使用mutable时,参数列表不可以省略(即使参数为空)。一般情况下mutable都是省略的。


->返回值类型

       ->和返回值类型是一体的,如->int表示lambda的返回值是int类型。一般情况下省略->返回值类型,因为编译器可以根据函数体中的return推导出返回值类型。为了提高程序的可读性可以写上。


{函数体}

和普通函数一样,{ }里的是lambda的具体实现逻辑。


注意:


在lambda函数定义中,参数列表和返回值类型都是可选部分,可写可不写。

而捕捉列表和函数体必须写,但是内容可以为空。

因此C++11中最简单的lambda函数为:[ ]{ }; 该lambda函数不能做任何事情。

写两个简单的lambda:

int main()
{
  // 两个数相加的lambda
  auto add1 = [](int a, int b)->int {return a + b;};
  auto add2 = [](int a, int b) {return a + b;};
 
  cout << add1(1, 6) << endl;
  cout << add2(1, 6) << endl;
  return 0;
}


C++11之前函数的缺陷例子用lambda表达式解决:

struct Goods
{
  string _name; // 名字
  double _price; // 价格
  int _evaluate; // 评价
  Goods(const char* str, double price, int evaluate)
    :_name(str)
    , _price(price)
    , _evaluate(evaluate)
  {}
};
int main()
{
  vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 7 }, { "西瓜", 5.5,3 }, { "橘子", 7.7, 4 } };
  sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) 
    {return g1._price < g2._price; });
  sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) 
    {return g1._price > g2._price; });
  sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) 
    {return g1._evaluate < g2._evaluate; });
  sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) 
    {return g1._evaluate > g2._evaluate; });
  return 0;
}

交换变量的lambda:

int main()
{
  // 交换变量的lambda
  int x = 0, y = 7;
  auto swap1 = [](int& x1, int& x2)->void{int tmp = x1; x1 = x2; x2 = tmp; };
  swap1(x, y);
  cout << x << "-------" << y << endl;
 
  auto swap2 = [](int& x1, int& x2)
  {
    int tmp = x1;
    x1 = x2;
    x2 = tmp;
  };
  swap2(x, y);
  cout << x << "-------" << y << endl;
  return 0;
}

能不能不传参数交换x和y呢?用上捕捉列表就行:

int main()
{
  // 交换变量的lambda
  int x = 0, y = 7;
  auto swap1 = [](int& x1, int& x2)->void{int tmp = x1; x1 = x2; x2 = tmp; };
  swap1(x, y);
  cout << x << "-------" << y << endl;
 
  auto swap2 = [](int& x1, int& x2)
  {
    int tmp = x1;
    x1 = x2;
    x2 = tmp;
  };
  swap2(x, y);
  cout << x << "-------" << y << endl;
 
  auto swap3 = [x, y](int& x1, int& x2)
  {
    int tmp = x1;
    x1 = x2;
    x2 = tmp;
  };
  swap3(x, y);
  cout << x << "-------" << y << endl;
  swap3(x, y);
  cout << x << "-------" << y << endl;
  return 0;
}

捕获列表说明:

捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用。

  • [var]:表示值传递方式捕捉变量var

[=]:表示值传递方式捕获所有父作用域中的变量(包括this)

[&var]:表示引用传递捕捉变量var,捕捉,不是传参,捕捉中就不存在取地址这一语法。

[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

注意:


① 父作用域指包含lambda函数的语句块。


② 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。


③ 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。


④ 在块作用域以外的lambda函数捕捉列表必须为空。


⑤ 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。


⑥ lambda表达式之间不能相互赋值,即使看起来类型相同。

简单使用:

int main()
{
  int a, b, c, d, e;
  a = b = c = d = e = 1;
 
  auto f1 = [=]() // 全部传值捕捉
  {
    cout << a << b << c << d << e << endl;
  };
  f1();
 
  auto f2 = [=, &a]() // 混合捕捉
  {
    a++;
    cout << a << b << c << d << e << endl;
  };
  f2();
 
  return 0;
}

865af43dc3bf4a78b4114e80ccd192b6.png


2.3 函数对象与lambda表达式

       函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。

class Rate
{
public:
  Rate(double rate) : _rate(rate)
  {}
  double operator()(double money, int year)
  {
    return money * year * _rate;
  }
private:
  double _rate;
};
 
int main()
{
  // 函数对象
  double rate = 0.5;
  Rate r1(rate);
  cout << r1(518, 3) << endl;
 
  // lambda
  auto r2 = [=](double money, int year)->double {return money * year * rate;};
  cout << r2(518, 3) << endl;
 
  return 0;
}

使用函数对象和lambda两种方式进行利率计算,执行的函数体内容相同。

  • 从使用方式上来看,函数对象和lambda表达式完全一样,如中r1和r2所示。
  • rate是函数对象的成员变量,通过构造函数初始化,lambda通过捕获列表来捕获该变量。

调试起来后,查看汇编代码,如上图所示是调用函数对象部分的汇编代码。

  • 创建函数对象时,调用了Rate类域中的构造函数。
  • 调用函数对象时,调用了Rate类域中的operator()成员函数。

上图所示是lambda表达式部分的汇编代码。


       创建lambda表达式时,也是调用了某个类中的构造函数。该类不像函数对象那样明确,而是有很长一串,如上图所示的lambda_0d841c589991fabbf3e571d463f613ab。


       调用lambda表达式时,调用的是该类中的operator()成员函数。函数对象和lambda在汇编代码上是一样的,只是类不同而已。函数对象的类名是我们自己定义的。


lambda的类名是编译器自己生成的。

       编译器在遇到lambda表达式的时候,会执行一个算法,生成长串数字,而且几乎每次生成的数字都不同,也就意味着每次创建的类名都不同。


       lambda表达式的类型只有编译器自己知道,用户是无法知道的。所以要通过auto来推演它的类型,才能接收这个匿名的函数对象。lambda表达式和函数对象其实是一回事,只是lambda表达式的类是由编译器自动生成的。


       此时应该就理解了为什么说lambda其实就是一个匿名的函数对象了吧。所以:lambda表达式相互不可以赋值,因为编译器生成的类不一样,也就意味着不是一个类型。

3. 包装器

3.1 function包装器

function包装器:也叫作适配器。

C++中的function本质是一个类模板,也是一个包装器。

为什么需要function呢?:

ret = func(x);

       上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lambda表达式对象?所以这些都是可调用的类型,如此丰富的类型,可能会导致模板的效率低下。为什么呢?往下看:

template<class F, class T>
T useF(F f, T x)
{
  static int count = 0;
  cout << "count:" << ++count << endl;
  cout << "count:" << &count << endl;
  return f(x);
}
double f(double i)
{
  return i / 2;
}
struct Functor
{
  double operator()(double d)
  {
    return d / 3;
  }
};
int main()
{
  // 函数名
  cout << useF(f, 11.11) << endl;
  // 函数对象
  cout << useF(Functor(), 11.11) << endl;
  // lambda表达式对象
  cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
  return 0;
}

通过上面的程序验证,发现useF函数模板实例化了三份,这会导致效率低下。


3.1.1 function包装器使用

包装器可以很好的解决上面的问题:

std::function在头文件

类模板原型如下

template  function;     // undefined


template

class function;



模板参数说明:

Ret: 被调用函数的返回类型

Args…:被调用函数的形参


function包装器使用:

int f(int a, int b)
{
  return a + b;
}
struct Functor
{
public:
  int operator() (int a, int b)
  {
    return a + b;
  }
};
class Plus
{
public:
  static int plusi(int a, int b)
  {
    return a + b;
  }
  double plusd(double a, double b)
  {
    return a + b;
  }
};
int main()
{
  std::function<int(int, int)> func1 = f; // 函数名(函数指针)
  cout << func1(1, 2) << endl;
 
  std::function<int(int, int)> func2 = Functor(); // 函数对象
  cout << func2(1, 2) << endl;
 
  std::function<int(int, int)> func3 = [](const int a, const int b)
  {return a + b; }; // lambda表达式
  cout << func3(1, 2) << endl;
 
  std::function<int(int, int)> func4 = &Plus::plusi; // 类的静态成员函数
  cout << func4(1, 2) << endl;
 
  std::function<double(Plus, double, double)> func5 = &Plus::plusd;
  cout << func5(Plus(), 1.1, 2.2) << endl; // 类的成员函数需要用对象调用
  return 0;
}


第二份使用代码:

int f(int a, int b)
{
  return a + b;
}
struct Functor
{
public:
  int operator() (int a, int b)
  {
    return a + b;
  }
};
int Plus(int a, int b)
{
  return a + b;
}
 
int main()
{
  function<int(int, int)> f1 = Plus;
  function<int(int, int)> f2 = Functor();
  function<int(int, int)> f3 = [](int a, int b) 
  {
    cout << "[](int a, int b) {return a + b;}" << endl;
    return a + b;
  };
 
  cout << f1(1, 2) << endl;
  cout << f2(10, 20) << endl;
  cout << f3(100, 200) << endl << endl;;
 
  map<string, function<int(int, int)>> opFuncMap; 
  // 类模板又做map第二个模板参数,此时就可以声明可调用对象类型
  opFuncMap["函数指针"] = f;
  opFuncMap["仿函数"] = Functor();
  opFuncMap["lambda"] = [](int a, int b) 
  {
    cout << "[](int a, int b) {return a + b;}" << endl;
    return a + b;
  };
  cout << opFuncMap["函数指针"](1, 2) << endl;
  cout << opFuncMap["仿函数"](10, 20) << endl;
  cout << opFuncMap["lambda"](100, 200) << endl;
 
  return 0;
}


function包装器解决模板的效率低下问题:

template<class F, class T>
T useF(F f, T x)
{
  static int count = 0;
  cout << "count:" << ++count << endl;
  cout << "count:" << &count << endl;
  return f(x);
}
double f(double i)
{
  return i / 2;
}
struct Functor
{
  double operator()(double d)
  {
    return d / 3;
  }
};
int main()
{
  // 函数名
  function<double(double)> f1 = f;
  cout << useF(f1, 11.11) << endl;
  // 函数对象
  function<double(double)> f2 = Functor();
  cout << useF(f2, 11.11) << endl;
  // lambda表达式对象
  function<double(double)> f3 = [](double d)->double { return d / 4; };
  cout << useF(f3, 11.11) << endl;
  return 0;
}

useF函数模板只实例化了一份,这就提高了效率。

从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下):https://developer.aliyun.com/article/1522416

目录
相关文章
|
5天前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
15 1
|
10天前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
26天前
|
算法 安全 编译器
【C++航海王:追寻罗杰的编程之路】C++11(四)
【C++航海王:追寻罗杰的编程之路】C++11(四)
19 0
|
10天前
|
存储 算法 编译器
【C++11】C++11深度解剖(下)
【C++11】C++11深度解剖(下)
13 0
|
10天前
|
存储 安全 程序员
【C++11】C++11深度解剖(上)
【C++11】C++11深度解剖(上)
12 0
|
2天前
|
存储 缓存 C语言
【C语言】字符函数,字符串函数,内存函数
C语言中的字符串函数和内存函数
9 0
【C语言】字符函数,字符串函数,内存函数
|
8天前
|
C语言
C语言5 字符输出函数和格式输出函数
C语言5 字符输出函数和格式输出函数
13 1
|
10天前
|
算法 编译器 C语言
深入浅出C语言—【函数】下
深入浅出C语言—【函数】下
|
22天前
|
Java C语言 C++
定义C语言的int main()函数
定义C语言的int main()函数
|
23天前
|
存储 移动开发 C语言
技术心得记录:嵌入式开发中常用到的C语言库函数
技术心得记录:嵌入式开发中常用到的C语言库函数
16 1