当前位置: 首页 > news >正文

【C++】-- C++11基础常用知识点(下)

上篇: 【C++】-- C++11基础常用知识点(上)_川入的博客-CSDN博客


目录

新的类功能

默认成员函数

可变参数模板

可变参数

可变参数模板

empalce

lambda表达式

C++98中的一个例子

lambda表达式

lambda表达式语法

捕获列表

lambda表达底层

包装器

function包装器

bind绑定


新的类功能

默认成员函数

原来C++类中,有6个默认成员函数:
  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

        最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。 C++11 新增了两个:移动构造函数和移动赋值运算符重载

        所以到了C++11后有8个默认成员函数。

移动构造函数和移动赋值运算符重载的又来以及原理:
【C++】-- C++11 - 右值引用和移动语义(上万字详细配图配代码从执行一步步讲解)_川入的博客-CSDN博客

只有在深拷贝的情况下才会有移动构造函数移动赋值运算符重载。可以认为:

  • 拷贝构造函数与拷贝赋值重载:针对于左值的拷贝。
  • 移动构造函数和移动赋值重载:针对于右值的拷贝。

        移动构造函数移动赋值重载,编译器自行生成的默认成员函数,能用的条件的复杂度与苛刻程度远远大于:构造函数、析构函数 、拷贝构造函数 、拷贝赋值重载4个默认成员函数。(由于:取地址重载 、const 取地址重载几乎不用自己写,用编译器的即可,所以忽略)

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
  • 编译器生成默认移动构造函数条件

        没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。

  • 编译器生成默认移动构造函数实现

        默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝。自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

  • 编译器生成默认动赋值重载函数条件
        你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
  • 编译器生成默认动赋值重载函数实现
        默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)。
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
强制生成默认函数的关键字default:
 
        C++11可以让我们更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
class Person
{
public:Person(const char *name = "", int age = 0): _name(name), _age(age){}Person(const Person &p): _name(p._name), _age(p._age){}Person(Person &&p) = default;private:bit::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}
禁止生成默认函数的关键字delete:
        如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:Person(const char *name = "", int age = 0): _name(name), _age(age){}Person(const Person &p) = delete;private:bit::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

        可以使用default关键字强行让编译器生成,但是需要注意析构函数 、拷贝构造、拷贝赋值重载也会收到影响,需要自己写或也强制生成。没有什么意义,所以一般default关键字是用于构造,因为拷贝构造也属于构造,如果写了拷贝构造就不会默认生成构造了。

#问:如何用delete关键字实现一个类,只能再堆上创建对象?

        平时我们创建的类,是可以在栈区、全局数据区上创建的。

class HeapOnly
{};int main()
{HeapOnly hp1;  // 栈区static HeapOnly h2; // 全局数据区return 0;
}

        我们可以通过delete析构函数,然后使用new开辟类。


class HeapOnly
{
public:// HeapOnly()// {//     str_ = new char[10];// }// void Destroy()// {//     delete[] str_;//     operator delete(this); // 内存管理之重载operator delete// }~HeapOnly() = delete;
private:char* str_;
};int main()
{// HeapOnly hp1;  // 栈区 -- 会调析构// static HeapOnly h2; // 全局数据区 -- 会调析构// new出来的对象会调用构造 -- 这个时候会导致资源泄漏HeapOnly *ptr = new HeapOnly;operator delete(ptr);return 0;
}
  • new是c++中的操作符,malloc是c中的一个函数。
  • new不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数
  • malloc只会单纯的分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数。

#问:

class HeapOnly
{
public:HeapOnly(){str_ = new char[10];}~HeapOnly() = delete;
private:char* str_;
};

        对于构造函数是new空间,因为不能调用析构而不能使用delete,导致值空间泄漏怎么办?

        我们可以搞一个函数,利用函数将其释放。

class HeapOnly
{
public:HeapOnly(){str_ = new char[10];}void Destroy(){delete[] str_;operator delete(this); // 内存管理之重载operator delete// 也可以使用free}~HeapOnly() = delete;
private:char* str_;
};int main()
{// HeapOnly hp1;  // 栈区 -- 会调析构// static HeapOnly h2; // 全局数据区 -- 会调析构// new出来的对象会调用构造 -- 这个时候会导致资源泄漏HeapOnly *ptr = new HeapOnly;ptr->Destroy();return 0;
}

        继承的时候要小心,因为指针是可能出现偏移的,继承之后,切片可能成员位置发生变化,operator delete(this);的释放位置就可能不对。

可变参数模板

可变参数

可变参数最早的出现是在C语言:

         以printf,不确定参数传多少个参数,后面可以传一串值,也就可变参数,可以有0 ~ n个参数。底层是用数组实现的。

可变参数模板

下面就是一个基本可变参数的函数模板:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}// (不一定非要写作:Args、args,可以换一个名字,只是这两个常用)
#include <string>// 可变参数的函数模板
template <class ...Args>
void ShowList(Args... args)
{}int main()
{std::string str("hello");ShowList();ShowList(1);ShowList(1, 'A');ShowList(1, 'A', str);return 0;
}
        上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
        如果,我们想拿到参数包里面的参数,是不好拿的,sizeof可以帮助我们算参数包里面有多少个参数:
#include <string>
#include <iostream>// 可变参数的函数模板
template <class ...Args>
void ShowList(Args... args)
{std::cout << sizeof...(args) << std::endl;
}int main()
{std::string str("hello");ShowList();ShowList(1);ShowList(1, 'A');ShowList(1, 'A', str);return 0;
}
Note:
for(int i = 0; i< sizeof...(args); i++)
{std::cout << args[i] << " "; // error:args[i]不支持
}

        语法不支持使用args[i]这样方式获取可变参数,所以我们需要用一些奇招来 一一 获取参数包的值。

第一种:递归函数方式展开参数包

        将参数包改一改,增加一个参数。

#include <iostream>
#include <string>// 递归终止函数
template <class T>
void ShowList()
{std::cout << std::endl;
}// 展开函数
template <class T, class... Args>
void ShowList(const T& value, Args... args) // 第一个参数传给value,剩下的传给参数包args。
{cout << value << " ";ShowList(args...); // 参数超过0个递归调自己,参数0个调递归终止函数。
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

        利用递归不断地推出参数包中的内容。

第二种:逗号表达式展开参数包

        这种展开参数包的方式,不需要通过递归终止函数,是直接在ShowList函数体中展开的, PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式,因为逗号表达式会按顺序执行逗号前面的表达式。

#include <iostream>
#include <string> template <class T>
void PrintArg(cosnt T t)
{std::cout << t << " ";
}// 展开函数
template <class... Args>
void ShowList(Args... args)
{// 利用逗号表达式去初始化arr,arr编译的时候就会知道要开多大,这个时候就会依次展开args参数包。// 利用逗号表达式去取右边的值0。(逗号表达式会按顺序执行逗号前面的表达式)int arr[] = {(PrintArg(args), 0)...};std::cout << std::endl;
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

同理,也可以优化为不适用逗号表达式展开参数包:

#include <iostream>
#include <string> template <class T>
int PrintArg(cosnt T t)
{std::cout << t << " ";return 0; 
}// 展开函数
template <class... Args>
void ShowList(Args... args)
{// arr编译的时候就会知道要开多大,这个时候就会依次展开args参数包。int arr[] = { PrintArg(args)... };std::cout << std::endl;
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

empalce

        分析STL容器中的empalce相关接口函数:

https://cplusplus.com/reference/vector/vector/emplace/

https://cplusplus.com/reference/vector/vector/emplace_back/
https://cplusplus.com/reference/list/list/emplace_back/
以vector容器的emplace_back为例:

          emplace_back是在一个函数模板里面,把一个成员函数是实现成可变参数包。其就是通过将可变参数包不断不断的往下传,传到最下面去初始化对应数据,或者是链表的话就初始化节点里的数据。

template <class... Args>
void emplace_back (Args&&... args);
        首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用/引用折叠(即可以引用左值,也可以引用右值)
#问:那么相对insert和emplace系列接口的优势到底在哪里呢?
// vector::emplace_back
#include <iostream>
#include <vector>int main ()
{std::vector<int> myvector;myvector.push_back(100);myvector.emplace_back(200);return 0;
}
如果只是简单的int的,其与push_back就没有什么区别。主要的区别在于:
// vector::emplace_back
#include <iostream>
#include <vector>
#include <string>
#include <utility>int main()
{std::vector<std::pair<std::string, int>> myvector;myvector.push_back(std::make_pair("sort", 1));myvector.emplace_back(std::make_pair("sort", 1));myvector.emplace_back("sort", 1);return 0;
}

       效率上就emplace_back更好,因为make_pair是先构造,构造了一个pair。如此push_back就传了一个pair对象。所以调push_back是:

  • 左值:构造 + 拷贝构造。
  • 右值:构造 + 移动构造。

        emplace_back是不用着急创建pair对象,我们可将这个参数包一直向下传递,直到最后需要插入数据的时候,直接用这个数据包创建pair对象。

  • 直接构造。

        所以emplace系列比insert系列接口不一定高效。

通过代码凸显区别:

        不一定所有容器都会出现,于源码的实现有关系,此处使用list容器,并在VS2019实现出来的:

#include <iostream>
#include <list>
#include <string>class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){std::cout << "Date(int year = 1, int month = 1, int day = 1)" << std::endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){std::cout << "Date(const Date& d)" << std::endl;}private:int _year;int _month;int _day;
};int main()
{std::list<Date> lt1;lt1.push_back(Date(2022, 11, 16));std::cout << "---------------------------------" << std::endl;lt1.emplace_back(2022, 11, 16);return 0;
}

        所以建议:这个这种场景下直接使用emplace系列接口。

lambda表达式

        lambda也叫做匿名函数。

像函数使用的对象 / 类型:

  1. 函数指针 -- C++不喜欢的操作,所以有了仿函数。(全局的函数)
  2. 仿函数 / 函数对象。(全局的类)
  3. lambda。(局部)

 C++98中的一个例子

        因为由于仿函数有诸多的不便。如果待排序元素为自定义类型,需要用户定义排序时的比较规则,对于以下的三个成员一个就要创建2个(less、greater),就是6个。

#include <string>struct Goods
{std::string _name;  // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
        随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

lambda表达式

lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

1. lambda表达式各部分说明:
  • [capture-list] : 捕捉列表。该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • (parameters):参数列表。普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略(无参时可以省略)
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

        在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
#include <iostream>int main()
{// 两个数相加的lambda// 没有函数名,加一个捕捉列表[]而已。因为没有名字,所以调用不好调// 但是[](int a, int b) -> int{ return a + b; }整体是一个对象,所以就可以巧用auto。auto add1 = [](int a, int b) -> int{ return a + b; };std::cout << add1(1, 2) << std::endl;// 省略返回值auto add2 = [](int a, int b){ return a + b; };std::cout << add2(1, 2) << std::endl;
}

        于是对于前面的三个成员一个就要创建2个(less、greater),就是6个。解决:

#include <string>
#include <vector>
#include <algorithm>struct Goods
{std::string _name; // 名字double _price;     // 价格int _evaluate;     // 评价//...Goods(const char *str, double price, int evaluate): _name(str), _price(price), _evaluate(evaluate){}
};int main()
{std::vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2){ return g1._name < g2._name; });sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2){ return g1._name > g2._name; });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; });
}

#问:如何写一个交换swap函数?

        可以像上面那样写,但是会非常的难看。

#include <iostream>int main()
{// 交换变量的lambda - 行数会多int x = 0, y = 1;auto swap1 = [](int &x1, int &x2) -> void{int tmp = x1; x1 = x2; x2 = tmp; };swap1(x, y);std::cout << x << ":" << y << std::endl;
}

        我们可以这样写:

#include <iostream>int main()
{// 交换变量的lambda - 行数会多int x = 0, y = 1;auto swap1 = [](int &x1, int &x2) -> void{int tmp = x1; x1 = x2; x2 = tmp; };swap1(x, y);std::cout << x << ":" << y << std::endl;
}

捕获列表

#问:假如我们想不传参数交换x,y呢?

利用捕捉列表实现,注意:

  • 想捕捉谁就写谁,只能捕捉跟lambda表达式同一个作用域的对象。
  • 默认捕捉过来的变量不能修改 —— 加mutable让捕捉过来的变量可以修改(使用mutable须加())。
  • 默认捕捉是拷贝的方式捕捉,严格意义上说是传值捕捉。(lambda还是一个函数调用,是有栈帧的 —— 可以理解为:改变形参,不会改变实参)
#include <iostream>int main()
{// 交换变量的lambda - 行数会多int x = 0, y = 1;// 可以理解为:改变形参,不会改变实参auto swap = [x, y]()mutable{int tmp = x; x = y; y = tmp; };swap();std::cout << x << ":" << y << std::endl;
}

        所以mutable在实际中不起价值作用。

捕获列表说明:

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

  • [var]:表示值传递方式捕捉变量var。
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)。
  • [&var]:表示引用传递捕捉变量var。
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)。
  • [this]:表示值传递方式捕捉当前的this指针。

注意:

  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
    • 比如:
      • [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。
      • [&, a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误
    • 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同
#include <iostream>void (*PF)();int main()
{auto f1 = []{ std::cout << "hello world" << std::endl; };auto f2 = []{ std::cout << "hello world" << std::endl; };// f1 = f2;   // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF = f2;PF();return 0;
}

lambda表达底层

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

范围for:

        并没有看起来这么的智能,实际上是底层运用迭代器实现的。

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);// lamberauto r2 = [=](double monty, int year) -> double{return monty * rate * year;};r2(10000, 2);return 0;
}

        仿函数的名称就是:lambda_uuid。所以lambda表达式对于我们是匿名的,对于编译器而言是有名的。实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

包装器

function包装器

        function包装器也叫作适配器,C++中的function本质是一个类模板,也是一个包装器。
#include <iostream>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()
{// 函数名std::cout << useF(f, 11.11) << std::endl;// 仿函数对象std::cout << useF(Functor(), 11.11) << std::endl;// lamber表达式对象std::cout << useF([](double d)->double{ return d/4; }, 11.11) << std::endl;return 0;
}

        因为上述的 f 的类型不同,于是会被实例化成三个

        包装器可以很好的解决上面的问题,将其变为1份。

std::function在头文件<functional>
// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
使用方法:
// 使用方法如下:
#include <functional>
#include <iostream>int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator()(int a, int b){return a + b;}
};int main()
{// 函数名(函数指针)std::function<int(int, int)> func1 = f;std::cout << func1(1, 2) << std::endl;// 函数对象std::function<int(int, int)> func2 = Functor();std::cout << func2(1, 2) << std::endl;// lamber表达式std::function<int(int, int)> func3 = [](const int a, const int b){ return a + b; };std::cout << func3(1, 2) << std::endl;return 0;
}

        对于静态成员函数与非静态成员函数的不同:

//使用方法如下:
#include <functional>
#include <iostream>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)> func4 = Plus::plusi; std::cout << func4(1, 2) << std::endl;// 非静态成员函数需要加&,并且不能直接调用,需要传对象,此处为Plus。(成员函数多传一个)std::function<double(Plus, double, double)> func5 = &Plus::plusd; std::cout << func5(Plus(), 1.1, 2.2) << std::endl;return 0;
}

        如果对于非静态成员函数,不想多传一个类对象的参数,可以通过绑定的方式解决这个问题。

        所以对于上面的,因为上述的 f 的类型不同,于是会被实例化成三个,就可以解决了:

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

包装器的其他一些场景:
 

class Solution
{
public:int evalRPN(vector<string> &tokens){stack<long long> st;map<string, function<long long(long long, long long)>> opFuncMap ={{"+", [](long long i, long long j){ return i + j; }},{"-", [](long long i, long long j){ return i - j; }},{"*", [](long long i, long long j){ return i * j; }},{"/", [](long long i, long long j){ return i / j; }}};for (auto &str : tokens){if (opFuncMap.find(str) != opFuncMap.end()){long long right = st.top();st.pop();long long left = st.top();st.pop();st.push(opFuncMap[str](left, right));}else{// 1、atoi itoa// 2、sprintf scanf// 3、stoi to_string C++11st.push(stoll(str));}}return st.top();}
};

bind绑定

        std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器)接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

库中就是使用了placeholders来占位:

https://legacy.cplusplus.com/reference/functional/placeholders/

        其中的_1、_2、_3等,就是用来占位的。_1代表第1个参数,_2代表第2个参数……。调整的是形参的顺序。

#include <functional>
#include <iostream>int Div(int a, int b)
{return a / b;
}int main()
{int x = 10, y = 2;std::cout << Div(x, y) << std::endl;// 调整顺序 -- 鸡肋,一般用不上// _1, _2.... 定义在placeholders命名空间中,代表绑定函数对象的形参,// _1,_2... 分别代表第一个形参、第二个形参...//std::function<int(int, int)> bindFunc = bind(Div, std::placeholders::_2, std::placeholders::_1);auto bindFunc = bind(Div, std::placeholders::_2, std::placeholders::_1);// 传时候不会变std::cout << bindFunc(x, y) << std::endl;return 0;
}

可以理解为:

// x -> _1 ->a
// y -> _2 ->b。
auto bindFunc = bind(Div, _1, _2);
bindFunc(x, y);// x -> _2 ->b
// y -> _1 ->a。
auto bindFunc = bind(Div, _2, _1);
bindFunc(x, y);

        可以用绑定解决前面的非静态成员函数,需要传类对象(成员函数多传一个),以绑定参数解决 -> 调整个数。

#include <functional>
#include <iostream>
#include <map>int Plus(int a, int b)
{return a + b;
}int Mul(int a, int b, double rate)
{return a * b * rate;
}class Sub
{
public:int sub(int a, int b){return a - b;}
};// 11:50继续
int main()
{// 调整个数, 绑定死固定参数std::function<int(int, int)> funcPlus = Plus;// 本来要传3个.// function<int(Sub, int, int)> funcSub = &Sub::sub;// 将其变为只传2个,将1个(此处Sub())固定在这个地方绑死 — 不能变。std::function<int(int, int)> funcSub = std::bind(&Sub::sub, Sub(), std::placeholders::_1, std::placeholders::_2);// 1.5就固定死了std::function<int(int, int)> funcMul = std::bind(Mul, std::placeholders::_1, std::placeholders::_2, 1.5);std::map<std::string, std::function<int(int, int)>> opFuncMap = {{ "+", Plus},{ "-", std::bind(&Sub::sub, Sub(), std::placeholders::_1, std::placeholders::_2)}};std::cout << funcPlus(1, 2) << std::endl;std::cout << funcMul(2, 2) << std::endl;std::cout << funcSub(1, 2) << std::endl;std::cout << opFuncMap["+"](1, 2) << std::endl;std::cout << opFuncMap["-"](1, 2) << std::endl;return 0;
}

相关文章:

【C++】-- C++11基础常用知识点(下)

上篇&#xff1a; 【C】-- C11基础常用知识点&#xff08;上&#xff09;_川入的博客-CSDN博客 目录 新的类功能 默认成员函数 可变参数模板 可变参数 可变参数模板 empalce lambda表达式 C98中的一个例子 lambda表达式 lambda表达式语法 捕获列表 lambda表达底层 …...

提到数字化,你想到哪些关键词

我们的生活中已经充满了数据&#xff0c;各种岗位例如运营、市场、营销上也都喜欢在职位要求加上一条利用数据、亦或是懂得数据分析。事实上&#xff0c;数据已经成为了构建现代社会的基本生产要素&#xff0c;并且因为不受自然环境的限制&#xff0c;已经成为了人们对未来社会…...

【蓝桥杯集训·每日一题】AcWing 1249. 亲戚

文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴并查集一、题目 1、原题链接 1249. 亲戚 2、题目描述 或许你并不知道&#xff0c;你的某个朋友是你的亲戚。 他可能是你的曾祖父的外公的女婿的外甥女的表姐的孙子。 如果…...

iphone所有机型的屏幕尺寸

手机设备型号屏幕尺寸(吋)分辨率点数(pt)屏幕显示模式分辨率像素(px)屏幕比例iPhone SE4.03205682x640113616:9iPhone 6/6s/7/8/SE 24.73756672x750133416:9iPhone 6P/7P/8P5.54147363x1242220816:9iPhone XR/116.14148962x828179219.5:9iPhone X/XS/11P5.83758123x1125243619.…...

Windows10使用-处理IE自动跳转至Edge

文章目录 前言一、调整Edge二、调整Internet选项三、搜索栏的恢复总结前言 微软官方宣布,自2023年2月14日永久停止支持Internet Explorer 11浏览器。后期点击IE 图标将会自动跳转到Edge界面。对于一些网站,可能需要使用IE模式才能正常使用,这时候就需要做相应的调整,才能够…...

linux input子系统,gpio-keys,gpio中断使用

GPIO控制 嵌入式linux下应用编程会经常使用到gpio&#xff0c;GPIO 可以通过 sysfs 方式进行操控&#xff0c;进入到/sys/class/gpio 目录下&#xff0c;如下所示&#xff1a; 可以看到该目录下包含两个文件 export、 unexport 以及 5 个 gpiochipX&#xff08;X 等于 0、 32、…...

分析称勒索攻击在非洲、中东与中国增长最快

Orange Cyberdefense&#xff08;OCD&#xff09;于 2022 年 12 月 1 日发布了最新的网络威胁年度报告。报告中指出&#xff0c;网络勒索仍然是头号威胁 &#xff0c;也逐渐泛滥到世界各地。 报告中的网络威胁指的是企业网络中的某些资产被包括勒索软件在内的攻击进行勒索&…...

ArcPy批量合并矢量shape文件

当有大量矢量&#xff08;.shp&#xff09;格式文件需要合并成一个矢量文件时&#xff0c;可以考虑使用 ArcPy 进行批量合并&#xff0c;代码如下&#xff1a; # coding:utf-8 import os import arcpy from arcpy import envenv.workspace "C:/Users/Desktop/demo"…...

改写有序表的题目核心点

1、核心点 1&#xff09;分析增加什么数据项可以支持题目 2&#xff09;有序表一定要保持内部参与排序的key不重复 【补充说明&#xff1a;要存储重复的key值&#xff0c;要么将相同的key压在一起&#xff0c;要么将每个key再封装一层&#xff0c;用内存地址区分】 3&#…...

收藏这几个开源管理系统做项目,领导看了直呼牛X!

项目SCUI Admin 中后台前端解决方案Vue .NetCore 前后端分离的快速发开框架next-admin 适配移动端、pc的后台模板django-vue-admin-pro 快速开发平台Admin.NET 通用管理平台RuoYi 若依权限管理系统Vue3.2 Element-Plus 后台管理框架Pig RABC权限管理系统zheng 分布式敏捷开发…...

【刷题篇】链表(下)

前言&#x1f338;各位读者们好&#xff0c;本期我们来填填之前留下的坑&#xff0c;继续来讲解几道和链表相关的OJ题。但和上期单向链表不一样的是&#xff0c;我们今天的题目主要是于环形链表有关&#xff0c;下面让我们一起看看吧。&#x1f4bb;本期的题目有&#xff1a;环…...

Shiro

Shiro 1.权限管理概述 2.Shiro权限框架   2.1 概念   2.2 Apache Shiro 与Spring Security区别 3.Shiro认证   3.1 基于ini认证   3.2 自定义Realm --认证 4.Shiro授权   4.1 基于ini授权   4.2 自定义realm – 授权 5.项目集成shiro 认证-授权注意点   5.1 认证…...

使用nginx进行负载均衡配置详细说明

使用nginx进行负载均衡 1. nginx负载均衡介绍 nginx应用场景之一就是负载均衡。在访问量较多的时候&#xff0c;可以通过负载均衡&#xff0c;将多个请求分摊到多台服务器上&#xff0c;相当于把一台服务器需要承担的负载量交给多台服务器处理&#xff0c;进而提高系统的吞吐…...

N皇后问题

#include<iostream> #include<string> #include<vector> using namespace std; #define MAX 20//最大20个皇后 int n ;//实际皇后个数 int sum ;//答案个数 vector<vector<int>> attack(MAX, vector<int>(MAX, 0));//标记攻击位置 vector&…...

强化学习DQN之俄罗斯方块

强化学习DQN之俄罗斯方块强化学习DQN之俄罗斯方块算法流程文件目录结构模型结构游戏环境训练代码测试代码结果展示强化学习DQN之俄罗斯方块 算法流程 本项目目的是训练一个基于深度强化学习的俄罗斯方块。具体来说&#xff0c;这个代码通过以下步骤实现训练&#xff1a; 首先…...

1.3总线:并行总线、串行总线、单工、半双工、全双工、总线宽度、总线带宽、总线的分类、数据总线、地址总线、控制总线

1.3总线&#xff1a;并行总线、串行总线、单工、半双工、全双工、总线宽度、总线带宽、总线的分类、数据总线、地址总线、控制总线总线并行总线、串行总线单工、半双工、全双工总线宽度总线带宽总线的分类数据总线&#xff08;Data Bus&#xff0c;DB&#xff09;地址总线&…...

Linux驱动开发—设备树开发详解

设备树开发详解 设备树概念 Device Tree是一种描述硬件的数据结构&#xff0c;以便于操作系统的内核可以管理和使用这些硬件&#xff0c;包括CPU或CPU&#xff0c;内存&#xff0c;总线和其他一些外设。 Linux内核从3.x版本之后开始支持使用设备树&#xff0c;可以实现驱动代…...

深入浅出C++ ——继承

文章目录一、继承的相关概念1. 继承的概念2. 继承格式3. 继承方式4. 访问限定符5. 继承基类成员访问方式的变化二、基类和派生类对象赋值转换三、继承中的作用域四、派生类的默认成员函数五、继承与友元六、继承与静态成员七、菱形继承及菱形虚拟继承1. 单继承2. 多继承3. 菱形…...

设计模式C++实现20: 桥接模式(Bridge)

部分内容参考大话设计模式第22章&#xff1b;本实验通过C语言实现。 一 基本原理 意图&#xff1a;将抽象部分和实现部分分离&#xff0c;使它们都可以独立变化。 上下文&#xff1a;某些类型由于自身的逻辑&#xff0c;具有两个或多个维度的变化。如何应对“多维度的变化”…...

Android中的Rxjava

要使用Rxjava首先要导入两个包&#xff0c;其中rxandroid是rxjava在android中的扩展 implementation io.reactivex:rxandroid:1.2.1implementation io.reactivex:rxjava:1.2.0observer 是一个观察者接口&#xff0c;泛型T为观察者观察数据的类型&#xff0c;里面只有三个方法&a…...

【RocketMQ】源码详解:消息储存服务加载、文件恢复、异常恢复

消息储存服务加载 入口:org.apache.rocketmq.store.DefaultMessageStore#load 在创建brokerContriller时会调用初始化方法初始化brokerController,在初始化方法中会进行消息储存服务的加载 this.messageStore.load(); 加载方法主要是加载一些必要的参数和数据&#xff0c;如配…...

数字IC设计工程师是做什么的?

随着我国半导体产业的发展&#xff0c;近几年的新入行的从业人员&#xff0c;除了微电子相关专业的&#xff0c;还有就是物理、机械、数学、计算机等专业&#xff0c;很多人对这一高薪行业充满了好奇&#xff0c;那么数字IC设计工程师到底是做什么的&#xff1f; 首先来看看数…...

【040】134. 加油站[简单模拟 + 逻辑转化]

在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油箱为空。 给定两个整数数组 gas 和 cost &am…...

Python用selenium实现自动登录和下单的脚本

前言 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI&#xff08;用户界面&#xff09;自动化测试套件之一。Selenium 支持的语言包括C#&#xff0c;Java&#xff0c;Perl&#xff0c;PHP&#xff0c;Python 和 Ruby。目前&#xff0c;Selenium Web 驱动…...

(02)Cartographer源码无死角解析-(55) 2D后端优化→AppendNode()、class MapById、 PoseGraphData、

讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885 文末正下方中心提供了本…...

如何在jmeter中把响应中的数据提取出来并引用

jmeter做接口测试过程中&#xff0c;经常遇到请求需要用到token的时候&#xff0c;我们可以把返回token的接口用后置处理器提取出来&#xff0c;但是在这种情况下&#xff0c;只能适用于当前的线程组&#xff0c;其他线程组无法引用到提取的token变量值&#xff0c;所以必须要生…...

2023环翠区编程挑战赛中学组题解

T1. 出栈序列 题目描述 栈是一种“先进后出”的数据结构&#xff0c;对于一个序列1,2,...,n1,2, ...,n1,2,...,n&#xff0c;其入栈顺序是1,2,...n1,2, ...n1,2,...n&#xff0c;但每个元素出栈的时机可以自由选择。 例如111入栈、111出栈&#xff0c;222入栈、333入栈、333…...

手撸一个Switch开关组件

一、前言 手撸系列又来了&#xff0c;这次咱们来撸一个Switch开关组件&#xff0c;废话不多说&#xff0c;咱们立刻发车。 二、使用效果 三、实现分析 首先我们先不想它的这个交互效果&#xff0c;我们就实现“不合格”时的一个静态页面&#xff0c;静态页面大致如下&#x…...

2023年1月冰箱品牌销量排行:销量环比增长26%,销售额36亿+

鲸参谋电商大数据2023年1月京东平台“冰箱”销售数据出炉&#xff01; 根据鲸参谋平台电商数据显示&#xff0c;2023年1月份&#xff0c;在京东平台上&#xff0c;冰箱的销量将近130万件&#xff0c;环比增长26%&#xff0c;同比下滑8%&#xff1b;销售额达36亿&#xff0c;环比…...

DSP CCS 开发问题总结及解决办法

文章目录 问题汇总 1. CCS编译器的Project菜单栏工程导入选项丢失&#xff0c;怎么解决&#xff01; 1.1启动CCS后发现导入工程菜单栏丢失&#xff0c;无法导入工程文件。 1.2方法一 工程选项的导入工程文件丢失&#xff0c;如果要重新获得相应的选项&#xff0c;就需要删除当前…...