C++进阶:C++11(列表初始化、右值引用与移动构造移动赋值、可变参数模版...Args、lambda表达式、function包装器)

C++进阶:C++11(列表初始化、右值引用与移动构造移动赋值、可变参数模版…Args、lambda表达式、function包装器)

今天接着进行语法方面知识点的讲解


文章目录

  • 1.统一的列表初始化
    • 1.1`{}`初始化
    • 1.2 initializer_list
      • pair的补充
  • 2.声明相关关键字
    • 2.1auto
    • 2.2decltype
  • 3.右值引用和移动语义
    • 3.1左值与右值
    • 3.2左值引用与右值引用
    • 3.3右值引用的场景与意义
      • 移动构造
      • 移动赋值
    • 引用性质与结论(万能引用、完美转发)
  • 4.新的类功能
    • 4.1默认构造函数
    • 4.2关键字default与delete
  • 5.可变参数模板
    • 展开参数包
    • 容器的emplace_back()和insert
  • 6. lambda表达式
    • 6.1引入
    • 6.2lambda表达式
  • 7.function包装器
    • 成员函数的包装


1.统一的列表初始化

1.1{}初始化

在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可直接省略

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

这样做的目的就是: 一切皆可用{}(初始化列表)初始化

#include<iostream>

using namespace std;

struct Point
{
	int _x;
	int _y;
};

int main()
{
	int a1 = 1;
	int a2 = { 1 };
	int a3{ 1 };//这些都能初始化

	int a4(1);
	int a5 =int(1);//这两个是模版支持的基本类型int构造和拷贝构造

	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[]{ 1, 2, 3, 4, 5 };//也能省略


	Point p1 = { 1, 2 };
	Point p2 = { 1 };// _y默认初始化成0了
	return 0;
}

111

本质:

int main()
{
	Point p1 = { 1, 2 };//本质就是:构造 + 拷贝构造 -> 优化,直接构造
	//相应的构造函数支持隐式类型转换,产生的临时对象是Point类型(构造出来的)
	Point* pp = new Point[2]{ {1,2},{2,3} };//这样的的意义比较大
	return 0;
}

1.2 initializer_list

int main()
{
	Point p1 = { 1, 2 };
	vector<int> v = { 1,2,3,4 };
	//这里两个是一样的吗?
	//不一样,前者数量限定死了;后者还可以随便加减
	return 0;
}

112

  • 那这个initializer_list是何方神圣呢?C++11里新增的类型

在这里插入图片描述

在C++中,初始化列表(Initializer list)提供了一种方便的方式来使用一组值对对象进行初始化。std::initializer_list是C++标准库提供的一个模板类

当我们使用初始化列表初始化对象时,编译器会自动从用大括号{}括起来的值列表构造一个std::initializer_list对象。这样你就可以使用std::initializer_list对象来访问列表中的值。

举个例子,在代码片段auto il = { 10, 20, 30 };中,一个std::initializer_list<int>对象il被创建,其中包含值10、20和30。这里使用auto关键字让编译器推断il的类型,这种情况下类型会是std::initializer_list<int>

需要注意的是,std::initializer_list并不会隐式定义,因此你需要包含<initializer_list>头文件才能访问它,即使你在隐式使用它。这个头文件提供了与初始化列表相关的必要定义。

  • 所以vector v = { 1,2,3,4 }; 相当于:右侧就是一个initializer_list的对象(隐式类型转换构造出一个vector),然后拷贝构造

在语句vector<int> v = { 1, 2, 3, 4 };中,右侧的{ 1, 2, 3, 4 }就是一个初始化列表对象(std::initializer_list<int>)。这个初始化列表对象会隐式地进行类型转换,构造出一个std::vector<int>对象,然后通过拷贝构造函数将这个std::vector<int>对象赋值给变量v


114

map<string, string> dict2 = { {"sort", "排序"}, {"string", "字符串"} };

pair的补充

int main()
{
	pair<const char*, const char*> kv3("sort", "排序");
	pair<const string, string> kv4(kv3);//明明是不同类型,但是却可以初始化
	return 0;
}

在这里插入图片描述


2.声明相关关键字

2.1auto

在C++11标准中引入了auto关键字,它可以用于声明变量时让编译器自动推断变量的类型。使用auto关键字可以简化代码,减少重复的类型声明,提高代码的可读性和可维护性。

自动类型推断: 当使用auto关键字声明变量时,编译器会根据变量的初始化表达式推断出变量的类型

int main()
{
	map<string, string> dict2 = { {"sort", "排序"}, {"string", "字符串"} };
	auto it2 = dict2.begin();
	map<string, string> ::iterator it2 = dict2.begin();
	//这样看是不是auto真香啊
	return 0;
}

2.2decltype

decltype 是 C++11 中引入的一个关键字,用于获取表达式的类型(推断类型)。它通常与 auto 结合使用,用于声明变量的类型或者作为模板参数推断的一部分。推导完类型后,还能用来定义变量

  • const修饰变量本身时,在使用decltype获取时,会去掉const

获取表达式类型: decltype 可以获取表达式的类型,包括变量、函数返回值、表达式等。例如:

int x = 10;
decltype(x) y = 20; // y的类型为int,与x相同  这里decltype(x)就是一个类型

3.右值引用和移动语义

3.1左值与右值

  • 左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名
  • 右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名
  • 右值可以进一步分为纯右值(prvalue)和将亡值(xvalue)。这两种右值的区别在于它们的生命周期和可修改性。
    1. 纯右值(prvalue):(内置类型的右值)纯右值通常是表达式的结果或字面常量

    2. 将亡值(xvalue,expiring value):(自定义类型的右值)将亡值是指一个即将被销毁的值,它具有“将亡的”特性。通常是临时对象、匿名对象

int& fun1()
{
	static int a = 1;
	return a;
}

int fmin(int a,int b)
{
	if (a < b)
		return a;
	else
		return b;
}

int main()
{
	int a = 0;
	int* pa = &a;
	int& ret = fun1();//这里a,pa,fun1的返回值都是左值,都能取地址

	// 以下几个都是常见的右值
	10;
	a + ret;
	fmin(a, ret);

	return 0;
}

在函数返回一个临时对象时,编译器会将该临时对象视为一个右值(rvalue)。

右值是一个临时的、无法被修改的值,因此在传值返回时,编译器会将该临时对象隐式地添加 const 修饰符,使其成为一个常量对象

3.2左值引用与右值引用

我们之前使用的引用都是左值引用,那么现在右值引用就是&&

	// 以下几个都是对右值的右值引用
	int&& r1 = 10;
	double&& r2 = a + ret;
	double&& r3 = fmin(a, ret);
  • 左值引用是给左值取别名,不能给右值引用取别名。但是const 左值引用可以给右值取别名

  • 右值引用不能给左值取别名,但是右值引用可以给move以后的左值取别名

    move其实就是移动语义,move后的左值会变成将亡值

int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a; 

    // const左值引用既可引用左值,也可引用右值。
    const int& ra2 = 10;
    const int& ra3 = a;
 //----------------------------------------------------------------
    // 右值引用只能右值,不能引用左值。
    int&& r1 = 10;

    // 右值引用可以引用move以后的左值,move会返回右值引用
    int&& r2 = std::move(a);

    return 0;
}

3.3右值引用的场景与意义

我们先来回顾一下左值引用的意义——解决了什么问题

  1. 传参的拷贝问题:在函数调用时,如果参数是通过值传递(传值)的方式传递的,会导致参数的拷贝构造函数被调用,增加了额外的开销。通过使用左值引用(&)作为函数参数,可以避免不必要的拷贝构造,提高程序的性能和效率。

  2. 部分传返回值的问题(非局部对象):在函数返回一个临时对象时,如果返回类型是一个对象而不是引用或指针,会导致拷贝构造函数被调用,产生额外的开销。通过使用左值引用作为返回类型,可以避免不必要的拷贝构造,提高程序的性能。

但是,如果我们要返回一个局部对象呢?就只能使用传值返回,一旦返回一个巨大的容器对象之类的,那开销是非常大的。

此时右值引用的意义之一就是解决:局部对象(出了作用域就销毁的对象)返回的拷贝问题

函数返回局部对象的问题:

  • 对于内置类型(如 intdoublechar 等)因为内置类型的赋值和返回通常是按值传递的,而不是按引用传递。这意味着内置类型的值会被直接复制或返回,而不需要调用拷贝构造函数。

    在返回内置类型时,编译器会进行优化,避免不必要的拷贝操作,直接将返回值传递给调用者或存储在临时变量中

  • 将局部变量作为返回值返回,编译器会创建一个临时变量(临时对象)来存储这个返回值,从而避免返回一个指向已经被销毁内存的引用

    编译器会调用拷贝构造函数来初始化临时变量。拷贝构造函数的目的是将一个对象的值复制到另一个对象中,以确保临时变量拥有正确的值

  • 那这个临时变量存在哪里呢?

new11.5

移动构造

  • 问题的提出:
	mystring::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;//负数变正数
		}

		mystring::string str;//这里是定义了一个局部的string
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;

			str += ('0' + x);
		}

		if (flag == false)
		{
			str += '-';
		}

		std::reverse(str.begin(), str.end());
		return str;//最后返回这个局部的string,不能返回左值引用。目前采取的是传值返回
		//核心问题是,出了作用域就销毁了,返回什么引用都不行
	}

在这里插入图片描述

  • 解决问题
		// 拷贝构造 -- 左值
		//一开始只有这个拷贝构造时,因为const 左值引用能给右值取别名
		//所以左值走这个,右值也走这个
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s)--正常拷贝构造" << endl;

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		//现在我们为右值特地编写一个构造函数,右值引用就走这个了
		// 移动构造 -- 右值(将亡值)
		string(string&& s)
		{
			cout << "string(string&& s)-- 移动拷贝" << endl;//方便观察结果
			swap(s);
		}

在这里插入图片描述

在这里插入图片描述

这里也能证明编译器进行了优化

移动构造:移动构造函数是 C++11 引入的一个重要特性,用于实现对象的移动语义。通过移动构造函数,可以将一个临时对象(右值引用)的资源(如堆上分配的内存)“移动”给另一个对象,而不是进行昂贵的拷贝操作。这可以提高程序的性能和效率

  • 浅拷贝的类不需要移动构造,深拷贝的类才需要移动构造

深拷贝就说明我们进行了涉及到动态内存分配和释放,那么如果进行每次返回局部变量都进行内容拷贝,代价极大

而浅拷贝没有涉及到动态内存分配和释放,那么移动构造函数可能并不是必需的,因为浅拷贝只是简单地复制值,不存在资源的所有权转移

移动赋值

问题提出:

在这里插入图片描述

此时str还是左值,那么如果我们move后,使之变为右值(将亡值)呢?

解决问题:

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

			return *this;
		}

		//移动赋值
		string& operator=(string&& s)//接收右值
		{
			swap(s);//移动将亡值的资源,并且把不要空间给将亡值,让将亡值帮着释放
			return *this;
		}

此时为了处理右值,我们写出移动赋值的函数。直接进行资源的交换,还能顺便让将亡值帮着释放

在这里插入图片描述

引用性质与结论(万能引用、完美转发)

  • 对于自定义类型左值,我们最好不要随便去move,有可能move后里面的数据就被转移走了

  • 右值被右值引用以后,右值引用本身的属性是左值

    为什么这样设计?

    我们右值引用是为了解决返回局部变量的拷贝问题,具体实现是进行资源的转换。那么如果右值引用本身还是右值,不能被改变,那还怎么进行资源的转移

  • const 右值 ,右值引用后不能改变

万能引用是 C++11 中引入的一种引用类型,用于实现泛型编程时处理模板类型参数的值类别(左值或右值)。

在 C++11 中,引入了右值引用(Rvalue Reference),其语法为 T&&,其中 T 是类型。右值引用主要用于移动语义和完美转发。当右值引用绑定到一个右值时,可以实现移动语义,避免不必要的对象拷贝。但右值引用也可以绑定到一个左值,这时就无法区分左值和右值。

为了解决这个问题,C++11 引入了万能引用的概念,也称为转发引用(Forwarding Reference)。万能引用的语法是 T&&,其中 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 test(T&& t)
{
	cout << "void test(T&& t)" << endl;
	Fun(t);
}

int main()
{
	test(10); // 右值
	cout << "----------------------------------------------------" << endl;
	int a;
	test(a); // 左值
	cout << "----------------------------------------------------" << endl;
	test(std::move(a)); // 右值
	cout << "----------------------------------------------------" << endl;
	return 0;
}

在这里插入图片描述

这里我们使用move也不行——不确定是左值还是右值。这里就能使用完美转发

完美转发是 C++11 引入的一个特性,用于在函数模板中保持参数的值类别(左值或右值)和常量性,同时将参数原样传递给另一个函数。完美转发通常与模板和引用折叠相关联,并在实现泛型代码时非常有用。

  1. 引用折叠

    • 引用折叠是 C++11 中的一个规则,用于确定引用的最终类型。在模板中使用引用时,引用可能会发生折叠,最终得到左值引用或右值引用。
    • 引用折叠规则:T& & 折叠为 T&T&& && 折叠为 T&&T& &&T&& & 都折叠为 T&
  2. std::forward

    • std::forward 是一个模板函数,用于在函数模板中完美转发参数。
    • std::forward 接受一个参数和参数的类型,并根据参数的值类别(左值或右值)进行转发。
    • 当传递左值时,std::forward 将返回左值引用;当传递右值时,std::forward 将返回右值引用。

在这里插入图片描述


4.新的类功能

4.1默认构造函数

之前我们学习的C++类中,有6个默认成员函数:

  1. 构造函数

  2. 析构函数

  3. 拷贝构造函数

  4. 拷贝赋值重载

  5. 取地址重载

  6. const 取地址重载

现在就新增了两个我们上面才讲的:移动构造和移动赋值

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝(浅拷贝 ),自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

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

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

4.2关键字default与delete

强制生成默认函数的关键字default:

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成

禁止生成默认函数的关键字delete:

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。经常用来防止类可以被拷贝。成员函数声明尾部加上delete,表示该成员函数被禁掉,编译器会删除这样的类成员函数

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}

	Person(const Person& p) = delete;//禁止生成

	Person(Person&& p) = default;//强制生成
private:
	string _name;
	int _age;
};

一般也可不加上参数名


5.可变参数模板

1112

早在c语言里的printf函数,就有可变参数的概念,我们能随意传入不同数量想参数。虽然底层很难,但是我们用起来舒服

可变参数模板是 C++11 中引入的一个特性,允许函数模板接受任意数量的参数。通过可变参数模板,可以实现灵活的函数接口,处理不定数量的参数,类似于可变参数函数(如 printf)的功能。

在 C++ 中,可变参数模板通常使用模板参数包(template parameter pack)来实现。模板参数包允许在模板参数列表中接受任意数量的参数,并通过展开(expansion)来处理这些参数。

一个基础的模版:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>//不一定非要叫Args
void ShowArgs(Args... args)
{

}
  • 上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。

  • 我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

  • 我们可以使用sizeof...(args)来得到有几个参数

展开参数包

  • 递归函数方式展开参数包:使用编译时解析(编译时递归)
void _ShowArgs()//编译时递归的结束条件
{
	cout << endl;
}

template <class T, class ...Args>
void _ShowArgs(const T& t, Args... args)//把第一个参数拿出来
{
	cout << t << endl;
	_ShowArgs(args...);
}

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowArgs(Args... args)
{
	_ShowArgs(args...);
}


int main()
{
	ShowArgs(1, "abc", 'c');
	return 0;
}

在这里插入图片描述

容器的emplace_back()和insert

1114

emplace_back() 是 C++ 容器类(如 std::vector, std::deque, std::list 等)提供的一个成员函数,用于在容器的末尾直接构造一个新元素,而不是先创建一个临时对象再拷贝或移动到容器中。这样可以避免不必要的对象创建和拷贝操作,提高代码的性能和效率。

  1. emplace_back() 的优势
  • 避免不必要的对象创建和拷贝:使用 emplace_back() 直接在容器中构造对象,避免了先创建临时对象再拷贝或移动的开销。
  • 更高效的内存管理emplace_back() 可以在容器的末尾直接构造对象,减少了不必要的内存分配和释放操作。
  • 支持完美转发emplace_back() 可以完美转发参数给对象的构造函数,保留了原始参数的类型和属性。
  • 直接构造函数的前提是直接传入参数,而不是现成的对象或者匿名对象
  1. push_back() 的区别
  • push_back() 接受一个对象的副本(拷贝或移动),而 emplace_back() 直接在容器中构造对象。
  • emplace_back() 通常比 push_back() 更高效,特别是在构造对象开销较大时。
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;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		cout << "拷贝构造Date(const Date& d)" << endl;
	}
private:
	int _year = 200;
	int _month = 1;
	int _day = 1;
};

int main()
{
	std::list<Date> lt1;
	lt1.push_back({ 2024,3,30 });
	cout << endl;
	//支持这个因为隐式类型转换,构造出一个节点后,再push
	
	//lt1.emplace_back({ 2024,3,30 });
	// 不支持。因为底层是模版,根据这个推出不data类
	// 

	// 这样是可以的,可以自己推导
	lt1.emplace_back(2024, 3, 30);
	cout << "----------------------------------------------------" << endl;

	Date d1(2023, 1, 1);
	lt1.push_back(d1);
	cout << endl;
	lt1.emplace_back(d1);
	cout << "----------------------------------------------------" << endl;

	lt1.push_back(Date(2023, 1, 1));
	cout << endl;
	lt1.emplace_back(Date(2023, 1, 1));

	return 0;
}

在这里插入图片描述

对于使用 emplace_back() 或者 emplace 系列函数来直接在容器中构造对象的情况,需要传入构造函数所需的参数,而不是现成的对象或者匿名对象。

这是因为 emplace_back() 或者 emplace 系列函数是通过完美转发参数给构造函数来在容器中构造对象的


6. lambda表达式

6.1引入

之前我们想要对自定义类型进行排序的话,要自己给出比较方法的仿函数

#include<algorithm>
struct Good
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价

	Good(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

struct ComparePriceLess
{
	bool operator()(const Good& gl, const Good& gr)
	{
		return gl._price < gr._price;
	}
};

struct ComparePriceGreater
{
	bool operator()(const Good& gl, const Good& gr)
	{
		return gl._price > gr._price;
	}
};

int main()
{
	vector<Good> v = { { "apple", 2.1, 5 }, { "banana", 3, 4 }, { "orange", 2.2,3 }};

	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());//这里我们想要比较就只能自己给出仿函数
}

我们现在给出是按照价格排序,如果我们想要按照名称或者数量排序呢?那又要写额外的仿函数,那就有点麻烦了

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


6.2lambda表达式

在C++中,Lambda表达式是一种匿名函数(本质是一个局部的匿名函数对象),可以在需要函数对象的地方使用,例如作为参数传递给标准算法函数。Lambda表达式的一般形式如下:

[capture-list](parameters) mutable -> return-type {
    // Lambda函数体
    // 可以使用捕捉列表中的变量和参数列表中的参数
    return expression; // 可选
}

下面是Lambda表达式的各个部分说明:

  • 捕捉列表 [capture-list]:捕捉列表用于捕捉上下文中的变量,供Lambda函数使用。捕捉列表可以为空,也可以包含以下内容:

    • []:表示不捕捉任何变量。
    • [&]:通过引用捕捉所有外部变量。
    • [=]:通过值捕捉所有外部变量。
    • [var]:通过值捕捉特定变量 var
    • [&var]:通过引用捕捉特定变量 var
  • 参数列表 (parameters):与普通函数的参数列表一致,可以省略参数列表,即使不需要参数传递。

  • mutable:可选关键字,用于取消Lambda函数的常量性。如果Lambda函数需要修改捕捉的变量,则需要使用 mutable 关键字。

  • 返回值类型 -> return-type:用于指定Lambda函数的返回值类型,可以省略,编译器会根据返回语句进行推导(一般情况都不写)。

  • 函数体 { statement }:Lambda函数体,包含了Lambda函数的实际逻辑。在函数体内可以使用捕捉的变量和参数。

示例:

int main()
{
	vector<Good> v = { { "apple", 2.1, 5 }, { "banana", 3, 4 }, { "orange", 2.2,3 }};

	auto add = [](int a, int b) {return a + b; };
	cout << add(1, 2) << endl;

	auto swap1 = [](int& a, int& b)->void {
		int tmp = a;
		a = b;
		b = tmp;
	};

	auto print = [] {
		cout << "auro print" << endl;
	};//这个就是能省的都省了,当然函数体里面也能什么都不写

	print();//调用上面这个

	return 0;
}

学会了基本使用后,我们能来解决上面的问题

#include<algorithm>
struct Good
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价

	Good(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

int main()
{
	vector<Good> v = { { "apple", 2.1, 5 }, { "banana", 3, 4 }, { "orange", 2.2,3 } };
	sort(v.begin(), v.end(), [](const Good& g1, const Good& g2) {
		return g1._price < g2._price;
		});
	sort(v.begin(), v.end(), [](const Good& g1, const Good& g2) {
		return g1._price > g2._price;
		});

	sort(v.begin(), v.end(), [](const Good& g1, const Good& g2) {
		return g1._evaluate < g2._evaluate;
		});
	return 0;
}
int main()
{
	int x = 1, y = 2;

	// 传引用捕捉
	auto swap = [&x, &y]{
		int tmp = x;
		x = y;
		y = tmp;
	};

	swap();
	cout << x << endl;
	cout << y << endl;

	int m = 3, n = 4;
	// 传值捕捉当前域的所有对象
	auto func1 = [=] {
		return x + y * m - n;
	};

	cout << func1() << endl;

	// 传引用捕捉当前域的所有对象
	auto func2 = [&] {
		x++;
		m++;
		return x + y * m - n;
	};

	cout << func2() << endl;
	cout << x << endl;
	cout << m << endl;

	// 传引用捕捉当前域的所有对象,对个别对象传值捕捉
	auto func3 = [&, n] {
		x++;
		m++;

		// n++; 不行
		return x + y * m - n;
	};

	cout << func3() << endl;
	cout << x << endl;
	cout << m << endl;

	//----------------------------------
	auto DateLess = [](const Date* p1, const Date* p2){
				return p1 < p2;
			};//只能用auto来接收,这样也表明不能得到具体的类型。要结合decltype使用
	priority_queue<Date*, vector<Date*>, decltype(DateLess)> p1(DateLess);
	return 0;
}

7.function包装器

在C++中,std::function是一个通用的函数包装器,它可以用来存储、复制和调用任何可调用对象,包括函数指针、函数对象、Lambda表达式等std::function提供了一种统一的接口,使得可以将不同类型的可调用对象存储在同一个对象中,并且可以通过该对象进行调用。

C++中的function本质是一个类模板,function可以封装他们,目标是统一类型,统一后我们能传给各种容器使用,函数指针的话类型复杂、仿函数的类型不同、Lambda表达式没有类型。三者各有各的问题。

std::function的一般形式如下:

// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
//模板参数说明:
//Ret: 被调用函数的返回类型
//Args…:被调用函数的形参

function<return_type(parameters)> func;

return_type是函数的返回类型,parameters是函数的参数列表。通过std::function的模板参数,可以指定存储的可调用对象的类型。

  1. 存储可调用对象std::function可以存储各种可调用对象,如函数指针、函数对象、Lambda表达式等。
  2. 调用函数:通过operator()运算符,可以调用std::function对象所包装的可调用对象,就像调用普通函数一样。
  3. 空对象:如果std::function未与任何可调用对象关联,即为空对象,调用空对象会引发未定义行为。因此,在使用前需要确保std::function对象不为空。
#include<functional>

double fun1(double i)
{
	return i * 2;
}
struct Function
{
	double operator()(double f)
	{
		return f / 3;
	}
};

int main()
{
	// 函数指针
	function<double(double)> fc1 = fun1;
	fc1(1.1);

	// 函数对象
	function<double(double)> fc2 = Function();
	fc2(1.1);

	// lambda表达式
	function<double(double)> fc3 = [](double d)->double { return d / 4; };
	fc3(1.1);

	return 0;
}

成员函数的包装

class Plus
{
public:
	static int plus_static(int a, int b)//静态成员函数
	{
		return a + b;
	}

	double plus(double a, double b)//普通成员函数
	{
		return a + b;
	}
};

int main()
{
	//包装静态的
	function<int(int, int)> fun1 = Plus::plus_static;//注意要指明类域
	cout << fun1(1, 1) << endl;
	//包装普通
	function<double(Plus,double, double)> fun2 = &Plus::plus;//注意要指明类域
	function<double(Plus*,double, double)> fun3 = &Plus::plus;//这两种都行

	cout << fun1(1, 1) << endl;
	return 0;
}

非静态成员函数需要对象的指针或者对象去进行调用


好了内容是真不少啊,下面就带来c++里面的异常了

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

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

相关文章

springboot+vue2+elementui实现时间段查询

1.前端代码 使用elementui的时间段选择器&#xff1a; <el-date-picker v-model"queryPage.itemTime" type"daterange"value-format"yyyy-MM-dd" class"filter-item" range-separator"至" start-placeholder"创建…

Python筑基之旅-MySQL数据库(三)

目录 一、数据库操作 1、创建 1-1、用mysql-connector-python库 1-2、用PyMySQL库 1-3、用PeeWee库 1-4、用SQLAlchemy库 2、删除 2-1、用mysql-connector-python库 2-2、用PyMySQL库 2-3、用PeeWee库 2-4、用SQLAlchemy库 二、数据表操作 1、创建 1-1、用mysql-…

Kubernetes常用命令

目录 一.资源管理办法 1.陈述式资源管理方法 &#xff08;1&#xff09;kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口 &#xff08;2&#xff09;kubectl 是官方的CLI命令行工具&#xff0c;用于与 apiserver 进行通信&#xff0c;将用户在…

python+pytest+pytest-html+allure集成测试案例

pythonpytestpytest-htmlallure集成测试案例 下面是pythonpytestpytest-htmlallure四个组件同时集成使用的简单案例。 1. 项目结构 project/│├── src/│ ├── __init__.py│ ├── main.py│├── tests/│ ├── __init__.py│ ├── conftest.py│ └──…

MySQL主从复制(二):高可用

正常情况下&#xff0c; 只要主库执行更新生成的所有binlog&#xff0c; 都可以传到备库并被正确地执行&#xff0c; 备库就能达到跟主库一致的状态&#xff0c; 这就是最终一致性。 但是&#xff0c; MySQL要提供高可用能力&#xff0c; 只有最终一致性是不够的。 双M结构的…

用Python代码批量提取PDF文件中的表格

PDF文档中常常包含大量数据&#xff0c;尤其是官方报告、学术论文、财务报表等文档&#xff0c;往往包含了结构化的表格数据。表格作为承载关键信息的载体&#xff0c;其内容的准确提取对于数据分析、研究论证乃至业务决策具有重大意义。然而&#xff0c;PDF格式虽保证了文档的…

STM32——DAC篇(基于f103)

技术笔记&#xff01; 一、DAC简介&#xff08;了解&#xff09; 1.1 DAC概念 传感器信号采集改变电信号&#xff0c;通过ADC转换成单片机可以处理的数字信号&#xff0c;处理后&#xff0c;通过DAC转换成电信号&#xff0c;进而实现对系统的控制。 1.2 DAC的特性参数 1.3…

你以为的私域是真正的私域嘛??你的私域流量真的属于你嘛?

大家好 我是一个软件开发公司的产品经理 专注私域电商行业7年有余 您的私域流量是真正的属于你自己嘛&#xff1f; 私域的定义 私域的界定&#xff1a;一个互联网私有数据&#xff08;资产&#xff09;积蓄的载体。这个载体的数据权益私有&#xff0c;且具备用户规则制定权…

继承和多态

目录: 1. 继承 2. 多态&#xff1a; 转型 重写 正文&#xff1a; 1. 继承&#xff1a; 观察以下代码&#xff1a; 我们发现Cat类和Dog类中有许多相同的属性&#xff0c;那不妨思考一下是否能有一种办法能把它们的相同点都归结到一块儿呢&#xff1f; 当然有&#xff0c;它就…

【html】网页布局模板01---简谱风

模板效果: 这是一种最简单,最干净的一种网页布局。 模板介绍: 模板概述: 这个模板是一个基础的网页布局模板,包括一个头部区域(header),其中包含网站标题(logo)和导航菜单(nav),以及一个页脚区域(copy),用于显示版权信息。整体布局简洁明了,适合作为各种类…

构建全面的无障碍学习环境:科技之光,照亮学习之旅

在信息与科技日益发展的当下&#xff0c;为所有人群提供一个包容和平等的学习环境显得尤为重要&#xff0c;特别是对于盲人朋友而言&#xff0c;无障碍学习环境的构建成为了一项亟待关注与深化的课题。一款名为“蝙蝠避障”的辅助软件&#xff0c;以其创新的设计理念与实用功能…

贪心算法--区间调度问题

贪心算法 引言 贪心算法是一种简单而有效的算法设计技巧&#xff0c;在解决一些优化问题时具有广泛的应用。其基本思想是通过每一步的局部最优选择&#xff0c;最终达到全局最优解。贪心算法通常不会回溯之前的决策&#xff0c;而是根据当前状态作出最优决策&#xff0c;因此…

d20(184-190)-勇敢开始Java,咖啡拯救人生

目录 网络通信 网络通信三要素&#xff08;IP地址&#xff0c;端口号&#xff0c;协议 IP地址 InetAddress 端口号 协议 传输层的两个通信协议 UDP通信 java.net.Datagramsocket类 客户端 服务端 UDP通信多收多发 客户端 服务端 TCP通信 java.net.Socket类 客…

UWA DAY 2024 正式启动|创新潜藏无限可能

备受期待的UWA DAY 2024即将盛大开幕&#xff01;由侑虎科技UWA主办的这场年度游戏开发者大会&#xff0c;以“创新潜藏无限可能”为主题&#xff0c;致力于为游戏开发者呈现最前沿的技术盛宴。 大会定于2024年9月7日至9月8日&#xff08;周六、周日&#xff09;在上海举行&am…

YOLOv9改进策略 | 图像去雾 | 利用图像去雾网络UnfogNet辅助YOLOv9进行图像去雾检测(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是利用UnfogNet超轻量化图像去雾网络,我将该网络结合YOLOv9针对图像进行去雾检测(也适用于一些模糊场景),我将该网络结构和YOLOv9的网络进行结合同时该网络的结构的参数量非常的小,我们将其添加到模型里增加的计算量和参数量基本可…

【R语言】ggplot中点的样式shape参数汇总

ggplot中点的样式展示&#xff1a; library(ggplot2)# 创建数据框 a<- data.frame(x 0:25, y 0:25) # 创建散点图 ggplot(a, aes(x x, y y, shape as.factor(y))) geom_point(size 4) scale_shape_manual(values 0:25) labs(shape "形状") theme(legend.…

k8s二进制安装与部署

目录 一、实验目的 二、实验环境 三、实验步骤 3.1 操作系统初始化配置 3.2 部署 docker引擎 3.3 部署 etcd 集群 3.3.1 在 master01 节点上操作 ​3.3.2 在 node01 节点上操作 3.3.3 在 node02 节点上操作 3.4 部署 Master 组件 3.4.1 在 mast…

【QT实战】汇总导航

✨Welcome 大家好&#xff0c;欢迎来到瑾芳玉洁的博客&#xff01; &#x1f611;励志开源分享诗和代码&#xff0c;三餐却无汤&#xff0c;顿顿都被噎。 &#x1f62d;有幸结识那个值得被认真、被珍惜、被捧在手掌心的女孩&#xff0c;不出意外被敷衍、被唾弃、被埋在了垃圾堆…

EN6347QI 开关稳压器 4A 贴片QFN-38 参数资料 应用案例 相关型号

EN6347QI 是一款直流/直流开关转换器。它是一款高效率的 buck (降压) 转换器&#xff0c;内置了电感器&#xff0c;能够提供高达 4A 的输出电流。其工作电压范围为 4.5V 至 12V&#xff0c;输出电压可调&#xff0c;最高可达 15V。EN6347QI 适合于各种电子设备中&#xff0c;用…

C#学习指南:重要内容与实用技巧

学习C#编程是一段充满挑战但又非常充实的旅程。以下是我在学习过程中积累的一些经验&#xff0c;希望能对大家有所帮助。 一、掌握基础概念 类及其成员 C#中的类是编程的基础模块。理解类的结构、属性、方法和构造函数是至关重要的。每个类都有其特定的功能&#xff0c;学会如…