当前位置: 首页 > news >正文

C++11详解

一、C++11发展史

C++11 是 C++ 的第⼆个主要版本,并且是从 C++98 起的最重要更新。它引⼊了⼤量更改,标准化了既 有实践,并改进了对 C++ 程序员可⽤的抽象。在它最终由 ISO 在 2011 年 8 ⽉ 12 ⽇采纳前,⼈们曾使⽤名称“C++0x”,因为它曾被期待在 2010 年之前发布。C++03 与 C++11 期间花了 8 年时间,故⽽是迄今为⽌最⻓的版本间隔。从那时起,C++ 有规律地每 3 年更新⼀次。

二、列表初始化

1、C++98传统的{}

C++98一般结构体和数组可以用{}进行初始化

struct Point
{
	int _x;
	int _y;
};

int mian()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[5] = { 0 };
	Point p{ 1,2 };
	return 0;
}

2、C++11中的{}

  1. C++11后想要统一初始化,试图想要使得一切对象都可以用{}进行初始化,用{}进行初始化也叫列表初始化;
  2. 内置类型和自定义类型都可以用{}进行初始化,自定义类型用{}进行初始化本质是类型转换之后构造产生临时对象再拷贝构造,但是优化之后就是直接构造;
  3. {}初始化的过程中=可以省略;
  4. C++11使用{}进行初始化本身是想要实现初始化的统一,其次在很多情形中使用{}进行初始化带来了很多遍历,比如容器的初始化。
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_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;
	int _month;
	int _day;
};
// ⼀切皆可⽤列表初始化,且可以不加=
int mian()
{
	// C++98支持的
	int arr1[] = { 1,2,3,4,5 };
	int arr2[5] = { 0 };
	Point p{ 1,2 };
	// C++11⽀持的
	// 内置类型⽀持
	int x1 = { 2 };
	// ⾃定义类型⽀持
	// 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象
	// 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化d1
	// 运⾏⼀下,我们可以验证上⾯的理论,发现是没调⽤拷⻉构造的
	Date d1 = { 2025, 1, 1 };
	// 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象
	const Date& d2 = { 2024, 7, 25 };
	// 需要注意的是C++98⽀持单参数时类型转换,也可以不⽤{}
	Date d3 = { 2025 };
	Date d4 = 2025;
	// 可以省略掉=
	Point p1{ 1, 2 };
	int x2{ 2 };
	Date d6{ 2024, 7, 25 };
	const Date& d7{ 2024, 7, 25 };
	// 不⽀持,只有{}初始化,才能省略=
	// Date d8 2025;
	vector<Date> v;
	v.push_back(d1);
	v.push_back(Date(2025, 1, 1));
	// ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐
	v.push_back({ 2025, 1, 1 });
	return 0;
}

注意const Date& d2 = {2024, 7, 2}引用的是产生的临时对象,因为临时对象具有常性,所以用const,加了&,那么d2就是那个临时对象;一般来说,不使用const和&时,临时对象会作为拷贝蓝图给d2进行拷贝构造之后这个临时对象的生命周期就结束了,但是这里用const和&表示d2就是那个临时对象,那么这个临时对象也就没有立即销毁,相当于延长了临时对象的生命周期 。

3、C++11中的std::inlitiallizer_list

在C++11中很多STL容器初始化想用很多值去初始化,会显得很不方便;此时引入了初始化列表这个类型,这个类型支持迭代器(可以理解为一个带有两个指针的数组,一个指向前,一个指向后)。这个类型可以作为STL容器构造函数中的一个参数类型,用来一次性用多个值初始化容器。

// STL 中的容器都增加了⼀个 initializer_list 的构造
vector (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type());
list (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type());
map (initializer_list<value_type> il,const key_compare& comp =
key_compare(),const allocator_type& alloc = allocator_type());
// ...
template<class T>
class vector {
public:
	typedef T* iterator;
	vector(initializer_list<T> l)
	{
		for (auto e : l)
			push_back(e)
	}
private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _endofstorage = nullptr;
};
// 另外,容器的赋值也⽀持initializer_list的版本
vector& operator= (initializer_list<value_type> il);
map& operator= (initializer_list<value_type> il);


int main()
{
	std::initializer_list<int> mylist;
	mylist = { 10, 20, 30 };
	cout << sizeof(mylist) << endl;
	// 这⾥begin和end返回的值initializer_list对象中存的两个指针
	// 这两个指针的值跟i的地址跟接近,说明数组存在栈上
	int i = 0;
	cout << mylist.begin() << endl;
	cout << mylist.end() << endl;
	cout << &i << endl;
	// {}列表中可以有任意多个值
	// 这两个写法语义上还是有差别的,第⼀个v1是直接构造,
	// 第⼆个v2是构造临时对象+临时对象拷⻉v2+优化为直接构造
	vector<int> v1({ 1,2,3,4,5 });
	vector<int> v2 = { 1,2,3,4,5 };
	const vector<int>& v3 = { 1,2,3,4,5 };
	// 这⾥是pair对象的{}初始化和map的initializer_list构造结合到⼀起⽤了
	map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };
	// initializer_list版本的赋值⽀持
	v1 = { 10,20,30,40,50 };
	return 0;
}

###值得注意的是:

只要是带有{}的构造就是列表初始化,{}是语法;初始化列表是一个类型;在初始化容器时,容器的构造函数有一个initializerlist类型形参接受然后去初始化,无论是形为({})还是=({})的都是优先匹配含有初始化列表类型形参的构造函数去初始化。

也就是所,初始化列表和列表初始化不是一个层面的东西,一个是语法,一个是类型;在使用列表初始化的过程中可能用到初始化列表,初始化列表是幕后处理的机制;两者共同作用实现批量元素的初始化。

三、右值引用和移动语义

C++98的C++语法中就有引⽤的语法,⽽C++11中新增了的右值引⽤语法特性,⽆论左值引⽤还是右值引⽤,都是给对象取别名。

1、左值和右值

左值是⼀个表⽰数据的表达式(如变量名或解引⽤的指针),⼀般是有持久状态,存储在内存中,我
可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const
修饰符后的左值,不能给他赋值,但是可以取它的地址。
右值也是⼀个表⽰数据的表达式,要么是字⾯值常量、要么是表达式求值过程中创建的临时对象
等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
值得⼀提的是,左值的英⽂简写为lvalue,右值的英⽂简写为rvalue。传统认为它们分别是left
value、right value 的缩写。现代C++中,lvalue 被解释为locator value的缩写,可意为存储在内
存中、有明确存储地址可以取地址的对象,⽽ rvalue 被解释为 read value,指的是那些可以提供
数据值,但是不可以寻址,例如:临时变量,字⾯量常量,存储于寄存器中的变量等,也就是说左
值和右值的核⼼区别就是能否取地址。
int main()
{
		// 左值:可以取地址
		// 以下的p、b、c、*p、s、s[0]就是常⻅的左值
		int* p = new int(0);
		int b = 1;
		const int c = b;
		*p = 10;
		string s("111111");
		s[0] = 'x';
		cout << &c << endl;
		cout << (void*)&s[0] << endl;
		// 右值:不能取地址
		double x = 1.1, y = 2.2;
		// 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值
		10;
		x + y;
		fmin(x, y);
		string("11111");
		//cout << &10 << endl;//常量
		//cout << &(x+y) << endl;//表达式返回值是一个临时对象,具有常性
		//cout << &(fmin(x, y)) << endl;
		//cout << &string("11111") << endl;//匿名对象
	return 0;
}

2、左值引用和右值引用

Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引⽤,左值引⽤就是给左值取别
名,第⼆个就是右值引⽤,同样的道理,右值引⽤就是给右值取别名。
左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值);(move()相当于强制类型转换)
需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引⽤变量变
量表达式的属性是左值
语法层⾯看,左值引⽤和右值引⽤都是取别名,不开空间。从汇编底层的⻆度看下⾯代码中r1和rr1
汇编层实现,底层都是⽤指针实现的,没什么区别。
int main()
{
		// 左值:可以取地址
		// 以下的p、b、c、*p、s、s[0]就是常⻅的左值
		int* p = new int(0);
		int b = 1;
		const int c = b;
		*p = 10;
		string s("111111");
		s[0] = 'x';
		double x = 1.1, y = 2.2;
		// 左值引⽤给左值取别名
		int& r1 = b;
		int*& r2 = p;
		int& r3 = *p;
		string& r4 = s;
		char& r5 = s[0];
		// 右值引⽤给右值取别名
		int&& rr1 = 10;
		double&& rr2 = x + y;
		double&& rr3 = fmin(x, y);
		string&& rr4 = string("11111");
		// 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
		const int& rx1 = 10;
		const double& rx2 = x + y;
		const double& rx3 = fmin(x, y);
		const string& rx4 = string("11111");
		// 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)
		int&& rrx1 = move(b);
		int*&& rrx2 = move(p);
		int&& rrx3 = move(*p);
		string&& rrx4 = move(s);
		string&& rrx5 = (string&&)s;
		// b、r1、rr1都是变量表达式,都是左值
		cout << &b << endl;
		cout << &r1 << endl;
		cout << &rr1 << endl;
		// 这⾥要注意的是,rr1的属性是左值,所以不能再被右值引⽤绑定,除⾮move⼀下
		int& r6 = r1;
		// int&& rrx6 = rr1;
		int&& rrx6 = move(rr1);
		int& r7 = rr1;//再次说明右值引用变量的属性是左值
		return 0;
}

3、引用延长生命周期

int main()
{
	std::string s1 = "Test";
	// std::string&& r1 = s1; // 错误:不能绑定到左值
	const std::string& r2 = s1 + s1; // OK:到 const 的左值引⽤延⻓⽣存期
	// r2 += "Test"; // 错误:不能通过到 const 的引⽤修改
	std::string&& r3 = s1 + s1; // OK:右值引⽤延⻓⽣存期
		r3 += "Test"; // OK:能通过到⾮ const 的引⽤修改
	std::cout << r3 << '\n';
	return 0;
}

一般来说,s1+s1会返回一个具有常性的临时变量给接收方拷贝,那么这个临时对象在拷贝结束后会销毁即生命周期结束了,但是若是用引用接收这个临时变量就能在程序结束前一直使用这个临时变量,这就相当于延长了对象的生命周期。

值得注意的是延长的生命周期只是在当前作用域的临时变量生命周期被延长了。

4、左值和右值的参数匹配

C++98中,我们实现⼀个const左值引⽤作为参数的函数,那么实参传递左值和右值都可以匹配。
C++11以后,分别重载左值引⽤、const左值引⽤、右值引⽤作为形参的f函数,那么实参是左值会
匹配f(左值引⽤),实参是const左值会匹配f(const 左值引⽤),实参是右值会匹配f(右值引⽤)。
void f(int& x)
{
	std::cout << "左值引用重载 f(" << x << ")\n";
}
void f(const int& x)
{
	std::cout << "到 const 的左值引用重载 f(" << x << ")\n";
}
void f(int&& x)
{
	std::cout << "右值引用重载 f(" << x << ")\n";
}
int main()
{
	int i = 1;
	const int ci = 2;
	f(i); // 调⽤ f(int&)
	f(ci); // 调⽤ f(const int&)
	f(3); // 调⽤ f(int&&),如果没有 f(int&&) 重载则会调⽤ f(const int&)
	f(std::move(i)); // 调⽤ f(int&&)
	// 右值引⽤变量在⽤于表达式时是左值
	int&& x = 1;//右值引用表达式属性是左值
	f(x); // 调⽤ f(int& x)
	f(std::move(x)); // 调⽤ f(int&& x)
	return 0;
}

5、右值引用和移动语义的使用场景

回顾

左值引⽤主要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉,同时还可以修改实参和修改返回对象的价值。左值引⽤已经解决⼤多数场景的拷⻉效率问题,但是有些场景不能使⽤传左值引⽤返回,如addStrings和generate函数,C++98中的解决⽅案只能是被迫使⽤输出型参数解决。那么C++11以后这⾥可以使⽤右值引⽤做返回值解决吗?显然是不可能的,因为这⾥的本质是返回对象是⼀个局部对象,函数结束这个对象就析构销毁了,右值引⽤返回也⽆法概念对象已经析构销毁的事实。
class Solution {
public:
	// 传值返回需要拷⻉
	string addStrings(string num1, string num2) {
		string str;
		int end1 = num1.size() - 1, end2 = num2.size() - 1;
		// 进位
		int next = 0;
		while (end1 >= 0 || end2 >= 0)
		{
			int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
			int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
			int ret = val1 + val2 + next;
			next = ret / 10;
			ret = ret % 10;
			str += ('0' + ret);
		}
		if (next == 1)
			str += '1';
		reverse(str.begin(), str.end());
		return str;
	}
};
class Solution {
public:
	// 这⾥的传值返回拷⻉代价就太⼤了
	vector<vector<int>> generate(int numRows) {
		vector<vector<int>> vv(numRows);
		for (int i = 0; i < numRows; ++i)
		{
			vv[i].resize(i + 1, 1);
		}
		for (int i = 2; i < numRows; ++i)
		{
			for (int j = 1; j < i; ++j)
			{
				vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
			}
		}
		return vv;
	}
};

移动构造和移动赋值

移动构造函数是⼀种构造函数,类似拷⻉构造函数,移动构造函数要求第⼀个参数是该类类型的引
⽤,但是不同的是要求这个参数是右值引⽤如果还有其他参数,额外的参数必须有缺省值
移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函
数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。
对于像string/vector这样的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值才有
意义,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的类型,他的本质是要“窃取”引⽤的
右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从提⾼效率。下⾯的bit::string
样例实现了移动构造和移动赋值,我们需要结合场景理解。
namespace bit

{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			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);
		}
		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;

			reserve(s._capacity);
			for (auto ch : s)
			{
				push_back(ch);
			}
		}
		// 移动构造
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 拷⻉赋值" <<
				endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;
				reserve(s._capacity);
				for (auto ch : s)
				{
					push_back(ch);
				}
			}
			return *this;
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		~string()
		{
			cout << "~string() -- 析构" << endl;
			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];
				if (_str)
				{
					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)
		{
			push_back(ch);
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}

		size_t size() const
		{
			return _size;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}
int main()
{
	bit::string s1("xxxxx");
	// 拷⻉构造
	bit::string s2 = s1;
	// 构造+移动构造,优化后直接构造
	bit::string s3 = bit::string("yyyyy");
	// 移动构造
	bit::string s4 = move(s1);
	cout << "******************************" << endl;
	return 0;
}

这里的移动构造和赋值相当于窃取了右值的内容,swap(右值)就是不管右值的“死活”,因为右值可能只是常量,临时对象,匿名对象,对于我们的程序来说,这些右值被窃取了不会有影响;所以这样就无需再向系统申请空间了,大大提高了效率;

右值对于函数返回值的优化:

完全不优化:即返回值会拷贝构造一个临时对象,这个临时对象再去拷贝构造ret;

一代优化:直接用局部变量str去拷贝构造ret;

二代优化:str作为ret的引用,返回是不会有任何拷贝构造;

对于赋值重载:

完全不优化:str先拷贝构造一个临时对象再去赋值重载ret;

一代优化:将str作为临时对象的引用,省去一步拷贝构造;

总结:

左值引用让传参效率提高,不需要拷贝传参;并且可以修改实参以及返回值;但是左值不能优化返回值;

对于返回值的优化,编译器的优化确实可以省去很多拷贝步骤让效率得到提高 ;但是不是任何地方使用的都是新版本具有优化功能的编译器;此时需要C++11的移动语义了;

移动语义的移动拷贝和移动赋值都是掠夺其他对象的资源,并且是直接swap,在这种情况下就算不去使用编译器的优化,就是直接返回,返回值的拷贝和赋值也会是移动拷贝和赋值,而这种移动语义效率极高;

一般来说并不是任何地方都要写移动拷贝和赋值,只有当对象是自定义对象要进行深拷贝时才需要;因为若是浅拷贝就是简单的赋值,而没有涉及到开辟新的空间;

6、类型分类

C++11中所有的表达式分为泛左值(generalized lvalue)和右值(rvalue),而泛左值又分为左值和将亡值(expiring value),右值分为纯右值(pure rvalue)和将亡值;

将亡值:将亡值通常是由 std::move() 函数或 static_cast 到右值引用类型转换产生的。它表示一个对象的资源即将被转移,其生命周期即将结束。

7、引用折叠

	typedef int& lref;
	typedef int&& rref;
	int n = 0;

	lref& r1 = n;//lref&是左值
	lref&& r2 = n;//lref&&是左值
	rref& r3 = n;//rref&是左值
	//rref&& r4 = n;//报错:无法将右值引用绑定到左值->rref&&是右值
	rref&& r4 = 0;//rref&&是右值

也就是所但凡出现一个左值引用那么这个引用就是左值引用,只用全是右值引用时最终才是右值引用,比如这里的rref是int&&右值,r4前面又有&&,那么就全是右值引用,最终就是右值引用;

再比如:像f2这样的函数模板中,T&& x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。

template<class T>
void f1(T& x)//T&本身就是左值,那么无论如何x都是一个左值引用
{}

template<class T>
void f2(T&& x)//这里可以控制
{}


f1<int>(n);
//f1<int>(0);//报错,这里0是右值,但是int传过去推导出来x是左值引用

f1<int&>(n);//两个左值,最终x就是左值引用
//f1<int&>(0);//报错

f1<int&&>(n);//T&是左值,那么就算T是右值int&&,折叠之后仍是左值引用
//f1<int&& (0);//报错

f1<const int&>(n);//两个左值,x仍是左值引用,类型是const int&
f1<const int&>(0);//x类型是const int&是左值但是因为const出现,可以接受右值

f1<const int&&>(n);//折叠->void f1(const int& x)
f1<const int&&>(0);//折叠->void f1(const int& x)

//f2<int>(n);//T为int那么x就是右值引用,不能接收左值n
f2<int>(0);

f2<int&>(n);//int&是左值那么折叠->void f2(T&)
//f2<int&>(0);

//f2<int&&>(n);//两个右折叠之后->void f1(int&&)
f2<int&&>(0);

//f2<const int&&>(n);//折叠之后->void f2(const int&&)
f2<const int&&>(0);

再比如:

template<class T>
void function(T&& t)
{
	int a = 0;
	T x = a;
	x++;
	cout << &x << endl;//就算x是右值表达式,但是其属性还是左值,就可以取地址
	cout << &a << endl;
}
	function(10);//10是一个右值,传过去推导为int&&
	int a;
	// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t)
	function(a); // 左值
	// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
	function(std::move(a)); // 右值
	const int b = 8;
	// a是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int&t)
	// 所以function内部会编译报错,x不能++
	function(b); // const 左值
	// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&&t)
	// 所以function内部会编译报错,x不能++
	function(std::move(b)); // const 右值

8、完美转发

在我们使用右值引用参数接收传过来数据时,可以根据数据是什么类型来推导出现在的模板T是什么类型,但是若是在这个函数内部再将传过来的值传给其他的函数无论这个实参时是右值引用还是左值引用,它们的属性都是左值,那么此时传给其他的函数就不能传右值引用过去了,所以这里引入了完美转发;

完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现,下⾯⽰例中传递给 Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引⽤返回;传递给 Function的实参是左值,T被推导为int&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤ 返回。

不使用完美转发:

//template <class _Ty>
//_Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
//{ // forward an lvalue as either an lvalue or an rvalue
//	return static_cast<_Ty&&>(_Arg);
//}
void func(int& x) { cout << "左值引用"<< endl; }
void func(const int& x) { cout << "const 左值引用" << endl; }
void func(int&& x) { cout << "右值引用"<<endl; }
void func(const int&& x) { cout << "const 右值引用" << endl; }

template<class T>
void function(T&& t)
{
	func(t);
	//func(forward<T>(t));
}
int main()
{
	function(10);
	int a = 0;
	function(a);
	function(std::move(a));
	const int b = 0;
	function(b);
	function(std::move(b));
	return 0;
}

使用完美转发func(forward<T>(t)):

四、可变参数模板

1、基本语法以及原理

C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称
为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函
数参数。
template <class ...Args> void Func(Args... args) {}
template <class ...Args> void Func(Args&... args) {}
template <class ...Args> void Func(Args&&... args) {}
们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或
typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出
接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板
⼀样,每个参数实例化时遵循引⽤折叠规则。
可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
示例:用sizeof...来计算参数包的参数个数
template<class...Args>//表示类型参数包
void Print(Args&&... args)//表示参数包中每个类型的对应的形参
{
	cout << sizeof...(args) << endl;//sizeof...专门计算参数包中参数个数
}
int main()
{
	double x = 2.2;
	Print();
	Print(1);
	Print(1, string("aaaaaaa"));
	Print(1, string("aaaaaaa"), x);
	return 0;
}

原理:
//原理1:编译时会结合引用折叠实例化出这四个函数
void Print();
void Print(int&& args1);
void Print(int&& args1, string&& args2);
void Print(int&& args1, string&& args2, double& args3);

//原理2:更本质去看没有可变参数模板,我们实现出这样的多个函数模板才能⽀持
// 这⾥的功能,有了可变参数模板,我们进⼀步被解放,他是类型泛化基础
// 上叠加数量变化,让我们泛型编程更灵活
void Print();
template<class T1>
void Print(T1&& args1);
template<class T1,class T2>
void Print(T1&& args1, T2&& args2);
template<class T1, class T2,class T3>
void Print(T1&& args1, T2&& args2, T3&& args3);

可变参数模板就像是模板的模板,就像它可以通过传不同的参数来生成把不同的函数模板,从而实例化出不同的函数;

并且,可变参数模板的类型参数包的个数没有限制。

2、包扩展

对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个
包时,我们还要提供用于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元
素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。
void Showlist()
{
	//最后依次传的是空
	cout << endl;
}
template<class T, class...Args>
void Showlist(T x ,Args...args)
{
	cout << x << endl;
	//每次传过来的N个参数被分为1和N-1两个部分
	Showlist(args...);
}
// 编译时递归推导解析参数
template<class...Args>
void Print(Args...args)
{
	Showlist(args...);//将参数包传过去
}
int main()
{
	double x = 2.2;
	Print(1, string("aaaaaaa"), x);
	return 0;
}

// 本质编译器将可变参数模板通过模式的包扩展,编译器推导的以下三个重载函数函数
//void Print(int x, string y, double z)
//{
//	Showlist(x, y, z);
//}
//void Showlist(int x, string y, double z)
//{
//	cout << x << endl;
//	Showlist(y, z);
//}
//void Showlist(string x, double y)
//{
//	cout << x << endl;
//	Showlist(y);
//}
//void Showlist(double x)
//{
//	cout << x << endl;
//	Showlist();
//}
C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。
template<class T>
const T& GetArg(const T& arg)
{
	cout << arg << endl;
	return arg;
}
template<class...Args>
void Argument(Args...args)
{}
template<class...Args>
void Print(Args...args)
{
	//GetArg必须返回,Argument才能接收到值
	Argument(GetArg(args)...);
}
本质上可以理解为
//void Print(int x, string y, double z)
//{
//	Argument(GetArg(x), GetArg(y), GetArg(z));
//}
int main()
{
	double x = 2.2;
	Print(1, string("aaaaaaa"), x);
	return 0;
}

若是没有Argument,那么GetArg只能一个一个使用,有了Argument就能把多个GetArg以参数包的方式一起使用;这里的GetArg必须要有返回值,Argument才能接收到多个参数,才能有参数包。

3、emplace系列接口

template <class... Args> void emplace_back (Args&&... args);
template <class... Args> iterator emplace (const_iterator position,
Args&&... args);
C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上
兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container<T>,empalce还⽀持
直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象
emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列
传递参数包过程中,如果是 Args&&... args 的参数包,要⽤完美转发参数包,⽅式如下
std::forward<Args>(args)... ,否则编译时包扩展后右值引⽤变量表达式就变成了左 值。
int main()
{
	bit::list<bit::string> lt;
	// 传左值,跟push_back⼀样,⾛拷⻉构造
	bit::string s1("111111111111");
	lt.emplace_back(s1);
	cout << "*********************************" << endl;
	// 右值,跟push_back⼀样,⾛移动构造
	lt.emplace_back(move(s1));
	cout << "*********************************" << endl;
	// 直接把构造string参数包往下传,直接⽤string参数包构造string
	// 这⾥达到的效果是push_back做不到的
	lt.emplace_back("111111111111");
	cout << "*********************************" << endl;
	bit::list<pair<bit::string, int>> lt1;
	// 跟push_back⼀样
	// 构造pair + 拷⻉/移动构造pair到list的节点中data上
	pair<bit::string, int> kv("苹果", 1);
	lt1.emplace_back(kv);
	cout << "*********************************" << endl;
	// 跟push_back⼀样
	lt1.emplace_back(move(kv));
	cout << "*********************************" << endl;
	
	// 直接把构造pair参数包往下传,直接⽤pair参数包构造pair
		// 这⾥达到的效果是push_back做不到的
		lt1.emplace_back("苹果", 1);
	cout << "*********************************" << endl;
	return 0;
}
// List.h
#define	_CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<string>

using namespace std;
namespace bit
{
	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _data;
		ListNode(T&& data)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(move(data))
		{}
		template <class... Args>
		ListNode(Args&&... args)
			: _next(nullptr)
			, _prev(nullptr)
			, _data(std::forward<Args>(args)...)
		{}
	};
	template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr> Self;
		Node* _node;
		ListIterator(Node* node)
			:_node(node)
		{}
		// ++it;
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		Ref operator*()
		{
			return _node->_data;
		}
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
	};
	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		void empty_init()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}
		list()
		{
			empty_init();
		}
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		void push_back(T&& x)
		{
			insert(end(), move(x));
		}
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(x);
			Node* prev = cur->_prev;
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return iterator(newnode);
		}
		iterator insert(iterator pos, T&& x)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(move(x));
			Node* prev = cur->_prev;
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return iterator(newnode);
		}
		template <class... Args>
		void emplace_back(Args&&... args)
		{
			insert(end(), std::forward<Args>(args)...);
		}
		// 原理:本质编译器根据可变参数模板⽣成对应参数的函数
		/*void emplace_back(string& s)
		{
		insert(end(), std::forward<string>(s));
		}
		void emplace_back(string&& s)
		{
		insert(end(), std::forward<string>(s));
		}
		void emplace_back(const char* s)
		{
		insert(end(), std::forward<const char*>(s));
		}
		*/
		template <class... Args>
		iterator insert(iterator pos, Args&&... args)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(std::forward<Args>(args)...);
			Node* prev = cur->_prev;
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return iterator(newnode);
		}
	private:
		Node* _head;
	};
}

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			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);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 拷贝构造" << endl;
			reserve(s._capacity);
			for (auto ch : s)
			{
				push_back(ch);
			}
		}

		void swap(string& ss)
		{
			::swap(_str, ss._str);
			::swap(_size, ss._size);
			::swap(_capacity, ss._capacity);
		}

		// 移动构造
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			// 转移掠夺你的资源
			swap(s);
		}

		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 拷贝赋值" <<
				endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;
				reserve(s._capacity);
				for (auto ch : s)
				{
					push_back(ch);
				}
			}
			return *this;
		}

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

		~string()
		{
			//cout << "~string() -- 析构" << endl;
			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];
				if (_str)
				{
					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)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}

		size_t size() const
		{
			return _size;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}

emplace的新增的点就是它的模板参数是可变参数模板,也就是说,无论此时的容器中存放的是什么类型的值,使用emplace都可以传过去,并且这个参数包无需解析,只需要一直往下传,传到初始化节点时,这个参数包就被使用了,也就是直接用参数包里面的值进行构造节点。

五、类的新功能

1、默认的移动构造和移动赋值

原来C++类中,有6个默认成员函数:构造函数/析构函数/拷⻉构造函数/拷⻉赋值重载/取地址重
载/const 取地址重载,最后重要的是前4个,后两个⽤处不⼤,默认成员函数就是我们不写编译器
会⽣成⼀个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
如果你没有⾃⼰实现移动构造函数,且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀
。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执
⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤
移动构造,没有实现就调⽤拷⻉构造。
如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)
如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name),
		_age(age)
	{}

	拷贝构造
	//Person(const Person& p)
	//	:_name(p._name),
	//	_age(p._age)
	//{}

private:
	bit::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = move(s1);
	return 0;
}

此时没有Person没有写析构、拷贝构造和赋值重载,并且也没有写移动赋值或者重载,那么编译器会自己生成默认的移动构造以及重载,对于内置类型进行浅拷贝,对于自定义类型则看其是否实现了移动构造函数,实现了就调用这个移动构造,反之则调用其拷贝构造(对于赋值重载也是一样的)

去掉拷贝构造的注释之后:

2、声明时给缺省值

3、default和delete

C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因
这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤
default关键字显⽰指定移动构造⽣成
如果能想要限制某些默认函数的⽣成,在C++98中,是该函数设置成private,并且只声明补丁已,
这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数
示例:
还是上述代码,去注释的,那么不会生成默认移动构造,但是此时加了    Person(Person&& p) = default;就会生成默认的移动构造函数;
上述代码加上Person(const Person& p)=delete;那么就无法调用拷贝构造函数

4、final和override

六、stl中的变化

最重要的变化:unordered_map、set以及移动语义加入一些容器的接口,比如list的emplace.

 七、lambda表达式

1、语法

lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是它可以定义在函数内部
lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接
lambda 对象
lambda表达式的格式: [capture-list] (parameters)-> return type { function boby }
[capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来
判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使
⽤,捕捉列表可以传值和传引⽤捕捉捕捉列表为空也不能省略
(parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连
同()⼀起省略
->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,可以省略
{function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以
使⽤其参数外,还可以使⽤所有捕获到的变量函数体为空也不能省略
示例:
int main()
{
	//lambda表达式
	// 1、捕捉为空也不能省略
	// 2、参数为空可以省略
	// 3、返回值可以省略,可以通过返回对象⾃动推导
	// 4、函数题不能省略
	auto add1 = [](int x, int y)->int { return x + y; };
	cout << add1(10, 20) << endl;

	auto func = [] {cout << "hello world!" << endl; };
	func();

	auto swap = [](int& x, int& y)
		{
			int tmp = 0;
			tmp = x;
			x = y;
			y = tmp;
		};
	int a = 10, b = 20;
	swap(a, b);
	cout << "a:" << a << endl << "b:" << b << endl;
	return 0;
}

2、捕捉列表

lambda表达式默认只能使用lambda表达式内部的变量以及静态变量或者全局变量;想要使用外部变量需要进行捕捉;

捕捉分为引用捕捉和值捕捉,被捕捉变量前面加上&就是引用捕捉,反之就是值捕捉;值捕捉无法改变变量的值,引用捕捉可以改变变量的值

int main()
{
	int a = 0, b = 1, c = 2, d = 3;
	auto f1 = [a, &b]
		{
			//报错,可以理解为捕捉默认有const修饰
			//a++;
			b++;
			cout << "a:" << a << " ; " << "b:" << b << endl;
		};
	f1();
	return 0;
}

隐式捕捉:在[]里面写了=或者&表示隐式捕捉,此时在函数体中写了什么就会捕捉什么并且使用,但是=是隐式值捕捉,&是隐式引捕捉;

int main()
{
	int a = 0, b = 1, c = 2, d = 3;
	auto f2 = [=]
		{
			cout << "a:" << a << " ; " << "b:" << b << endl;
		};
	f2();

	auto f3 = [&]
		{
			a++;
			b++;
			cout << "a:" << a << " ; " << "b:" << b << endl;
		};
	f3();
	return 0;
}

混合捕捉,就是=或者&和变量在捕捉列表中同时出现,[]中第一个必须是=或者&,若是=那么后面的变量要求全是引用捕捉,若是&要求后面捕捉的全部是值捕捉 

捕捉是用哪些变量就捕捉,而并不是全部都捕捉

	int a = 0, b = 1, c = 2, d = 3;
	auto f4 = [&, c, d]
		{
			a++;
			b++;
			//c++;//后面是值捕捉,报错
			cout << "a:" << a << " ; " << "b:" << b << endl;
			cout<< "c:" << c << " ; " << "d:" << d << endl;
		};
	f4();
	auto f5 = [=, &c, &d]
		{
			c++;
			d++;
			//a++;//后面是值捕捉,报错
			cout << "a:" << a << " ; " << "b:" << b << endl;
			cout << "c:" << c << " ; " << "d:" << d << endl;
		};
	f5();

lambda只能捕捉所在函数体定义在其之前的变量,不能捕捉全局和静态变量,当然全局和静态也不需要捕捉,可以直接使用;那么当lambda表达式定义在全局时,捕捉列表必须是空

//定义在全局的lambda不需要捕捉
int x = 100;
auto f6 = []{cout << x << endl;};

int main()
{
	f6();
	int a = 0, b = 1, c = 2, d = 3;
	//可以直接使用全局和静态变量
	auto f7 = [c, d] {cout << x + c + d << endl; };
	f7();
	return 0;
}

使用mutable加在lamb表达式参数列表后面那么值捕捉的变量就可以改变了,取消了常性;但是此时改变的是形参而不是原本的变量的值; 那么参数列表不能省略了,即使是空,因为要在括号后面加mutable;

int main()
{
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
// mutable相当于去掉const属性,可以修改了
// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉
	int a = 0, b = 1, c = 2, d = 3;
	auto f8 = [=]()mutable
		{
			a++;
			b++;
			c++;
			d++;
			cout << "a:" << a << " ; " << "b:" << b << endl;
			cout << "c:" << c << " ; " << "d:" << d << endl;
		};
	f8();
	cout << "a:" << a << " ; " << "b:" << b << endl;
	cout << "c:" << c << " ; " << "d:" << d << endl;
	return 0;
}

3、应用

在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的
类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对
象,既简单⼜⽅便。
struct Goods 
{
	string _name;//名字
	double _price;//价格
	int _evaluate;//评价
	Goods(const char* name,double price,int evaluate)
		:_name(name),
		_price(price),
		_evaluate(evaluate)
	{}
	Goods(const Goods& goods)//拷贝构造
		: _name(goods._name),
		_price(goods._price),
		_evaluate(goods._evaluate)
	{}
};

void Print(vector<Goods>& goods)
{
	for (auto x : goods)
	{
		cout << x._name << ":" << x._price << ":" << x._evaluate << endl;
	}
}

//商品按价格比较的仿函数
struct ComparePriceLess
{
	bool operator()(Goods& g1, Goods& g2)
	{
		return g1._price < g2._price;
	}
};

struct ComparePriceGreater
{
	bool operator()(Goods& g1, Goods& g2)
	{
		return g1._price > g2._price;
	}
};


int main()
{
	vector<Goods> goods = { { "苹果", 2.1, 2 }, { "香蕉", 3, 1 }, { "橙子", 2.2, 4}, { "菠萝", 1.5,3 } };
	vector<Goods> v1 = goods;
	sort(v1.begin(), v1.end(), ComparePriceLess());
	Print(v1);
	cout << endl;
	vector<Goods> v2 = goods;
	sort(v2.begin(), v2.end(), ComparePriceGreater());
	Print(v2);
	cout << "***************************" << endl;
	//类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
	//不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了
	vector<Goods> v3 = goods;
	vector<Goods> v4 = goods;
	vector<Goods> v5 = goods;
	vector<Goods> v6 = goods;
	//按价格排序
	cout << "按价格排序:" << endl;
	cout << "升序:" << endl;
	sort(v3.begin(), v3.end(), [](Goods& g1, Goods& g2) ->bool
		{return g1._price < g2._price; });
	Print(v3);
	cout << "降序:" << endl;
	sort(v4.begin(), v4.end(), [](Goods& g1, Goods& g2) ->bool
		{return g1._price > g2._price; });
	Print(v4);
	cout << endl;
	//按评价排序
	cout << "按评价排序" << endl;
	cout << "好评:" << endl;
	sort(v5.begin(), v5.end(), [](Goods& g1, Goods& g2) ->bool 
		{return g1._evaluate < g2._evaluate; });
	Print(v5);
	cout << "差评:" << endl;
	sort(v6.begin(), v6.end(), [](Goods& g1, Goods& g2) ->bool 
		{return g1._evaluate > g2._evaluate; });
	Print(v6);
	return 0;
}

4、lambda表达式原理

其实就是生成了一个仿函数的类,类名按照一定的编译规则生成,不同lambda表达式的生成的类名不同;捕捉列表对应的就是仿函数的成员变量,返回类型就是仿函数的返回类型,参数列表和函数体就是仿函数的参数列表以及函数体;

八、包装器

1、function

std:function是一个类模板也是一个包装器,function的实例出来的对象可包装其他的可调用对象例如:函数指针、仿函数、lambda表达以及bind;存储的可调用对象被称为std:function的目标,若目标为空时,调用function实例对象会出现抛出 bad_function_call 异常

function头文件是<functional>

int f(int a, int b)
{
	return a + b;
}
struct Functor
{
	int operator()(int a, int b)
	{
		return a + b;
	}
};
int main()
{
	//function对象包装各种可调用对象
	function<int(int, int)> f1 = f;
	function<int(int, int)> f2 = Functor();
	function<int(int, int)> f3 = [](int a, int b)->int {return a + b; };

	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;
	return 0;
}

使用function包装类里面的静成员函数需要指定类域在类域前面加上&;包装普通成员函数也要指定类域,可以不加&但是一般建议是加上,最重要的是,普通成员函数隐含this指针,所以此时function需要在()里面的第一个参数加上类类型或者类指针或者类的右值引用类型; 

这里虽然成员函数的第一个参数是this指针,也就是类的指针类型,但是实际上对象调用函数指针时都是用对象.*去获得的((obj.*ptr)),那么当传类类型时就是生成临时对象(obj.*ptr),当传类指针时就是(*(pobj).*ptr),传类匿名对象更直接不需要再生成一个临时对象直接(obj.*ptr)

class Plus
{
private:
	int _n = 10;
public:
	Plus(int n = 10)
		:_n(n)
	{}
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return _n * (a + b);
	}
};

int main()
{
	//静态成员函数的包装
	function<int(int, int)> f1 = &Plus::plusi;
	cout << f1(1, 1) << endl;
	//包装普通的成员函数
	Plus pd;
	function<double(Plus*, double, double)> f2 = &Plus::plusd;
	cout << f2(&pd, 1.11, 1.11) << endl;
	function<double(Plus, double, double)> f3 = &Plus::plusd;
	cout << f3(pd, 1.11, 1.11) << endl;
	function<double(Plus&&, double, double)> f4 = &Plus::plusd;
	cout << f4(Plus(), 1.11, 1.11) << endl;
	return 0;

function在求解逆波兰表达式求值的作用:

class Solution {
public:
//一般思路:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        for(auto str : tokens)
        {
            if(str == "+" || str == "-" || str == "*" || str == "/")
            {
                int num1 = st.top();
                st.pop();
                int num2 = st.top();
                st.pop(); 
                switch(str[0])
                {
                    case '+':
                        st.push(num2 + num1);
                        break;
                    case '-':
                        st.push(num2 - num1);
                        break;
                    case '*':
                        st.push(num2 * num1);
                        break;
                    case '/':
                        st.push(num2 / num1);
                        break;
                }
            }
            else
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

使用function将运算符存起来,并且给每一个运算符相对应的表达式存在map里面

class Solution {
public:
//一般思路:
    int evalRPN(vector<string>& tokens) 
    {
        map<string , function<int(int,int)>> hash=
        {
            {"+",[](int a,int b)->int{return a+b;}},
            {"-",[](int a,int b)->int{return a-b;}},
            {"*",[](int a,int b)->int{return a*b;}},
            {"/",[](int a,int b)->int{return a/b;}}
        };
        stack<int> st;
        for(auto& str : tokens)
        {
            if(hash.count(str))
            {
                int right = st.top();
                st.pop();
                int left  = st.top();
                st.pop();
                int ans = hash[str](left,right);
                st.push(ans); 
            }
            else
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

2、bind

simple ( 1 )
template < class Fn , class ... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
with return type ( 2 )
template < class Ret, class Fn, class ... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收
的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。
bind 也在<functional>这个头⽂件中
调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中
newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的
参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
	return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}

int main()
{
	//调整参数顺序(不常用)
	auto sub1 = bind(Sub, _1, _2);
	cout << sub1(10, 5) << endl;
	auto sub2 = bind(Sub, _2, _1);//_1代表第一个实参,_2代表第二个实参
	cout << sub2(10, 5) << endl;
	//调整参数个数(常用)
	//绑定某个参数,剩下需要传的参数个数减少
	auto sub3 = bind(SubX, 10, _1, _2);//绑定第一个参数
	cout << sub3(2, 3) << endl;
	auto sub4 = bind(SubX, _1 ,10, _2);//绑定第二个参数
	cout << sub4(2, 3) << endl;
	auto sub5 = bind(SubX, _1, _2 ,10);//绑定第三个参数
	cout << sub5(2, 3) << endl;
	return 0;
}

 使用bind可以绑定function中一些固定参数,那么每次传参就简化了:

using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
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<double(Plus&&, double, double)> f1 = &Plus::plusd;
	Plus pd;
	cout << f1(move(pd), 1.1, 1.1) << endl;
	cout << f1(Plus(), 1.1, 1.1) << endl;
	//使用bind绑定一些固定参数,就不需要每次都传了
	function<double(double, double)> f2 = bind(&Plus::plusd, Plus(), _1, _2);//Plus()为固定参数
	cout << f2( 1.1, 1.1) << endl;
	cout << f2(1.1, 1.1) << endl;
	return 0;
}

 计算年化率的function,用bind绑定固定参数,只需要传本金就行

//计算年化率的lambda
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int main()
{
	auto f = [](double rate, double money, int year)->double
		{
			int ret = money;
			for (int i = 0; i < year; i++)
			{
				ret += ret * rate;
			}
			return ret - money;
		};
	//3 15\5 15\10 25\20 35
	// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息
	function<double(int)> func_3_15 = bind(f, 0.015, _1, 3);
	function<double(int)> func_5_15 = bind(f, 0.015, _1, 5);
	function<double(int)> func_10_25 = bind(f, 0.025, _1, 10);
	function<double(int)> func_20_35 = bind(f, 0.05, _1, 20);

	cout << func_3_15(1000000) << endl;
	cout << func_5_15(1000000) << endl;
	cout << func_10_25(1000000) << endl;
	cout << func_20_35(1000000) << endl;
	return 0;
}

相关文章:

  • Spring Boot 与 TDengine 的深度集成实践(三)
  • 利用C++编写操作OpenCV常用操作
  • 编程速递-Delphi is 30 Delphi诞生30周年!
  • MySQL-SQL-DQL语句、DQL基本查询、DQL条件查询、DQL分组查询、聚合函数、DQL排序查询、DQL分页查询
  • 【勒让德公式】欧拉筛-阶乘分解
  • 【硬件开发技巧】如何通过元器件丝印反查型号
  • vector模拟实现(2)
  • 蓝桥杯2024年第十五届省赛真题-拔河
  • 专栏:区块链入门到放弃查看目录
  • el-tabs添加按钮增加点击禁止样式
  • ubuntu 配置固定ip
  • getline(cin, )
  • Qt音频输出:QAudioOutput详解与示例
  • 玄机-apache日志分析
  • PDF预览-搜索并高亮文本
  • 基于OpenXLSX库创建的CAPL中可用的解析xlsx文件的DLL
  • traefik k3s配置
  • MongoDB基础知识
  • 页游(弹弹堂)刷怪脚本教程(一)---大漠插件制作颜色变化的字体字库(按键精灵)
  • Java项目之基于ssm的个性化旅游攻略定制系统(源码+文档)
  • 江西做网站的公司有哪些/怎么创建公司网站
  • 什么才是网络营销/北京网站快速排名优化
  • 网站链接dw怎么做/广告传媒公司经营范围
  • 河北响应式网站建设平台/网站友链交换平台
  • 网站建设学习学校/营销推广方案包括哪些内容
  • 做网站练手项目/网络营销方案策划