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

C++ 之【C++11的简介】(可变参数模板、lambda表达式、function\bind包装器)

目录

1.可变参数模板

1.1 递归函数方式展开参数包

1.2逗号表达式展开参数包

1.3 STL容器中的empalce相关接口函数

2.lambda表达式

2.1lambda表达式语法

2.2函数对象与lambda表达式

3.包装器

3.1function

3.2bind


1.可变参数模板

template <class ...Args>void ShowList(Args... args){}

Args (名字可以随便取) 是一个模板参数包,args是一个函数形参参数包
声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。

1.1 递归函数方式展开参数包

// 递归终止函数
template <class T>
void ShowList(const T& t)
{cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value << " ";ShowList(args...);
}int main(){ShowList(1);//args中参数为0个ShowList(1, 'A');//args中参数为1个ShowList(1, 'A', std::string("sort"));//args中参数为2个return 0;}

1.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;}

在创建数组的过程中会先执行逗号表达式前面的部分printarg(args) 打印出参数,

也就是说在构造int数组的过程中就将参数包展开了,

这个数组的目的纯粹是为了在数组构造的过程展开参数包

struct Date
{int _year;int _month;int _day;Date(int year = 2025, int month = 9, int day = 15):_year(year),_month(month),_day(day){ }
};template<class...Args>
Date* Create(Args&&... args)
{return new Date(std::forward<Args>(args)...);
}int main()
{Date* p1 = Create();Date* p2 = Create(2025);Date* p3 = Create(2025, 9);Date* p4 = Create(2025, 9, 9);return 0;
}

Create函数支持万能引用与完美转发,且Date函数存在默认构造函数

所有后续调用Create函数

1.3 STL容器中的empalce相关接口函数

 template <class... Args>void emplace_back (Args&&... args);

emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象(参数包匹配)
那么在这里我们可以看到除了用法上,和push_back没什么太大的区别

int main()
{std::list< std::pair<int, char> > mylist;mylist.emplace_back(10, 'a');//参数包接收参数往下传mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}

下面我们试一下带有拷贝构造和移动构造的string
我们会发现其实差别也不到,emplace_back是直接构造了,

push_back是先构造,再移动构造,其实也还好。

int main()
{std::list< std::pair<int, string> > mylist;mylist.emplace_back(10, "sort");//参数包往下传,直接构造pair对象mylist.emplace_back(make_pair(20, "sort"));mylist.push_back(make_pair(30, "sort"));//先创建pair对象,再移动构造节点中的pairmylist.push_back({ 40, "sort" });return 0;
}

类似实现

template <typename... Args>
void emplace_back(Args&&... args) {if (size() >= capacity()) {// 可能触发内存重新分配(影响所有元素)reserve(std::max(size() * 2, 1));}// 在容器内存中直接构造对象(完美转发参数)new (data() + size()) T(std::forward<Args>(args)...);++size_;
}

2.lambda表达式

2.1lambda表达式语法

lambda表达式书写格式:

[capture-list] (parameters) mutable -> return-type { statement }

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

int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[] {};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=] {return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c) {b = a + c; };fun1(10);cout << a << " " << b << endl;// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int {return b += a + c; };cout << fun2(10) << endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0;
}

lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量

2. 捕获列表说明
捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式传值还是传引用
[var]:表示值传递方式捕捉变量var
 [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
 [&var]:表示引用传递捕捉变量var
 [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
 [this]:表示值传递方式捕捉当前的this指针
注意:
    a. 父作用域指包含lambda函数的语句块 

Lambda 的父作用域包括:

局部变量:在 Lambda 定义之前声明的局部变量。

全局变量:在任何函数外部定义的变量。

静态变量:使用 static 定义的变量。

类成员(如果 Lambda 定义在成员函数内):可以通过 this 捕获。

但不包括:

在 Lambda 定义之后声明的变量(因为它们在 Lambda 的作用域之外)。

局部变量的作用域仅限于它们所在的块({})。

    b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
           [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
    c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
 d. 如果 Lambda 函数定义在全局作用域(即不在任何函数或代码块 {} 内部),那么它的捕获列表(Capture List)必须为空
    e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量都会导致编译报错。
    f. lambda表达式之间不能相互赋值,即使看起来类型相同

void (*PF)();
int main()
{auto f1 = [] {cout << "hello world" << endl; };auto f2 = [] {cout << "hello world" << endl; };// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了//f1 = f2;    // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF = f2;PF();return 0;
}

2.2函数对象与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表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()

3.包装器

3.1function

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;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

通过上面的程序验证,我们会发现useF函数模板实例化了三份

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

std::function

作用:通用函数包装器,可以存储可调用对象(函数、Lambda、函数对象、成员函数等)

#include <functional>
#include <iostream>// 示例函数
int add(int a, int b) { return a + b; }int main() {// 1. 包装普通函数std::function<int(int, int)> func1 = add;std::cout << func1(3, 4) << std::endl;  // 输出 7// 2. 包装 Lambda 表达式std::function<int(int, int)> func2 = [](int a, int b) { return a * b; };std::cout << func2(3, 4) << std::endl;  // 输出 12// 3. 包装函数对象(仿函数)struct Divide {int operator()(int a, int b) const { return a / b; }};std::function<int(int, int)> func3 = Divide();std::cout << func3(10, 2) << std::endl;  // 输出 5
}

function<返回值(参数列表)> 

3.2bind

std::bind

作用:部分应用(Partial Application)或调整参数顺序,生成一个新的可调用对象。

特点:

可以绑定固定参数,使用占位符(_1, _2)调整参数顺序。通常与 std::function 结合使用。

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表

一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。

同时,使用std::bind函数还可以实现参数顺序调整等操作

  • 语法

auto newCallable = std::bind(originalCallable, arg1, arg2, ...);

originalCallable:原始的可调用对象(函数、函数指针、成员函数、函数对象等)。

arg1, arg2, ...:绑定参数,可以是:

具体值(绑定后固定不变)。

占位符(std::placeholders::_1, _2, ...),表示调用时传入的参数

  • 绑定普通函数

  • 绑定部分参数
#include <iostream>
#include <functional> // 包含 std::bindvoid printSum(int a, int b) {std::cout << a + b << std::endl;
}int main() {// 绑定第一个参数为 10,第二个参数待定(用 _1 表示)auto f = std::bind(printSum, 10, std::placeholders::_1);f(20); // 相当于 printSum(10, 20),输出 30f(30); // 相当于 printSum(10, 30),输出 40return 0;
}

std::bind(printSum, 10, _1) 生成一个新的函数对象 f,其中:

第一个参数固定为 10。

第二个参数由调用 f 时的第一个参数(_1)决定。

  • 调整参数顺序
void printValues(int a, int b, int c) {std::cout << a << ", " << b << ", " << c << std::endl;
}int main() {// 调整参数顺序:_1, _3, _2auto f = std::bind(printValues, std::placeholders::_1, std::placeholders::_3, std::placeholders::_2);f(1, 2, 3); // 相当于 printValues(1, 3, 2),输出 "1, 3, 2"return 0;
}

_1 表示调用 f 时的第一个参数。

_3 表示调用 f 时的第三个参数。

_2 表示调用 f 时的第二个参数。

  • 绑定成员函数

绑定成员函数时,第一个参数必须是对象实例(this 或对象引用/指针)

#include <iostream>
#include <functional>class Printer {
public:void print(int x) {std::cout << x << std::endl;}
};int main() {Printer p;// 绑定成员函数,第一个参数是对象(可以用 & 或 std::ref)auto f1 = std::bind(&Printer::print, &p, std::placeholders::_1);f1(10); // 相当于 p.print(10),输出 10// 也可以用 std::ref 避免拷贝auto f2 = std::bind(&Printer::print, std::ref(p), std::placeholders::_1);f2(20); // 相当于 p.print(20),输出 20return 0;
}

&Printer::print 是成员函数指针。

&p 或 std::ref(p) 是对象实例(如果用 std::ref,可以避免拷贝)

这是因为成员函数中默认有一个this指针参数

  • 绑定仿函数

#include <iostream>
#include <functional>struct Multiplier {int operator()(int a, int b) {return a * b;}
};int main() {Multiplier multiplier;// 绑定第一个参数为 5,第二个参数待定(_1)auto f = std::bind(multiplier, 5, std::placeholders::_1);std::cout << f(10) << std::endl; // 5 * 10 = 50std::cout << f(20) << std::endl; // 5 * 20 = 100return 0;
}

文章转载自:

http://qirWWqbE.kwnbd.cn
http://cwJbW7eG.kwnbd.cn
http://n47KWEQ8.kwnbd.cn
http://PRTBvYxt.kwnbd.cn
http://K02vFgMX.kwnbd.cn
http://QQlkCvUO.kwnbd.cn
http://AExvyZ8t.kwnbd.cn
http://HioE9maY.kwnbd.cn
http://gvIVtsO1.kwnbd.cn
http://3jo4qjDY.kwnbd.cn
http://AVDze8xY.kwnbd.cn
http://AK80xAe7.kwnbd.cn
http://i1G4px85.kwnbd.cn
http://PdKkJc0A.kwnbd.cn
http://rmiPNOGN.kwnbd.cn
http://dXF93fBG.kwnbd.cn
http://UVLKYCGB.kwnbd.cn
http://6YR3X5VL.kwnbd.cn
http://YvNDa9Va.kwnbd.cn
http://exmQ6u6Y.kwnbd.cn
http://XYrlEgz6.kwnbd.cn
http://renW4SCo.kwnbd.cn
http://NzjtOIo7.kwnbd.cn
http://54PUVd3F.kwnbd.cn
http://OSFczUaj.kwnbd.cn
http://rdefIR7t.kwnbd.cn
http://Jj1zhovl.kwnbd.cn
http://hii93Bf7.kwnbd.cn
http://KyEeEpoD.kwnbd.cn
http://c5oyzfqH.kwnbd.cn
http://www.dtcms.com/a/385575.html

相关文章:

  • 【基础组件 and 网络编程】对 DPDK 的 MPMC 无锁队列 rte-ring 组件的思考分析(同时也是实战原子操作的好机会)
  • ingress-nginx-controller 414 Request—URI Too Large
  • Java 定时任务与分布式调度工具分析
  • 【热点】最优传输(Optimal Transport)及matlab案例
  • 用 Kotlin 玩转 Protocol Buffers(proto3)
  • leecode73 矩阵置零
  • SELECT INTO 和 INSERT INTO SELECT 区别
  • dhtmlx-gantt
  • Spring如何巧妙解决循环依赖问题
  • 第四章:职业初印象:打造你的个人品牌(1)
  • (九)Python高级应用-文件与IO操作
  • FFmpeg06:SDL渲染
  • javadoc命令 错误: 编码 GBK 的不可映射字符 (0x80)
  • 【面试场景题】自增主键、UUID、雪花算法都有什么问题
  • 数据整理器(Data Collators)总结 (95)
  • 代码评价:std::shared_ptr用法分析
  • 23种设计模式案例
  • AI Agent案例与实践全解析:字节智能运维
  • MyBatis-Plus分页插件实现导致total为0问题
  • S32DS仿真环境问题
  • 黑马JavaWeb+AI笔记 Day07 Web后端实战(部门管理模块)
  • 【AI开发】【前后端全栈】[特殊字符] AI 时代的快速开发思维
  • kimi-k2论文阅读笔记
  • [SC]一个使用前向声明的SystemC项目例子
  • Gunicorn 部署与调优全指南(2025 版)
  • 第二十一篇|新宿平和日本语学校的结构化解读:费用函数、文化网络与AI教育建模
  • 数据结构(C语言篇):(十五)二叉树OJ题
  • RIFE.py代码学习 自学
  • Gateway-路由-规则配置
  • 低端影视官网入口 - 免费看影视资源网站|网页版|电脑版地址