[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

相关文章

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

MySQL中between and的基本用法、范围查询示例详解

《MySQL中betweenand的基本用法、范围查询示例详解》BETWEENAND操作符在MySQL中用于选择在两个值之间的数据,包括边界值,它支持数值和日期类型,示例展示了如何使用BETWEEN... 目录一、between and语法二、使用示例2.1、betwphpeen and数值查询2.2、be

python中的flask_sqlalchemy的使用及示例详解

《python中的flask_sqlalchemy的使用及示例详解》文章主要介绍了在使用SQLAlchemy创建模型实例时,通过元类动态创建实例的方式,并说明了如何在实例化时执行__init__方法,... 目录@orm.reconstructorSQLAlchemy的回滚关联其他模型数据库基本操作将数据添

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

Java利用Spire.Doc for Java实现在模板的基础上创建Word文档

《Java利用Spire.DocforJava实现在模板的基础上创建Word文档》在日常开发中,我们经常需要根据特定数据动态生成Word文档,本文将深入探讨如何利用强大的Java库Spire.Do... 目录1. Spire.Doc for Java 库介绍与安装特点与优势Maven 依赖配置2. 通过替换

Android使用java实现网络连通性检查详解

《Android使用java实现网络连通性检查详解》这篇文章主要为大家详细介绍了Android使用java实现网络连通性检查的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录NetCheck.Java(可直接拷贝)使用示例(Activity/Fragment 内)权限要求

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav