如何做自己的简历网站,别具光芒 Flash互动网站设计,一般通过山女是什么梗,怎么办个人网站上篇文章#xff08;C11的新特性#xff08;上#xff09;#xff09;我们讲述了C11中的部分重要特性。本篇接着上篇文章进行讲解。本篇文章主要进行讲解#xff1a;完美转发、新类的功能、可变参数模板、lambda 表达式、包装器。希望本篇文章会对你有所帮助。 文章目录 一… 上篇文章C11的新特性上我们讲述了C11中的部分重要特性。本篇接着上篇文章进行讲解。本篇文章主要进行讲解完美转发、新类的功能、可变参数模板、lambda 表达式、包装器。希望本篇文章会对你有所帮助。 文章目录 一、完美转发 1、1 实例详解 1、2 应用场景 二、新类的功能 2、1 默认成员函数 2、2 缺省参数初始化 2、3 强制生成默认函数的关键字default 2、4 禁止生成默认函数的关键字delete 2、5 继承和多态中的final和override关键字 三、可变参数模板 3、2 递归函数方式展开参数包 3、2 逗号表达式展开参数包 3、3 STL容器中的empalce相关接口函数 四、lambda 表达式 4、1 C98例子引入 4、2 lambda 表达式详解 4、2、1 lambda 表达式语法 4、2、2 lambda 表达式实例 4、2、3 lambda 表达式与函数对象仿函数 五、包装器 5、1 function包装器用法 5、2 function包装器举例使用 5、3 bind 捆绑器 5、3、1 bind 绑定参数 5、3、2 bind绑定 交换参数顺序 ♂️ 作者Ggggggtm ♂️ 专栏C 标题C11 ❣️ 寄语与其忙着诉苦不如低头赶路奋路前行终将遇到一番好风景 ❣️ 一、完美转发
1、1 实例详解 上衣拍案文章末尾我们学习了右值引用。那么右值引用加上模板会出现一种特殊的情况。我们先看效果代码如下 void Fun(int x) { cout 左值引用 endl; }
void Fun(const int x) { cout const 左值引用 endl; }void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }templatetypename T
void PerfectForward(T t)
{Fun(t);
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
} 我们直接看输出结果 怎么全部是左值或左值引用呢 模板中的不代表右值引用而是万能引用其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力。但是不管是接收的左值还是右值都会将其实参绑定到形参的左值引用上引用折叠。引用类型的唯一作用就是限制了接收的类型后续使用中都退化成了左值我们希望能够在传递过程中保持它的左值或者右值的属性, 这时就需要引入完美转发了。 完美转发的具体用法std::forward 完美转发在传参的过程中保留对象原生类型属性。下面我们改写一下上述的代码进行理解 void Fun(int x) { cout 左值引用 endl; }
void Fun(const int x) { cout const 左值引用 endl; }void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }templatetypename T
void PerfectForward(T t)
{// std::forwardT(t)在传参的过程中保持了t的原生类型属性。Fun(std::forwardT(t));
}
int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
} 运行结果如下图 1、2 应用场景 我们之前模拟实现过 list 的底层。当我们学完右值引用后我们再看一下 list 的底层。代码如下 templateclass T
struct ListNode
{ListNode* _next nullptr;ListNode* _prev nullptr;T _data;
};
templateclass T
class List
{typedef ListNodeT Node;
public:List(){_head new Node;_head-_next _head;_head-_prev _head;}void PushBack(T x){//Insert(_head, x);Insert(_head, std::forwardT(x));}void PushFront(T x){//Insert(_head-_next, x);Insert(_head-_next, std::forwardT(x));}void Insert(Node* pos, T x){Node* prev pos-_prev;Node* newnode new Node;newnode-_data std::forwardT(x); // 关键位置// prev newnode posprev-_next newnode;newnode-_prev prev;newnode-_next pos;pos-_prev newnode;}void Insert(Node* pos, const T x){Node* prev pos-_prev;Node* newnode new Node;newnode-_data x; // 关键位置// prev newnode posprev-_next newnode;newnode-_prev prev;newnode-_next pos;pos-_prev newnode;}
private:Node* _head;
};
int main()
{Liststring lt;lt.PushBack(1111);lt.PushFront(2222);return 0;
} 上述代码中关键点在于插入添加了右指引用的接口。当然上述情况的 list 在插入内置类型int、char、double……时并没有任何影响。但是当我们插入的是自定义类型呢就上述的例子解释 为什么前面说内置类型并没有任何影响但是自定义类型就不同了呢注意在调用 Insert 函数时如果没有完美转发的话x退化为左值。进而会调用参数为左值引用的 Insert 函数。但是这样底层在插入数据时会多出来一次拷贝构造。而如果使用完美转发保持参数的原有属性时底层会进行移动构造。进而会提升效率。 二、新类的功能
2、1 默认成员函数 我们之前在初学C类时有6个默认成员函数 构造函数析构函数拷贝构造函数拷贝赋值重载取地址重载const 取地址重载。 最后重要的是前4个后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。 C11 新增了两个移动构造函数和移动赋值运算符重载。针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下 如果你没有自己实现移动构造函数且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动构造如果实现了就调用移动构造没有实现就调用拷贝构造。如果你没有自己实现移动赋值重载函数且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动赋值如果实现了就调用移动赋值没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值编译器不会自动提供拷贝构造和拷贝赋值。 2、2 缺省参数初始化 我们之前在学类和对象时就学过了参数可以给缺省值。这个功能是C11新增的这里就不再过多详细解释了。 2、3 强制生成默认函数的关键字default C11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数但是因为一些原因这个函数没有默认生成。比如我们提供了拷贝构造就不会生成移动构造了那么我们可以使用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;
} 2、4 禁止生成默认函数的关键字delete 如果能想要限制某些默认函数的生成在C98中是该函数设置成private并且只声明就可以。这样只要其他人想要调用就会报错。在C11中更简单只需在该函数声明加上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;
} 2、5 继承和多态中的final和override关键字 在C中final关键字用于修饰类、成员函数用于表示它们是最终的不能被继承或覆盖。 修饰类当在类声明时使用final关键字表示该类是最终的不能被其他类继承。这样一来其他类将无法派生出继承自该类的子类。 class Base final {// class definition
};class Derived : public Base { // 错误无法派生自标记为final的类// class definition
}; 当在C中使用final关键字修饰成员函数时它的作用是表示该成员函数是最终的不能在派生类中被覆盖或重写。 class Base {
public:virtual void foo() final {// 确定的行为实现}
};class Derived : public Base {
public:void foo() {// 错误由于被标记为final无法在派生类中重写foo函数// 可以直接使用基类中定义的行为实现}
}; 在C中override关键字用于显式地标记派生类中的成员函数表示该函数是对基类中同名函数的重写。 当我们在派生类中使用override关键字修饰一个成员函数时编译器会检查该函数是否满足以下条件 函数必须是虚函数或纯虚函数。函数在基类中必须有相同的名称、返回值和参数列表。 如果派生类中的函数没有满足以上两个条件中的任意一个编译器将产生编译错误。这样我们可以确保在派生类中重写的函数与基类中的函数一致避免了潜在的错误或误用。下面是一个示例 class Base {
public:virtual void foo() {// 基类的实现}
};class Derived : public Base {
public:void foo(int n) override {// 错误 并没有完成重写}
}; 三、可变参数模板 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板相比C98/03类模版和函数模版中只能含固定数量的模版参数可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象使用起来需要一定的技巧所以这块还是比较晦涩的。但是我们也需要了解一下其简单使用放法。 当涉及到处理不确定数量的参数时C11的可变参数模板非常有用。它提供了一种灵活的方式来定义接受任意数量参数的函数模板或类模板。以下是一些示例解释了如何使用C11的可变参数模板 template class ...Args
void ShowList(Args... args)
{} 上面的参数args前面有省略号所以它就是一个可变模版参数我们把带省略号的参数称为“参数包”它里面包含了0到NN0个模版参数。我们无法直接获取参数包args中的每个参数的只能通过展开参数包的方式来获取参数包中的每个参数这是使用可变模版参数的一个主要特点也是最大的难点即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数所以我们的用一些奇招来一一获取参数包的值。 当然我们再调用此函数时可以传入任何数量的参数。我们再看下述代码 template class ...Args
void ShowList(Args... args)
{//计算传入的参数个数cout sizeof...(args) endl;// 下述的打印实参的方法是错误的//for (size_t i 0; i sizeof...(args); i)//{// cout args[i] ;//}//cout endl;
}int main()
{string str(hello);ShowList();ShowList(1);ShowList(1, A);ShowList(1, A, str);return 0;
} 问题来了到底怎么取出参数包中的参数呢我们接着往下看。 3、2 递归函数方式展开参数包 我们先看如下代码 #include iostream// 基本情况没有额外参数时终止递归
void print() {std::cout std::endl;
}// 递归情况打印参数并继续递归
templatetypename T, typename... Args
void print(const T firstArg, const Args... args) {std::cout firstArg ;print(args...); // 递归调用print函数
}int main() {print(1, 2, 3, Hello, 4.5); // 调用print函数打印多个参数return 0;
} 在上面的示例代码中我们定义了一个print函数它采用可变参数模板的形式。该函数的基本情况是没有额外参数时打印一个换行符并终止递归。递归情况下它会打印第一个参数然后通过递归调用print函数来处理剩余的参数。 在main函数中我们调用了print函数并传递了多个参数整数、字符串和浮点数。这些参数被逐个打印出来最终结果是1 2 3 Hello 4.5。 3、2 逗号表达式展开参数包 template class T
void PrintArg(T t)
{cout t ;
}
//展开函数
template class ...Args
void ShowList(Args... args)
{int arr[] { (PrintArg(args), 0)... };cout endl;
}
int main()
{ShowList(1);ShowList(1, A);ShowList(1, A, std::string(sort));return 0;
} 这种展开参数包的方式不需要通过递归终止函数是直接在ShowList函数体中展开的, PrintArg不是一个递归终止函数只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。 ShowList函数中的逗号表达式(PrintArg(args), 0)也是按照这个执行顺序先执行PrintArg(args)再得到逗号表达式的结果0。同时还用到了C11的另外一个特性——初始化列表通过初始化列表来初始化一个变长数组, {(PrintArg(args), 0)...}将会展开成((PrintArg(arg1),0), (PrintArg(arg2),0), (PrintArg(arg3),0), etc... )最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。由于是逗号表达式在创建数组的过程中会先执行逗号表达式前面的部分PrintArg(args)打印出参数也就是说在构造int数组的过程中就将参数包展开了这个数组的目的纯粹是为了在数组构造的过程展开参数包。 3、3 STL容器中的empalce相关接口函数 在上篇文章中C11的新特性上我们讲述到了STL中的变化。但是由于可变参数模板并没有进行详细的解释所以把emplace相关接口放到此处进行详细讲解。 在C11标准中STL容器提供了emplace系列函数用于在容器中构造对象并插入新元素。如下 emplace_back emplace_back函数用于在容器的末尾直接构造一个新的元素通过将传递给该函数的参数直接传递给元素的构造函数来完成构造。这样再某些可以避免创建临时对象和多次复制或移动操作。 emplace emplace函数用于在容器中指定位置(迭代器)之前插入新的元素并通过将传递给该函数的参数直接传递给元素的构造函数来完成构造。 上述我们了解 emplace 和 emplace_back 后我们来看看其具体用法和到底在哪些情况下有效率提升。我们先看如下代码 class Date
{
public:Date(int year 1, int month 1, int day 1):_year(year), _month(month), _day(day){cout Date(int year 1, int month 1, int day 1) endl;}Date(const Date d):_year(d._year), _month(d._month), _day(d._day){cout Date(const Date d) endl;}Date operator(const Date d){cout Date operator(const Date d)) endl;return *this;}private:int _year;int _month;int _day;
};int main()
{// 没有区别vectorint v1;v1.push_back(1);v1.emplace_back(2);vectorpairstring,int v2;v2.push_back(make_pair(sort, 1));v2.emplace_back(make_pair(sort, 1));v2.emplace_back(sort, 1);listDate lt1;lt1.push_back(Date(2022, 11, 16));cout --------------------------------- endl;lt1.emplace_back(2022, 11, 16);return 0;
} 在上述代码中我们就使用 vector来举例解释一下emplace系列函数的使用和优势。我们在 v1 中插入一些内置类型其实并没有任何的效率提升与push系列的函数用法、效果、效率可以说是一样的。但是在 v2 种插入 pair 对象就会有所区别的。 当使用 push_back 插入自定义类型对象时首先我们需要构造处对象。其次再插入的时候底层在插入时采用的是拷贝对象进行插入。但是 emplace_back 插入对象我们传入的是可变参数并不用构造出pair对象底层会自动识别出 pair 对象。底层在插入时会直接把我们传入的参数进行构造到所要插入的位置。相对 push_back 插入减少一次拷贝构造。 我们自己创建一个Date类进行测试代码如上。我们看运行结果 确实是少了一次拷贝构造。基本上所有提供emplace系列函数的容器在插入自定义类型对象时都会有效率提升。而内置类型并没有构造、拷贝等所以与push系列函数一样。 四、lambda 表达式
4、1 C98例子引入 我们知道在 algorithm 头文件中有一个排序算法。默认排序是升序当我们需要排降序的时候可以通过传递第三个参数仿函数对象进行控制。具体例子如下 #include algorithm
#include functional
int main()
{int array[] { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较排出来结果是升序std::sort(array, array sizeof(array) / sizeof(array[0]));// 如果需要降序需要改变元素的比较规则std::sort(array, array sizeof(array) / sizeof(array[0]), std::greaterint());return 0;
} 上述情况并没有任何复杂的情况。那要是对复杂自定义类型进行排序呢我们在看如下实例 struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
struct ComparePriceLess
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};
int main()
{vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2,3 }, { 菠萝, 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());
} 上述代码中发现需要自己进行写仿函数类。假如 Goods 的属性更多排序的情况更加复杂呢还需要我们进行写更多了仿函数。这样有什么问题呢 随着C语法的发展人们开始觉得上面的写法太复杂了每次为了实现一个algorithm算法都要重新去写一个类如果每次比较的逻辑不一样还要去实现多个类特别是相同类的命名这些都给编程者带来了极大的不便。因此在C11语法中出现了Lambda表达式。 4、2 lambda 表达式详解
4、2、1 lambda 表达式语法 lambda表达式的基本语法如下 [capture list](parameters) mutable - return type
{ function body
} capture list捕获列表。捕获列表是lambda表达式的一部分用于访问外部的变量。可以使用空括号[]表示不捕获任何变量也可以使用方括号[变量名]来显式捕获一个或多个变量。捕获列表还可以使用值捕获和引用捕获即使用[]和[]。parameters参数列表。参数列表定义了传递给lambda表达式的参数类似于函数的参数列表。可以省略参数类型编译器会自动推导。mutable在lambda表达式中默认情况下是不允许修改被捕获的变量的。如果需要修改则需要使用mutable关键字进行声明。return type返回类型是可选的如果省略则编译器会自动推导返回类型。一般情况下我们都是选择省略的交给编译器自行推导。function body函数体用于定义具体的操作和逻辑。 注意在lambda函数定义中参数列表和返回值类型都是可选部分而捕捉列表和函数体可以为空。因此C11中最简单的lambda函数为[]{}; 该lambda函数不能做任何事情。 捕获列表说明捕捉列表描述了上下文中那些数据可以被lambda使用以及使用的方式传值还是传引用。 [var]表示值传递方式捕捉变量var[]表示值传递方式捕获所有父作用域中的变量(包括this) [var]表示引用传递捕捉变量var[]表示引用传递捕捉所有父作用域中的变量(包括this)[this]表示值传递方式捕捉当前的this指针 注意 父作用域指包含lambda函数的语句块。语法上捕捉列表可由多个捕捉项组成并以逗号分割。比如[, a, b]以引用传递的方式捕捉变量a和b值传递方式捕捉其他所有变量[a, this]值传递方式捕捉变量a和this引用方式捕捉其他变量。捕捉列表不允许变量重复传递否则就会导致编译错误。比如[, a]已经以值传递方式捕捉了所有变量捕捉a重复。在块作用域以外的lambda函数捕捉列表必须为空。在块作用域中的lambda函数仅能捕捉父作用域中局部变量捕捉任何非此作用域或者非局部变量都会导致编译报错。f. lambda表达式之间不能相互赋值即使看起来类型相同。 4、2、2 lambda 表达式实例 我们用一个完整的 lambda 表达式来完成一个两数求和的功能。具体代码如下 int main()
{int a 1, b 2;//auto f [](int a, int b)-int {return a b; };// 捕捉列表 指定捕捉变量。// 当我们指定捕捉变量时[] 捕捉列表中的变量名必须与当前作用域的变量名相同。// 下面是拷贝捕捉。当然我们想要对所捕捉的变量修改时可选择引用捕捉 [a , b]。//auto f [a, b]()-int {return a b; };// 捕捉列表 自动采用拷贝的方式将我们所用到的变量从当前的作用域中进行捕捉查找// auto f[](a)()-int{ return a b; };auto f []()-int { return a b; };cout f() endl;return 0;
} 上述例子我们给出了三种用法。大家都是需要进行理解掌握的。当然我们再用lambda表达式解决一下上述C98例子的问题。具体代码如下 struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价//...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};//struct ComparePriceLess
struct Compare1
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};//struct ComparePriceGreater
struct Compare2
{bool operator()(const Goods gl, const Goods gr){return gl._price gr._price;}
};int main()
{vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2, 3 }, { 菠萝, 1.5, 4 } };/*sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());*///sort(v.begin(), v.end(), Compare1());//sort(v.begin(), v.end(), Compare2());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;});
} 4、2、3 lambda 表达式与函数对象仿函数 函数对象又称为仿函数即可以想函数一样使用的对象就是在类中重载了operator()运算符的类对象。下面我们对比一下函数对象仿函数与lambda 表达式的区别。如下代码 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 表达示与函数对象使用起来并无任何差别。反而 lamdba 表达式使用起来更加简单。 lambda 表达式到底是怎么实现的呢我们大概了解一下。 当定义lambda表达式时编译器会生成一个匿名结构体其中包含了lambda表达式中用到的所有变量。这个结构体会重载函数调用运算符 ()使得我们可以像调用函数一样调用lambda表达式。同时编译器还会生成代码来初始化这个结构体的成员变量以保证在调用所生成的匿名结构体之前、所生成的匿名结构体内部能够访问正确的变量。具体如下图 五、包装器
5、1 function包装器用法 function包装器 也叫作适配器。C中的function本质是一个类模板也是一个包装器。那么我们来看看我们为什么需要function呢 C11引入了function模板类作为一个通用的函数包装器用于存储、传递和调用任意可调用对象函数、函数对象、lambda表达式等。它可以看作是一个类型安全的、灵活的函数指针。 function模板类的基本用法如下所示 #include iostream
#include functionalvoid foo() {std::cout Hello, World! std::endl;
}int add(int a, int b) {return a b;
}int main() {std::functionvoid() func1 foo;std::functionint(int, int) func2 add;func1(); // 调用无返回值的函数int result func2(3, 4); // 调用有返回值的函数std::cout Result: result std::endl;return 0;
}上述代码中我们使用了function模板类来包装两个不同的函数。首先我们声明了一个无返回值的函数foo和一个有返回值的函数add。然后在main函数中我们分别创建了两个function对象func1和func2。 在创建function对象时需要指定其签名即函数类型以便正确地匹配被包装的函数。这里func1的类型为std::functionvoid()表示接收无参数并返回void的函数而func2的类型为std::functionint(int, int)表示接收两个int类型参数并返回int类型的函数。 然后我们可以通过调用function对象来使用被包装的函数。对于无返回值的函数可以直接通过函数调用操作符()来执行对于有返回值的函数则需要将参数传递给function对象并接收返回值。最终我们打印了有返回值函数的结果。 5、2 function包装器举例使用 从上述例子中我们只是了解了function包装器的使用方法当时并不知道为什么要引入function包装器和其具体使用场景。下述我们将会结合实际例子进行讲解。 我们先看如下代码 // ret func(x);
// 上面func可能是什么呢那么func可能是函数名函数指针函数对象(仿函数对象)也有可能
// 是lamber表达式对象所以这些都是可调用的类型如此丰富的类型可能会导致模板的效率低下
// 为什么呢我们继续往下看templateclass 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;// lamber表达式对象cout useF([](double d)-double{ return d / 4; }, 11.11) endl;return 0;
} 通过上面的程序验证我们会发现useF函数模板实例化了三份。如下图 包装器可以很好的解决上面的问题。我们看使用包装器的代码 #include functional
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::functionint(int, int) func1 f;cout func1(1, 2) endl;// 函数对象std::functionint(int, int) func2 Functor();cout func2(1, 2) endl;// lamber表达式std::functionint(int, int) func3 [](const int a, const int b){return a b; };cout func3(1, 2) endl;// 类的静态成员函数std::functionint(int, int) func4 Plus::plusi;cout func4(1, 2) endl;//类的非静态成员函数std::functiondouble(Plus, double, double) func5 Plus::plusd;cout func5(Plus(), 1.1, 2.2) endl;return 0;
} 对上述的代码进行简单解释 首先函数f被赋值给std::functionint(int, int) func1func1可以像普通函数一样进行调用。其次Functor对象被赋值给std::functionint(int, int) func2func2可以通过()运算符调用该对象并将参数传递给operator()(int a, int b)。来到lambda表达式部分(const int a, const int b) { return a b; }被赋值给std::functionint(int, int) func3func3可以像函数一样进行调用。接下来静态成员函数Plus::plusi被赋值给std::functionint(int, int) func4func4可以像函数一样被调用。最后非静态成员函数Plus::plusd被赋值给std::functiondouble(Plus, double, double) func5但由于非静态成员函数需要一个实例来调用注意不能省略所以在调用时需要创建一个Plus对象作为参数传递给func5。包装非静态的成员函数时需要注意非静态成员函数的第一个参数是隐藏this指针因此在包装时需要指明第一个形参的类型为类的类型。 当用包装器进行封装后我们再来看会实例化出几份。代码和运行结果如下 #include functional
templateclass 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::functiondouble(double) func1 f;cout useF(func1, 11.11) endl;// 函数对象std::functiondouble(double) func2 Functor();cout useF(func2, 11.11) endl;// lamber表达式std::functiondouble(double) func3 [](double d)-double { return d / 4; };cout useF(func3, 11.11) endl;return 0;
} 发现确实只实例化了一份模板。解决了实例出多份模板造成效率低下的问题。但是又引出了一个问题。 上述代码我们在包装 f 、 Functor 和 lambda 表达式时被调用函数是只需要传2个参数。但是在包装plusd时就需要传递3个参数。 假设我现在想在map容器里建立包装器和字符串对应的映射关系这时由于被调用函数所需传入的参数不同无法很好的建立映射关系。于是这里就需要引入 bind 捆绑器了。 5、3 bind 捆绑器
5、3、1 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
template class Ret, class Fn, class... Args
/* unspecified */ bind(Fn fn, Args... args); 上述代码是 C 中bind函数的原型它是一个模板函数可以根据不同的参数类型进行实例化。bind函数的原型有两个版本其中一个版本没有指定返回类型使用了unspecified另一个版本可以指定返回类型。 无返回类型版本 Fn fn接受一个右值引用模板的右值引用会产生引用折叠也是万能引用表示要绑定的可调用对象。Args... args接受一个可变数量的参数表示要绑定给可调用对象的参数。 有返回类型版本 Ret表示要指定的返回类型。Fn fn接受一个右值引用表示要绑定的可调用对象。Args... args接受一个可变数量的参数表示要绑定给可调用对象的参数。 这两个版本的bind函数在调用时会将传入的可调用对象与参数进行绑定并生成一个绑定后的函数对象。 需要注意的是由于bind函数的返回类型是未指定的因此我们可以使用auto关键字来接收返回值或者使用std::function来显式指定返回类型。 可以将bind函数看作是一个通用的函数适配器它接受一个可调用对象生成一个新的可调用对象来“适应”原对象的参数列表。 调用bind的一般形式auto newCallable bind(callable,arg_list);其中newCallable本身是一个可调用对象arg_list是一个逗号分隔的参数列表对应给定的callable的参数。当我们调用newCallable时newCallable会调用callable,并传给它arg_list中的参数。 arg_list中的参数可能包含形如_n的名字其中n是一个整数这些参数是“占位符”表示newCallable的参数它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置_1为newCallable的第一个参数_2为第二个参数以此类推。 只有概念似乎并不能很好的理解 bind 的使用下面将为你提供几个具体的示例来详细解释bind的使用方法 绑定普通函数 #include iostream
#include functionalvoid func(int a, int b) {std::cout Sum: (a b) std::endl;
}int main() {auto sum_func std::bind(func, 10, std::placeholders::_1);sum_func(20); // 输出 Sum: 30return 0;
} 在这个例子中bind函数绑定了函数func并将值10作为其第一个参数。然后通过调用sum_func(20)来传递参数20给func从而实现了延迟调用。最终的输出是 Sum: 30。 绑定成员函数 #include iostream
#include functionalclass MyClass {
public:void print(int num) {std::cout Number: num std::endl;}
};int main() {MyClass obj;auto print_func std::bind(MyClass::print, obj, std::placeholders::_1);print_func(42); // 输出 Number: 42return 0;
} 在这个例子中bind函数绑定了成员函数print并指定了对象obj作为该成员函数的调用者。然后通过调用print_func(42)来传递参数42给print函数从而实现了延迟调用。最终的输出是 Number: 42。 绑定Lambda表达式 #include iostream
#include functionalint main() {int num 10;auto lambda [](int x) {std::cout Result: (x * x) std::endl;};auto square_func std::bind(lambda, std::placeholders::_1);square_func(num); // 输出 Result: 100return 0;
} 在这个例子中bind函数绑定了Lambda表达式并将参数num延迟传递给该Lambda表达式。通过调用square_func(num)实现了对参数num进行平方运算并输出结果。最终的输出是 Result: 100。 到这里我们就可以恨到的解决我们上述所遇到的问题了。我们在看如下代码 using namespace placeholders;int Div(int a, int b)
{return a / b;
}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;}
};int main()
{// 调整个数, 绑定死固定参数functionint(int, int) funcPlus Plus;//functionint(Sub, int, int) funcSub Sub::sub;functionint(int, int) funcSub bind(Sub::sub, Sub(), _1, _2);functionint(int, int) funcMul bind(Mul, _1, _2, 1.5);mapstring, functionint(int, int) opFuncMap {{ , Plus},{ -, bind(Sub::sub, Sub(), _1, _2)},{ *, bind(Mul, _1, _2, 1.5)}};cout funcPlus(1, 2) endl;cout funcSub(1, 2) endl;cout funcMul(2, 2) endl;cout opFuncMap[](1, 2) endl;cout opFuncMap[-](1, 2) endl;return 0;
} 5、3、2 bind绑定 交换参数顺序 在C11中可以使用std::bind函数来交换函数参数的顺序。std::bind是一个通用的函数适配器它接受一个可调用对象并生成一个新的可调用对象该对象可以延迟调用原始函数并改变传入参数的顺序。 下面是一个示例演示如何使用std::bind交换函数参数顺序 #include iostream
#include functionalvoid printNumbers(int a, int b) {std::cout a: a , b: b std::endl;
}int main() {// 使用std::bind交换参数顺序auto swappedPrint std::bind(printNumbers, std::placeholders::_2, std::placeholders::_1);// 调用交换参数顺序后的函数swappedPrint(3, 5); // 输出a: 5, b: 3return 0;
} 在上面的示例中我们定义了一个名为printNumbers的函数该函数接受两个整数参数并打印它们的值。然后我们使用std::bind来交换参数的顺序创建了一个新的可调用对象swappedPrint。std::bind的第一个参数是要适配的函数或函数指针然后是要传递给函数的参数。在这里我们使用了两个特殊的占位符std::placeholders::_1和std::placeholders::_2来表示原始函数的第一个和第二个参数。 最后我们通过调用swappedPrint来调用被适配的函数传递了两个整数参数3和5。由于std::bind改变了参数的顺序所以实际上会按照交换后的顺序将参数传递给原始函数即先传递参数5再传递参数3。因此输出结果为a: 5, b: 3。