【C++】一些C++11特性

news2024/11/16 4:12:21

C++特性

  • 1. 列表初始化
    • 1.1 {}初始化
    • 1.2 initializer_list
  • 2. 声明
    • 2.1 auto
    • 2.2 typeid
    • 2.3 decltype
    • 2.4 nullptr
  • 3. STL
    • 3.1 新容器
    • 3.2 新接口
  • 4. 右值引用
  • 5. 移动构造与移动赋值
  • 6. lambda表达式
  • 7. 可变参数模板
  • 8. 包装器
  • 9. bind


1. 列表初始化

1.1 {}初始化

C++11支持所有内置类型和自定义类型使用{}初始化,并且可以不写=。

struct A
{
	A(int a, int b)
		:_a(a)
		,_b(b)
	{}
	int _a;
	int _b;
};
int main()
{
	int a = 0;
	int b = { 1 };
	int c{ 2 };

	int d[] = { 1,2,3,4 };
	int e[]{ 5,6,7,8 };
	
	//本质都是调用构造函数,多参数的构造函数支持隐式类型转换。
	A aa(1, 2);
	A bb = { 3,4 };
	A cc{ 5,6 };

	return 0;
}

但为了一定的可读性,在日常定义中,还是不要省略=。

问题

vector<int> v = {1,2};

这是{}初始化吗?显然不是,这是vector的构造。那为什么vector支持{}这种构造,这与initializer_list有关。

1.2 initializer_list

在这里插入图片描述
C++11新增一个类initializer_list,它用常量数组来初始化。此类型的对象由编译器根据初始化列表声明自动构建,初始化列表声明是一个用逗号分隔的元素列表,用大括号括起来:initializer_list< int > il = { 1 , 2 }。本质还是调用initializer_list的构造函数。
C++11后,一些容器增加了initializer_list为参数的构造,这样初始化容器更方便,如:
在这里插入图片描述
在这里插入图片描述
这也是为什么vector支持{1,2}直接构造。因为vector有一个构造支持initializer_list构造,所以v调用的是initializer_list的构造。


2. 声明

2.1 auto

auto可以实现自动类型推断,这就意味着auto定义的变量必须初始化。
在这里插入图片描述

2.2 typeid

typeid(变量).name();//得到变量类型,但只是字符串,只能看不能用。

2.3 decltype

当想单纯定义一个变量而不想初始化时,就可以用decltype。decltype可以推导出对象或者表达式的类型,可以再定义变量。
在这里插入图片描述
综上
typeid推出的类型是字符串,只能看不能用;decltype可以推出对象的类型,可以再定义变量,或者作为模板实参;auto通过赋值自动推到类型。

2.4 nullptr

C++中NULL既可以是0,也可以是空指针。
在这里插入图片描述
func调用的是第一个func,在预处理阶段被替换成0。
为了解决这个问题,C++11新增了nullptr表示空指针。


3. STL

3.1 新容器

在这里插入图片描述

  1. array(静态数组)

在这里插入图片描述

int main()
{
	int arr1[10] = { 0,1,2,3,4,5,6,7,8,9 };
	array<int, 10> arr2 = { 0,1,2,3,4,5,6,7,8,9 };

	//数组和静态数组的真正区别在于对越界的处理
	arr1[12] = 0;//指针+解引用
	arr2[12] = 0;//调用operator[],函数内部会检查,会报错
}

同样是会对越界报错,vector也可以进行扩容,所以这个array有点鸡肋。

  1. forward_list(单链表)
    在这里插入图片描述
    forward_list支持在任何位置的下一个节点的插入和删除,因为它是单链表,不如list有前后指针。这也是它唯一的优点:节省一个指针。

  2. unordered_set和unordered_map已经介绍过,这里就不赘述。

3.2 新接口

  1. 迭代器

在这里插入图片描述
const修饰的正向迭代器和反向迭代器,但是普通迭代器也可以返回const修饰的迭代器,所以这几个接口还是用处不大。

  1. 所有容器均支持{}列表初始化的构造函数。
  2. 所有容器均新增了emplace系列。
  3. 新容器增加了移动构造和移动赋值。

4. 右值引用

  1. 什么是左值引用?什么是右值引用?

可以获得它的地址就是左值,不可以获得它的地址就是右值。左值引用就是给左值取别名,右值引用就是给右值取别名。左值可以在等号左边,也可以在等号右边,右值不可以在等号左边。

int main()
{
	//a,b,c都是左值
	int* a = new int(0);
	int b = 1;
	const int c = 2;

	//以下就是右值
	10;//常量
	b + c;//表达式
	add(b , c);//函数返回值(不是左值引用返回)
	
	//左值引用
	int& r1 = b;
	
	//右值引用
	int&& r2 = 10;
	int&& r3 = b + c;

	return 0;
}

拓展
内置类型的右值叫做纯右值,自定义类型的右值叫做将亡值(生命周期要结束)。

  1. 左值引用能否给右值取别名?右值引用能否给左值取别名?
int main()
{
	//int& r1 = 10;//(×)
	const int& r1 = 10;//(√)
	double x = 0.1;
	double y = 0.2;
	const double& r2 = x + y;//(√)
	//左值引用可以给右值取别名,但必须+const


	int a = 1;
	//int&& r3 = a;//(×)
	int&& r3 = move(a);//(√)
	//右值引用可以给左值取别名,但必须+move
}

拓展

//重载和调用歧义问题
void func1(int& x)
{
	cout << "void func1(int& x)" << endl;
}
void func1(int&& x)
{
	cout << "void func1(int&& x)" << endl;
}
void func2(const int& x)
{
	cout << "void func2(const int&& x)" << endl;
}
void func2(int&& x)
{
	cout << "void func2(int&& x)" << endl;
}
int main()
{
	int a = 1;
	func1(a);
	func1(1);
	int b = 2;
	func2(b);
	func2(a + b);
}

func1构成重载,func2也构成重载且不存在调用歧义,编译器会调用更匹配的,有右值引用就会调用右值引用版本。
在这里插入图片描述

  1. 左值引用的应用和缺点

左值引用可以用来做参数和返回值,可以减少拷贝提高效率。

string& func(string& s)
{
	for (auto& e : s)
	{
		++e;
	}
	return s;
}
int main()
{
	string str("1234");
	string ret = func(str);
	cout << str << endl;
}

但如果func返回的是一个局部变量(出了作用域就销毁),就不能左值引用返回,只能传值返回,这样就得调用拷贝构造。如何减少拷贝?用右值引用。

  1. 右值引用的应用

(1)以一个简洁的string(库的string太复杂了)为例

namespace zn
{
	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; // 不包含最后做标识的\0
	};
}
zn::string func()
{
	zn::string ret("nihao");
	return ret;
}
int main()
{
	zn::string str;
	str = func();
	return 0;
}

移动赋值

在这里插入图片描述
func函数传值返回,返回的ret是右值将亡值(生命周期结束),所以可以重载赋值,用右值引用接受参数。

// 赋值重载
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;
}

func如果返回左值就调用赋值,如果返回右值就调用移动赋值。移动拷贝交换了资源,旧资源会被将亡值释放,减少了拷贝,提高了效率。
在这里插入图片描述
移动拷贝

// 拷贝构造
string(const string& s)
	:_str(nullptr)
{
	cout << "string(const string& s) -- 深拷贝" << endl;
	string tmp(s._str);
	swap(tmp);
}

a. 拷贝构造
在这里插入图片描述
在拷贝构造的情况下,如果编译器优化就只有一次深拷贝,如果不优化就有两次深拷贝。
如果想要再减少拷贝次数,提高效率,可以使用移动构造来实现。
b. 移动构造

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

在这里插入图片描述
编译器优化后,直接交换将亡值的内容,让它顺便释放旧的资源。以上的优化都是基于传值返回,如果函数是传引用返回,就没有这些优化。
既然有了移动拷贝,我们再来看看移动赋值的例子,发现它还可以再优化。
在这里插入图片描述

(2)场景二:move函数将左值变成右值

int main()
{
	zn::string str("nihao");
	zn::string str2(str);
}

在这里插入图片描述

str是左值,str2会调用拷贝构造。但我想要让其调用移动构造,抢走str的资源,该如何做?用move修饰str2。

int main()
{
	zn::string str("nihao");
	zn::string str2(move(str));
}

在这里插入图片描述
str被move强制转换成右值,右值调用移动构造。
(3)场景三:STL容器插入接口函数也增加了右值引用版本,容器的插入接口如果插入对象是右值,可以利用移动构造转移资源给数据结构中的对象,也可以减少。
在这里插入图片描述

  1. 完美转发

引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。不理解这句话,没关系先看例子。
首先介绍下什么是万能引用?模板中的&&不是接收右值的意思,其代表万能引用,既可以接收右值,也可以接收左值。实参是左值,它就是左值引用,实参是右值,它就是右值引用。
例子

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

在这里插入图片描述
为什么会出现这种情况?我们平时都忽略了右值的另一种特征:不能修改。既然右值不可修改,那么在移动构造和移动赋值的时候是如何交换资源的?答案是在用右值引用接收时,右值的属性就发生了变化,它可以被修改,所以此时的右值是左值属性的。
在这里插入图片描述

rr能取地址,且地址和r的地址相同,但rr却是右值引用(对右值取别名)。原因是右值引用变量的属性会被编译器识别成左值。

回到例子,Func接收的参数虽然是右值,但它的属性早已变成左值属性,所以调用的函数是左值引用。
结论
由此,我们可以得出结论:模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。
问题
那如何保持住右值的右值属性?那就得用到完美转发。
完美转发
forward完美转发在对象的传参过程保持对象原生类型的属性。

// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t));
}

在传参过程中保持t的属性,t是左值引用就保持左值属性;t是右值属性就保持右值属性。
在这里插入图片描述
应用场景
在一些容器的插入中,我们通常需要保持插入数据的右值属性,这样就可以调用移动构造和移动赋值,或者就不用深拷贝,就可以减少拷贝提高效率。

template<class T>
class List
{
	//...
	void PushBack(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, 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)
	{
		//……
	}
}


5. 移动构造与移动赋值

C++11新增了两个默认成员函数:移动构造和移动赋值。在上面我们已经了解到它们的原理,现在来了解它们的一些细节。

  1. 深拷贝的类需要实现自己实现移动构造和移动赋值,浅拷贝的类就不需要自己实现。
  2. 如果没有自己实现移动构造和移动赋值,且没有实现析构、拷贝构造、赋值重载中任一个,编译器就会默认生成移动构造和移动赋值。默认生成的移动构造和移动赋值,对于内置类型实现值拷贝,对于自定义类型,如果有移动构造和移动赋值就调用其移动 构造和移动赋值,没有就调用拷贝构造和赋值重载。
  3. 一个类要写这三个函数:析构函数 、拷贝构造、拷贝赋值重载,说明这个类是深拷贝的类,这三个函数是三位一体的,通常一起实现。如果没有实现这三个,说明是浅拷贝的类,那就用编译器默认生成的析构、拷贝构造、赋值重载和移动构造和移动赋值;如果手动实现这三个,说明是深拷贝的类就需要自己手动实现移动构造和移动赋值。
  4. 强制生成成员函数,可以用=default修饰。
string(string&& p) = default;

6. lambda表达式

  1. 为什么会有lambda表达式?

如果我们要对自定义类型进行排序,我们可以根据自定义类型的成员进行排序,且可以排升序也可以排降序。

struct book
{
	string _name;
	int _id;
	double _price;
};
struct CompareByIdGreater
{
	bool operator()(const book& b1, const book& b2)
	{
		return b1._id < b2._id;
	}
};
struct CompareByIdLess
{
	bool operator()(const book& b1, const book& b2)
	{
		return b1._id > b2._id;
	}
};
int main()
{
	vector<book> lib = { {"活着",1,30.4},{"皮囊",2,24.9},{"我与地坛",3,33.5}};
	//要求对书按照_id进行排升序,需要写仿函数
	sort(lib.begin(), lib.end(), CompareByIdGreater());
	//要求对书按照_id进行排降序,需要写仿函数
	sort(lib.begin(), lib.end(), CompareByIdLess());

	//要求对书按照价格进行排降序,需要写仿函数
	//……
	//要求对书按照价格进行排升序,需要写仿函数
	//……
	//要求对书按照书名进行排降序,需要写仿函数
	//……
	//……
	return 0;
}

对一个自定义类型的各个成员排序,得专门写多个类进行排序,而且还得排升序和降序,还得再实现多个类。这样实现太复杂了,所以就有lambda表达式。

//按照_id进行排降序
sort(lib.begin(), lib.end(), [](const book& b1, const book& b2) {return b1._id > b2._id; });
//按照_id进行排升序
sort(lib.begin(), lib.end(), [](const book& b1, const book& b2) {return b1._id < b2._id; });
//按照价格进行排降序
sort(lib.begin(), lib.end(), [](const book& b1, const book& b2) {return b1._price > b2._price; });
//按照价格进行排升序
sort(lib.begin(), lib.end(), [](const book& b1, const book& b2) {return b1._price < b2._price; });
  1. lambda表达式怎么用?

在这里插入图片描述
捕捉列表

(1)[ var ]:传值捕捉变量var。因为是传值捕捉,捕捉列表中的var是外面var的拷贝;又因为lambda函数是const函数,捕捉过来的变量不能修改,如果想要修改,必须+mutable,但修改的也是var的拷贝。
(2)[ &var ]:传引用捕捉变量var。
(3)[ = ]:传值捕捉父作用域所有的变量(包括this)。这里的父作用域是指包含lambda表达式的代码块。
(4)[ & ]:传引用捕捉父作用域所有的变量(包括this)。
(5)在捕捉列表中,可以多次捕捉,然后用逗号分隔开。[ &,var ]:传值捕捉var,传引用捕捉其他变量。[ =,&var ]:传引用捕捉var,传值捕捉其他变量。不能重复捕捉,如[ =,var ]。
(6)lambda表达式只能捕捉父作用域的局部变量,不能捕捉父作用域以外的变量。

  1. 例子以及一些重要事项
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//1.lambda表达式是匿名函数对象,和函数指针、仿函数一样,都可以作为对象使用。
	auto add = [](int x, int y)->int {return x + y; };
	cout << add(1, 1) << endl;
	cout << endl;

	//2.函数体内可以有多条语句
	auto swap = [](int& x, int& y) {
		int temp = x;
		x = y;
		y = temp;
	};

	//3.在lambda函数体内不能调用局部函数,如add,但可以调用全局函数。
	//如果想要调用局部变量,就用捕捉列表捕捉。
	//auto func1 = [](int x, int y) {return add(x, y); };  (×)
	auto func1 = [](int x, int y) {return Add(x, y); };
	//auto func1 = [ = ](int x, int y) {return add(x, y); };   (√)
	cout << func1(1, 2) << endl;
	cout << endl;

	//4.捕捉列表
	int a = 1;
	int b = 2;
	auto func2 = [a, b]() { return a + b; };
	cout << func2() << endl;
	auto func3 = [&a, &b]()mutable {
		++a;
		++b;
		return a + b; };
	cout << "a + b = " << func3() << endl;
	cout << "a:" << a << " b:" << b << endl;

	//5.最简单的lambda表达式,没有任何意义
	[] {};
}

结果
在这里插入图片描述

  1. lambda表达式的原理

在前面我们已经发现lambda表达式与仿函数的使用方法一样,现在就来看看它与仿函数的关系。

class CompareGreater
{
public:
	bool operator()(const int& x, const int& y)
	{
		return x > y;
	}
};
int main()
{
	CompareGreater cg;//cg是仿函数,也叫函数对象,本质是类对象,通过重载(),可以像函数一样使用
	int x = 1;
	int y = 2;
	int ret1 = cg(x, y);

	auto func = [](int x, int y) {return x > y; };//lambda表达式
	int ret2 = func(x, y);
}

查看lambda表达式的底层代码后发现,有一个类调用operator()。
在这里插入图片描述
也就是说lambda表达式的底层其实是仿函数,编译器在我们定义lambda表达式时自动生成一个类,并且重载了operator()。
问题
如果lambda表达式相同,编译器生成的类是否相同?
在这里插入图片描述
如图,由相同的lambda表达式生成的类是不同的,因为后面的那串字符串。圈起来的字符串是UUID(通用唯一识别码),每个lambda都会自动生成,且通过UUID被识别为不同的类。


7. 可变参数模板

  1. 在以前C++只支持固定数目的模板参数,C++11有了可变参数模板就可以实现类模板参数和函数参数的传参自由,想传多少就传多少。
//1.Args/args是参数的意思。
//2.Args是模板参数包,args是函数参数包
//3.参数包内可以有任意个参数(0到N)
//4.第一个参数会被T提取,剩余的参数会形成参数包,没有T也是可以的
template<class T,class ...Args>
void Print(T value,Args ...args)//利用模板参数包定义一个函数参数包
{
	cout << value << endl;
}
  1. 那如何将参数包内的参数解析出来。args本质是将参数放入一个数组,但不能通过args[i]提取。
template<class T, class ...Args>
void Print(T value, Args ...args)
{
	cout << value << endl;
	//这个写法很奇怪,但是它的作用是获得参数包的参数个数,Print传4个实参,一个被value接收,剩下三个放到参数包中
	cout << sizeof...(args) << endl;
	for (size_t i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << endl;
	}
	
}
int main()
{
	Print(1,3.14,'x',"nihao");
}

但是我们发现好像不能直接获得参数包的参数。
在这里插入图片描述

(1)通过递归方式展开参数包

template<class T>
void Print(T value)
{
	//这个函数的作用是接受参数包的最后一个参数,同时作为递归结束条件。
	cout << value << endl;
}
template<class T, class ...Args>//加一个参数T,通过T,将参数包的参数一个一个取出来
void Print(T value, Args ...args)
{
	cout << value << " ";
	Print(args...);//这个传参也很奇怪
}
int main()
{
	Print(1,3.14,'x',"nihao");
}

在这里插入图片描述
如果不传参数,该怎么办?

//不传参数,就直接调用这个函数;同时如果参数包参数个数为0,也会调用这个函数作为递归结束的标志
void Print()
{
	cout << endl;
}
template<class T, class ...Args>
void Print(T value, Args ...args)
{
	cout << value << " ";
	Print(args...);
}
int main()
{
	Print();
	Print(1,3.14,'x',"nihao");
}

(2)逗号表达式
这是在网上看到的大佬的写法

void Print()//应付不传参数的情况
{
	cout << endl;
}
template<class T>
int _Print(T value)
{
	cout << value << " ";
	return 0;//为什么要返回0?用来给数组初始化。
}
template<class ...Args>
void Print(Args ...args)
{
	int arr[] = { _Print(args)... };
	// []没有给定大小,编译器就会去推到底有多少个,就会展开...(参数包的剩余参数),
	// 例如有两个参数就会展开为{(_Print(args,0),(_Print(args),0)},展开后就会调用_Print。
	cout << endl;
}
int main()
{
	Print();
	Print(1,3.14,'x',"nihao");
}

在这里插入图片描述

  1. emplace和emplace_back就应用到参数包

在这里插入图片描述
在这里插入图片描述
以list为例,这两个函数的作用都是构造+插入元素。

int main()
{
	list< pair<int, zn::string> > lt;
	lt.emplace_back(10, "sort");
	lt.emplace_back(make_pair(20, "sort"));
	lt.push_back(make_pair(30, "sort"));
	lt.push_back({ 40, "sort" });
	return 0;
}

在这里插入图片描述


8. 包装器

  1. 什么是包装器?function是一个类模板,实例化出的对象可以用来包装可调用对象(函数指针、仿函数、lambda表达式)。
    在这里插入图片描述

  2. 为什么要有包装器?它是如何进行包装的?

//包装器
//f是可调用对象,可以是函数指针,可以是仿函数,也可以是lambda表达式
template<class F,class T>
void func(F f,T x,T y)
{
	static int count = 0;//用来标记func是否有实例化多份
	cout << ++count << endl;
	f(x, y);
}
//函数
int Add1(int x, int y)
{
	return x + y;
}
//仿函数
struct Add2
{
	int operator()(int x, int y)
	{
		return x + y;
	}
};
int main()
{
	//lamda表达式
	auto Add3 = [](int x, int y) {return x + y; };
	
	//函数指针
	func(Add1, 1, 1);
	//仿函数
	func(Add2(), 1, 1);
	//lambda表达式
	func(Add3, 1, 1);
	cout << endl;
};

在这里插入图片描述

func会被实例化出3份,效率较低。如果我只想实例化一份func,该怎么办?用到function。

	function<int(int, int)> add1 = Add1;
	function<int(int, int)> add2 = Add2();
	function<int(int, int)> add3 = Add3;
	func(add1, 2, 2);
	func(add2, 2, 2);
	func(add3, 2, 2);

在这里插入图片描述
如图,func被实例化成一份,因为是传参是传包装器。


9. bind

  1. band绑定是一个函数模板,是一个函数适配器。它接收可调用对象和可变参数,返回可调用对象。它可以用来调整函数的参数顺序和增加函数参数等。
    在这里插入图片描述
  2. 一般格式
auto newCallable = bind(callable,arg_list);

newCallable是适配出的可调用对象,它的参数绑定了callable的参数,在arg_list中newCallable的占位符绑定了callable的参数。这个格式看不出什么,直接看例子。

  1. 例子(bind的作用)

(1)调整函数参数顺序
在这里插入图片描述
在这里插入图片描述
(2)减少函数参数
当一个函数有多个参数,且一些参数是固定的,这时我们就可以把这些参数绑死,适配出少参数的可调用对象。

//例如c不变,我们在绑定时就可以把c绑死
int func(int a, int b, int c)
{
	return a - b + c;
}
int main()
{
	function<int(int, int)> f1 = bind(func, placeholders::_1, placeholders::_2, 3);
	cout << f1(1,2) << endl;


	//还可以调整参数顺序
	function<int(int, int)> f2 = bind(func, placeholders::_2, placeholders::_1, 3);
	cout << f2(1, 2) << endl;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(3)绑定成员函数

class Sub
{
public:
	static int sub(int a, int b)
	{
		return a - b;
	}

	int ssub(int a, int b)
	{
		return a - b;
	}
};
int main()
{
	// 绑定静态成员函数,需要指定类域
	function<int(int, int)> func3 = bind(&Sub::sub,placeholders::_1, placeholders::_2);

	// 绑定成员函数,需要一个具体的可调用对象,需要创建一个类对象,
	Sub s;
	//function<int(int, int)> func4 = bind(&Sub::ssub, placeholders::_1, placeholders::_2);//(×)
	function<int(int, int)> func4 = bind(&Sub::ssub, s , placeholders::_1, placeholders::_2);
	function<int(int, int)> func5 = bind(&Sub::ssub, Sub(), placeholders::_1, placeholders::_2);
}

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

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

相关文章

Spring中自定义类型转换器

目录 一、什么是类型转换器 二、自定义类型转化器 2.1 实现Converter接口 2.2 在Spring中注册 三、自定义类型转换器中的细节 3.1 解决代码的耦合 3.2 注册标签id值必须唯一 ​3.3 Spring提供的日期转换器 一、什么是类型转换器 在Spring中类型转换器就是将配置文件中的字符串…

力扣一.链表的运用

一.合并生序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4] 输入:l1 = [], l2 = [] 输出:[] 输入:l1 = [], l2 = [0] 输出:[0]提示: 两个链表的…

【【萌新的FPGA学习之管脚设定xdc文件】】

萌新的FPGA学习之管脚设定xdc文件 xdc文件可以自己设置 也可以匹配 我们根据正点原子的流水灯管脚设定 主要讲述一下 各个英文设计是什么意思 Name&#xff1a;工程中顶层端口的名称。 Direction&#xff1a;说明管脚是输入还是输出。 Neg Diff Pair&#xff1a;负差分对&…

YOLOv5/v7/v8改进实验(三)之训练技巧篇

&#x1f680;&#x1f680; 前言 &#x1f680;&#x1f680; YOLO系列是一种目标检测算法&#xff0c;具有高效的实时性能和较高的准确性。一些常用的YOLO训练技巧往往可以帮助提高模型的性能和收敛速度。而这些技巧在YOLOv5、YOLOv7和YOLOv8几乎通用。 &#x1f525;&#…

CUDA编程入门系列(十一)CUDA程序优化技巧

优化原则&#xff1a;最大化并行执行 探索并行化&#xff1a; 优化线程块的规模 我们在设计CUDA程序的时候&#xff0c;要对线程块的个数进行考虑。因为GPU中流处理器SM的数量是相对固定的&#xff0c;所以我们应该尽量的将多个block放到同一个SM当中&#xff08;至少保证每个…

三、信号与槽

1. 信号槽的定义 信号函数和槽函数是Qt在C的基础上新增的功能&#xff0c;功能是实现对象之间的通信。 实现信号槽需要有两个先决条件&#xff1a; 通信的对象必须是从QObject派生出来的 QObject是Qt所有类的基类。 类中要有Q_OBJECT宏 2. 信号槽的使用 2.1 函数原型 最常…

免费SSL证书:JoySSL让您的网站更安全

在今天的数字化时代&#xff0c;保护网站和用户信息的安全至关重要。SSL&#xff08;Secure Sockets Layer&#xff09;证书通过加密网站与用户之间的通信&#xff0c;确保数据传输的安全性。让您拥有一个SSL加密的网站是至关重要的&#xff0c;但您可能会担心高昂的费用。不过…

秦丝科技“羽”深大计软学院同行,共庆深圳大学成立40周年

2023年10月21日&#xff0c; 由深圳大学工会主办 &#xff0c; 深圳大学体育学院 、深圳大学后勤部、丽湖校区管委会等单位协办的深圳大学2023年教职工羽毛球联赛-第一场正式举办&#xff1b; 由深圳市秦丝科技有限公司联合头部商户共同赞助的计算机与软件学院取得首场胜利&…

花生好车基于 KubeSphere 的微服务架构实践

公司简介 花生好车成立于 2015 年 6 月&#xff0c;致力于打造下沉市场汽车出行解决方案第一品牌。通过自建直营渠道&#xff0c;瞄准下沉市场&#xff0c;现形成以直租、批售、回租、新能源汽车零售&#xff0c;四大业务为核心驱动力的汽车新零售平台&#xff0c;目前拥有门店…

java基础--transient关键字减少序列化

前置内容 1.序列化 今天在看ArrayList源码的时候看到了这个&#xff0c;我之前应该是看过的&#xff0c;但是忘记了。现在在总结一下。 transient 用在类的属性上&#xff0c;不能修饰其他的。 作用&#xff1a;在序列化的时候transient修饰的属性不能被序列化 用途 在一些…

力扣:盛最多水的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 …

【公众号开发】图像文字识别 · 模板消息推送 · 素材管理 · 带参数二维码的生成与事件的处理

【公众号开发】&#xff08;4&#xff09; 文章目录 【公众号开发】&#xff08;4&#xff09;1. 图像文字识别功能1.1 百度AI图像文字识别接口申请1.2 查看文档学习如何调用百度AI1.3 程序开发1.3.1 导入依赖&#xff1a;1.3.2 公众号发来post请求格式1.3.3 对image类型的消息…

【JavaEE】synchronized原理 -- 多线程篇(6)

synchronized原理 1. synchronized具体采用了哪些加锁策略?2. synchronized内部实现策略(内部原理)2.1 偏向锁2.2 轻量级锁与重量级锁 3. synchronized 的其它优化策略3.1 锁消除3.2 锁的粒度 4. 总结 1. synchronized具体采用了哪些加锁策略? synchronized既是悲观锁, 也是…

Power BI 傻瓜入门 4. Power BI:亮点

本章内容包含&#xff1a; 在Power BI Desktop上学习诀窍摄入数据使用模型试用Power BI服务 就像评估一个由多种成分组成的蛋糕一样&#xff0c;Power BI要求其用户熟悉商业智能&#xff08;BI&#xff09;解决方案中的功能。几乎所有与Power BI交互的用户都是从桌面版开始的…

Locust负载测试工具实操

本中介绍如何使用Locust为开发的服务/网站执行负载测试。 Locust 是一个开源负载测试工具&#xff0c;可以通过 Python 代码构造来定义用户行为&#xff0c;避免混乱的 UI 和臃肿的 XML 配置。 步骤 设置Locust。 在简单的 HTTP 服务上模拟基本负载测试。 准备条件 Python…

2021年03月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 下列代码的输出结果是&#xff1f;&#xff08; &#xff09; x 0x10print(x)A&#xff1a;2 B&#xff1a;8 C&#xff…

NeurIPS 23 Spotlight丨3D-LLM:将3D世界注入大语言模型

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/pdf/2307.12981.pdf 开源代码&#xff1a;https://vis-www.cs.umass.edu/3dllm/ 摘要&#xff1a; 大型语言模型 (LLM) 和视觉语言模型 (VLM) 已被证明在多项任务上…

FPGA的256点FFT调用Quartus IP核实现VHDL傅里叶变换

名称&#xff1a;256点FFT调用Quartus IP核实现傅里叶变换 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a;使用VHDL实现256点FFT&#xff0c;调用Quartus IP核实现傅里叶变换 演示视频&#xff1a;http://www.hdlcode.com/index.php?mhome&cView…

Mysql基础与高级汇总

SQL语言分类 DDL:定义 DML&#xff1a;操作 DCL:控制(用于定义访问权限和安全级别) DQL:查询 Sql方言 ->sql&#xff1a;结构化查询语言 mysql:limit oracle:rownum sqlserver:top 但是存储过程&#xff1a;每一种数据库软件一样SQL语法要求: SQL语句可以单行或多行书写&…