【C++】C++11

1. C++简介

  • C++11标准相比于C++98/03标准,带来了数量可观的变化,对C++03作了约600个补正修订,增加了约400个新特性,eg:final、override、unordered系列容器、default、delete等。C++11能够更好的应用系统开发和库开发,语法更加泛化和简单化、更加安全和稳定、提升开发效率,项目开发应用较多。

2. 统一的列表初始化

2.1. { }初始化

  1. 一切皆列表初始化。

  2. C++11标准,允许对变量、数组、结构体、用户自定义类型、new表达式进行同一的列表初始值设定,使用列表初始化,可添加=,也可不添加=。

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>

using namespace std;

struct Person {
	int age;
	int height;
};

class Date {
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//一切皆列表初始化,=可加、也可不加
	int a1 = { 10 };  //变量
	int a2{ 10 };

	Person p1 = { 18, 100 }; //结构体
	Person p2{ 18, 100 };

	int arr1[10] = { 1, 2, 3 };  //数组
	int arr2[10]{ 1, 2, 3 };

	//C++98、C++11都支持
	//多参数构造函数支持隐式类型转化,构造+拷贝构造,新的编译器会优化成直接构造
	Date d1 = { 2022, 1, 1 }; //用户自定义类型
	Date d2{ 2022, 1, 1 };

	//单参数构造函数支持隐式类型转化,构造+拷贝构造,新的编译器会优化成直接构造
	string s = "lala";

	//new表达式
	Date d1(2022, 2, 1);
	Date d2(2022, 4, 1);
	Date d3(2022, 3, 1);
	Date* p3 = new Date[3]{d1, d2, d3};  //拷贝构造
	Date* p4 = new Date[3]{ {2024, 4, 15}, {2024, 4, 16},{2024, 4, 17} };  //构造+拷贝构造,优化直接构造

	Date* p5 = new Date(2023, 1, 1);  //构造
	Date* p5 = new Date{ 2024, 1, 1 };   //构造+拷贝构造,优化直接构造

    //Date d5 = (2024, 2, 1);  //不支持
}

2.2. std: :initializer_list

image.png

int main()
{
	auto il = { 1, 2, 3, 4 };
	cout << typeid(il).name() << endl;

	return 0;
}

image.png
image.png

  1. initializer_list是C++11引入的一个模板类,它允许我们用{ }初始化一个对象,和vector一样,可以表示某种特定类型值的数组,成员函数有无参构造、迭代器begin、end、size。

  2. initializer_list限制:

a.它是只读的,不能修改它的内容。

b.它是轻量级的,只存储元素指针和大小,不存储元素本身,initializer_list对象大小为8字节,只存储了两个指针,一个指针指向数组的开始,一个指针指向数组的末尾。

c.临时数组的声明周期和initializer_list对象生命周期一样。

  1. initializer_list一般是用作于构造函数的参数,STL中有不少的容器增加了std: :initializer_list作为参数的构造函数,这样初始化对象更方便,也可以作为operator=( )的参数,这样对象就可以用大括号进行赋值。
int main()
{
    //两者都是构造+拷贝构造,编译器直接优化为直接构造
	Date d = { 2024, 4, 16 };  //参数个数是受限制的

	vector<int> v = { 1, 2, 3, 4 }; //参数个数不受限制
	for (auto& e : v)
	{
		cout << e << ' ';
	}
    cout << endl;
    
    vector<int> v1({ 1, 2, 3 });  //直接构造

	return 0;
}

image.png
image.png

vector(initializer_list<T> il)  //initializer_list作为参数的构造函数
{
	reserve(il.size());
	for (auto& e : il)
	{
		push_back(e);
	}
}

屏幕截图 2024-04-16 011117.png

int main()
{
    pair<string, string> p1("sort", "排序");
    pair<string, string> p2("left", "左边");
    
    map<string, string> m1 = { p1, p2};  //initializer_list<pair>作为参数构造+拷贝构造,编译器优化为直接构造
    map<string, string> m2 = { {"right", "右边"}, {"count", "数目"} };
    	/*pair<const char*, const char*>与pair<const string, string>是不同的类型,需要先调用pair构造(在pair类中套了其他模板,
    	用于支持不同类型pair的构造)。其次initializer_list<pair>作为参数构造+拷贝构造,编译器优化为直接构造*/
	return 0;
}
template<class T1, class T2>
struct pair {
	pair(const T1& first, const T2& second) 
		:_first(first)
		,_second(second)
	{ }

	template<class U, class V>   //pair允许在内部套其他大的模板,达到用不同类型的pair去构造其他类型的piar
	pair(pair<U, V>& p)
		:_first(p._first)
	    ,_second(p._second)
	{ }

	T1 _first;
	T2 _second;
};

3. 声明

3.1. auto

  • C++98中auto是自动存储类型的说明符,表示变量是局部自动存储类型,但是在局部域定义的局部对象默认自动存储类型,所以在C++11中就摒弃了auto原来的作用,此处auto只用于实现自动类型推导,要求必须显示初始化,让编译器将定义对象的类型设置为初始值的类型。

3.2. decltype

decltype将变量的类型声明为表达式指定的类型。

int main()
{
    const int x = 3;   
	decltype(x) y = 4;  //顶层const,const修饰变量本身,decltype会去掉const
	cout << typeid(y).name() << endl;

	const int* p = &x;
	decltype(p) a1 = nullptr;  //底层const,const修饰指针指向的内容,decltype不会去掉const
	cout << typeid(a1).name() << endl;

	return 0;
}

image.png

  • 顶层const,const修饰变量本身,decltype会去掉const。底层const,const修饰指针指向的内容,decltype不会去掉const。

  • typeid( ).name( )求变量的类型,并进行打印。

3.3. nullptr

  • 因为NULL被定义为字面量0,NULL既能表示为指针常量,又能表示为整形常量。C++11将nullptr定义为空指针。

4. 范围for循环

  • STL容器只要支持迭代器,就支持范围for,打印容器中的元素。

5. STL的变化

  1. 新的容器:unordered_map、unordered_set、单链表(forward_list)、静态数组(array)。

  2. 新接口:

a.一些无关重要的方法,eg:cbegin、cend返回const迭代器,而begin、end也可以返回const迭代器。

b.initializer_list系列的构造。

c.push、insert、emplace等增加了右值引用版本,提高了效率。

d.容器新增的移动构造、移动赋值,它们可以减少拷贝,提高效率。

image.png

6. 右值引用和移动语义

6.1. 左值引用和右值引用比较

  1. 在C语言中,左值是可以被修改的,而右值不能被修改。而C++中这样定义是不准确的。

  2. 左值是表达式,常见形态有变量、指针、指针解引用后的值、函数返回值(传引用返回)、const修饰的变量。右值是表达式,常见形态有字面常量、表达式的返回值(运算)、函数返回值(传值返回)。

  3. 左值可以被取地址,一般可以对它进行赋值,const修饰后的左值,不能给它赋值,可以出现在赋值符号的左边。右值不可以被取地址,不能对它进行修改,不可以出现在赋值符号的左边。

  4. 左值引用是给左值取别名,左值引用不能给右值取别名,但是const左值引用可以给右值取别名。右值引用是给右值取别名,右值引用不能给左值取别名,但是右值引用可以给move(左值)取别名。

  5. 引用都是取别名,不开空间存储。底层,引用是用指针实现的,左值引用是存储当前左值的地址,右值引用是将右值拷贝给栈中的一块临时空间,存储这个临时空间的地址。可以对右值引用变量取地址,也可以对它进行修改。

int main()
{
    //左值:a、p、*p、c、传引用返回
	int a = 0;  
	int* p = new int(10);
	const int c = 6;
	fun1();

	//左值可以给左值取别名
	int* ptr = &a;

	//左值引用:给左值取别名
	int& aa = a;
	int*& pp = p;
	const int& cc = c;
	int& ret1 = fun1();

	//右值:10、运算、传值返回  注意:右值不能取地址,不能出现在左边
	10;
	a + c;
	fun2();

	//右值引用:给右值取别名
	int&& d = 10;
	int&& e = a + c;
	int&& ret2 = fun2();

    //左值引用不能给右值取别名,const左值引用可以给右值取别名
	const int& g = 10;
	//右值引用不能给左值取别名,右值引用可以给move(左值)取别名
	int&& h = move(a);
    
    return 0;
}

6.2. 右值引用使用场景和意义

6.2.1. 移动构造

移动构造.png

//移动构造 — 右值(将亡值)
string(string&& s)
{
	cout << "string(string&& s)" << endl;
	swap(s);
}
  • 右值引用,移动语义:右值引用是对右值的引用,移动语义是将右值的资源直接转移给左值对象,而不需要进行开销较大的深拷贝。移动语义是C++11引入的一个新特性,它允许我们将资源从一个对象转移到另一个对象,以提高效率和性能 ——》 移动构造、移动赋值。

总结:进行深拷贝的类需要进行移动构造、移动赋值,进行浅拷贝的类不需要进行移动构造、移动赋值。

6.2.2. 移动赋值

移动赋值.png

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

6.3. 万能引用和完美转发

  1. 万能引用:是一种既能接收左值、又能接收右值的引用,它的形式为T&&(T为模板参数),传左值,它就为左值引用、传右值,它就为右值引用,它不是C++的一个新特性,而是利用模板类型推导和引用折叠的规则来实现的功能。

  2. 引用折叠:是C++出现的新概念,用于处理引用的引用情况。在C++中,当创建一个引用的引用时,引用规则会将其中的引用消除,只保留一个单引用。& 、& -> & ;& 、&& -> & ;&& 、&& -> &&。

  3. 完美转发:forward(t), 是C++引入的新特性,在函数模板中保持参数的原属性,本身为左值,属性不变; 本身为右值(但经右值引用引用后,属性变为左值),将属性转变为右值,相当于move了一下。

  4. move:是一个函数模板,将左值属性变为右值属性。

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; }

/*1.引用折叠:&、&&->&、&&、&&->&&  不能单纯的把下面的模板理解为右值引用的模板
* 2.万能引用:传左值,它就为左值引用、 传右值,它就为右值引用
* 3.完美转发forward<T>(t):保持属性,本身是左值,属性不变、 本身是右值(被右值引用引用后,属性为左值),转化为右值,相当于move以下
* 4.move:已知其为右值(将亡值),右值引用引用后,属性为左值,将左值属性变为右值属性*/
template<class T>  
void PerfectForward(T&& t) //万能引用
{
	Fun(forward<T>(t));  //完美转发
}

int main()
{
	int a = 10; 
	PerfectForward(a);  //左值
	PerfectForward(move(a)); //右值

	const int b = 6;  
	PerfectForward(b);  //const左值
	PerfectForward(move(b));  //const右值
	return 0;
}

image.png

7. 默认移动构造、默认移动赋值

  1. C++类有八大默认成员函数:构造函数、拷贝构造函数、拷贝赋值重载、析构函数、移动构造函数、移动赋值运算符重载、取地址重载、const取地址重载。默认成员函数就是我们不写,编译器默认生成。重要的为前6个。

  2. 默认移动构造:如果我们没有显示实现移动构造,且没有显示实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,编译器就会自动生成一个默认移动构造。默认生成的移动构造构分为两部分,对于内置类型完成值拷贝、对于自定义类型,看它是否实现了移动构造,实现了就调用移动构造,没有实现,就调用拷贝构造。

  3. 默认移动赋值:如果我们没有显示实现移动赋值,且没有显示实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,编译器就会自动生成一个默认移动赋值。默认生成的移动赋值分为两部分,对于内置类型完成值拷贝、对于自定义类型,看它是否实现了移动赋值,实现了就调用移动赋值,没有实现,就调用拷贝赋值。

7.1. default和delete

  1. default关键字指示编译器强制生成对应的默认函数。

  2. 限制某些默认成员函数的生成:方法一:在C++98中,将该函数设置为private,只声明不实现。原因:将函数的定义设置为private,尽管类外访问不到,但在类中可以进行访问、若只声明,设置为公有,会出现链接错误,很可能在外面别人手动帮你实现该函数的定义(函数可以在类中声明,类外定义)。 方法二:在函数的声明后加上=defaule即可。delete关键字指示编译器不生成对应函数的默认版本,被=delete修饰的函数称为删除函数。

8. 可变参数模板

//可变参数的函数模板
template<class ...Args>  //Args是模板参数包 
void ShowList(Args ...args)  //args是函数形参数包   参数包里面函数0~N个模板参数
{   //...   }
  1. 带有省略号的称为"参数包", 它里面包含0~N(N>=0)个模板参数。Args是模板参数包 、args是函数形参数包。

  2. 我们无法直接拿到参数包args中每个参数的类型和值,语法上不支持通过args[i]来获取可变参数,因为这样是运行时解析参数。而此处是模板,是编译时解析参数。

8.1. 递归解析参数包、展开参数包

void _ShowList()   //递归终止函数
{
	cout << endl;
}

template<class T, class ...Args>
void _ShowList(const T& val, Args ...args) 
{
	cout << val << " ";
	_ShowList(args...);
}

//拿到每个参数值和类型,编译时递归解析
template<class ...Args>
void ShowList(Args ...args)
{
	_ShowList(args...);
}


void _ShowList(const string& s)
{
	cout << s << " ";
	_ShowList();
}

//实例化以后,推演生成的过程
void _ShowList(const char& ch, string s)
{
	cout << ch << " ";
	_ShowList(s);
}

void _ShowList(const int& val, char ch, string s)
{
	cout << val << " ";
	_ShowList(ch, s);
}

void ShowList(int val, char ch, string s)
{
	_ShowList(val, ch, s);
}

int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', string("sort"));

	return 0;
}

image.png

template <class T>
int PrintArg(T t)  
{
	cout << t << " ";
	return 0;
}

//展开函数
template <class ...Args>
void Show(Args... args)
{
	//在数组构造的过程中,展开参数包
	int arr[20] = { PrintArg(args)... };  //列表初始化,最终创建出一个元素值都为0的数组  
	cout << endl;
}

int main()
{
	Show();
	Show(10);
	Show(10, 'B');
	Show(10, 'B', string("Talent")); 

	return 0;
}

image.png

  • 在数组创建的时候,展开参数包。 { PrintArg(args)… }为列表初始化,用来初始化一个变长数组,它将会展开成(PrintArg(args1))、(PrintArg(args2) . . . (PrintArg(argsn),最终创建出一个元素值都为0的数组。

8.2. emplace相关的接口函数

image.png

  1. emplace_back、push_back都是C++标准库中的成员函数,都是在容器的尾部添加一个新元素。push_back接受一个值作为参数,并将这个值的副本添加到容器的末尾,可能会导致额外的性能开销,因为要复制整个对象。emplace_back是在容器的尾部直接构造新的元素,它使用对象的构造函数来创建对象。

  2. emplace系列,直接写插入对象参数:对于浅拷贝类的对象,减少(一次)拷贝构造,效率提高 、深拷贝类的对象,减少(一次)移动构造,效率提升不是很明显,与push_back无区别。

void test1()
{
	//直接写插入对象参数,深拷贝类的对象,减少(一次)移动构造,效率提升不是很明显,与push_back无区别
	list<bit::string> lt1;
	bit::string s1("xxxx");
	lt1.push_back(s1);
	lt1.push_back(move(s1));

	cout << endl;
	bit::string s2("vvvvvv");
	lt1.emplace_back(s2);
	lt1.emplace_back(move(s2));

	cout << endl;
	lt1.push_back("lala");
	lt1.emplace_back("haha");  //深拷贝类的对象,有移动构造、赋值,右值
	cout << "========================================" << endl;

	list<pair<bit::string, bit::string>> lt2;
	pair<bit::string, bit::string> kv1("xxxx", "yyyy");
	lt2.push_back(kv1);
	lt2.push_back(move(kv1));
	
	cout << endl;
	pair<bit::string, bit::string> kv2("xxxx", "yyyy");
	lt2.emplace_back(kv2);
	lt2.emplace_back(move(kv2));
	
	cout << endl;
	lt2.emplace_back("xxxx", "yyyy");  //
	cout << "=============================================" << endl;
	
	//直接写插入对象参数,浅拷贝类的对象,减少(一次)拷贝构造,效率提高
	list<Date> lt3;
	Date d1(2022, 10, 2);
	lt3.push_back(d1);
	lt3.push_back(move(d1));

	cout << endl;
	Date d2(2024, 10, 1);
	lt3.emplace_back(d2);
	lt3.emplace_back(move(d2));

	cout << endl;
	lt3.emplace_back(2023, 10, 1);  //浅拷贝对象,无移动构造、赋值,右值
	cout << "=============================================" << endl;
}

int main()
{
	test1();

	return 0;
}

image.png

template<class T>  //节点 
struct ListNode {  //struct类未用访问限定符修饰的变量为public,在类外指定类域就可以直接进行访问
	ListNode* _prev;  //带头双向循环链表
	ListNode* _next;
	T _data;

     //左值
	ListNode(const T& val = T()) //缺省值-》防止无参调用,因无默认构造函数,又显示写了构造函数,编译器会报错
		:_prev(nullptr)
		,_next(nullptr)
		,_data(val)
	{ }

	//右值
	ListNode(T&& val) 
		:_prev(nullptr)
		, _next(nullptr)
		, _data(forward<T>(val)) //右值引用引用右值,它的属性变为左值
    { }

	template<class ...Args>  //可变参数模板
	ListNode(Args&& ...args)   //万能引用+完美转发
		:_prev(nullptr)
		, _next(nullptr)
		, _data(forward<Args>(args)...) //右值引用引用右值,它的属性变为左值
	{ }
};

template<class T> //链表-带头双向循环链表,存储的元素为节点
	class list {   //class类未用访问限定符修饰的变量为private,在类外不可以访问
	public:
		typedef ListNode<T> Node; 
//左值
		void push_back(const T& val) //尾插
        {
			insert(end(), val);
		}

		//右值
		void push_back(T&& val)  
		{
			insert(end(), forward<T>(val)); //右值引用引用左值,它的属性变为左值
		}

		//尾插、直接调用构造函数
		template<class ...Args>    //可变参数模板
		void emplace_back(Args&& ...args)  //万能引用+完美转发
		{
			emplace(end(), forward<Args>(args)...);
		}
//左值
		iterator insert(iterator position, const T& val)
		{
			Node* newnode = new Node(val);
			Node* cur = position._node;  //struct中public变量访问可以 对象.变量名
			Node* prev = cur->_prev;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return newnode;  //有返回值,与erase匹配

		}

		//右值
		iterator insert(iterator position,T&& val)
		{
			Node* newnode = new Node(forward<T>(val)); //右值引用引用左值,它的属性变为左值
			Node* cur = position._node;  //struct中public变量访问可以 对象.变量名
			Node* prev = cur->_prev;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return move(newnode);  //有返回值,与erase匹配

		}

		template<class ...Args> 
		iterator emplace(iterator position, Args&& ...args)  //可变参数模板
		{
			Node* newnode = new Node(forward<Args>(args)...);  //万能引用+完美转发
			Node* cur = position._node;  //struct中public变量访问可以 对象.变量名
			Node* prev = cur->_prev;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return move(newnode);  //有返回值,与erase匹配

		}
}

9. lambda表达式

9.1. lambda表达式语法

lambda表达式书写格式:[capture-list] (paramerters) mutable -> return-types { statement }。

  1. 表达式各部分说明:

a. [capture-list]:捕捉列表,出现在lambda函数的开始位置,让编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉其所在作用域中的变量、静态存储期的变量(静态变量、全局变量),供lambda函数使用。捕捉列表说明什么数据能被lambda使用,以及捕捉的方式是传值还是传引用。

b. (paremerters):参数列表,与普通的参数列表一致,如果不需要参数传递,则可以连同( )一起省略。

c. mutable:默认情况下,一般lambda函数为const函数,mutablek可以取消其常量性 (捕捉列表以传值方式捕捉,被捕捉到的变量不能修改,若想要修改它,需要添加mutable),使用mutable,参数列表不能省略(即使参数为空)。

d. ->return-type:返回值类型。如果没有返回值,这部分可以省略、如果返回值类型是明确的,此时返回值也可以省略,由编译器对返回类型进行推导。

e. {statement}:函数体。在{ }内,只能使用参数、捕捉列表捕捉的变量。

注意:参数列表、返回值类型可以省略,捕捉列表、函数体不可以省略,即使为空。C++11最简单的lambda函数为[ ]{ },该lambda函数表示什么事情都不做。

  1. 捕捉列表说明:

a. 捕捉列表可以由多个捕捉项组成,以逗号分隔:当前作用域为包含lambda函数的语句块{ } 。[ val1 ]:传值捕捉变量var1、[ &val2 ]:传引用捕捉变量val2、[ & ]:传引用捕捉当前作用域内的所有变量,包括this、[ = ]:传值捕捉当前作用域内的所有变量,包括this、[ this ]:传值捕捉当前的this、[ & , n ]:传值捕捉n,传引用捕捉当前作用域的其他变量、[ =、&a , &b ]:传引用捕捉a、b,传值捕捉当前作用域的其他变量。

b.捕捉列表不允许变量重复传递,即:不允许某个变量被以相同的方式捕捉,否则编译器会报错。eg:[ = , a ]、[ & , &b ]。

c. 在块作用域以外的lambda函数捕捉列表必须为空:"在块作用域以外"是指在全局作用域定义的变量、在其它非直接封闭作用域定义的变量,如果它们具有静态存储期,即使lambda函数没有明确的捕捉列表(捕捉列表为空),lambda函数仍可以访问和使用它们。

d. lambda函数只能捕捉到当前作用域内的变量,如果捕捉了其他作用域内的变量(不具有静态存储期)、非全局变量,编译器都会报错。
e. lambda表达式之间不能相互赋值,即使lambda表达式完全相同。

int main()
{
	//test1();

	void (*PF)();  //与lambda表达式具有相同类型的函数指针
	 
	auto f1 = [] {cout << "hello" << endl; }; 
	auto f2 = [] {cout << "hello" << endl; };
	//f1 = f2;   不允许lambda表达式之间相互赋值

	auto f3(f2);  //可以用lambda表达式进行拷贝构造, lambda对象被禁掉了构造函数
	f3();

	PF = f2;  //赋值
	PF();
	return 0;
}

image.png

  • 可以用lambda表达式进行拷贝构造, lambda对象被禁掉了构造函数。可以将lambda表达式赋值给具有相同类型的函数指针。

9.2. lambada表达式

#include <algorithm>

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()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	for (auto& e : array) cout << e << ' ';
	cout << endl;

	// 如果需要降序,需要改变元素的比较规则
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());  //sort函数模板,传的是仿函数对象
	for (auto& e : array) cout << e << ' ';
	cout << endl;

	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());

	//lambda表达式,局部的匿名函数对象,没有类型,该函数无法直接调用,需要借助auto将其赋值给一个变量
	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;

  • sort为函数模板,需要的是仿函数对象,默认情况下,仿函数为less(),按照小于的方式进行比较,升序。若需要降序,需要改变函数的比较规则,手动传仿函数greater()。

💡Tips:lambda表达式为局部的匿名函数对象,语法层无类型,其类型是编译器去在编译时根据uuid算法随机生成一个唯一的、匿名的函数对象类型。

9.3. lambda表达式的底层

  1. 仿函数:也叫做函数对象,像函数一样使用对象,在类中重载operator()运算符,创建仿函数类对象,通过对象去调用operator( )运算符。

  2. 在使用方式上, lambda与函数对象,无区别,函数对象将rate定义为成员变量,在定义对象时给初始值即可,lambda通过捕捉列表可以将rate变量捕捉到。在底层,编译器将lambda表达式处理方式,是按照函数对象的方式进行处理的,如果定义了一个lambda表达式,编译器会自动生成一个类,在类中重载了operator()运算符。类名是编译器根据uuid算法随机生成的,唯一性,匿名性。——》lambda表达式底层是仿函数。

class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};

int main()
{
	// 函数对象
	double rate = 0.49;
	Rate r1(rate);
	r1(10000, 2);

	// lambda表达式
	auto r2 = [=](double money, int year)->double {  return money * rate * year; };
	r2(10000, 2);

	//在使用方式上,lambda与函数对象,无区别,函数对象将rate定义为成员变量,在定义对象时给初始值即可,lambda通过捕捉列表可以将rate变量捕捉到
	/*在底层,编译器将lambda表达式处理方式,是按照函数对象的方式进行处理的,如果定义了一个lambda表达式,
	编译器会默认生成一个类,在类中重载了operator()运算符。类名是编译器根据uuid算法随机生成的,唯一性,匿名性*/
	return 0;
}

image.png

int main()
{
	void (*PF)();  //与lambda表达式具有相同类型的函数指针
	 
	auto f1 = [] {cout << "hello" << endl; }; 
	auto f2 = [] {cout << "hello" << endl; };
	f1();
	f2();
	//f1 = f2;   不允许lambda表达式之间相互赋值

	auto f3(f2);  //可以用lambda表达式进行拷贝构造, lambda对象被禁掉了构造
	f3();

	PF = f2;  //赋值
	PF();
	return 0;
}

image.png

10. 包装器

10.1. function包装器

function包装器:也叫做适配器,C++中的function本质是一个类模板,也是一个包装器。

image.png

// functional包装器的使用方法如下:
#include <functional>

int f(int a, int b)
{
	return a + b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};

int main()
{
	function<int(int, int)> f1 = f;  //函数指针(普通函数)
	cout << f1(3, 4) << endl;
	function<int(int, int)> f2 = Functor();  //函数对象(仿函数)
	cout << f2(1, 2) << endl;
	function<int(int, int)> f3 = &Plus::plusi; //类中的静态成员函数
	cout << f3(2, 3) << endl;
	function<double(Plus, double, double)> f4 = &Plus::plusd;  //类中的成员函数
	cout << f4(Plus(), 1.1, 2.2) << endl;
	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus ps;
	cout << f5(&ps, 2.2, 3.3) << endl;

	return 0;
}

image.png

  • ret = fun( x ) , 此处的fun可能是 函数指针(函数名)、仿函数对象(函数对象)、lambda表达式对象,这些都是可调用的类型,但可能会导致模板的效率低下,因为函数模板可能会实例化为多份。下面可知useF函数模板实例化了三份。
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}

double f(double i)
{
	return i / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

屏幕截图 2024-04-20 205254.png

int main()
{
	//function包装器,也叫做适配器,将可调用的类型封装为一个类,统一类型,解决了函数模板实例化多份的问题导致的效率低下问题
	function<double(double)> f1 = f;  //函数指针
	function<double(double)> f2 = Functor();   //函数对象
	function<double(double)> f3 = [](double d)->double {return d / 4; };  //lambda表达式
	
	useF(f1, 11.1);
	useF(f2, 11.1);
	useF(f3, 11.1);

	return 0;
}

屏幕截图 2024-04-20 205936.png
https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/

class Solution {  //function包装器,lambda表达式(匿名函数对象,无类型)
public:
    int evalRPN(vector<string>& tokens) {
    stack<int> st;  //栈用来存储操作数

    map<string, function<int(int, int)>> m = 
    { {"+", [](int left, int right)->int{ return left + right;} }, 
      {"-", [](int left, int right)->int{ return left - right;} },
      {"*", [](int left, int right)->int{ return left * right;} },
      {"/", [](int left, int right)->int{ return left / right;} }
    };

    for(int i = 0; i < tokens.size(); i++)
    {
        if(m.count(tokens[i])) 
        {
            int right = st.top();
            st.pop();
            int left = st.top();
            st.pop();
            st.push(m[tokens[i]](left, right)); //
        }
        else st.push(stoi(tokens[i]));
    }
    return st.top();
    }
};

10.2. bind包装器

bind包装器:定义在#include头文件中,是一个函数模板,就像函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来"适应"原对象的参数列表。对可调用对象,调整参数:调整参数的顺序、调整参数的个数。

image.png

  1. bind的一般形式:auto newCallale = bind(callable, args_list) 。

  2. newCallable本身是一个可调用对象,args_list是一个逗号分割的参数列表,对应给定callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它args_list中的参数。

  3. args_list的参数可能包含形如_n的名字,n是个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable参数的"位置"。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数、_2为newCallable的第一个参数,以此类推。
    image.png

#include<functional>

int Sub(int a, int b)
{
	return a - b;
}

int main()
{
	auto fc1 = bind(Sub, placeholders::_1, placeholders::_2);
	cout << fc1(2, 3) << endl;
	auto fc2 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << fc2(2, 3) << endl;
}

image.png
image.png

int main()
{
	//调整参数的个数
	//bind返回值是个新的可调用对象, plusd为可调用对象,成员函数默认第一个参数为this,所以传Plus()
	function<double(double, double)> f1 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
	cout << f1(2, 3) << endl;
	//某些参数绑死
	function<double(double, double)> f2 = bind(&Plus::plusd, Plus(), placeholders::_1, 3);
	cout << f2(2, 3) << endl;
}

image.png

  • 91
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 84
    评论
评论 84
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值