【C++进阶】之C++11的简单介绍(三)

在这里插入图片描述

📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

【C++进阶】之C++11的简单介绍(三)

  • lambda表达式
    • 为什么要有lambda表达式
    • lambda表达式解决上述问题
    • lambda表达式的语法
      • lambda表达式中值捕获与引用捕获的区别
    • lambda表达式在类函数作用域中捕获this对象(非静态函数)
      • 深入理解值捕获lambda的常性与类里面的常量成员函数的区别
        • 再谈类和对象中的this指针
    • lambda表达式的底层原理
      • 不同的lambda表达式不能互相赋值
  • 包装器
    • 包装器引入的初衷
    • 包装器的语法
      • 包装器介绍
        • 包装器包装类成员函数的不同方式
      • 使用包装器解决上述问题
    • 包装器的其它应用
    • 绑定
      • 绑定修改可调用对象的参数个数
      • 绑定修改可调用对象的参数的调用顺序

lambda表达式

为什么要有lambda表达式

库里面有一个排序函数sort,它的接口如下:
在这里插入图片描述

前两个参数是要排序的容器的起始迭代器,最后面一个是比较规则,它是一个实例化后的对象,可以是类对象也可以是函数指针。

如果我们要排的是内置类型:

#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>using namespace std;template<class T>
void print(T& v)
{for (auto& num : v)cout << num << " ";cout << endl;
}
int main()
{srand(time(NULL));vector<int> v;for (int i = 0; i < 10; ++i)v.emplace_back(i);sort(v.begin(), v.end(), greater<int>());print(v);return 0;
}

运行结果:

在这里插入图片描述
对内置类型排序可以使用类里面的比较函数,传greater<T>对象就是降序,传less<T>对象就是升序,但是加入我们要对日期类排序呢?就必须自己写一个仿函数或者函数指针对象传进去了。

#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>using namespace std;class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){//cout << "Date(int year, int month, int day)" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){//cout << "Date(const Date& d)" << endl;}friend ostream& operator<<(ostream& is, const Date& date);friend int compare(const Date& a, const Date& b);
private:int _year = 1;int _month = 1;int _day = 1;
};ostream& operator<<(ostream& os, const Date& date)
{os << date._year << " " << date._month << " " << date._day;return os;
}
template<class T>
void print(T& v)
{for (auto& num : v)cout << num <<   endl;}int compare(const Date& a, const Date& b)
{return a._year > b._year || (a._year == b._year && a._month > b._month) || (a._year == b._year && a._month == b._month && a._day > b._day);
}
int main()
{srand(time(NULL));vector<Date> v;for (int i = 0; i < 10; ++i)v.emplace_back(i+rand(),rand()%13,rand()%31);print(v);cout << "-----------------------------------------" << endl;sort(v.begin(), v.end(), compare);print(v);return 0;
}

运行结果:

在这里插入图片描述
也可以创建一个仿函数对象传进去。

这样做太复杂了,我们只需要实现一个排序的功能,就要去写一个函数或者是一个类,如果每次排序逻辑不一样,岂不是每次都要多写一个类,这给程序员们带来了极大的不便,所以lambda表达式出现了。

lambda表达式解决上述问题

#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>using namespace std;class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){//cout << "Date(int year, int month, int day)" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){//cout << "Date(const Date& d)" << endl;}friend ostream& operator<<(ostream& is, const Date& date);friend int compare(const Date& a, const Date& b);
public:int _year = 1;int _month = 1;int _day = 1;
};ostream& operator<<(ostream& os, const Date& date)
{os << date._year << " " << date._month << " " << date._day;return os;
}
template<class T>
void print(T& v)
{for (auto& num : v)cout << num <<   endl;}int main()
{srand(time(NULL));vector<Date> v;for (int i = 0; i < 10; ++i)v.emplace_back(i+rand(),rand()%13,rand()%31);print(v);cout << "-----------------------------------------" << endl;sort(v.begin(), v.end(),[](const Date& a,const Date& b){return a._year > b._year || (a._year == b._year && a._month > b._month) || (a._year == b._year && a._month == b._month && a._day > b._day);});print(v);return 0;
}

运行结果:

在这里插入图片描述

lambda表达式的语法

从上面的代码可以看出lambda表达式是一个匿名函数,下面让我们具体学习它的语法:
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

  • [capture-list]:捕捉列表,该表达式总是出现在开始位置,编译器通过捕捉列表判断接下来的代码是否是lambda表达式,捕捉列表能够捕捉上下文的变量供lambda函数使用。(同一作用域函数类的变量或者是全局的变量或类型)。
  • parameters):参数列表,该表达式内需想一般函数一样写出lambda函数的形参列表。如果是无参函数,可以和()一起省略。
  • mutable:在lambda表达式中,如果捕获列表中的变量是捕获的,并且你希望在lambda函数体内修改这些变量的值,你需要在lambda定义时加上mutable关键字。这允许lambda函数修改其捕获的变量。
  • ->returntyp没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。

lambda表达式中值捕获与引用捕获的区别

  1. 值捕获

当你在lambda表达式中值捕获了一个变量,这个函数是默认具有常性的,也就是你值捕获的变量都不可修改,带有const修饰符,这样的设计的初衷可能是认为既然你值捕获了一个变量,肯定是只希望准确的使用它,所以修改是无意义的(而且修改后可能会导致我们的结果出现错误),所以lambda表达式默认给它带const属性。

  1. 值捕获是对原变量的拷贝(地址不同):
#include<iostream>using namespace std;int main()
{int b = 0;cout << &b << endl;auto Func = [b] {cout << &b << endl; const int& b_ = b;};Func();return 0;
}

运行结果:

在这里插入图片描述
const int 是它的类型,如果你把引用变量的类型的const属性丢掉,就无法对其引用:

在这里插入图片描述

  1. 添加mutable修饰符,去掉值捕获变量的const属性,添加mutable后参数列表的()不能省略,即使它的参数为空:
#include<iostream>using namespace std;int main()
{int b = 0;cout << &b << endl;auto Func = [b] () mutable{b = 3;cout << &b << endl;};Func();cout << b << endl;return 0;
}

在这里插入图片描述
这里添加了mutable修饰符后我们对lambda函数中捕获的b可以修改了,但是由于是值捕获,并无法影响外面。

  1. lambda值捕获一个变量后,类型和外面的被捕获变量保持一致,即使是const修饰符也会一致(&不会)。

在这里插入图片描述
2.引用捕获

引用捕获的变量,在lambda表达式中,不会给它加上const属性,所以mutable对引用捕获的变量无影响,因为我们本意就是想修改它们,如果默认加上const属性就无意义了。

#include<iostream>using namespace std;int main()
{int b = 0;auto Func = [&b] (){b = 3;cout << &b << endl;};Func();cout << b << endl;return 0;
}

运行结果:

在这里插入图片描述

捕获列表的语法:

  • []:空捕获,不捕获任何变量。
  • [var]:值捕获var变量。
  • [&var]:引用捕获var变量。
  • [=]:值捕获所有变量,包括this类中的指针。(在父作用域内或者全局中可访问的变量)
  • [&]:引用捕获所有变量。包括this指针。(在父作用域内或者全局中可访问的变量)
  • [=,&var]:值捕获其它变量,引用捕获var变量。
  • [&,var]:引用捕获其它变量,引用捕获var变量。

注意:

  • 捕捉列表不能捕获重复的变量否则会报错。

在这里插入图片描述

  • 父作用域指包括lambda表达式的语句块。

lambda表达式在类函数作用域中捕获this对象(非静态函数)

#include <iostream>  
#include <functional>  
using namespace std;class MyClass {
public:int value;void doSomething() {// 使用lambda表达式,并在内部直接使用this指针  auto lambda = [this]() {std::cout << "Accessing member via this: " << this->value << std::endl;// 也可以直接访问,无需this->,如:std::cout << value << std::endl;  };// 调用lambda  lambda();std::cout << value << std::endl;// 修改成员变量,并通过lambda再次访问以验证修改  value = 42;lambda();}
};int main() {MyClass obj;obj.value = 10;obj.doSomething();return 0;
}

运行结果:

在这里插入图片描述

深入理解值捕获lambda的常性与类里面的常量成员函数的区别

#include <iostream>  
#include <functional>  
using namespace std;class MyClass {
public:int value;void doSomething() {// 使用lambda表达式,并在内部直接使用this指针  auto lambda = [this]() {std::cout << "Accessing member via this: " << this->value << std::endl;this->value = 2;// 也可以直接访问,无需this->,如:std::cout << value << std::endl;  };// 调用lambda  lambda();std::cout << value << std::endl;}void Func() const//const MyClass * const this{//Func为常量函数,不可修改//value = 3;}
};int main() {MyClass obj;obj.value = 10;obj.doSomething();return 0;
}

运行结果:

在这里插入图片描述

为什么lambda表达式使用的是值捕获,还是可以修改this指针指向的内容呢?但是常量成员函数却不能修改:

  • lambda表达式的const修饰的是变量本身,而不是变量指向的内容,在这个例子中也就是:MyClass * const this,const修饰的是this变量本身,它本身保存的地址不能修改,但是它指向的内容可以修改,这就是我们可以修改它的成员的原因,值捕获是拷贝了和this指针一样的地址。
  • const修饰成员函数则不同,它修饰的是this指针指向的内容,这点我们在类和对象部分讲过,在本例子中也就是:MyClass const * const this,但是由于this指针变量本身就具有常量属性(它是一个右值,我们稍后会证明)。
再谈类和对象中的this指针

this指针是一个右值,具有常性。我们无法对右值取地址。

class MyClass {
public:int value;void Func() //const MyClass * const this{cout << &this << endl; //this指针是一个右值无法取地址}
};

报错截图:

在这里插入图片描述

当我们使用const修饰成员函数时,即使使用右值引用引用this指针,也只能做到修改右值引用变量本身,无法修改它指向的内容。这是因为const修饰的是*this,也就是this指针指向的内容。我们上面在右值引用引用指针部分已经谈到了。

class MyClass {
public:int value;void Func() const//const MyClass * const this{MyClass const* const &lptr = this;//少一个const都无法使用左值引用MyClass const* ptr = this;//不是引用,指针变量可以不使用const修饰,但是它指向的内容必须用const修饰MyClass const*&& rptr = this;//const不修饰*rptr就会报错}
};

只要在成员函数中就可以访问本类的this指针,都可以省略->或者.访问符号。(特指lambada表达式在类成员函数时)

#include <iostream>  
#include <functional>  
using namespace std;class MyClass {
public:int value;void doSomething() {// 使用lambda表达式,并在内部直接使用this指针  auto lambda = [this]() {//MyClass * const thisstd::cout << "Accessing member via this: " << value << std::endl;value = 2;// 也可以直接访问,无需this->,如:std::cout << value << std::endl;  };// 调用lambda  lambda();std::cout << value << std::endl;}
};int main() {MyClass obj;obj.value = 10;obj.doSomething();return 0;
}

但是有重名的情况还是不能省略,不然可能出现未定义行为。友元函数和友元类中不能这样做,因为它们只是拥有访问我们类私有成员的权利,并不直接属于类域。

运行结果:

在这里插入图片描述

引用捕获this指针和值捕获区别不大(由于this指针是右值,无法修改),我们这里不再谈到。

lambda表达式的底层原理

lambda表达式是借助仿函数实现的。

#include<iostream>
using namespace std;class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};int main()
{double rate = 0.49;Rate r1(rate);r1(10000, 2);// lambdaauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);auto f1 = [] {cout << "hello world" << endl; };auto f2 = [] {cout << "hello world" << endl; };f1();f2();return 0;
}

反汇编查看其底层调用:

在这里插入图片描述

  • 从反汇编的底层调用中可以看出,lambda表达式其实就是一个匿名的仿函数类,只能创建一次,因为再次创建的时候你无法知道其类名称,也就无法再创建对象了。我们通过auto自动推导类型的功能,创建了一个仿函数对象,调用的时候是调用其底层的operator()函数。

这里虽然f1f2的函数体一样,但是它们仍然是两个不同类型的lambda表达式。

在这里插入图片描述

其实我们通过decltype关键字也可以实现再次创建相同类型的lambda表达式。

#include<iostream>
using namespace std;int main()
{double rate = 0.49;// lambdaauto r1 = [=](double monty, int year)->double {return monty * rate * year; };decltype(r1) r2 = r1;//支持赋值运算符重载decltype(r1) r3(r2);//支持拷贝构造//decltype(r1) r4;禁掉了 默认构造cout << typeid(r1).name() << endl;cout << typeid(r2).name() << endl;cout << typeid(r3).name() << endl;return 0;
}

运行结果:

在这里插入图片描述

不同的lambda表达式不能互相赋值

在介绍完lambda表达式的原理之后想必就很好理解了,两个仿函数对象连类型都不同,怎么能互相赋值呢。

#include<iostream>
using namespace std;int main()
{auto f1 = [](){cout << "我是lambada表达式" << endl;};auto f2 = [=](){cout << "我是lambada表达式" << endl;};//f2 = f1;这是不正确的,会报错return 0;
}

在这里插入图片描述

但是可以将lambda对象赋值给同类型的函数指针(参数和返回值一样)。前提是这个lambda对象没有捕获外部变量(如果它捕获了外部变量,则单纯的函数无法表示这种状态)。此时我们可以认为lambda表达式的行为和函数没有区别,但是底层还是一个类。

在这里插入图片描述

  • 我们可以看到,即使f1没有捕获任何外部变量,打印出它的类型还是一个自定义的类,至于它是如何赋值给符合要求的函数指针的,我们就不得而知了。
#include<iostream>
using namespace std;typedef void (*pf) ();class MyClass
{
public:void operator()(){cout << "我是仿函数对象" << endl;}
};
int main()
{int a = 0;auto f1 = [](){cout << "我是lambada表达式" << endl;};f1();auto f2 = [=](){cout << a << endl;cout << "我是lambada表达式" << endl;};f2();MyClass f3;pf Pf1 = f1;//可以赋值//pf Pf2 = f2;return 0;
}

如果把f2赋值给同类型的函数指针变量就会报错,因为它依赖外部变量,函数无法表示这种状态,此时可以把lambda表达式看作一个仿函数对象。

在这里插入图片描述

包装器

包装器引入的初衷

我们已经学习了仿函数、lambda表达式和函数指针,它们三个都可以去像函数一样使用。但是传参上有各有不同,特别是函数指针,在类里面还不能通过传类型来定义,必须传函数指针给类方法,很不方便;lambda表达式更是连类型我们都不能直观的看见。这不是最主要的,最主要的是,它们三个的存在会让模板的效率变得低下。

看下面的代码:

#include<iostream>using namespace std;template<class F,class T>
T Functor(F f,T data)
{static int count = 0;count++;cout << count << endl;cout << &count << endl;return f(data);
}class  MyClass
{
public:double operator()(double data){return data / 2;}
};double func1(double x)
{return x * 3;
}int main()
{MyClass fun;Functor(fun, 3.4);Functor(func1, 10.9);Functor([](double x)-> double {return x / 4; }, 2.2);return 0;
}

运行结果:

在这里插入图片描述

如果调用的是同一份模板函数,count是静态成员,应该只会定义一次。但运行结果显示count定义了三次,所以函数模板实例化了三份。

能否存在一种机制,可以让我们不区分它们三个,让它们的使用方式一样呢?于是C++11引入了包装器的语法,也叫function

包装器的语法

包装器介绍

包装器是functional头文件中封装的一个类模板。

在这里插入图片描述

template <class Ret, class... Args> class function<Ret(Args...)>;

Ret是函数的返回值,Args是参数包,也就是函数的一系列参数。
我们只需要了解包装器的使用即可,对于其底层不用过多研究。

下面介绍function的各种使用场景:

#include<functional>
#include<iostream>using namespace std;
//函数
int f1(int a, int b)
{return a + b;
}//仿函数对象
class f2
{
public:int operator()(int a, int b){return a + b;}
};//普通类class f3
{
public:static int f1(int a, int b){return a + b;}int f2(int a, int b){return a + b;}
};int main()
{function<int(int, int)> func1 = f1;//包装函数cout << func1(1, 2) << endl;//包装仿函数对象两种方式function<int(int, int)> func2 = f2();//方式1function<int(f2, int, int)> func3 = &f2::operator();//它的底层我们不关心如何实现cout << func2(1, 2) << endl;cout << func3(f2(),1, 2) << endl;//包装类中的静态函数function<int(int, int)> func4 = &f3::f1;//包装类中的普通成员方法function<int(f3, int, int)> func5 = &f3::f2;cout << func4(1, 2) << endl;cout << func5(f3(), 1, 2) << endl;return 0;
}

运行结果:

在这里插入图片描述

包装器包装类成员函数的不同方式

对于其第一个参数(类对象)支持不同传参方式

#include<iostream>
#include<functional>using namespace std;class MyClass
{
public:int Add(int a, int b){return a + b;}
};int main()
{包装类中的方法的几种方式//1.第一个参数是类指针function<int(MyClass*, int, int)> func1 = &MyClass::Add;//但是这里必须带取地址MyClass object;cout << func1(&object, 3, 2) << endl;//2.第一个参数是类的右值引用function<int(MyClass&&, int, int)> func2 = &MyClass::Add;cout << func2(MyClass(), 3, 2) << endl;int num = 3;//3.第一个参数是普通类对象function<int(const MyClass,int,int)> func3 = &MyClass::Add;cout << func3(MyClass(),3, 2) << endl;//4.第一个参数是左值引用function<int(MyClass&, int, int)> func4 = &MyClass::Add;cout << func4(object, 3, 2) << endl;return 0;
}

运行结果:

在这里插入图片描述

在使用function的时候,我们还发现function中的类型应该尽量和原调用对象的类型保持一致,(特别是const的有无)。不然就很可能出现一些问题:

在这里插入图片描述

这里我们将MyClass对象的左值引用加上const修饰就报错了,也能理解因为函数方法不具有常性(没有加const修饰),我们使用function封装,最终肯定也要回调原可调用对象(这里就是类中的成员方法),会涉及强制类型转化。这个成员方法没有加const修饰,它的this指针指向的内容是可修改的,const &->&会报错我们可以理解。

但是下面这种情况呢?

我们给非引用的MyClass对象加上const修饰传进去,它没有报错。

再看下面的代码,你就会发现这并不是偶然:

#include<iostream>
#include<functional>using namespace std;class MyClass
{
public:int Add(int& a, int b){return a + b;}
};int main()
{int num1 = 2;function<int(MyClass&&, int&, const int)> func2 = &MyClass::Add;cout << func2(MyClass(),num1, 2) << endl;function<int(const MyClass, const int&,int)> func3 = &MyClass::Add;cout << func3(MyClass(), 4, 2) << endl;return 0;
}

报错截图:

在这里插入图片描述

MyClass对象的传参太特殊了,不能说明问题。那现在普通的参数呢?当我们的函数方法中的第一个参数是int&const int&->int &是无法强制类型转换成功的,但是const intint只是值传递可以转成功,这和我们之前学的知识是逻辑自洽的。至于类中的第一个参数this指针具体在function中是如何处理的,我们就不得而知了。

  • 总结:在使用function包装调用对象时,尽可能的不要加const修饰符,如果你对这方面的语法不是很熟悉的话(int &可以->const int&,int*可以->const int* 。但是反过来就不行。==(等价于) 权限可以缩小,但是不能放大)。也可以选择和原调用对象的参数保持严格的一致。

通过这里对function类的学习,我们对this指针又产生了一个疑惑,到底是function内部如此处理,还是类和对象中这样去处理的呢?为什么对于类中的方法的第一个参数,能有如此多的传参方式。但是不管其底层如何,我们只要明确一点,在function这里就不会出错,函数方法没有const修饰的,不要给引用变量本身或者指针指向的内容加上const修饰符

使用包装器解决上述问题

代码如下:

#include<iostream>
#include<functional>
using namespace std;template<class F,class T>
T Functor(F f,T data)
{static int count = 0;count++;cout << count << endl;cout << &count << endl;return f(data);
}class  MyClass
{
public:double operator()(double data){return data / 2;}
};double func1(double x)
{return x * 3;
}int main()
{function<double(double)> func1_ = MyClass();Functor(func1_, 2.2);function<double(double)> func2_ = func1;Functor(func2_, 2.2);function<double(double)> func3_ = [](double x)->double {return x / 4; };Functor(func3_, 10.9);return 0;
}

运行结果:

在这里插入图片描述

  • function模板类的模板参数一样,所以它们三个是一样的类型(对于类模板来说,实例化后的模板类的类型是类模板带上模板参数)。仿函数的function类型比较特殊(相比于类中的普通方法),是为了便于与函数和lambda表达式调用方式统一。lambda表达式和仿函数的function封装方式是最相似的,都是把一个创建好的对象赋值给function。这与它们的底层有一定的关系。

包装器的其它应用

看下面一到OJ题,思考如何使用包装器来做。这里是题目链接

题干如下:

在这里插入图片描述

ak代码:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st1;map<string,function<int(int,int)>> mp1 = {{"+",[](int a,int b)->int{return a+b;}},{"*",[](int a,int b)->int{return a*b;}},{"-",[](int a,int b)->int{return a-b;}},{"/",[](int a,int b)->int{return a/b;}}};for(auto& str:tokens){if(str == "+" || str == "-" || str == "*" || str == "/"){int b = st1.top();st1.pop();int a = st1.top();st1.pop();st1.push(mp1[str](a,b)); }else{st1.push(stoi(str));}}return st1.top();}
};

ak截图:

在这里插入图片描述

  • 这题如果不使用function,就要用if或者switch表达式代码看着很冗余,不够优雅,但是使用map容器建立op-method的映射关系,就看起来十分优雅。(这题的具体算法思路,参考这篇博客)

绑定

绑定是C++11中新出来的语法,它是一个函数模板,下面是它的函数模板参数及其函数形参。

在这里插入图片描述

bind的形参使用万能引用,既可传左值也可传右值,它的返回值类型比较复杂,没有给出,但是我们可以把它的返回值认为是function类型。它重载了两个版本,我们只介绍第一个版本,第二个和第一个区别不大,就是多了一个返回值类型让我们传。

  • Fn是可调用对象的类型,args是可调用对象的参数包。

在这里插入图片描述

下面我们将介绍bind的使用,它有如下两种用途:

绑定修改可调用对象的参数个数

下面是代码:

#include<iostream>
#include<functional>using namespace std;class Sub//仿函数对象
{
public:int operator()(int x, int y){return x - y;}int Mul(int x, int y)//仿函数中的普通函数方法{return x * y;}
};
int Add(int x, int y)//函数
{return x + y;
}
int main()
{//绑定普通函数int num1 = 3;function<int(int)> func1 = bind(Add,num1,placeholders::_1);//placeholders::_1代表我们要传的第一个参数对应Add中的第二个参数cout << typeid(func1).name() << endl;cout << func1(3) << endl;//绑定仿函数对象int num2 = 4;auto func2 = bind(Sub(), num2, placeholders::_1);cout << typeid(func2).name() << endl;cout << func2(3) << endl;//绑定仿函数对象中的普通方法int num3 = 4;auto func3 = bind(&Sub::Mul,Sub(), placeholders::_1, placeholders::_2);cout << typeid(func3).name() << endl;cout << func3(num3, 4) << endl;//绑定lambda表达式auto func4 = bind([](int x, int y)->int {return x + 2 * y; }, 5, placeholders::_1);cout << typeid(func4).name() << endl;cout << func4(5) << endl;return 0;
}

运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,使用bind我们确实可以绑死可调用对象的一些参数,特别当我们需要在其它文件中调用类中的方法时,把第一个类对象给绑死,可以让我们使用起来像普通函数一样很方便。

另外从运行结果中还可以看出,虽然auto关键字推导出的bind返回值的类型很复杂,但是我们的第一个普通函数使用function类模板作为其类型也是可以的,前提是其返回值类型和参数类型和原调用对象以及bind中相应的处理保持一致。(特指使用bind绑死了一个参数,或者调换了某些参数的调用顺序)

绑定修改可调用对象的参数的调用顺序

下面是代码:

#include<iostream>
#include<algorithm>
#include<functional>
#include<vector>
using namespace std;int main()
{//我们可以使用bind调整类中方法的调用顺序auto func1 = bind(sort<vector<int>::iterator,less<int>>, placeholders::_2, placeholders::_1,placeholders::_3);vector<int> v1;for (int i = 0; i < 10; ++i)v1.emplace_back(10 - i);for (auto& i : v1)cout << i << " ";cout << endl;func1(v1.end(), v1.begin(),less<int>());for (auto& i : v1)cout << i << " ";cout << endl;return 0;
}
  • 上述代码中我们修改了algorithm库中sort函数模板的前两个参数的调用顺序。对于函数模板来说,绑定可调用对象必须传参数编译器才会帮助我们实例化出具体的函数。下面是sort函数模板的参数及其模板类型,它重载了两个函数模板:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们不需要传比较器(用于比较的仿函数对象),就可以使用第一个函数模板,不显式的传它的比较器类型;否则必须传。

运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于bind的底层实现我们不用过多关注,我们可以发现它和function的传参(特别是在传类中的函数方法时)很相似。它的返回值类型也肯定和function类型有一定联系,如果你感兴趣可以自行阅读源码,本篇博客只讲使用。

  • 本人知识、能力有限,若有错漏,烦请指正,非常非常感谢!!!
  • 转发或者引用需标明来源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue项目实战-新能源汽车可视化(一)(持续更新中)

一.项目代码 1.App.vue <template><!-- 模板--><div id"wrapper"><div style"width: 100%"><el-tabs v-model"activeName" id"tabs"> <!-- 标签栏里包含了三个标签面板&#xff0c;分别是研发与维…

web前端-html:简单创建表格表单相结合的网页

效果&#xff1a; <body><form action"这里如果为空表单提交后不会有任何操作"method"get"<label for"edit">用户名</label><input type"text" name"用户名" id"最好不要空&#xff0c;id属性…

【论文学习与撰写】论文里配图的题注、多张图片同列排版格式等

目录 1、插入题注 2、多张图排版 1、插入题注 word--引用--插入题注&#xff0c;就会出来这个 直接点确定的话&#xff0c;是会出来图1/图2/图3.。。。之类的 那是因为标签设置的是 图 如图新建标签为&#xff1a; 图 1. 那么&#xff0c;插入题注之后&#xff0c;就会…

【分立元件】贴片电阻的额定功率

贴片电阻器通过电流后将会发热。而贴片电阻的额定功率(Power Rating)是在额定环境温度中可在连续工作状态下使用的最大功率值。 此外,由于使用温度的上限是确定的,因此在高于额定环境温度的条件下使用时,需要按照以下的功率降额曲线来降低功率。额定环境温度是能够…

ionic Capacitor 生成 Android 应用

官方文档 https://ionic.nodejs.cn/developing/android/ https://capacitorjs.com/docs/getting-started 1、创建新的 Capacitor 应用程序 空目录下面 npm init capacitor/app2、install Capacitor npm install npm start在这里插入图片描述 3、生成dist目录 npm run buil…

SpringBoot篇(缓存层)

目录 前言 缓存是什么&#xff1f; 一、SpringBoot内置缓存解决方案 1. 简介 2. 手机验证码案例 二、SpringBoot整合Ehcache缓存 1. 简介 2. 总结 三、SpringBoot整合Redis缓存 1. 简介 2. 总结 四、SpringBoot整合Memcached缓存 1. 简介 2. 安装 3. 变更缓存为M…

5G 现网信令参数学习(1) - MIB

MIB消息中的参数 systemFrameNumber 000101B, subCarrierSpacingCommon scs30or120, ssb-SubcarrierOffset 6, dmrs-TypeA-Position pos2, pdcch-ConfigSIB1 { controlResourceSetZero 10, searchSpaceZero 4 }, cellBarred notBarred, intraFreqReselection allowed, sp…

1024程序员节特惠题解!

#题外话&#xff1a;/ #先看题目 题目传送门https://www.luogu.com.cn/problem/P1035#思路&#xff1a;直接模拟&#xff08;Shift1&#xff09; #代码&#xff1a; #include <bits/stdc.h> using namespace std; int main(){double cnt0,k,sum0,x;cin>>k;while…

2.1 > Shell 是什么、如何更熟练的使用 Bash Shell

Shell 基础知识 Shell是计算机操作系统中的一个命令行解释器&#xff0c;由C语言编写&#xff0c;用于用户与操作系统之间进行交互。用户可以通过Shell输入命令&#xff0c;操作系统接收到这些命令后执行相应的操作。Shell一般还提供了编程语言的基本功能&#xff0c;允许用户…

【Mac 上将 MOV 格式转换为 MP4 格式的简易指南】

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

R语言笔记(四):函数

文章目录 一、Function basics1、Creating your own function2、Function structure3、Using your created function4、Multiple inputs5、Default inputs 二、Return values and side effects1、Returning more than one thing2、Side effectsExample of side effect: plot 三…

从零开始学python必看,最强“Python编程三剑客(pdf)”

目录 三剑客PDF传送门&#xff1a;三剑客 第一本&#xff1a;《Python编程&#xff1a;从入门到实践》 1.1《Python编程&#xff1a;从入门到实践》第一部分&#xff1a;基础知识 1.2《Python编程&#xff1a;从入门到实践》第二部分&#xff1a;项目 第二本&#xff1a;《…

Metasploit渗透测试之模块学习与开发

# 概述 Metasploit 框架采用模块化架构&#xff0c;即所有漏洞利用、有效载荷、编码器等都以模块形式存在。模块化架构使框架功能的扩展更加容易。任何程序员都可以开发自己的模块&#xff0c;并将其轻松移植到框架中。 # 1、使用辅助模块 在之前的"信息收集和扫描 &qu…

【设计模式-原型】

**原型模式&#xff08;Prototype Pattern&#xff09;**是一种创建型设计模式&#xff0c;旨在通过复制现有对象的方式来创建新对象&#xff0c;而不是通过实例化类来创建对象。该模式允许对象通过克隆&#xff08;复制&#xff09;来创建新的实例&#xff0c;因此避免了重新创…

QT-使用QSS美化UI界面

一、QSS简介&#xff1a; Qt Style Sheet&#xff1a;Qt样式表&#xff0c;用来自定义控件外观的一种机制&#xff0c;可以把他类比成CSS&#xff08;CSS主要功能与最终目的都是能使界面的表现与界面的元素分离&#xff09;。QSS机制使应用程序也能像web界面那样随意地改变外观…

构建后端为etcd的CoreDNS的容器集群(二)、下载最新的etcd容器镜像

在尝试获取etcd的容器的最新版本镜像时&#xff0c;使用latest作为tag取到的并非最新版本&#xff0c;本文尝试用实际最新版本的版本号进行pull&#xff0c;从而取到想的最新版etcd容器镜像。 一、用latest作为tag尝试下载最新etcd的镜像 1、下载镜像 [rootlocalhost opt]# …

基于vue框架的的高校消防设施管理系统06y99(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;设备分类,设备信息,维修人员,报修信息,维修进度,院系,消防知识,培训记录,培训信息,备件信息,备件申请,派发信息,采购信息 开题报告内容 基于Vue框架的高校消防设施管理系统开题报告 一、项目背景与意义 随着高校规模的不断扩大和校园建…

OpenCV和HALCON

OpenCV和HALCON是两种广泛用于图像处理和计算机视觉的开发库&#xff0c;它们各有优缺点&#xff0c;适合不同的应用场景。以下是两者的比较&#xff1a; 1. 开发背景与定位 OpenCV (Open Source Computer Vision Library)&#xff1a; 开源库&#xff0c;最初由Intel开发&…

【EmbeddedGUI】PFB设计说明

PFB设计说明 背景介绍 一般来说&#xff0c;要实现屏幕显示&#xff0c;就是向特定像素点写入颜色值&#xff0c;最简单的办法就是直接通过SPI接口&#xff0c;向显示器芯片的特定缓存地址&#xff0c;写入像素点。一般来说&#xff0c;显示器芯片会提供2个基本操作API&#…

qt QNetworkProxy详解

一、概述 QNetworkProxy通过设置代理类型、主机、端口和认证信息&#xff0c;可以使应用程序的所有网络请求通过代理服务器进行。它支持为Qt网络类&#xff08;如QAbstractSocket、QTcpSocket、QUdpSocket、QTcpServer、QNetworkAccessManager等&#xff09;配置网络层代理支持…