C++11 右值引用和移动语义,完美转发和万能引用,移动构造和移动赋值,可变参数模板,lambda表达式,包装器

news2024/10/6 18:21:42

文章目录

    • C++11简介
    • 统一的列表初始化
      • {}初始化
      • std::initializer_list
      • 声明
        • auto
        • decltype
        • nullptr
      • 范围for循环
    • 智能指针
    • STL中一些变化
    • 右值引用和移动语义
      • 左值引用和右值引用
      • 左值引用与右值引用比较
    • 右值引用使用场景和意义
    • 右值引用引用左值及其一些更深入的使用场景分析
    • 完美转发 && 万能引用
    • 新的类功能
      • 移动构造函数和移动赋值运算符重载
      • 强制生成默认函数的关键字default:
      • 禁止生成默认函数的关键字delete:
      • 继承和多态中的final与override关键字
    • 可变参数模板
    • 逗号表达式展开参数包
    • lambda表达式
      • lambda表达式
      • lambda表达式语法
    • 包装器
    • bind
    • 线程库

C++11简介

C++11,先前被称作C++0x,即ISO/IEC 14882:2011,是C++编程语言的一个标准。它取代第二版标准ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998公开于1998年,第二版于2003年更新,分别通称C++98以及C++03,两者差异很小),且已被C++14取代。相比于C++03,C++11标准包含核心语言的新机能,而且扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数除外)。 ISO/IEC JTC1/SC22/WG21 C++标准委员会计划在2010年8月之前完成对最终委员会草案的投票,以及于2011年3月召开的标准会议完成国际标准的最终草案。然而,WG21预期ISO将要花费六个月到一年的时间才能正式发布新的C++标准。为了能够如期完成,委员会决定致力于直至2006年为止的提案,忽略新的提案[1]。最终于2011年8月12日公布,并于2011年9月出版。

统一的列表初始化

{}初始化

  • 在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;
};
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()
{
	// 内置类型
	int i = 0;
	int j = { 0 };
	int k{ 0 };
	int array1[]{ 1, 2, 3, 4, 5 };
	int array2[5]{ 0 };
	
	// 自定义类型
	Point p{ 1, 2 };
	Date d1(2023, 11, 25);

	// 类型转换  构造+拷贝构造->优化直接构造
	Date d2 = { 2024, 5, 15 };
	Date d3{ 2024, 5, 15 };

	string s1 = "xxxx";

	const Date& d4 = { 2023, 11, 25 };

	// new 表达式
	Date* p1 = new Date[3]{ d1, d2, d3 };
	Date* p2 = new Date[3]{ {2022, 11, 25}, {2022, 11, 26}, {2022, 11, 27} };

	return 0;
}

std::initializer_list

std::initializer_list的介绍文档:

https://legacy.cplusplus.com/reference/initializer_list/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=的参数,这样就可以用大括号赋值。
int main()
{
	vector<int> v1 = { 1,2,3,4 };
	vector<int> v2 = { 1,2,3,4,5,6};

	v1 = { 10,20,30 };

	list<int> lt = { 10,20,30 };

	// 多参数构造类型转换  构造+拷贝构造->优化直接构造
	// 跟对应构造函数参数个数匹配
	Date d2 = { 2023, 11, 25 };

	// the type of il is an initializer_list 
	auto il1 = { 10, 20, 30, 40, 50 };
	cout << typeid(il1).name() << endl;

	initializer_list<int> il2 = { 10, 20, 30};

	initializer_list<int>::iterator it2 = il2.begin();
	while (it2 != il2.end())
	{
		cout << *it2 << " ";
		++it2;
	}
	cout << endl;
	
	//for (int e : il2)
	for (auto e : il2)
	{
		cout << e << " ";
	}
	cout<< endl;

	pair<string, string> kv1("sort", "排序");
	map<string, string> dict = {{"insert", "插入"}, {"get","获取"} };
	for (auto& kv : dict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}

	Date dd2 = { 2023, 11, 25 };
	// Date dd3 = { 2023, 11, 25, 20}; // 报错

	return 0;
}
  • 让模拟实现的vector也支持{}初始化和赋值
namespace lsl
{
	template<class T>
	class vector {
	public:
		typedef T* iterator;

		vector(initializer_list<T> l)
		{
			_start = new T[l.size()];
			_finish = _start + l.size();
			_endofstorage = _start + l.size();
			iterator vit = _start;
			typename initializer_list<T>::iterator lit = l.begin();
			while (lit != l.end())
			{
				*vit++ = *lit++;
			}
		}
		vector<T>& operator=(initializer_list<T> l) {
			vector<T> tmp(l);
			std::swap(_start, tmp._start);
			std::swap(_finish, tmp._finish);
			std::swap(_endofstorage, tmp._endofstorage);
			return *this;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

声明

  • c++11提供了多种简化声明的方式,尤其是在使用模板时。
auto
  • 在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型截断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
decltype
  • 关键字decltype将变量的类型声明为表达式指定的类型。
int main()
{
	int i = 1;
	double d = 2.2;

	// 类型以字符串形式获取到
	cout << typeid(i).name() << endl;
	cout << typeid(d).name() << endl;

	// typeid(i).name() j;
	auto j = i;

	auto ret = i * d;
	decltype(ret) x = ret;

	// 用ret的类型去实例化vector
	// decltype可以推导对象的类型。这个类型是可以用来模板实参,或者再定义对象
	vector<decltype(ret)> v;
	v.push_back(1);
	v.push_back(1.1);
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

在这里插入图片描述

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

范围for循环

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	for (auto e : v)
	{
		// 自动解引用,自动++
		cout << e << " ";
	}

	cout << endl;

	return 0;
}

智能指针

新开篇幅进行讲解

STL中一些变化

下图是C++11中新加的一些容器,但是实际最有用的是unordered_mapunordered_set

在这里插入图片描述

实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本:

  • http://www.cplusplus.com/reference/vector/vector/emplace_back/
  • http://www.cplusplus.com/reference/vector/vector/push_back/
  • http://www.cplusplus.com/reference/map/map/insert/
  • http://www.cplusplus.com/reference/map/map/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还要提出右值引用呢?是不是画蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

  • 在我们之前写的模拟实现string,再进行改造一下:

#include<assert.h>

namespace lsl
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//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;
	};
	
	lsl::string to_string(int x)
	{
		lsl::string ret;
		while (x)
		{
			int val = x % 10;
			x /= 10;
			ret += ('0' + val);
		}
		reverse(ret.begin(), ret.end());

		return ret;
	}
}

左值引用的使用场景:

  • 做参数和做返回值都可以提高效率。
void func1(lsl::string s)
{}
void func2(const lsl::string& s)
{}
int main()
{
	lsl::string s1("hello world");
	// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
	func1(s1);
	func2(s1);

	s1 += '!';
	return 0;
}

左值引用的短板:

  • 但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如:lsl::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
lsl::string to_string(int x)
{
	lsl::string ret;
	while (x)
	{
		int val = x % 10;
		x /= 10;
		ret += ('0' + val);
	}
	reverse(ret.begin(), ret.end());

	return ret;
}
int main()
{
	// 在bit::string to_string(int value)函数中可以看到,这里
    // 只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
	lsl::string ret = lsl::to_string(1234);

	return 0;
}
  • 这里的to_string的返回值是一个右值,用这个右值构造ret2,如果没有移动构造,调用就会匹配调用拷贝构造,因为const左值引用是可以引用右值的,这里就是一个深拷贝

右值引用和移动语义解决上述问题:

  • bit::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己
// 移动构造
string(string&& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	cout << "string(string&& s) -- 移动语义" << endl;
	swap(s);
}
int main()
{
	lsl::string ret = lsl::to_string(1234);
	return 0;
}
  • 再运行上面lsl::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。

  • to_string的返回值是一个右值,用这个右值构造ret,如果即有拷贝构造又有移动构造,调用就会匹配调用移动构造,因为编译器会选择最匹配的参数调用。那么这里就是一个移动语义

不仅仅有移动构造,还有移动赋值:

  • lsl::string类中增加移动赋值函数,再去调用lsl::to_string(1234),不过这次是lsl::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。
// 移动赋值
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动语义" << endl;
	swap(s);
	return *this;
}
  • 这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。lsl::to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为lsl::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。

STL中的容器都是增加了移动构造和移动赋值:

  • http://www.cplusplus.com/reference/string/string/string/
  • http://www.cplusplus.com/reference/vector/vector/vector/

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

  • 按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过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()
{
	lsl::string s1("hello world");
	// 这里s1是左值,调用的是拷贝构造
	lsl::string s2(s1);

	// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
	// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
	// 资源被转移给了s3,s1被置空了。
	lsl::string s3(std::move(s1));
	return 0;
}

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

  • http://www.cplusplus.com/reference/list/list/push_back/
  • http://www.cplusplus.com/reference/vector/vector/push_back/
int main()
{
	list<lsl::string> lt;
	lsl::string s1("1111");
	
	// 这里调用的是拷贝构造
	lt.push_back(s1);

	// 下面调用都是移动构造
	lt.push_back("2222");
	lt.push_back(std::move(s1));
	return 0;
}

在这里插入图片描述

完美转发 && 万能引用

  • 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
  • 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力
  • 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
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;
}

在这里插入图片描述

void PerfectForward(T&& t)
{
	Fun(move(t));
	// Fun(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(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;
};

新的类功能

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。

移动构造函数和移动赋值运算符重载

  • C++11 新增了两个:移动构造函数移动赋值运算符重载

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
  • 如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
    完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
private:
	lsl::string _name; 
	int _age;
};

int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

在这里插入图片描述

  • 只要写了其中一个就不会生成
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& operator=(const Person& p)
		{
		if(this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
			return *this;
		}*/
	
		~Person()
		{}

private:
	lsl::string _name; 
	int _age;
};

int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

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

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

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

  • 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	Person(const Person& p) = delete;
private:
	lsl::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);

	return 0;
}

继承和多态中的final与override关键字

  1. final:修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};

在这里插入图片描述

  1. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car {
public:
	 void Drive() {}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

在这里插入图片描述

可变参数模板

  • C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。

下面就是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
  • 上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N( N > = 0 N>=0 N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

递归函数方式展开参数包

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " ";
	ShowList(args...);
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

逗号表达式展开参数包

  • 这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。

  • expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包

template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
	// 要初始化arr,强行让解析参数包,参数包有一个参数,PrintArg就依次推演生成几个
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

STL容器中的empalce相关接口函数:

  • http://www.cplusplus.com/reference/vector/vector/emplace_back/
  • http://www.cplusplus.com/reference/list/list/emplace_back/
template <class... Args>
void emplace_back (Args&&... args);
  • 首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和emplace系列接口的优势到底在哪里呢?

    • emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
    • 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
int main()
{
	std::list<lsl::string> lt;
	lsl::string s1("1111");
	lt.push_back(s1);
	lt.push_back(move(s1));

	cout << endl;
	lsl::string s2("1111");
	lt.emplace_back(s2);
	lt.emplace_back(move(s2));

	cout << endl;
	lt.push_back("xxxx");
	lt.emplace_back("xxxx");

	return 0;
}

在这里插入图片描述


int main()
{
	std::list<pair<lsl::string, int>> lt;
	lt.push_back(make_pair("1111", 1));
	
	cout << endl;
	lt.emplace_back("2222", 2);
	lt.emplace_back(make_pair("1111", 1));

	cout << endl;
	pair<lsl::string, int> kv("1111", 1);
	lt.emplace_back(kv);

	return 0;
}

在这里插入图片描述

  • 多参数时,分开一个一个传参
  • emplace_back的形参是可变参数
  • 直接把参数包不断往下传,直接构造到节点中的val上
  • emplace略微高效一点,并没有很大的提升,因为移动构造的成本也是足够低的

lambda表达式

C++98中的一个例子

  • 在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
#include<algorithm>

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表达式

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函数不能做任何事情。

可以看出lambda表达式像是一个匿名函数

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

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {g1._price > g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {g1._evaluate < g2._evaluate; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {g1._evaluate > g2._evaluate; });
}

int main()
{
	auto f1 = [](int x) {cout << x << endl; return 0; };

	cout << typeid(f1).name() << endl;

	f1(1);
	f1(2);

	return 0;
}
  • 可以看到类型是一个,在编译之前会被生成类,可以调用operator()

在这里插入图片描述

  • 名字是lambda_uuid

在这里插入图片描述

  1. 捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

  • a. 父作用域指包含lambda函数的语句块

  • b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。

  • 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

  • c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。

    • 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • d. 在块作用域以外的lambda函数捕捉列表必须为空。

  • e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

    f. lambda表达式之间不能相互赋值,即使看起来类型相同

引用捕捉:

int main()
{
	int x = 0, y = 1;
	cout << x << " " << y << endl;

	auto f1 = [](int& r1, int& r2) {
		int tmp = r1;
		r1 = r2;
		r2 = tmp;
	};

	f1(x, y);
	cout << x << " " << y << endl << endl;


	cout << x << " " << y << endl;
	cout << &x << ":" << &y << endl;

	auto f2 = [x, y]() mutable {
		cout << &x << ":" << &y << endl;

		int tmp = x;
		x = y;
		y = tmp;
	};

	f2();
	cout << x << " " << y << endl << endl;

	cout << x << " " << y << endl;
	cout << &x << ":" << &y << endl;
	auto f3 = [&x, &y]() {
		cout << &x << ":" << &y << endl;

		int tmp = x;
		x = y;
		y = tmp;
	};

	f3();
	cout << x << " " << y << endl << endl;
	return 0;
}

在这里插入图片描述


class AA
{
public:
	void func()
	{
		// 捕捉this
		auto f1 = [this]() {
			cout << a1 << endl;
			cout << a2 << endl;
		};

		// 全部捕捉
		auto f1 = [=]() {
			cout << a1 << endl;
			cout << a2 << endl;
		};
		f1();
	}
private:
	int a1 = 1;
	int a2 = 1;
};


int main()
{
	int x = 0, y = 1, z = 2;

	// = 为全部捕捉,z为引用捕捉
	auto f1 = [=, &z]() {
		z++;

		cout << x << endl;
		cout << y << endl;
		cout << z << endl;
	};

	f1();
	return 0;
}

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象。

从使用方式上来看,函数对象与lambda表达式完全一样

  • 函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。

在这里插入图片描述

  • 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

包装器

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

function

在这里插入图片描述

  • 可调用对象有:函数指针,仿函数,lambda

包装器的使用:

#include<functional>

void swap_func(int& r1, int& r2)
{
	int tmp = r1;
	r1 = r2;
	r2 = tmp;
}

struct Swap
{
	void operator()(int& r1, int& r2)
	{
		int tmp = r1;
		r1 = r2;
		r2 = tmp;
	}
};


int main()
{
	int x = 0, y = 1;
	cout << x << ":" << y << endl;

	// lambda
	auto swaplambda = [](int& r1, int& r2) {int tmp = r1; r1 = r2; r2 = tmp; };

	// 函数指针
	function<void(int&, int&)> f1 = swap_func;
	f1(x, y);
	cout << x << " " << y << endl << endl;
	// 类[匿名对象]
	function<void(int&, int&)> f2 = Swap();
	f2(x, y);
	cout << x << " " << y << endl << endl;
	// lambda
	function<void(int&, int&)> f3 = swaplambda;
	f3(x, y);
	cout << x << " " << y << endl << endl;

	map<std::string, std::function<void(int&, int&)>> CmdOp{
		{"函数指针", swap_func},
		{"仿函数", Swap()},
		{"lambda", swaplambda},

		//{"函数指针", f1},
		//{"仿函数", f2},
		//{"lambda", f3},
	};

	// 使用
	CmdOp["函数指针"](x, y);
	cout << x << " " << y << endl << endl;

	CmdOp["仿函数"](x, y);
	cout << x << " " << y << endl << endl;

	CmdOp["lambda"](x, y);
	cout << x << " " << y << endl << endl;

	return 0;
}
  • 有了包装器,如何解决模板的效率低下,实例化多份的问题呢?
#include <functional>
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;
}
int main()
{
	struct Functor
	{
		double operator()(double d)
		{
			return d / 3;
		}
	};
	// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double { return d / 4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}
  • 使用场景

150. 逆波兰表达式求值

  • 我们之前实现的:
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;

        for (auto& str : tokens) {
            if (str == "+" || str == "-" || str == "*" || str == "/") {
                // 操作符
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                switch (str[0]) {
                case '+':
                    st.push(left + right);
                    break;
                case '-':
                    st.push(left - right);
                    break;
                case '*':
                    st.push(left * right);
                    break;
                case '/':
                    st.push(left / right);
                    break;
                }
            } else {
                // 操作数
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};
  • 使用包装器实现:
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        // 包装器
        map<string, function<int(int, int)>> CmdOp{
            {"+", [](int x, int y) { return x + y; }},
            {"-", [](int x, int y) { return x - y; }},
            {"*", [](int x, int y) { return x * y; }},
            {"/", [](int x, int y) { return x / y; }}};

        for (auto& str : tokens) {
            if (CmdOp.count(str)) {
                // 操作符
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                st.push(CmdOp[str](left, right));
            } else {
                // 操作数
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

bind

  • std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

原型:bind

在这里插入图片描述

  • 可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

  • 调用bind的一般形式:auto newCallable = bind(callable,arg_list);

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

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

例如:


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

int main()
{
	function<int(int, int)> f1 = Sub;
	cout << f1(10, 5) << endl;

	// 调整参数顺序
	function<int(int, int)> f2 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << f2(10, 5) << endl;

	// 调整参数个数,有些参数可以bind时写死
	function<int(int)> f3 = bind(Sub, 20, placeholders::_1);
	cout << f3(5) << endl;

	return 0;
}
  • 我们在使用包装器取类成员,成员函数取地址,比较特殊,要加一个类域和&,还有一个this
class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}
};

void func(int a, int b, int c)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

int main()
{
	// 静态成员可以不加,但是建议加上&
	function<int(int, int)> f1 = Plus::plusi;
	cout << f1(1, 2) << endl;

	// 成员函数取地址,比较特殊,要加一个类域和&,还有一个this
	function<double(Plus*, double, double)> f2 = &Plus::plusd;
	Plus ps; 
	cout << f2(&ps, 1.1, 2.2) << endl;

	// 或者这样,也可以
	function<double(Plus, double, double)> f3 = &Plus::plusd;
	cout << f3(Plus(), 1.11, 2.22) << endl;

	// 但是上面太麻烦,每次写都需要写
	// 将参数固定bind,就可以省略不写
	function<double(double, double)> f4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
	cout << f4(1.11, 2.22) << endl;
	
	// 也可以间隔的绑定
	function<void(int, int)> f5 = bind(func, placeholders::_1, 10, placeholders::_2);
	f5(1, 3);
	return 0;
}

线程库

新开篇幅讲解

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据------JavaWeb------Maven(完整知识点汇总)

额外知识点 IDE IDE是集成开发环境的缩写&#xff0c;它是一种软件应用程序&#xff0c;提供了编码、调试和部署软件的一站式解决方案。这些功能集成在一起&#xff0c;使开发人员能够在一个环境中完成整个软件开发过程&#xff0c;从编写代码到调试和测试&#xff0c;直到最终…

机械装备制造行业MES,实时监控生产流程

装备制造行业MES&#xff0c;是专门为装备制造行业设计的生产信息化管理系统。旨在实时监控装备制造生产流程&#xff0c;实现全流程的精细化管理和监控&#xff0c;提高生产效率、降低生产成本、提升产品质量。 本文将详细介绍装备制造行业MES的概念、技术及应用&#xff0c;…

七天速通javaSE:第四天 数组基础

文章目录 前言一、认识数组二、数组的声明和创建1. 声明数组变量2. 创建数组3. 变量的初始化&#xff08;赋值&#xff09;3.1 静态初始化3.2 动态初始化 3. 示例 三、数组的使用1. 循环1.1 普通for循环1.2 For-Each 循环 2. 数组作为函数的参数和返回值 前言 本文将为大家介绍…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(十九)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 29 节&#xff09; P29《28.网络连接-第三方库axios》 要想使用第三方库axios&#xff0c;需要先安装ohpm&#xff0c;因为 axios…

Redis优化之持久化

目录 1.Redis高可用 2.Redis持久化 2.1 RDB持久化 2.1.1 触发条件 2.1.2 执行流程 2.1.3 启动时加载 2.2 AOF持久化 2.2.1 开启AOF 2.2.2 执行流程 2.2.3 文件重写触发方式 2.2.4 文件重写的流程 2.2.5 启动时加载 2.3 RDB和AOF的优缺点 3.Redis性能管理 3.1 查看…

LEED绿色建筑认证的优缺点是什么

LEED绿色建筑认证的优缺点&#xff1a; 优点&#xff1a; 环保可持续性&#xff1a;LEED认证强调建筑的节能、节水以及材料循环利用等环保策略&#xff0c;有助于减少资源消耗和降低对环境的负面影响&#xff0c;推动建筑行业向更加环保的方向转变。 降低运营成本&#xff1a…

『Z-Workshop』 6月22日线下ALCOVE分享活动

2024 求是创新 ZJUBCA Sponsored by the ALCOVE Community TIME&#xff1a;2024/06/22 ADD&#xff1a;浙江大学紫金港校区 --- Alcove 是 Aptos 公链与 Alibaba Cloud 共同打造的亚洲首个 Move 开发者社区&#xff0c;致力于支持开发者使用 Move 语言构建下一代 Web3 应用&am…

华为笔记本电脑d盘数据丢失:原因、恢复方案与防范建议

华为笔记本电脑以其高性能和稳定的品质赢得了众多用户的青睐&#xff0c;但即使是如此优质的设备&#xff0c;也难免遭遇数据丢失的困境。本文将围绕华为笔记本电脑D盘数据丢失这一问题&#xff0c;探讨其常见原因、恢复方案&#xff0c;并提出未来防范的建议&#xff0c;以帮助…

用户订单管理API:轻松管理,购物无忧

在当今数字化时代&#xff0c;电子商务已经成为人们购物的首选方式。与传统的实体店相比&#xff0c;电商的优势在于便捷、快速、多样化等特点&#xff0c;使得更多的消费者选择了通过网络购物。而作为电商平台&#xff0c;如何提供更好的购物体验&#xff0c;是每个平台都需要…

ForkJoin框架与工作窃取算法详解

文章目录 一、ForkJoin框架概述1_核心概念2_主要类和方法1_ForkJoinPool2_ForkJoinTask 二、启用异步模式与否的区别三、ForkJoinPool的三种任务提交方式四、执行逻辑及使用示例1_示例&#xff1a;并行计算数组元素和2_forkJoinPool.submit3_ForkJoinTask<?>中任务的执行…

支持向量回归原理详解及Python代码示例

支持向量回归原理详解 支持向量回归&#xff08;Support Vector Regression, SVR&#xff09;是支持向量机&#xff08;SVM&#xff09;的一种扩展&#xff0c;用于回归问题。SVR通过寻找一个最佳的回归超平面&#xff0c;使得尽可能多的数据点落在超平面附近的ε-管内&#xf…

ubuntu16安装DHCP

一、安装dns server软件包 $ apt-get install bind9 二、配置BIND9 配置文件的目录默认为/etc/bind cd /etc/bind 进入该目录。。。 1、vi /etc/bind/named.conf.local zone "xuehai.com" {type master;file "/etc/bind/db.xuehai.com"; }; image.png …

win11记事本错误打开一次非常多的文件再次使用时造成卡住

错误地不小心一次性打开数十个数百个文件造成再次使用时&#xff0c;文件卡住。 亲测有效。

ISP IC/FPGA设计-第一部分-SC130GS摄像头分析-IIC通信(1)

1.摄像头模组 SC130GS通过一个引脚&#xff08;SPI_I2C_MODE&#xff09;选择使用IIC或SPI配置接口&#xff0c;通过查看摄像头模组的原理图&#xff0c;可知是使用IIC接口&#xff1b; 通过手册可知IIC设备地址通过一个引脚控制&#xff0c;查看摄像头模组的原理图&#xff…

离散傅里叶变化

傅里叶变换 对傅里叶变换了解不是很清楚的朋友推荐一下这个帖子&#xff0c;讲得很详细 傅里叶变换 源码 先看源码链接 #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "open…

Yolov8可视化界面使用说明,含代码

⭐⭐ YOLOv8改进专栏|包含主干、模块、注意力机制、检测头等前沿创新 ​ ⭐⭐ YOLOv8可视化界面如下 使用需要安装opencv-python、torch、numpy及PySide6(python版本>3.9) pip install PySide6 pip install numpy pip install opencv-python 使用说明 运行下方代码&#xf…

《software architecture patterns》学习笔记

了解通用的架构模式并知道什么时候使用它们。 软件架构定义了软件的基本特点和行为。比如&#xff0c;有些软件架构会让软件变得可扩展&#xff0c;而有些软件架构会让软件变得易于修改。 知道每一种软件架构的特点、优缺点是非常有必要的&#xff0c;因为它们能帮助你选择一种…

LeetCode刷题之HOT100之课程表

吃完普通的食堂饭菜&#xff0c;回到实验室&#xff0c;继续做一道题&#xff01; 1、题目描述 2、逻辑分析 这道题涉及到图相关知识&#xff0c;应用到了拓扑排序。 题意解释 一共有 n 门课要上&#xff0c;编号为 0 ~ n-1。先决条件 [1, 0]&#xff0c;意思是必须先上课 0…

湖北大学2024年成人高考函授报名专升本法学专业介绍

湖北大学&#xff0c;这所承载着深厚文化底蕴和学术积淀的高等学府&#xff0c;始终致力于为广大有志之士提供多元化的学习机会。在时代的浪潮中&#xff0c;为了满足社会对于高层次法律人才的需求&#xff0c;湖北大学特别推出了成人高等继续教育项目&#xff0c;为广大在职人…

双击跳转到 BP 事务代码 CALL TRANSACTION BP AND SKIP FIRST SCREEN

BP 维护业务伙伴 BP事务代码不能像普通的VA03 这样跳转&#xff0c;下面介绍3种方法。 1. 单纯跳转到BP FORM FRM_SHOW_BP USING LV_BP TYPE BU_PARTNER.CALL METHOD CL_RMPS_ADDRESS>SHOW_BPEXPORTINGIM_BP_NUMBER LV_BP. ENDFORM. 2. 带业务伙伴角色跳转到BP&#xff…