[C++11#46](三) 详解lambda | 可变参数模板 | emplace_back | 默认的移动构造

2024-09-04 16:52

本文主要是介绍[C++11#46](三) 详解lambda | 可变参数模板 | emplace_back | 默认的移动构造,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一.lambda

1. 捕捉列表

2. 底层原理

二. 可变参数模板

1. 递归函数方式展开参数包

2. 数组接收方式展开参数包

3. 运用

4.emplace_back

5.移动构造和拷贝构造

强制生成 default


一.lambda

可调用类的对象

  • 函数指针--少用 void(*ptr) (int x)
  • 仿函数--构造类 重载 operator() 对象可以像函数一样使用--eg.模板参数
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceGreater());
}
  • lambda--匿名函数对象 函数内部,直接定义使用

相当于一个局部的没有函数名的函数对象

int main()
{auto compare = [](int x, int y) {return x > y; };cout << compare(1, 2) << endl;//可以像函数一样调用return 0;
}

内部可以调用全局函数吗?可以

局部函数呢?不可以

1. 捕捉列表

捕捉的方式

  1. [a,b] 传值捕捉
  2. [&a,&b] 传引用捕捉
  3. [=] 传值捕捉方式父作用域中所有变量(包括this指针)
  4. [&] 传引用捕捉方式父作用域中所有变量(包括this指针)
  5. 混合使用,例如捕捉[&x,y]

测试:

int main()
{int a = 0, b = 1;auto add1 = [](int x, int y) { return x + y; };cout << add1(a, b) << endl;auto add2 = [b](int x) {return x + b; };cout << add2(a) << endl;//引用的方式捕捉auto swap = [&a, &b](){int tmp = a;a = b;b = tmp;};swap();cout << "After swapping: a = " << a << ", b = " << b << endl;return 0;
}

特例:

  1. [=, &a, &b]:使用值传递方式捕获所有变量,除了 ab,这两个变量将以引用方式捕获。
  2. [&,a, this]:使用引用传递方式捕获变量 athis,其他变量将以值传递方式捕获。
  3. [=, a]:这个捕捉列表是错误的,因为 = 已经以值传递方式捕获了所有变量,而 a 也被重复捕获了。
  4. 在块作用域以外的lambda函数:捕捉列表必须为空。这意味着lambda不能捕获任何外部变量。
  5. 在块作用域中的lambda函数:只能捕获父作用域中局部变量。
  6. lambda表达式之间不能相互赋值:即使看起来类型相同,也不能直接将一个lambda表达式赋值给另一个。这是因为lambda表达式有特定的捕获行为和生命周期,它们不能像普通变量那样直接赋值。
int main(){int a = 0;int b = 1;int c = 2;int d = 3;const int e = 1;cout << &e << endl;// 引用的方式捕捉所有对象,除了a// a用传值的方式捕捉auto func = [&, a] {//a++;b++;c++;d++;//e++;cout << &e << endl;//const & 得到的是同一个地址};func();return 0;
}

所以对于局部函数我们也可以捕捉后调用,但是没什么必要

2. 底层原理

UUID:唯一识别码

打印查看仿函数:

#include <iostream>class Add {
public:int operator()(int a, int b) const {return a + b;}
};int main() {Add add;int result = add(10, 20);std::cout << "Result: " << result << std::endl;return 0;
}

底层:

将 lambda 和仿函数的底层对比查看:

int main()
{auto f1 = [](int x, int y) {return x + y; };auto f2 = [](int x, int y) {return x + y; };//f1 = f2;cout << typeid(f1).name() << endl;cout << typeid(f2).name() << endl;f1(1, 2);return 0;
}

底层:

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的

  • lambda_uuid(如上编译器没显示)
  • 即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()

编译器底层只有类和仿函数 operate()


二. 可变参数模板

例如 printf,想要几个参数就传几个

  • 模板参数:类型
  • 函数参数:对象

表示为  ...

//模板可变参数
template <class ...Args>
void CppPrint(Args... args)
{cout << sizeof...(args) << endl;//打印参数个数
}
int main()
{CppPrint();CppPrint(1);CppPrint(1, 2);CppPrint(1, 2, 2.2);CppPrint(1, 2, 2.2, string("xxxx"));// ...return 0;
}

我有一技: 可以对可变参数进行打印吗?不可以

类似实现 CppPrint:

要通过编译时的函数重载,递归推演来打印

1. 递归函数方式展开参数包

  • 给函数模板增加一个模板参数这样就可以从接收到的参数包中分离出一个参数出来
    • 在函数模板中递归调用该函数模板,调用时传入剩下的参数包。
    • 如此递归下去,每次分离出参数包中的一个参数,直到参数包中的所有参数都被取出来
  • 还需要给一个递归终止函数
void _ShowList()
{// 结束条件的函数cout << endl;
}template <class T, class ...Args>
void _ShowList(T val, Args... args)
{cout << val << " ";_ShowList(args...);
}//args代表0-N的参数包
template <class ...Args>
void CppPrint(Args... args)
{_ShowList(args...);
}
//传给_,递归挨个解析出第一个值int main()
{CppPrint(1);CppPrint(1, 2);CppPrint(1, 2, 2.2);CppPrint(1, 2, 2.2, string("xxxx"));// ...return 0;
}

2. 数组接收方式展开参数包

还有一种新方式:利用编译器去推演,数组存储,可变参数包一个一个的取出来

  • 这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数
template <class T>
void PrintArg(T t)
{cout << t << " ";
}// 参数包有几个值,就展开调用几次
template <class... Args>
void ShowList(Args... args)
{int arr[] = {PrintArg(args)...};//利用编译器去推演,数组存储cout << endl;
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

3. 运用

模板的可变参数实现调用的多样化,灵活

class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day){cout << "Date构造" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date拷贝构造" << endl;}private:int _year;int _month;int _day;
};template <class ...Args>
Date* Create(Args... args)
{Date* ret = new Date(args...);return ret;
}int main()
{Date* p1 = Create();Date* p2 = Create(2023);Date* p3 = Create(2023, 9);Date* p4 = Create(2023, 9, 27);
//拷贝构造Date d(2023, 1, 1);Date* p5 = Create(d);return 0;
}

4.emplace_back

带模板参数的&&是万能引用,结合可变参数

emplace 效果的体现场景:主要体现在浅拷贝的拷贝构造的优化

  • emplace 能一直往下传
  • push_back 是先构造,再拷贝构造/移动构造

emplace 可以理解为一个优化的 push_back

5.移动构造和拷贝构造

新的类功能:默认成员函数

在原来的C++类中,编译器会默认生成六个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值运算符重载
  5. 取地址运算符重载
  6. const 取地址运算符重载

其中,最为常用的是前四个,后两个通常使用较少。这些默认成员函数在我们没有显式定义时,编译器会自动生成。

C++11 新增了两个默认成员函数:

  1. 移动构造函数
  2. 移动赋值运算符重载

移动构造函数和移动赋值运算符重载的注意点

  • 如果没有自己定义移动构造函数,并且没有实现析构函数、拷贝构造函数或拷贝赋值运算符中的任意一个,那么编译器会自动生成一个默认的移动构造函数。
    • 对于内置类型成员,执行逐成员按字节的浅拷贝。
    • 对于自定义类型成员,若该成员实现了移动构造函数,则调用移动构造函数;若没有,则调用拷贝构造函数。
  • 类似地,如果没有定义移动赋值运算符重载函数,如上
  • 一旦提供了移动构造函数或移动赋值运算符,编译器就不会再自动生成拷贝构造函数和拷贝赋值运算符。
class Person
{
public:Person(const char* name = "", int age = 0): _name(name), _age(age) {}private:bit::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;            // 调用默认的拷贝构造函数Person s3 = std::move(s1);  // 调用默认的移动构造函数Person s4;s4 = std::move(s2);         // 调用默认的移动赋值运算符return 0;
}

在上述代码中,由于没有定义析构函数、拷贝构造函数和拷贝赋值运算符,编译器会自动生成默认的移动构造和移动赋值运算符。

为什么编译器会自动生成默认的移动构造和移动赋值?

通常,对于需要深拷贝或需要释放资源的类,开发者会显式定义拷贝构造函数、赋值运算符重载和析构函数。如果没有显式定义这些函数,编译器生成的默认移动构造函数和移动赋值运算符将对内置类型执行值拷贝

强制生成 default

在C++11中,可以使用default关键字来强制生成某些默认成员函数,即使开发者提供了其他构造函数。例如,提供了拷贝构造函数后,编译器不会再生成默认的移动构造函数,但可以通过= 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;
};

即使手动编写了拷贝构造函数,仍然可以通过= default来让编译器生成移动构造函数。

这篇关于[C++11#46](三) 详解lambda | 可变参数模板 | emplace_back | 默认的移动构造的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1136491

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca