C++——C++11特性


❀保持低旋律节奏->个人主页

专栏链接:《C++学习》《Linux学习》


文章目录

本章着重讲解C++11 引入的一些特性,这些特性是为了修复C++98存在的一些问题
中带你在 {} 统一初始化、隐式类型转换
由左值和右值 引出折叠 完美转发的知识
除此之外 lambda、范围for、缺省参数 语法糖新加为3个

一、👍{}功能

1.1普通初始化

  • 适用于简单结构的初始化

C++98{} 可以用来初始化一段数组、或是结构体

复制代码
struct Point
{
	int _x;
	int _y;
};
int main()
{
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = { 0 };
	Point p = { 1, 2 };
	return 0;
}

1.2列表初始化(统一初始化)------有initializer_list

  • 适用于对容器的初始化

对于容器,他会调用initializer_list构造函数,{......}他这个整体属于initializer_list类型。并且支持迭代器。

cpp 复制代码
//  标准容器初始化(调用 initializer_list 构造函数)
#include <vector>
#include <map>
vector<int> vec{1, 2, 3, 4};  // 正确:调用 vector(initializer_list<int>)
map<int, std::string> mp{{1, "a"}, {2, "b"}};  // 正确:调用 map(initializer_list<pair<int, string>>)
  • 在insert、push_back、emplace中的便利

标准容器的 emplace、push_back 等函数不接受 initializer_list(除非元素类型本身是 initializer_list),但容器的构造函数支持。

cpp 复制代码
vector<Date> v;
v.push_back(Date(2025,1,1));
cpp 复制代码
v.push_back({2025,1,1});
cpp 复制代码
// 函数参数为 initializer_list<int>
void print_list(std::initializer_list<int> lst) {
    for (auto x : lst) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
}

作为返回值第二出现的场景

cpp 复制代码
std::initializer_list<int> get_list() {
    return {10, 20, 30};  // 正确:返回 initializer_list<int> 对象
}
  • initializer_list类型场景
    作为参数函数------最常见的场景

1.3结构体、无initializer_list类的初始化

//适用于 一般数组,结构体,以及无initializer_list的初始化

cpp 复制代码
// 1. 数组初始化(聚合初始化,C++11前就支持类似语法,C++11后统一为{})
int arr1[]{1, 2, 3};  // 正确:数组长度3,元素1、2、3
int arr2[3]{1, 2};    // 正确:剩余元素默认初始化(int→0)
cpp 复制代码
struct Person {
    std::string name;
    int age;
};

Person p{"张三", 20};  // 正确:聚合初始化,name="张三",age=20(无构造函数也能初始化)

1.4多参隐式类型转换

目标类型(函数参数类型)必须有多参数的构造函数(非 initializer_list 构造);

{...} 中的参数数量、类型需匹配该构造函数的参数列表;

编译器会自动将 {...} 转换为目标类型的临时对象(隐式转换)

cpp 复制代码
class Point {
public:
    Point(int x, int y) : x_(x), y_(y) {}  // 多参数构造函数(2个int参数)
private:
    int x_, y_;
};

// 函数参数为 Point 类型
void print_point(Point p) {
    // 假设这里有打印逻辑
}

int main() {
    // 关键:{1, 2} 被隐式转换为 Point 类型(调用 Point(1, 2))
    print_point({1, 2});  // 正确:多参隐式类型转换
    
    // 等价于:print_point(Point(1, 2));  // 显式转换
    return 0;
}

小总结

{}小总结

如果有initializer_list构造和隐式构造,会优先匹配initializer_list构造
对于普通类型、是普通构造。
对于容器 是initializer_list构造
对于结构体、无initializer_list构造的类啥的。大概就属于多惨隐式类型转换

二、左值引用、右值引用。移动构造、移动拷贝。

1.1左值、右值介绍。

  • 左值和右值的区别

左值可以取地址、右值不可以取地址

cpp 复制代码
    //下列b p s 都可以再次取地址 他们都是左值
	int b;
	int* p;
	string s("abcde");//左值 s是左值
cpp 复制代码
    //下面这些都是右值 无法取地址
	int x{ 0 }; int y{0};
	x + y;//右值无法取地址
	string("abcde");//右值
	fmin(x, y);//右值 函数体

1.2左值引用、右值引用的书写方式

  • 1.Type& r1 = x; Type&& rr1 = y;左值引用右值引用的书写

  • 2.左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值

    右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)

  • 3.需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引⽤变量变
    量表达式的属性是左值

  • 四种情况 左值引用书写、右值引用书写、左值引用右值书写、右值引用左值书写

cpp 复制代码
	//
	int& a1 = b;
	int*& b1 = p;
	int& c1 = *p;
	string& d1 = s;
	char& e1 = s[0];
	//
	int m{ 0 }; int n{0};
	int&& a2 = 5;
	int && b2 = m + n;
	double&& c2 = fmin(x, y);
	string&& e2 = string("abcde");

	//
	const int& a3 = b;
	const int& b3 = m + n;
	const double c3 = fmin(x,y);
	const string& e3 = string("abcde");

	//
	int&& a4 = move(b);
	int&& b4 = move(*p);
	string s4("abcde");
	string e4 = move(s);

需要注意的是上面的第3点

rrx1 右值引用, 与右值绑定,rrx1变为左值!!!

1.3左值引用右值引用的参数匹配------就近匹配

cpp 复制代码
#include<iostream>
using namespace std;
void f(int& x)
{
	std::cout << "左值引⽤重载 f(" << x << ")\n";
}
void f(const int& x)
{
	std::cout << "到 const 的左值引⽤重载 f(" << x << ")\n";
}
void f(int&& x)
{
	std::cout << "右值引⽤重载 f(" << x << ")\n";
}
int main()
{
	int i = 1;
	const int ci = 2;
	f(i); // 调⽤ f(int&) 
	f(ci); // 调⽤ f(const int&) 
	f(3); // 调⽤ f(int&&),如果没有 f(int&&) 重载则会调⽤ f(const int&) 
	f(std::move(i)); // 调⽤ f(int&&) 
	// 右值引⽤变量在⽤于表达式时是左值 
	int&& x = 1;
	f(x); // 调⽤ f(int& x) 
	f(std::move(x)); // 调⽤ f(int&& x) 
	return 0;
}

1.4声明周期的延长

右值引⽤可⽤于为临时对象延⻓⽣命周期,const的左值引⽤也能延⻓临时对象⽣存期,但这些对象⽆

法被修改

cpp 复制代码
int main()
{
	std::string s1 = "Test";
	// std::string&& r1 = s1; // 错误:不能绑定到左值 
	const std::string& r2 = s1 + s1; // OK:到 const 的左值引⽤延⻓⽣存期 
	// r2 += "Test"; // 错误:不能通过到 const 的引⽤修改 
	std::string&& r3 = s1 + s1; // OK:右值引⽤延⻓⽣存期 
	r3 += "Test"; // OK:能通过到⾮ const 的引⽤修改 
	std::cout << r3 << '\n';
	return 0;
}

2.1移动构造和移动赋值引入的必要行

我们学习左值引用,右值引用。他们都有&,在C++里面引用就是给对象取别名。&的使用可以避免拷贝,加快效率。

那么我们下述讲的移动构造和移动赋值,他们两者也可以减少拷贝、构造。加快效率。

你可以理解为移动的本质是一种窃取方式

2.2移动构造和移动赋值的书写方式

  • 移动构造函数是⼀种构造函数,类似拷⻉构造函数移动构造函数要求第⼀个参数是该类类型的引
    ⽤,但是不同的是要求这个参数是右值引⽤,如果还有其他参数,额外的参数必须有缺省值
  • 移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函
    数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。
cpp 复制代码
// 移动构造 
string(string&& s)
{
	cout << "string(string&& s) -- 移动构造" << endl;
	swap(s);
}
// 移动赋值 
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动赋值" << endl;
	swap(s);
	return *this;
}

2.3.1右值对象构造,只有拷⻉构造,没有移动构造的场景

• 图1展⽰了vs2019debug环境下编译器对拷⻉的优化,左边为不优化的情况下,两次拷⻉构造,右

边为编译器优化的场景下连续步骤中的拷⻉合⼆为⼀变为⼀次拷⻉构造。

• 需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码优化为⾮常恐怖,会直接

将str对象的构造,str拷⻉构造临时对象,临时对象拷⻉构造ret对象,合三为⼀,变为直接构造。

变为直接构造。要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解,如图3所⽰。

• linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elideconstructors

的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次拷⻉。

2.3.2 右值对象构造,有拷⻉构造,也有移动构造的场景

• 图2展⽰了vs2019debug环境下编译器对拷⻉的优化,左边为不优化的情况下,两次移动构造,右

边为编译器优化的场景下连续步骤中的拷⻉合⼆为⼀变为⼀次移动构造。

• 需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码优化为⾮常恐怖,会直接

将str对象的构造,str拷⻉构造临时对象,临时对象拷⻉构造ret对象,合三为⼀,变为直接构造。

要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解,如图3所⽰。

• linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elideconstructors

的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次移动。

2.3.3右值对象赋值,只有拷⻉构造和拷⻉赋值,没有移动构造和移动赋值的场景

• 图4左边展⽰了vs2019debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境

下编译器的处理,⼀次拷⻉构造,⼀次拷⻉赋值。

• 需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造

要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以

看到str的析构是在赋值以后,说明str就是临时对象的别名。

2.3.4右值对象赋值,既有拷⻉构造和拷⻉赋值,也有移动构造和移动赋值的场景

• 图5左边展⽰了vs2019debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境

下编译器的处理,⼀次移动构造,⼀次移动赋值。

• 需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造

要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以

看到str的析构是在赋值以后,说明str就是临时对象的别名。

三、👍折叠

3.1引用折叠

  • C++中不能直接定义引⽤的引⽤如 int& && r = i; ,这样写会直接报错,通过模板或typedef
    中的类型操作可以构成引⽤的引⽤。
  • 通过模板或typedef中的类型操作可以构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规
    则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。
    详细案例请分析下面的代码
  • 非常实用的两个小结论
    1.T&& 万能引用,当传递右值时 就会成为右值引用,传递左值时就会成为左值引用。
    2.易混淆点:``int && x =右值 x本身的属性为左值
    这一点再3.2中都有说明
cpp 复制代码
#include<iostream>
using namespace std;

template<class T>
void func1(T& x)
{}

template<class T>
void func2(T&&x)
{}


//折叠
int main()
{
	int n=0;

	func1<int>(n);//int & 接受左值 不接受右值
	func1<int>(0);

	func1<int&>(n); //int& & 接受左值 不接受右值
	func1<int&>(0);

	func1<int&&>(n);//int&& & 接受左值 不接受右值
	func1<int&&>(0);

	func1<const int&>(n);// const int& & 接受左值和右值
	func1<const int&>(0);



	func2<int>(n);//int && 接受右值 不接受左值
	func2<int>(0);

	func2<int&>(n);//int& && 接受左值 不接受右值
	func2<int&>(0);

	func2<int&&>(n);//int&& && 接受右值 不接受左值
	func2<int&&>(0);

	func2<const int&>(n);//const int& &&接受左值和右值
	func2<const int&>(0);


	return 0;
}

3.2根据参数反推折叠

cpp 复制代码
#include<iostream>
using namespace std;


template<class T>
void Func(T&& t)
{
	int a = 0;
	T x = a;
	//x++;
	cout << &a << endl;
	cout << &x << endl << endl;
}





int main()
{

	Func(10); //int && 

	int a = 8;
	Func(a); //int& &&
	Func(move(a));//int &&

	const int b = 5;
	Func(b);//const int & &&
	//Q1这里需要注意
	Func(move(b)); //const int && &&

	return 0;
}

完美转发

Function(T&&t)函数模板程序中 当我们传左值实例化后 是左值引用Function函数,当我们传入右值时 会发现 还是左值引用Function函数。

!!!这是因为我们前面讲到的int && x =右值 x本身的属性为左值

因此当我们传入右值 想要让右值引用Function函数 那么我们就需要一个完美转发来实现。

cpp 复制代码
#include<iostream>
using namespace std;



void Func(int& x) { cout << "左值引用" << endl; }
void Func(int&& x) { cout << "右值引用" << endl; }
void Func(const int& x) { cout << "左值引用" << endl; }
void Func(const int&& x) { cout << "右值引用" << endl; }

template<class T>
void Function(T&&x)
{
	//Func(x);
	//Q1完美转发是一个模板函数
	Func(forward<T>(x));
}



int main()
{
	Function(10);//预期右值  但这里实际输出的是右值
	int a = 10;
	Function(a);//预期左值
	const int b = 5;
	Function(b);//预期左值
	Function(move(a));//预期右值  但实际是左值
	Function(move(b));//预期右值  但实际是左值



	return 0;
}

四、可变参数模板

可变参数模板与模板的主要区别是,模板只能泛型类型,无法泛型传入的个数。而可变参数模板既可以泛型类型,也可以泛型传入的个数。你可以将它理解成一个参数包。

4.1基本语法及原理

需要注意的是 参数包它在模板里面声明时,和作为参数书写时,...的位置不同

  • 每个参数都遵循折叠规则
cpp 复制代码
template <class ...Args> void Func(Args... args) {}

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

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

下面cpp样例

cpp 复制代码
#include<iostream>
#include<vector>
#include<string>
using namespace std;


template<class ...Args>
//Q1sizeof 打印的是参数包里面有多少个参数
//Q2 作为模板和参数的时候书写方式是不一样的
void Print(Args... args)
{
	cout << sizeof ...(args) << endl;
}
int main()
{
	Print();
	Print(1);
	Print(1, 3.1415926);
	Print(1, 3.1415926, "What's your name?");
	return 0;
}

/*
// 原理1:编译本质这⾥会结合引⽤折叠规则实例化出以下四个函数

void Print();

void Print(int&& arg1);

void Print(int&& arg1, string&& arg2);

void Print(double&& arg1, string&& arg2, double& arg3);

// 原理2:更本质去看没有可变参数模板,我们实现出这样的多个函数模板才能⽀持

// 这⾥的功能,有了可变参数模板,我们进⼀步被解放,他是类型泛化基础

// 上叠加数量变化,让我们泛型编程更灵活。

void Print();

template <class T1>

void Print(T1&& arg1);

template <class T1, class T2>

void Print(T1&& arg1, T2&& arg2);

template <class T1, class T2, class T3>

void Print(T1&& arg1, T2&& arg2, T3&& arg3);
*/

4.2包拓展

实质就是拆包

cpp 复制代码
void ShowList()
{
 // 编译器时递归的终⽌条件,参数包是0个时,直接匹配这个函数 
 cout << endl;
}

template <class T, class ...Args>

void ShowList(T x, Args... args)
{
 cout << x << " ";
 // args是N个参数的参数包 
 // 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包 
 ShowList(args...);
}

// 编译时递归推导解析参数 

template <class ...Args>

void Print(Args... args)
{
 ShowList(args...);
}

int main()
{
 Print();
 Print(1);
 Print(1, string("xxxxx"));
 Print(1, string("xxxxx"), 2.2);
 return 0;
}

4.3emplace_back 系列接口

由于emplace_back 参数是模板

push_back参数是实参

所以导致emplace_back 和push_back 插入元素的原理不同

emplace_back 它插入元素 是直接再容器里面直接插入

push_back 需要先经过 (有的还需要经过隐式类型转换)构建中间临时变量,然后再将这个中间变量拷贝到 原容器里面。

cpp 复制代码
// emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列 

int main()
{
 list<bit::string> lt;
 // 传左值,跟push_back⼀样,⾛拷⻉构造 
 bit::string s1("111111111111");
 lt.emplace_back(s1);
 cout << "*********************************" << endl;
 // 右值,跟push_back⼀样,⾛移动构造 
 lt.emplace_back(move(s1));
 cout << "*********************************" << endl;
 // 直接把构造string参数包往下传,直接⽤string参数包构造string 
 // 这⾥达到的效果是push_back做不到的 
 lt.emplace_back("111111111111");
 cout << "*********************************" << endl;
 list<pair<bit::string, int>> lt1;
 // 跟push_back⼀样 
 // 构造pair + 拷⻉/移动构造pair到list的节点中data上 
 pair<bit::string, int> kv("苹果", 1);
 lt1.emplace_back(kv);
 cout << "*********************************" << endl;
 // 跟push_back⼀样 
 lt1.emplace_back(move(kv));
 cout << "*********************************" << endl;
 ////////////////////////////////////////////////////////////////////

 // 直接把构造pair参数包往下传,直接⽤pair参数包构造pair 
 // 这⾥达到的效果是push_back做不到的 
 //如果是push_back 他会先进性隐式类型转换 构造中间临时变量,然后再拷贝。
 lt1.emplace_back("苹果", 1);
 cout << "*********************************" << endl;
 return 0;
}

这里推荐可以用emplace_back 替代 push_back 和insert

但是在以下场景无法替代
1.insert 在指定位置插入元素

2.insert插入多个元素的时候无法替代

3.push_back 插入一个initializer_list 类型时。由于emplace_back只支持插入一个元素 因此无法替代。

五、新的类功能

5.1 默认的移动构造和移动赋值

• 如果你没有⾃⼰实现移动构造函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤移动构造,没有实现就调⽤拷⻉构造。

• 如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)

• 如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值

总结就是 移动构造和移动赋值 往往都需要配套出现。否则会与原来的构造赋值 产生类型冲突

5.2成员变量声明时给缺省值

适用场景:初始化列表中没有显式初始化,这个时候我们就需要给缺省值

一个成员变量的初始化遵循以下优先级顺序(从高到低):

构造函数初始化列表:如果在初始化列表中显式指定了初始值,那么就用这个值。

声明时的缺省值:如果没有在初始化列表中指定,但在声明时提供了缺省值,那么就用这个缺省值。

默认初始化:如果以上两者都没有,那么成员变量会被默认初始化。对于内置类型(如 int, double),这意味着它的值是不确定的(垃圾值);对于类类型(如 std::string),会调用其默认构造函数进行初始化。

5.3defult和delete

• C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因

这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤

default关键字显⽰指定移动构造⽣成。

• 如果能想要限制某些默认函数的⽣成,在C++98中,是该函数设置成private,并且只声明补丁已,

这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语

法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数。

5.4 final与override

这个我们在继承和多态章节已经进⾏了详细讲过了,忘了就去复习吧。

六、👍lambda

6.1lambda函数的介绍

lambda是一个匿名函数对象,他有固定的书写方式

[] () -> {}
捕捉列表 参数列表(可省) 返回类型(可省略) 函数体
cpp 复制代码
[]()->{}

由于lambda语法没有类型,因此我们在接收它时,往往适用auto来接收

6.2捕捉列表

lambda默认只能适用lambda函数体和参数中的变量。因此如果像使用外层作用域中的变量就需要捕捉。

3种捕捉方式

  • 1.传值捕捉,引用捕捉[x,y,&z]

    传值捕捉不会修改原来的变量,引用捕捉可以修改原来的变量

  • 2.隐式传值捕捉、隐式引用捕捉[=]、[&] 这样如果lambda表达式中用了哪些变量,编译器就会自动捕捉哪些变量。

  • 3.混合捕捉 [=,&x] 传值捕捉和引用捕捉混用。

    这里需要注意是 当第一个为传值捕捉,后面的必须为引用捕捉。当第一个为引用捕捉,后面的必须是传值捕捉。

  • 当lamdba在全局定义时,lambda参数列表必须为空

  • 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改.

    mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了.

    但是修改还是形参对象,不会影响实参。如果想要影响实参必须使用引用捕捉。使⽤该修饰符后,参数列表不可省略(即使参数为空)

cpp 复制代码
int x = 0;

// 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量 

auto func1 = []()
	{
		x++;
	};

int main()
{
	// 只能⽤当前lambda局部域和捕捉的对象和全局对象 
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [a, &b]
		{
			// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改 
			//a++;

			b++;
			int ret = a + b;
			return ret;
		};
	cout << func1() << endl;
	// 隐式值捕捉 
	// ⽤了哪些变量就捕捉哪些变量 
	auto func2 = [=]
		{
			int ret = a + b + c;
			return ret;
		};
	cout << func2() << endl;
	// 隐式引⽤捕捉 
	// ⽤了哪些变量就捕捉哪些变量 
	auto func3 = [&]
		{
			a++;
			c++;
			d++;
		};
	func3();
	cout << a << " " << b << " " << c << " " << d << endl;
	// 混合捕捉1 
	auto func4 = [&, a, b]
		{
			//a++;

			//b++;

			c++;
			d++;
			return a + b + c + d;
		};
	func4();
	cout << a << " " << b << " " << c << " " << d << endl;
	// 混合捕捉1 
	auto func5 = [=, &a, &b]
		{
			a++;
			b++;
			/*c++;
			d++;*/

			return a + b + c + d;
		};
	func5();
	cout << a << " " << b << " " << c << " " << d << endl;
	// 局部的静态和全局变量不能捕捉,也不需要捕捉 
	static int m = 0;
	auto func6 = []
		{
			int ret = x + m;
			return ret;
		};
	// 传值捕捉本质是⼀种拷⻉,并且被const修饰了 
	// mutable相当于去掉const属性,可以修改了 
	// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉ 
	auto func7 = [=]()mutable

		{
			a++;
			b++;
			c++;
			d++;
			return a + b + c + d;
		};
	cout << func7() << endl;
	cout << a << " " << b << " " << c << " " << d << endl;
	return 0;
}

6.3lambda的应用

  • 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
  • lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不断接触到。

如果这里使用的是lambda将会非常非常方便

前K个高频单词

6.4lambda原理

  • 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。

lambda和范围for 类似,都是编译器后面根据需要生成的。 设置缺省参数、auto范围for、lambda都是语法糖。很实用。用起来也很方便。

相关推荐
q***T58324 分钟前
C++在游戏中的Unreal Engine
c++·游戏·虚幻
ID_1800790547341 分钟前
基于 Python 的淘宝商品详情数据结构化解析:SKU、价格与库存字段提取
开发语言·数据结构·python
ol木子李lo44 分钟前
Visual studio 2022高亮汇编(ASM)语法方法
汇编·ide·windows·visual studio
星释1 小时前
Rust 练习册 82:Hamming与字符串处理
开发语言·算法·rust
时间不说谎1 小时前
c/c++的语法糖
开发语言
Laughtin1 小时前
终端Python环境的选择与切换
开发语言·python
头发还在的女程序员1 小时前
基于JAVA语言的短剧小程序-抖音短剧小程序
java·开发语言·小程序
JHC0000001 小时前
Python PDF 相关操作
开发语言·python·pdf
小张成长计划..2 小时前
【C++】16:模板进阶
c++·算法