
文章目录
前言
之前我们介绍了C++的相关语法,但之前的介绍大多是基于C++98标准的,C++11标准在C++98的标准上进行了扩充,这篇文章就简单介绍一下C++11新扩展的语法。
C++11发展历史
C++11是C++的第二个主要版本,并且是C++98之后的最重要的一次更新。C++11引入了大量新的语法内容,标准化了既有实践,并改进了对C++程序员可用的抽象。C++03与C++11期间花了8年时间,故而这是迄今为止最长的版本间隔。从那时起,C++有规律地每3年更新⼀次。如下图所示:

列表初始化
在C++98中,一般数组和结构体可以用{}进行初始化,如下所示:
#include <iostream>
using namespace std;
struct Point
{
int _x;
int _y;
};
int main()
{
int arr1[] = { 1,2,3,4 };
int arr2[4] = { 0 };
Point p = { 1,2 };
return 0;
}
在C++11之后,一切对象都可以用{}进行初始化,{}方式的初始化也叫做列表初始化。不管是内置类型还是自定义类型,都可以用初始化列表的方式进行初始化,自定义类型的初始化本质是类型转换,在初始化过程中会产生临时对象,但优化以后都变成了直接构造。在{}初始化过程中,也可以省略掉=。
如下所示:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
int x1 = { 12 };
//也可以这样进行初始化
int x2{ 13 };
Date d1 = { 2025 };
//C++98⽀持单参数时类型转换,也可以不⽤{}
Date d2 = 2025;
return 0;
}
除此之外,C++11引入了initializer_list类,之前vector容器中也介绍过,这个类的本质是底层开辟一个数组,将数据进行拷贝。
右值引用和移动语义
在C++98中就有引用的相关语法,而C++11中新增了右值引用的语法特性,这两者本质都是给对象和变量取别名。但二者也有很大的差别。
左值和右值
左值是一个表示数据的表达式(如变量名或解引用指针),一般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边。定义时const修饰符后的左值,无法赋值,但是可以获取地址。以下是常见的左值:
// 左值:可以取地址
// 以下的p、b、c、*p、s、s[0]就是常⻅的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
右值也是一个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,同时,右值不能取地址,左值和右值的核心区别就是能否取地址。 以下是常见的右值:
// 右值:不能取地址
double x = 1.1, y = 2.2;
// 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值
10;
x + y;
fmin(x, y);
string("11111");
//cout << &10 << endl;
//cout << &(x+y) << endl;
//cout << &(fmin(x, y)) << endl;
//cout << &string("11111") << endl;
左值引用和右值引用
我们来看以下两条语句:
Type& r1 = x;
Type&& r2 = y;
其中第一个语句就是左值引用,左值引用就是给左值取别名,第二个就是右值引用,同理,右值引用就是给右值取别名。
通常情况下,左值引用不能直接引用右值,但是const左值引用可以引用右值,右值引用不能直接引用左值,但是右值引用可以引用move(左值),move是库函数里面的一个函数模版,本质是进行强制类型转换。
需要注意的是,变量表达式都是左值属性,也就是说,一个右值被右值引用绑定后,右值引用变量表达式的属性是左值。
二者简单使用如下所示:
#include <iostream>
using namespace std;
int main()
{
// 左值:可以取地址
// 以下的p、b、c、*p、s、s[0]就是常⻅的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
double x = 1.1, y = 2.2;
// 左值引⽤给左值取别名
int& r1 = b;
int*& r2 = p;
int& r3 = *p;
string& r4 = s;
char& r5 = s[0];
// 右值引⽤给右值取别名
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
string&& rr4 = string("11111");
// 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
const int& rx1 = 10;
const double& rx2 = x + y;
const double& rx3 = fmin(x, y);
const string& rx4 = string("11111");
// 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
string&& rrx5 = (string&&)s;
// b、r1、rr1都是变量表达式,都是左值
cout << &b << endl;
cout << &r1 << endl;
cout << &rr1 << endl;
// 这⾥要注意的是,rr1的属性是左值,所以不能再被右值引⽤绑定,除⾮move⼀下
int& r6 = r1;
// int&& rrx6 = rr1;
int&& rrx6 = move(rr1);
return 0;
}
当一个临时变量被右值引用绑定后,可以延长临时对象的生命周期,使临时变量的生命周期跟右值引用变量绑定,const的左值引用也能延长临时对象的生存期,但这些对象都无法被修改。如下所示:
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;
}
左值和右值的参数匹配
C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配左值引用的f函数,实参是const左值会匹配const左值引用的f函数,实参是右值会匹配右值引用的f函数。如下所示:
#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;
}
运行结果如下:

移动构造和移动赋值
移动构造是一种构造函数,类似于拷贝构造函数,移动构造函数要求第一个参数是该类类型的引用,但是不同的是,要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值。
移动赋值是一个赋值运算符的重载,与拷贝赋值构成函数重载,类似于拷贝赋值函数,但是移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。
由于右值引用的对象都是临时对象,移动构造和移动赋值本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值函数那样去拷贝资源,从而提高效率,这也是C++11为什么要引入右值引用的原因。以下是在之前实现过的string类对象中加入了移动构造和移动赋值:
#include<iostream>
#include<assert.h>
#include<string.h>
#include<algorithm>
using namespace std;
namespace cgc
{
class string
{
public:
//...其它操作
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
//...其它操作
};
}
int main()
{
cgc::string s1("xxxxx");
// 拷⻉构造
cgc::string s2 = s1;
// 构造+移动构造,优化后直接构造
cgc::string s3 = cgc::string("yyyyy");
// 移动构造
cgc::string s4 = move(s1);
cout << "******************************" << endl;
return 0;
}
运行结果如下:

对于s4来说,相当于直接得到s1对应的对象的数据,同时s1经过move后为临时变量,当移动构造函数结束后,会由系统将s1对应的资源清理掉,这样就大大提升了构造的效率,使得将一个一个数据拷贝构造的过程转换为交换“窃取”资源的过程。C++11中STL库中很多就是通过移动构造的方式来实现相关的库函数。
我们也可以自己实现一个list容器中移动构造方式的插入:
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
//左值引用构造...
ListNode(T&& data)
:_next(nullptr)
, _prev(nullptr)
, _data(move(data))
{}
};
class
{
public:
//...其它操作
void push_back(T&& x)
{
insert(end(), move(x));
}
iterator insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* newnode = new Node(move(x));
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
//...其它操作
};
之前我们介绍过,右值引用的属性是左值,因此,为了要保证右值引用在传递过程的一致性,要确保相应的构造函数也要有右值引用的版本,同时在传递参数过程中要用move函数。
引用折叠
通过模版或typedef中的类型操作可以构成引用的引用时,C++11给出了一个引用折叠的规则:右值引用与右值引用折叠成右值引用,所有其它组合均折叠为左值引用。如下所示:
#include <iostream>
using namespace std;
template<class T>
void f1(T& x)
{
}
template<class T>
void f2(T&& x)
{
}
int main()
{
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&
// 没有折叠->实例化为void f1(int& x)
f1<int>(n);
//f1<int>(0); // 报错
// 折叠->实例化为void f1(int& x)
f1<int&>(n);
//f1<int&>(0); // 报错
// 折叠->实例化为void f1(int& x)
f1<int&&>(n);
//f1<int&&>(0); // 报错
// 折叠->实例化为void f1(const int& x)
f1<const int&>(n);
f1<const int&>(0);
// 折叠->实例化为void f1(const int& x)
f1<const int&&>(n);
f1<const int&&>(0);
// 没有折叠->实例化为void f2(int&& x)
//f2<int>(n); // 报错
f2<int>(0);
// 折叠->实例化为void f2(int& x)
f2<int&>(n);
//f2<int&>(0); // 报错
// 折叠->实例化为void f2(int&& x)
//f2<int&&>(n); // 报错
f2<int&&>(0);
return 0;
}
像f2函数这种函数模板,T&& x参数看起来是右值引用,但由于引用折叠的规则,参数传递左值时就是左值引用,传递右值时就是右值引用,这种函数模板的参数也叫做万能引用。一个简单使用如下:
#include <iostream>
using namespace std;
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++;
cout << &a << endl;
cout << &x << endl << endl;
}
int main()
{
// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(10); // 右值
int a;
// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
Function(a); // 左值
// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(std::move(a)); // 右值
const int b = 8;
// b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int& t)
// 所以Function内部会编译报错,x不能++
Function(b);
// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
// 所以Function内部会编译报错,x不能++
Function(std::move(b)); // const 右值
return 0;
}
Function(T&& t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模板参数T的推导int&,再结合引用折叠规则,就实现了实参是左值,实例化出左值引用版本形参的Function,实参是右值,实例化出右值引用版本形参的Function。
需要注意的是,只有当有右值引用参数的函数未指出模板的具体类型,自动推导才是万能模板,若已经显示指明了参数,那就不是万能模板。
完美转发
上面介绍过,变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式是左值,也就意味着Function函数中的t的属性是左值,那么如果将t插入下一层函数Fun,那么匹配的都是左值引用版本的Fun函数。如果我们想要保持t对象的属性,就要使用完美转发。如下所示:
#include <iostream>
using namespace std;
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; }
template<class T>
void Function(T&& t)
{
//Fun(t);
Fun(forward<T>(t));
}
int main()
{
// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(10); // 右值
int a;
// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
Function(a); // 左值
// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(std::move(a)); // 右值
const int b = 8;
// b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int& t)
Function(b); // const 左值
// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
Function(std::move(b)); // const 右值
return 0;
}
完美转发forward本质是⼀个函数模板,他主要还是通过引用折叠的⽅式实现,下面示例中传递给Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引用返回;传递给Function的实参是左值,T被推导为int&,引⽤折叠为左值引用,forward内部t被强转为左值引用返回。
运行结果如下:

可变参数模板
基本语法
C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数和函数参数包,分别表示零或多个模板参数和函数参数。如下所示:
template <class ...Args> void Func(Args... args) {}
template <class ...Args> void Func(Args&... args) {}
template <class ...Args> void Func(Args&&... args) {}
用省略号来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class…或typename…指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后跟…指出接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通的模板一样,每个参数实例化时遵循引用折叠规则。
可变参数模板的原理与模板类似,本质还是去实例化对应类型和个数的多个函数。
同时,可以使用sizeof…运算符去计算参数包中参数的个数。
使用示例如下所示:
#include <iostream>
using namespace std;
template <class ...Args>
void Print(Args&&... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
double x = 2.2;
Print();
Print(1);
Print(1, string("xxxx"));
Print(1.1, string("xxxx"), x);
return 0;
}
运行结果如下:

包扩展
对于一个参数包,我们除了能计算它的参数个数,还可以进行扩展。扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。当扩展一个包时,我们还要提供用于每个扩展元素的模式,我们通常在模式的右边放一个省略号(…)来触发扩展操作。如下所示:
#include <iostream>
using namespace std;
void ShowList()
{
cout << endl;
}
template<class T, class ...Args>
void ShowList(T x,Args&&... args)
{
cout << x << " ";
ShowList(args...);
}
template <class ...Args>
void Print(Args&&... args)
{
ShowList(args...);
}
int main()
{
Print(1, 4, string("hello"), 1.1);
return 0;
}
在编译过程中,本质是将可变参数模板通过模式的包扩展,编译器将自动进行递归推导,如下所示:

同时,也可以通过以下方式进行包扩展:
#include <iostream>
using namespace std;
template <class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
template <class ...Args>
void Arguments(Args... args)
{}
template <class ...Args>
void Print(Args... args)
{
// 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
Arguments(GetArg(args)...);
}
// 本质可以理解为编译器编译时,包的扩展模式
// 将上⾯的函数模板扩展实例化为下⾯的函数
//void Print(int x, string y, double z)
//{
// Arguments(GetArg(x), GetArg(y), GetArg(z));
//}
int main()
{
Print(1, string("hello"), 2.2);
return 0;
}
运行结果如下:

emplace系列接口
C++11以后STL容器新增了emplace接口,emplace系列的接口均为模板可变参数,功能上兼容push和insert系列函数,但同时对两个函数进行了扩展,emplace还支持直接插入构造对象的参数,这样有些场景会高效一些,可以直接在容器上构造T对象。以下是一个使用示例:
int main()
{
list<cgc::string> lt;
// 传左值,跟push_back⼀样,⾛拷⻉构造
cgc::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");
lt.emplace_back(10, 'x');
lt.push_back("111111111111");
cout << "*********************************" << endl;
return 0;
}
运行结果如下所示:

emplace简单实现部分如下:
template<class T>
struct ListNode
{
//...其它操作
template <class... Args>
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(std::forward<Args>(args)...)
{}
//...其它操作
}
template<class T>
class list
{
//...其它操作
template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* newnode = new Node(std::forward<Args>(args)...);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
template <class... Args>
void emplace_back(Args&&... args)
{
insert(end(), std::forward<Args>(args)...);
}
//...其它操作
}
新的类功能
默认的移动构造和移动赋值
C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。使用规则如下:
- 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的函数中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按自己拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 如果你没有自己实现移动赋值函数重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值函数,对于内置类型成员会执行逐成员按自己拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。
- 如果你提供了移动构造或者移动赋值,编译器不会自动生成拷贝构造和拷贝赋值。
default和delete
在C++中若没有显示生成相关默认的构造函数,可以使用default关键字显示指定默认的构造函数。若不想在类外调用默认的构造函数,只需在该声明加上=delete即可,=delete修饰的函数为删除函数。如下所示:
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;
Person(const Person& p) = delete;
private:
cgc::string _name;
int _age;
};
int main()
{
Person s1;
//Person s2 = s1; //编译报错
Person s3 = std::move(s1);
return 0;
}
lambda表达式
表达式语法
lambda表达式本质是一个匿名函数对象,跟普通函数不同的是它可以定义在函数的内部,lambda表达式语法使用层来说没有一个具体的类型,所以一般是使用auto或者模板参数定义的对象去接受lambda对象。
lambda的语法格式如下:
[capture-list] (parameters)-> return type {
function boby
}
各部分的组成如下:
[capture-list]:捕捉列表,该列表出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用,捕捉列表可以传值和传引用捕捉。
(parameters):参数列表,与普通函数的参数列表功能类似,如果不需要传递参数,则可以连同()一起省略。
->return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值是此部分可省略。一般返回值类型明确的情况下,也可省略,,由编译器对返回类型进行推导。
{function body}:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。
如下所示:
#include <iostream>
using namespace std;
int main()
{
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 6) << endl;
}
捕捉列表
lambda表达式中默认只能用lambda函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。
第一种捕捉方式实在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分隔。如[x, y, &z]表示x和y值捕捉,z引用捕捉。
第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表中写一个=表示隐式值捕捉,在捕捉列表时写一个&表示隐式引用捕捉,这样lambda表达式中国用了哪些变量,编译器就会自动捕捉那些变量。
第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=,&x]表示其它变量隐式值捕捉,x引用捕捉;[&,x,y]表示其它变量引用捕捉,x和y值捕捉。当使用混合捕捉时,第一个元素必须是&或=,并且&混和捕捉时,后面的捕捉变量必须是值捕捉,同理,=混合捕捉时,后面的变量必须是引用捕捉。
同时,lambda表达式如果在函数局部域中,它可以捕捉lambda位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda表达式中可以直接使用。这也意味着,lambda表达式如果定义在全局位置,捕捉列表必须为空。
默认情况下,lambda捕捉列表是被const修饰的,也就是说传值捕捉的对象不能被修改,mutable加在参数列表的后面可以取消其常性,传值捕捉的对象就可以修改,但修改的还是形参,不会影响实参。使用该修饰符后,参数列表不可省略(即使为空)。
lambda使用如下所示:
#include <iostream>
using namespace std;
int x = 0;
auto func1 = []()
{
x++;
};
int main()
{
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b]
{
// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改
//a++;
b++;
int ret = a + b;
return ret;
};
cout << func1() << endl;
cout << b << 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;
}
lambda原理
lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会生成⼀个对应的仿函数的类。仿函数的类名是编译按⼀定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。
因此,我们在STL设定排序规则的时候,不仅可以定义一个仿函数,也可以传一个lambda表达式,如下所示:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
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()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3}, { "菠萝", 1.5, 4 } };
// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
// 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
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;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate < g2._evaluate;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate;
});
return 0;
}

1059

被折叠的 条评论
为什么被折叠?



