C++进阶:C++11

C++11简介

2003 C++ 标准委员会曾经提交了一份技术勘误表 ( 简称 TC1) ,使得 C++03 这个名字已经取代了
C++98 称为 C++11 之前的最新 C++ 标准名称。不过由于 C++03(TC1) 主要是对 C++98 标准中的漏洞
进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为 C++98/03 标准。
C++0x C++11 C++ 标准 10 年磨一剑,第二个真正意义上的标准珊珊来迟。 相比 C++98/03 C++11 则带来了数量可观的变化,其中包含了约 140 个新特性,以及对 C++03 标准中 600 个缺陷的修正,这使得 C++11 更像是从 C++98/03 中孕育出的一种新语言 。相比较而言, C++11 能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更 强大,而且能提升程序员的开发效率 C++11 增加的语法特性非常篇幅非常多,这里没办法一 一讲解,所以本节主要讲解实际中比较实用的语法。
C++11 - cppreference.com

 统一的列表初始化

{}初始化

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 ;
}
C++11 扩大了用大括号括起的列表 ( 初始化列表 ) 的使用范围,使其可用于所有的内置类型和用户自
定义的类型,使用列表初始化时,可添加等号(=) ,也可不添加
struct Point
{
int _x ;
int _y ;
};
int main ()
{
int x1 = 1 ;
int x2 { 2 };
int array1 []{ 1 , 2 , 3 , 4 , 5 };
int array2 [ 5 ]{ 0 };
Point p { 1 , 2 };
// C++11 中列表初始化也可以适用于 new 表达式中
int* pa = new int [ 4 ]{ 0 };
return 0 ;
}

创建对象时也可以使用列表初始化方式调用构造函数初始化

class Date
{
public :
Date ( int year , int month , int day )
: _year ( year )
, _month ( month )
, _day ( day )
{
cout << "Date(int year, int month, int day)" << endl ;
}
private :
int _year ;
int _month ;
int _day ;
};
int main ()
{
Date d1 ( 2022 , 1 , 1 ); // old style
// C++11 支持的列表初始化,这里会调用构造函数初始化
Date d2 { 2022 , 1 , 2 };
Date d3 = { 2022 , 1 , 3 };
return 0 ;
}

std::initializer_list

std::initializer_list的介绍文档:

initializer_list - C++参考 (cplusplus.com) 

std::initializer_list 是什么类型:
int main ()
{
// the type of il is an initializer_list
auto il = { 10 , 20 , 30 };
cout << typeid ( il ). name () << endl ;
return 0 ;
}
std::initializer_list 使用场景:
std::initializer_list 一般是作为构造函数的参数, C++11 STL 中的不少容器就增加
std::initializer_list 作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为 operator=
的参数,这样就可以用大括号赋值。
即:容器想用不固定的{}数据个数初始化,initializer_list支持

声明

c++11 提供了多种简化声明的方式,尤其是在使用模板时。

auto

C++98 auto 是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以 auto 就没什么价值了。 C++11 中废弃 auto 原来的用法,将
其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
int main ()
{
int i = 10 ;
auto p = & i ;
auto pf = strcpy ;
cout << typeid ( p ). name () << endl ;
cout << typeid ( pf ). name () << endl ;
map < string , string > dict = { { "sort" , " 排序 " }, { "insert" , " 插入 " } };
//map<string, string>::iterator it = dict.begin();
auto it = dict . begin ();
return 0 ;
}

 decltype

关键字 decltype 将变量的类型声明为表达式指定的类型。
int main()
{
	list<int>::iterator it1;

	// typeid推出时一个单纯的字符串
	cout << typeid(it1).name() << endl;
	// 不能用来定义对象
	//typeid(it1).name() it2;

	// 可以用来定义对象
	decltype(it1) it2;
	cout << typeid(it2).name() << endl;

	auto it3 = it1;
	cout << typeid(it3).name() << endl;

	auto ret3 = func1();
	B<decltype(ret3)> bb1;

	map<string, string> dict2 = { {"sort", "排序"}, {"insert", "插入"} };
	auto it4 = dict2.begin();

	B<decltype(it4)> bb2;
	B<std::map<std::string, std::string>::iterator> bb2;
	// auto和decltype有些地方增加代码读起来难度

	return 0;
}

nullptr

由于 C++ NULL 被定义成字面量 0 ,这样就可能回带来一些问题,因为 0 既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑, C++11 中新增了 nullptr ,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

STL中一些变化

新容器
用橘色圈起来是 C++11 中的一些几个新容器,但是实际最有用的是 unordered_map
unordered_set 。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。

容器中的一些新方法
如果我们再细细去看会发现基本每个容器中都增加了一些 C++11 的方法,但是其实很多都是用得
比较少的。
比如提供了 cbegin cend 方法返回 const 迭代器等等,但是实际意义不大,因为 begin end 也是
可以返回 const 迭代器的,这些都是属于锦上添花的操作。
实际上 C++11 更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本。
但是这些接口到底意义在哪?网上都说他们能提高效率,他们是如何提高效率的?
请看下面的右值引用和移动语义章节的讲解。另外 emplace 还涉及模板的可变参数,也需要再继
续深入学习后面章节的知识。

右值引用和移动语义

左值引用和右值引用

传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,所以从现在开始我们
之前学习的引用就叫做左值引用。 无论左值引用还是右值引用,都是给对象取别名
什么是左值?什么是左值引用?
左值是一个表示数据的表达式 ( 如变量名或解引用的指针 ) 我们可以获取它的地址 ,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边 。定义时 const 修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
    // 左值是一个表达式,可以取地址的
    // 左值和右值,能否取地址
    // 左值:可以取地址的
    // 右值:不可以取地址的
int main ()
{
// 以下的 p b c *p 都是左值
int* p = new int ( 0 );
int b = 1 ;
const int c = 2 ;
// 以下几个是对上面左值的左值引用
int* & rp = p ;
int & rb = b ;
const int & rc = c ;
int & pvalue = * p ;
return 0 ;
}
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值 ( 这个不能是左值引
用返回 ) 等等, 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址 。右值引用就是对右值的引用,给右值取别名。
右值分为纯右值(内置类型)和将亡值(自定义类型)
int main ()
{
double x = 1.1 , y = 2.2 ;
// 以下几个都是常见的右值
10 ;
x + y ;
fmin ( x , y );
// 以下几个都是对右值的右值引用
int && rr1 = 10 ;
double && rr2 = x + y ;
double && rr3 = fmin ( x , y );
// 这里编译会报错: error C2106: “=”: 左操作数必须为左值
10 = 1 ;
x + y = 1 ;
fmin ( x , y ) = 1 ;
return 0 ;
}
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量 10 的地址,但是 rr1 引用后,可以对 rr1 取地
址,也可以修改 rr1 。如果不想 rr1 被修改,可以用 const int&& rr1 去引用,是不是感觉很神奇,
这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
int main ()
{
double x = 1.1 , y = 2.2 ;
int && rr1 = 10 ;
const double && rr2 = x + y ;
rr1 = 20 ;
rr2 = 5.5 ;   // 报错
return 0 ;
}

 左值引用与右值引用比较

左值引用总结:
1. 左值引用只能引用左值,不能引用右值。
2. 但是 const 左值引用既可引用左值,也可引用右值。
int main ()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10 ;
    int & ra1 = a ;   // ra a 的别名
    //int& ra2 = 10;   // 编译失败,因为 10 是右值
    // const 左值引用既可引用左值,也可引用右值。
    const int & ra3 = 10 ;
    const int & ra4 = a ;
    return 0 ;
}
右值引用总结:
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以 move 以后的左值。
int main ()
{
// 右值引用只能右值,不能引用左值。
int && r1 = 10 ;
// error C2440: “ 初始化 ”: 无法从 “int” 转换为 “int &&”
// message : 无法将左值绑定到右值引用
int a = 10 ;
int && r2 = a ;
// 右值引用可以引用 move 以后的左值
int && r3 = std::move ( a );
return 0 ;
}

右值引用使用场景和意义

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么 C++11 还要提出右值引
用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!
意义:减少拷贝,提高效率。
左值引用返回值的问题没有彻底解决,如果返回值是函数中的局部对象,不能用引用返回。
且效率提升,针对的是自定义类型的深拷贝的类,因为深拷贝的类才有转移资源的移动系列函数
对于内置类型,和浅拷贝自定义类型,没有移动系列函数。
namespace xx
{
	class string
	{
	public:
		//...
		
		
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动语义" << endl;
			swap(s);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动语义" << endl;
			swap(s);
			return *this;
		}

		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

左值引用的使用场景:
做参数和做返回值都可以提高效率。
void func1 ( bit::string s )
{}
void func2 ( const bit::string & s )
{}
int main ()
{
bit::string s1 ( "hello world" );
// func1 func2 的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
func1 ( s1 );
func2 ( s1 );
// string operator+=(char ch) 传值返回存在深拷贝
// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
s1 += '!' ;
return 0 ;
}
左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如: bit::string to_string(int value) 函数中可以看到,这里只能使用传值返回,
传值返回会导致至少 1 次拷贝构造 ( 如果是一些旧一点的编译器可能是两次拷贝构造 )

 

 

namespace xx
{
	bit::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		bit::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
}
int main()
{
	// 在bit::string to_string(int value)函数中可以看到,这里
	// 只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷
	贝构造)。
	bit::string ret1 = bit::to_string(1234);
	bit::string ret2 = bit::to_string(-1234);
	return 0;
}

右值引用和移动语义解决上述问题:
bit::string 中增加移动构造, 移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己
// 移动构造
string ( string && s )
: _str ( nullptr )
, _size ( 0 )
, _capacity ( 0 )
{
cout << "string(string&& s) -- 移动语义 " << endl ;
swap ( s );
}
int main ()
{
bit::string ret2 = bit::to_string (- 1234 );
return 0 ;
}
再运行上面 bit::to_string 的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用
了移动构造,原因:str被编译器自动识别成右值,即将亡值(出了作用域str就销毁)。移动构造中没有新开空间,拷贝数据,所以效率提高了。
不仅仅有移动构造,还有移动赋值:
bit::string 类中增加移动赋值函数,再去调用 bit::to_string(1234) ,不过这次是将
bit::to_string(1234) 返回的右值对象赋值给 ret1 对象,这时调用的是移动构造。
// 移动赋值
string & operator = ( string && s )
{
cout << "string& operator=(string&& s) -- 移动语义 " << endl ;
swap ( s );
return * this ;
}
int main ()
{
bit::string ret1 ;
ret1 = bit::to_string ( 1234 );
return 0 ;
}
// 运行结果:
// string(string&& s) -- 移动语义
// string& operator=(string&& s) -- 移动语义
这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象
接收,编译器就没办法优化了。 bit::to_string 函数中会先用 str 生成构造生成一个临时对象,但是
我们可以看到,编译器很聪明的在这里把 str 识别成了右值,调用了移动构造。然后在把这个临时
对象做为 bit::to_string 函数调用的返回值赋值给 ret1 ,这里调用的移动赋值。
STL 中的容器都是增加了移动构造和移动赋值:
字符串::字符串 - C++ 参考 (cplusplus.com)
vector::vector - C++ 参考 (cplusplus.com)

 

右值引用引用左值及其一些更深入的使用场景分析

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能
真的需要用右值去引用左值实现移动语义。 当需要用右值引用引用一个左值时,可以通过 move
函数将左值转化为右值 C++11 中, std::move() 函数 位于 头文件中,该函数名字具有迷惑性,
并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义
template < class _Ty >
inline typename remove_reference < _Ty > :: type && move ( _Ty && _Arg ) _NOEXCEPT
{
// forward _Arg as movable
return (( typename remove_reference < _Ty > :: type && ) _Arg );
}
int main ()
{
bit::string s1 ( "hello world" );
// 这里 s1 是左值,调用的是拷贝构造
bit::string s2 ( s1 );
// 这里我们把 s1 move 处理以后 , 会被当成右值,调用移动构造
// 但是这里要注意,一般是不要这样用的,因为我们会发现 s1
// 资源被转移给了 s3 s1 被置空了。
bit::string s3 ( std::move ( s1 ));
return 0 ;
}

 

STL容器插入接口函数也增加了右值引用版本:

list::p ush_back - C++参考 (cplusplus.com) 

vector::p ush_back - C++参考 (cplusplus.com)

void push_back ( value_type && val );
int main ()
{
list < bit::string > lt ;
bit::string s1 ( "1111" );
// 这里调用的是拷贝构造
lt . push_back ( s1 );
// 下面调用都是移动构造
lt . push_back ( "2222" );
lt . push_back ( std::move ( s1 ));
return 0 ;
}
运行结果:
// string(const string& s) -- 深拷贝
// string(string&& s) -- 移动语义
// string(string&& s) -- 移动语义

完美转发 

函数模板中的 && 万能引用
(将T&&看成一个整体)

 

 
 

// 函数模版里面,这里可以叫万能引用

// 实参传左值,就推成左值引用

// 实参传右值,就推成右值引用

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 < typename T >
void PerfectForward ( T && t )
{
Fun ( t );
}
int main ()
{
PerfectForward ( 10 );           // 右值
int a ;
PerfectForward ( a );             // 左值
PerfectForward ( std::move ( a )); // 右值
const int b = 8 ;
PerfectForward ( b );       // const 左值
PerfectForward ( std::move ( b )); // const 右值
return 0 ;
}
说明:右值引用属性是左值,因为它可以取地址

解决方法:

 std::forward 完美转发在传参的过程中保留对象原生类型属性

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 ; }
// std::forward<T>(t) 在传参的过程中保持了 t 的原生类型属性。
template < typename T >
void PerfectForward ( T && t )
{
Fun ( std::forward < T > ( t ));
}
int main ()
{
PerfectForward ( 10 );           // 右值
int a ;
PerfectForward ( a );             // 左值
PerfectForward ( std::move ( a )); // 右值
const int b = 8 ;
PerfectForward ( b );       // const 左值
PerfectForward ( std::move ( b )); // const 右值
return 0 ;
}

 完美转发实际中的使用场景:

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};
template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushBack(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, std::forward<T>(x));
	}
	void PushFront(T&& x)
	{
		//Insert(_head->_next, x);
		Insert(_head->_next, std::forward<T>(x));
	}
	void Insert(Node* pos, T&& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = std::forward<T>(x); // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
	void Insert(Node* pos, const T& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = x; // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};
int main()
{
	List<bit::string> lt;
	lt.PushBack("1111");
	lt.PushFront("2222");
	return 0;
}

确保参数层层传递的过程中保持右值属性!!!不然传一层就成左值了

lambda表达式

C++98 中的一个例子
C++98 中,如果想要对一个数据集合中的元素进行排序,可以使用 std::sort 方法。
#include <algorithm>
#include <functional>
int main ()
{
int array [] = { 4 , 1 , 8 , 5 , 3 , 7 , 0 , 9 , 2 , 6 };
// 默认按照小于比较,排出来结果是升序
std::sort ( array , array + sizeof ( array ) / sizeof ( array [ 0 ]));
// 如果需要降序,需要改变元素的比较规则
std::sort ( array , array + sizeof ( array ) / sizeof ( array [ 0 ]), greater < int > ());
return 0 ;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

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 } };
sort ( v . begin (), v . end (), ComparePriceLess ());
sort ( v . begin (), v . end (), ComparePriceGreater ());
}

 

随着 C++ 语法的发展, 人们开始觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法,
都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,
这些都给编程者带来了极大的不便 。因此,在 C++11 语法中出现了 Lambda 表达式。

lambda表达式例子

例1:


int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };


	//方法一:
	auto priceLess = [](const Goods& g1, const Goods& g2)
	{
		return g1._price < g2._price;
	};
	sort(v.begin(), v.end(), priceLess);

	//cout << typeid(priceLess).name() << endl;


	//方法二:
	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;
}

上述代码就是使用 C++11 中的 lambda 表达式来解决,可以看出 lambda 表达式实际是一个匿名函
例2:
int main()
{
	// lambda
	auto add1 = [](int a, int b)->int {return a + b; };
	// 返回值可以省略
	auto add2 = [](int a, int b) {return a + b; };
	// 没有参数,参数列表可以省略
	auto func1 = [] {cout << "hello world" << endl; };

	cout << typeid(add1).name() << endl;
	cout << typeid(add2).name() << endl;
	cout << typeid(func1).name() << endl;

	cout << add1(1, 2) << endl;
	func1();

	return 0;
}

lambda表达式语法

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

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/676121.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

初始操作系统

概念&#xff1a; 1.系统资源的管理者&#xff1a;实质控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机地工作和资源的分配 2.向上层提供方便易用的服务&#xff1a;以提供给用户和其他软件方便接口和环境 封装思想&#xff1a;操作系统把一些丑…

小熊家务帮day10- 门户管理

门户管理 1 门户介绍1.1 介绍1.2 常用技术方案 2 缓存技术方案2.1 需求分析2.1.1 C端用户界面原型2.1.2 缓存需求2.1.3 使用的工具 2.2 项目基础使用2.2.1 项目集成SpringCache2.2.2 测试Cacheable需求Service测试 2.1.3 缓存管理器&#xff08;设置过期时间&#xff09;2.1.4 …

React@16.x(17)Portals

目录 1&#xff0c;使用2&#xff0c;事件冒泡 一句话总结&#xff1a;和 Vue3 的 Teleport 一个效果。 1&#xff0c;使用 import React, { PureComponent } from "react"; import ReactDOM from "react-dom";// 返回一个 React 元素&#xff08;ReactNo…

【Go语言精进之路】构建高效Go程序:零值可用、使用复合字面值作为初值构造器

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 引言一、深入理解并利用零值提升代码质量1.1 深入Go类型零值原理1.2 零值可用性的实践与优势1.2.1 切片(Slice)的零值与动态扩展1.2.2 Map的零值与安全访问1.2.3 函数参数与零值 二、使用复合字面值作为初值构造器2.1 结构体…

十分钟快速搭建检索、排序的大模型RAG系统

以上为实现效果 RAG是目前最火的大模型应用之一&#xff0c;如何能快速实现一个不错的demo呢&#xff1f; 参考 https://github.com/LongxingTan/open-retrievalshttps://colab.research.google.com/drive/1fJC-8er-a4NRkdJkwWr4On7lGt9rAO4P?uspsharing#scrollTo2Hrfp96UY…

【算法】一文搞懂归并排序

概念 归并排序利用了分治思想&#xff0c;将待排序的数组范围层层划分&#xff0c;每次划分会得到两个大小相近的区间。当无法划分时&#xff0c;递归结束&#xff0c;自下而上进行区间合并merge操作&#xff0c;合并操作依次比较两个区间的元素&#xff0c;进而使合并后的区间…

开发和渗透偷懒利器utools

目录 1.前言 1.1 工具简介 1.2 核心特性 1.3 使用场景 1.4 安装与使用 1.4.1 下载&#xff1a; 1.4.2 安装&#xff1a; 1.4.3 配置&#xff1a; 1.4.4 插件市场&#xff1a; 2.懒狗插件介绍 基本介绍 2.1 数据模拟 2.2 随机生成虚假数据 2.3 API市场 2.4 Hoppscot…

学习小心意——简单的循坏语句

for循坏 基本语法格式 for 变量 in 序列:代码块 示例代码如下 for i in range(10):print(i)#输出结果:0 1 2 3 4 5 6 7 8 9 简单案例代码如下 利用for语句遍历序列 # 遍历字符串打印每个字母 for letter in "python":print(letter)# 遍历列表并打印每个元素 a …

Spring Boot API 编写的十个最佳实践,你知道几个?

一个好的 API 不仅能提高开发效率&#xff0c;还能确保系统的安全性和稳定性。 第一部分&#xff1a;RESTful API 设计 资源名称&#xff1a;使用名词表示资源&#xff0c;比如 /users。 HTTP 方法&#xff1a;GET、POST、PUT、DELETE 分别对应查询、创建、更新和删除操作。 …

SaaS 电商设计 (十一) 那些高并发电商系统的限流方案设计

目录 一.什么是限流二.怎么做限流呢2.1 有哪些常见的系统限流算法2.1.1 固定窗口2.1.1 滑动窗口2.1.2 令牌桶2.1.3 漏桶算法 2.2 常见的限流方式2.2.1 单机限流&集群限流2.2.2 前置限流&后置限流 2.3 实际落地是怎么做的2.3.1 流量链路2.3.2 各链路限流2.3.2.1 网关层2…

【Java面试】七、SpringMvc的执行流程、SpringBoot自动装配原理

文章目录 1、SpringMVC的执行流程1.1 视图阶段1.2 前后端分离阶段 2、SpringBoot自动配置原理3、框架常用的注解3.1 Spring的注解3.2 SpringMvc的注解3.3 SpringBoot的注解 4、面试 1、SpringMVC的执行流程 1.1 视图阶段 旧项目中&#xff0c;未前后端分离时&#xff0c;用到…

计算机视觉与模式识别实验2-2 SIFT特征提取与匹配

文章目录 &#x1f9e1;&#x1f9e1;实验流程&#x1f9e1;&#x1f9e1;SIFT算法原理总结&#xff1a;实现SIFT特征检测和匹配通过RANSAC 实现图片拼接更换其他图片再次测试效果&#xff08;依次进行SIFT特征提取、RANSAC 拼接&#xff09; &#x1f9e1;&#x1f9e1;全部代…

(CVPRW,2024)可学习的提示:遥感领域小样本语义分割

文章目录 相关资料摘要引言方法训练基础类别新类别推理 相关资料 论文&#xff1a;Learnable Prompt for Few-Shot Semantic Segmentation in Remote Sensing Domain 代码&#xff1a;https://github.com/SteveImmanuel/OEM-Few-Shot-Learnable-Prompt 摘要 小样本分割是一项…

JMeter性能测试实现与分析分享

JMeter是由著名开源软件巨头Apache组织开发的纯Java的压力测试工具&#xff0c;它即能测试动态服务&#xff08;WebService&#xff09;&#xff0c;也能测试静态资源&#xff0c;包括Servlet服务、CGI脚本等&#xff0c;还能测试动态语言服务&#xff08;PHP、Java、ASP.NET等…

外汇天眼:总是权衡利弊,投资注定失败

投资股票的人往往会频繁地评估自己的投资结果&#xff0c;尤其是在信息时代&#xff0c;手机上随时可以查看股票行情&#xff0c;导致很多人时不时地打开行情软件&#xff0c;看一看自己的股票是涨了还是跌了&#xff0c;盈利了还是亏损了。 频繁评估结果的弊端 一、引发急躁…

JVM学习-监控工具(二)

jmap&#xff1a;导出内存映像文件&内存使用情况 基本情况 jmap(JVM Memory Map)&#xff1a;一方法获取dump文件(堆转储快照文件&#xff0c;二进制文件)&#xff0c;还可以获取目标Java进程的内存相关信息&#xff0c;包括Java堆各区域的使用情况、堆中对象的统计信息、…

Golang TCP网络编程

文章目录 网络编程介绍TCP网络编程服务器监听客户端连接服务器服务端获取连接向连接中写入数据从连接中读取数据关闭连接/监听器 简易的TCP回声服务器效果展示服务端处理逻辑客户端处理逻辑 网络编程介绍 网络编程介绍 网络编程是指通过计算机网络实现程序间通信的一种编程技术…

成功解决“ModuleNotFoundError: No Module Named ‘utils’”错误的全面指南

成功解决“ModuleNotFoundError: No Module Named ‘utils’”错误的全面指南 在Python编程中&#xff0c;遇到ModuleNotFoundError: No Module Named utils这样的错误通常意味着Python解释器无法找到名为utils的模块。这可能是由于多种原因造成的&#xff0c;比如模块确实不存…

推荐一款开源Scada,数据采集必备

软件介绍 PySCADA的核心是一个基于HTML5的HMI&#xff0c;不仅确保现代性&#xff0c;还确保与各种设备的无缝集成。该框架通过提供对广泛工业协议的支持&#xff0c;进一步巩固其功能。这些包括Modbus TCP/IP、RTU、ASCII和Binary&#xff0c;让用户可以轻松地与不同的设备和系…

党历史纪念馆3d沉浸式虚拟展厅为参观者提供了一个身临其境的革命教育世界

传统的展览方式虽然能够传达一定的信息&#xff0c;但往往难以激发参观者的深度参与和共鸣。而廉政教育VR数字化展厅通过VR虚拟现实技术的运用&#xff0c;打破了传统展览的局限性&#xff0c;为参观者提供了一个身临其境的廉政世界。 在这个廉政教育VR数字化展厅中&#xff0c…