【ONE·C++ || C++11(一)】

news2024/11/27 15:43:21

总言

  主要介绍C++11中的一些功能语法。

文章目录

  • 总言
  • 0、思维导图
  • 1、基本情况简介
  • 2、统一的列表初始化
    • 2.1、{}的使用
    • 2.2、initializer_list
      • 2.2.1、基础介绍
      • 2.2.2、在各容器中实现说明
  • 3、声明
    • 3.1、auto
    • 3.2、nullptr
    • 3.3、decltype
  • 4、范围for
  • 5、智能指针
  • 6、STL中一些变化
    • 6.1、C++11中新增容器
    • 6.2、容器中的一些新方法
  • 7、右值引用和移动语义
    • 7.1、左值引用和右值引用基本介绍
    • 7.2、左值引用与右值引用使用比较
    • 7.3、相关应用
      • 7.3.1、问题引入
      • 7.3.2、解决方案一:输出型参数
      • 7.3.3、解决方案二:右值引用(介绍移动构造和移动赋值)
      • 7.3.4、一些说明
    • 7.4、完美转发
      • 7.4.1、介绍模板中的&&:万能引用/引用折叠
      • 7.4.2、介绍完美转发
  • 8、新的类功能
    • 8.1、新增默认成员函数:两个
    • 8.2、关键字
      • 8.2.1、default和delete、如何创建一个只在堆区的
      • 8.2.2、final和override
  • 9、可变参数模板
    • 9.1、基础概念介绍
    • 9.2、STL容器中的emplace相关接口函数
  • 10、lambda表达式
    • 10.1、问题引入
    • 10.2、lambda表达式基本介绍
      • 10.2.1、基本语法
      • 10.2.2、使用举例(基本使用、捕捉列表介绍)
      • 10.2.3、示例演示
      • 10.2.4、相关底层
  • 11、包装器
    • 11.1、function
      • 11.1.1、问题引入:为什么
      • 11.1.2、基本格式:是什么
      • 11.1.3、问题解决:有什么用
    • 11.2、bind
      • 11.2.1、基本说明
      • 11.2.2、使用演示
  • 12、多线程

  
  
  
  

0、思维导图

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  
  
  

1、基本情况简介

  
  情况说明:C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。
  
  
  

2、统一的列表初始化

2.1、{}的使用

  1)、对{}的使用改进说明

  在C++98中,能够使用到花括号{}的地方有:对数组或结构体元素进行统一的列表初始值设定。

struct Student
{
	int _ID;
	int _score;
};

void test01()
{
	//对数组初始化时使用花括号
	int arr[] = { 1,2,3,4,5 };
	int arr2[5] = { 0 };

	//对结构体初始化时使用花括号
	struct Student s1 = { 212121,92 };

}

  
  C++11扩大了用花括号括起的列表(即初始化列表)的使用范围。①使其可用于所有的内置类型和自定义类型,②创建对象时也可以使用列表初始化方式调用构造函数初始化
  
  
  2)、相关演示一

在这里插入图片描述使用列表初始化内置类型和自定义类型

  对内置类型和自定义类型:使用初始化列表{}初始化,可加等号,也可不加。

struct Student
{
	int _ID;
	int _score;
};

void test02()
{
	//常规写法:
	int x1 = 1;
	//C++11给出的初始化列表{}写法,使用初始化列表时,=可以省略
	int x2 = { 2 };
	int x3{ 3 };

	//常规写法:
	int arr1[] = { 1,2,3,4,5 };
	//C++11给出的初始化列表{}写法:
	int arr2[]{ 1,2,3,4,5 };
	int arr3[5]{ 0 };
	struct Student s1{202020,99};
}

  
  
  

在这里插入图片描述创建对象时可以使用列表初始化方式,调用构造函数进行初始化

  演示如下:下述d1、d2、d3都是在创建类对象时,调用其构造函数进行初始化。

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

void test03()
{
	Date d1(2023, 6, 1);
	Date d2 = { 2023,7,15 };
	Date d3{ 2023,8,24 };
}

在这里插入图片描述

  
  
  
  3)、相关演示二

  step1:
  问题:按道理,我们使用()即可解决很多初始化问题,那么 这种初始化方式有何意义价值? ==
  回答:主要体现在对各类容器的使用中。
  
在这里插入图片描述

  在C++98,当我们使用各类容器进行构造对象时,能初始化的方式如上述(这里只举例了vector、list,其它容器接口可参考相关网址查询)。

	//分别在两容器中存储1,2,3,4,5
	int arr[] = { 1,2,3,4,5 };
	
	//以下为一种对象初始化方式
	vector<int> v1;
	for (auto e : arr)
	{
		v1.push_back(e);
	}

	list<int> lt1;
	for (auto e : arr)
	{
		lt1.push_back(e);
	}

  
  C++11中,由于{}可使用于类对象,因此我们有了以下的初始化方式:(事实上在之前的博文中,我们也曾使用过,只是当时没有对其详细讲解)

	vector<int>v2 = { 1,2,3,4,5,6 };
	vector<int>v3{ 1,2,3,4,5,6 };

	list<int> lt2 = { 1,2,3,4,5,6 };
	list<int> lt3{ 1,2,3,4,5,6 };

在这里插入图片描述
  
  
  step2
  问题:这种初始化方式是如何支持的呢?

	auto x = { 1,2,3,4,5,6 };
	cout << typeid(x).name() << endl;

在这里插入图片描述

  回答:如上述,我们使用typeid().name()来确认其类型,发现其给出的是initializer_list

  
  
  

2.2、initializer_list

2.2.1、基础介绍

  
  相关链接:initializer_list
  可以看到它的相关接口很少,但实现了类似于begin、end的迭代器接口。
在这里插入图片描述

  其它容器的构造函数都支持这样一个接口,所以我们可以使用初始化列表进行初始化。(以下只是举例vector、list,其它容器可查询相关文档)
在这里插入图片描述
  
  

2.2.2、在各容器中实现说明

  1)、相关演示一:对自己模拟实现的vector、list演示
  基于上述,对于自己写的vector等容器,若要支持上述初始化列表方式创建对象,也需要提供相应的构造构造。此处以vector为例:

		//initializer_list构造:C++11
		vector(initializer_list<T> il)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(il.size());
			for (auto& e : il)
			{
				push_back(e);
			}
		}

在这里插入图片描述

  
  
  
  
  2)、相关演示二:vector< Date> 类型嵌套、map<string,string>构造与赋值重载
  演示代码如下:

void test03()
{
	Date d1(2023, 6, 1);
	Date d2 = { 2023,7,15 };
	Date d3{ 2023,8,24 };

	vector<Date> v1 = { d1,d2,d3 };
	vector<Date> v2 = { {2023,10,01},{2022,4,29},{2022,6,19} };

	//构造
	map<string, string> m1 = { {"决明子","明目"},{"薄荷","辛凉"},{"鱼腥草","消炎"} };

	//赋值重载
	initializer_list<pair<const string, string>> kvil = { {"决明子","明目"},{"薄荷","辛凉"},{"鱼腥草","消炎"} };
	map<string, string> m2;
	m2 = kvil;
}

  演示结果如下:
在这里插入图片描述

  
  
  
  

3、声明

  auto和nullptr在C++基础中有做介绍,故此处不多谈论。

3.1、auto

  在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

  
  

3.2、nullptr

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

  
  

3.3、decltype

  1)、基础介绍
  说明: 关键字decltype将变量的类型声明为表达式指定的类型。

  演示代码如下:

void test06()
{
	const int x = 2;
	double y = 2.2;
	decltype(x * y) ret; // ret的类型是double
	decltype(&x) p;      // p的类型是int*
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;
}

在这里插入图片描述
  
  有什么用处呢?
  比如下述这个场景中,我们需要定义一个变量ret用域记录T1,T2的乘积,但T1,T2为模板参数,其类型是不明确的,此处就可以使用template来解决。

template<class T1, class T2>
void F(T1 t1, T2 t2)
{
	decltype(t1 * t2) ret;
	cout << typeid(ret).name() << endl;
}

  
  
  2)、与typeid.name()的区别演示
  typeid只能获取类型,用于查看,decltype获取类型后可建立相应类型的变量。

	int val = 8;
	decltype(val) ret2 = 22;//right
	typeid(val).name() ret3 = 22;//error

  
  
  3)、与auto的区别演示
  auto是根据给定数值推到其类型,decltype直接将变量的类型定义为指定类型,因此存在类型转换。

	int val = 8;
	decltype(val) ret4 = 22.22;//浮点值,会发生隐式类型转换
	auto ret5 = 22.22;
	cout << ret4<< " " << typeid(ret4).name() << endl;
	cout << ret5 << " " << typeid(ret5).name() << endl;

在这里插入图片描述

  
  
  

4、范围for

  同C++基础章有提及。
  
  

5、智能指针

  暂补。
  
  
  

6、STL中一些变化

6.1、C++11中新增容器

  1)、C++11中新增容器说明
  哪些属于新增容器:unordered系列、forward_list单链表、array
在这里插入图片描述

  
  
  
  

6.2、容器中的一些新方法

  1)、C++11中新增容器说明
  容器中都增加了一些C++11的方法。例如:
  1、都支持initializer_list构造,用来支持列表初始化
  2、新提供了一些接口,如cbegin和cend,用于返回const迭代器等等
  3、移动构造和移动赋值:相关内容后续讲解
  4、右值引用参数插入
  
  

7、右值引用和移动语义

7.1、左值引用和右值引用基本介绍

  1)、左值和左值引用介绍与演示

  什么是左值?
  说明: 左值是一个表示数据的表达式(如变量名或解引用的指针)。
  ①对于左值,可以获取它的地址,也能对其赋值。(PS:const修饰符的左值,不能对其赋值,但是可以取地址。)
  ②左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。

	// 以下的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& pv = *p;

  
  
  2)、右值和右值引用介绍与演示
  什么是右值?
  说明: 右值也是一个表示数据的表达式,
  ①如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等
  ②右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,且右值不能取地址。

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

void test07()
{
	int a = 1, b = 2;

	//常见右值形式
	10;
	"string";
	a + b;
	add(a, b);
}

  
  
  什么是右值引用?
  说明: 右值引用就是对右值的引用,即给右值取别名。这里需要注意右值引用的写法。

	int&& rr1 = 10;
	int&& rr2 = a + b;
	int&& rr3 = add(a, b);

  
  
  
  

7.2、左值引用与右值引用使用比较

  1)、左值引用能引用右值吗?
  1、普通情况下,左值引用只能引用左值,不能引用右值。
  2、但是const左值引用既可引用左值,也可引用右值。
  

void test08()
{
	// 左值引用只能引用左值,不能引用右值
	int a = 10;
	int& ra1 = a;   //引用左值:right
	//int& ra2 = 10;   //引用右值:error

	//const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10; //引用右值
	const int& ra4 = a; //引用左值
}

在这里插入图片描述

  
  
  2)、右值引用可以引用左值吗?
  1、右值引用只能右值,不能引用左值。
  2、但是右值引用可以引用move以后的左值

void test08()
{
	// 右值引用只能右值,不能引用左值。
	int b = 8;
	int&& rr1 = 10;
	//int&& rr2 = b;//error

	//但是右值引用可以引用move以后的左值
	int&& rr3 = move(b);//right
}

在这里插入图片描述

  
  
  
  
  3)、右值与右值引用下的取地址说明&
  说明: 右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。
在这里插入图片描述
  相虽然x+y是右值,但作为右值引用用于存储的z实则为左值,因此可以被取地址。

	double x = 1.1, y = 1.2;
	&(x + y);//error
	double&& z = (x + y);
	&z;//right

  
  
  

7.3、相关应用

7.3.1、问题引入

  1)、左值引用能解决哪些问题?
  常见场景如下:
  1、左值引用可以做参数,①其能够减少拷贝,提高效率;②也可以作为输出型参数使用。
  2、左值引用可以做返回值,①其同样能够减少拷贝,提高效率;②同时,引用返回可以修改返回对象,如map::operator[]。
  
  
  2)、左值引用存在的场景缺陷说明
  例如:下述函数接口,实现时其返回值只能是传值返回,不用引用返回。

std::to_string
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);

  当初的杨辉三角OJ题中,返回值vector<vector<int>>也是传值返回。

//给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
//在「杨辉三角」中,每个数是它左上方和右上方的数的和。

class Solution {
public:
    vector<vector<int>> generate(int numRows) {

    }
};

  上述这类情况,函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。
  使用传值返回,会导致至少1次拷贝构造(这里需要看编译器是否做出优化,相关内容解释见之前博文)。
  
  问题:如果就要减少传值返回带来的拷贝消耗,可以如何做?
  
  
  

7.3.2、解决方案一:输出型参数

  1)、基本说明
  1、使用全局变量:事实上这种方法并不被建议采用,会存在线程安全问题。
  
  2、使用输出型参数:这种解决方案相对常见,只是其不太符合使用习惯。

//原先:
string to_string (int val);
string to_string (long val);
//修改为输出型参数:
void to_string(int val, string& str);
void to_stirng(long val, string& str);

//原先:
vector<vector<int>> generate(int numRows) {}
//修改为输出型参数;
void  generate(int numRows, vector<vector<int>>& V) {}

  
  
  
  

7.3.3、解决方案二:右值引用(介绍移动构造和移动赋值)

  1)、相关演示代码
  为了方便观察,我们以下面的string类,也就是我们自己模拟实现的类来演示:(库中整体情况相同)

#define _CRT_SECURE_NO_WARNINGS 
#pragma once
#include<iostream>
#include<string.h>
#include<utility>
#include<assert.h>

using namespace std;

namespace mystring
{
	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(string && s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}

		// 移动赋值
		string& operator=(string && s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			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;
		}

		string to_string(int value)
		{
			bool flag = true;

			if (value < 0)
			{
				flag = false;
				value = 0 - value;
			}

			string str;

			while (value > 0)
			{
				int x = value % 10;
				value /= 10;
				str += ('0' + x);
			}

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

			std::reverse(str.begin(), str.end());

			return str;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

  
  
  2)、实际演示
  主要用到的代码如下:
  1、拷贝构造、拷贝赋值我们很熟悉,此处不过多阐述。这里移动构造、移动赋值是通过右值引用来完成的。因此,我们的目标一是:学会移动构造、移动赋值的相关写法。 实际上,移动构造,本身也是构造函数中的一种,移动赋值,也是赋值运算符重载中的一种。

		// 拷贝构造
		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(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造:资源转移" << endl;
			swap(s);
		}
		
		// 移动赋值
		string& operator=(string && s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值:资源转移" << endl;
			swap(s);
			return *this;
		}
#include"mystring.h"
void test09()
{
	mystring::string str1 = mystring::string().to_string(23012);
}

  2、string(const string& s)string& operator=(const string& s):这里左值引用都被const修饰,那么其既可以引用左值,也能引用右值。上述代码,若我们没有实现移动构造,那么to_string(23012),其在返回时,会自动匹配拷贝构造,形成深拷贝。移动赋值同理,不写时会自动匹配赋值重载。
在这里插入图片描述

  to_string处传值返回,形成的拷贝构造如下:
在这里插入图片描述
  
  
  3、string(string&& s)string& operator=(string && s):移动构造,实际是将参数右值的资源直接占位已有,省去了深拷贝要做的事项:由于没有新开空间拷贝数据,所以效率得到提高。
在这里插入图片描述

void test09()
{
	mystring::string str1 = mystring::string().to_string(23012);
	mystring::string str2(str1);//拷贝构造
	mystring::string str3(move(str1));//移动语义
	
	cout << endl;
}

在这里插入图片描述

  
  

7.3.4、一些说明

  3)、相关说明
  1、关于纯右值和将亡值:实际上,C++11中,右值分为纯右值和将亡值。对于内置类型的右值,将其称为纯右值;对于自定义类型的右值,将其称为将亡值。将亡值的生命周期即将结束,因此可使用资源转移,即窃取的方式,变相利用其数据资源。
  2、std::move():当需要用右值引用引用一个左值时,可以使用move函数,它的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
  3、SQL容器中,各容器的构造在C++11中就提供移动构造。赋值运算符重载同理,此处可自行查阅文档。
在这里插入图片描述

  4、此外,一些与插入相关的接口也会增加右值引用:如果传递的对象是右值对象,那么就会进行资源转移,减少拷贝。

在这里插入图片描述

  
  
  
  
  

7.4、完美转发

7.4.1、介绍模板中的&&:万能引用/引用折叠

  1)、基本说明
  若在模板中使用&&,如下述T&& t。这里并不表示右值引用,而是万能引用。其含义是其既能接收左值,又能接收右值。

template<typename T>
void PerfectForward(T&& t)//万能引用:其既能接收左值又能接收右值
{
	Fun(t);
}

  如下,使用同一个模板,但我们传入的值即有左值,又有右值,即的万能引用提供了能够接收左值引用和右值引用的能力。

void test10()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值

	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);      // const 左值

	PerfectForward(std::move(b)); // const 右值
}

  
  
  2)、相关特性
  我们以上述代码来验证万能引用的效果。PerfectForward会根据传入参数分别调用不同的Fun

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

void test10()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值

	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);      // const 左值

	PerfectForward(std::move(b)); // const 右值
}

  结果如下,说明万能引用限制了参数接收的类型,使得后续使用中,都退化成了左值。左值->左值右值->左值const左值->const左值const右值->const左值。正因为有这样的语法特性,也称万能引用为引用折叠。
  也可以理解为用于接受的t为左值,所以后续Fun(t)获取结果为左值。
在这里插入图片描述

  
  问题:若需求为在模板传递过程中,保持它原有的左值或者右值的属性,该如何做?
  
  

7.4.2、介绍完美转发

  1)、基础介绍
  std::forward :完美转发。其作用是在传参的过程中保留对象原生类型属性
  如下,此处std::forward<T>(t)在传参的过程中保持了t的原生类型属性。

template<typename T>
void PerfectForward(T&& t)//万能引用:其既能接收左值又能接收右值
{
	//Fun(t);//移动折叠
	Fun(std::forward<T>(t));//完美转发
}

在这里插入图片描述

  
  
  
  2)、实际用途
  完美转发是在模板&&中为保持其t值原有属性而提出的,当t值层层递进传递给下一层或者其它函数时,为了保持其特性,也需要使用完美转发。
  
  此处以list为例:这里省略了一些内容。

#pragma once


#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace mylist
{
	//单节点
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _prev;
		list_node<T>* _next;

		list_node(const T& val = T())
			:_data(val)
			, _prev(nullptr)
			, _next(nullptr)
		{}

		list_node(T&& val)
			:_data(std::forward<T>(val))
			, _prev(nullptr)
			, _next(nullptr)
		{}
	};


	//迭代器
	template<class T, class Ref, class Ptr>
	struct __list_iterator//list中的迭代器:非原身指针,此处我们是用类来实现的
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;//重命名:能在整体上保持一致性

		Node* _node;//类成员变量:节点

		__list_iterator(Node* node)//迭代器中节点的的构造
			:_node(node)
		{}

		bool operator!=(const iterator& it)const
		{
			return _node != it._node;
		}


		bool operator==(const iterator& it)const
		{
			return _node == it._node;
		}

		//*it == it.operator*()
		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		//++it
		iterator& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		//it++
		iterator operator++(int)
		{
			iterator tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		//--it
		iterator& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		//it--
		iterator operator--(int)
		{
			iterator tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
	};



	//list
	template<class T>
	class list
	{
		typedef list_node<T> Node;//重命名单节点

	public:

		//迭代器
		typedef __list_iterator<T, T&, T*> iterator;

		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		//开辟空间并初始化哨兵位的头结点
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		//构造函数1.2
		list()
		{
			empty_init();
		}

		//构造函数2.0
		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//拷贝构造:lt1(lt2)
		list(const list<T>& lt)
		{
			list<T>tmp(lt.begin(), lt.end);
			swap(tmp);
		}

		void swap(list<T>& lt)
		{
			std::swap(lt._head, _head);
		}

		//赋值运算符重载:lt1 = lt2
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		//析构
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		//清除list中数据:保留哨兵位的头结点
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		//插入
		iterator insert(iterator pos, const T& val)
		{
			//保存节点
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			//新增节点
			Node* newnode = new Node(val);
			//修改关系: prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			//返回值
			return iterator(newnode);
		}

		//插入
		iterator insert(iterator pos, T&& val)
		{
			//保存节点
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			//新增节点
			Node* newnode = new Node(std::forward<T>(val));
			//修改关系: prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			//返回值
			return iterator(newnode);
		}



		//尾插
		void push_back(const T& val)
		{
			//写法二
			insert(end(), val);
		}

		//尾插
		void push_back(T&& val)
		{
			//写法二
			insert(end(), std::forward<T>(val));
		}

		//头插
		void push_front(const T& val)
		{
			insert(begin(), val);
		}


		//删除
		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next->_prev;
			next->_prev = prev->_next;

			delete cur;

			return iterator(next);
		}

		void pop_back()
		{
			erase(end());
		}

		void pop_front()
		{
			erase(begin());
		}

	private:
		Node* _head;
	};

}

  
  
  实际涉及函数如下:

		//尾插
		void push_back(const T& val)
		{
			//写法二
			insert(end(), val);
		}

		//尾插
		void push_back(T&& val)
		{
			//写法二
			insert(end(), std::forward<T>(val));
		}
		//插入
		iterator insert(iterator pos, const T& val)
		{
			//保存节点
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			//新增节点
			Node* newnode = new Node(val);
			//修改关系: prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			//返回值
			return iterator(newnode);
		}

		//插入
		iterator insert(iterator pos, T&& val)
		{
			//保存节点
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			//新增节点
			Node* newnode = new Node(std::forward<T>(val));
			//修改关系: prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			//返回值
			return iterator(newnode);
		}
	//单节点
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _prev;
		list_node<T>* _next;

		list_node(const T& val = T())
			:_data(val)
			, _prev(nullptr)
			, _next(nullptr)
		{}

		list_node(T&& val)
			:_data(std::forward<T>(val))
			, _prev(nullptr)
			, _next(nullptr)
		{}
	};

  
  相关演示与解释:
在这里插入图片描述

  
  
  
  

8、新的类功能

8.1、新增默认成员函数:两个

  1)、C++11下默认成员函数
  在原先的C++类中,有6个默认成员函数:构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、const 取地址重载。

  C++11 新增了两个默认成员函数:移动构造函数、移动赋值运算符重载。注意,默认成员函数,我们不写,编译器也会自动生成。
  
  2)、一些说明
  问题:哪些场景需要自己写移动构造、移动赋值?
  回答:
  1、拷贝对象需要深拷贝时,就可以自己写移动构造和移动赋值、拷贝构造和拷贝赋值。前者解决的是右值,后者可解决左值情况。
  2、即使没有移动构造和移动赋值,由于拷贝构造和拷贝赋值是const左值引用,因此对于右值也能适用。
  
  
  问题:什么情况下才会生成默认的移动构造、移动赋值(条件说明)?这两个默认函数具体做了哪些事情?
  回答:
  1、若没有显示实现移动构造函数,且没有显示实现析构函数 、拷贝构造、拷贝赋值重载,那么编译器会自动生成一个默认移动构造。
  2、默认生成的移动构造函数,对于内置类型成员会按字节拷贝(浅拷贝);对自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  3、默认移动赋值重载同理。
  
  3)、相关演示
  演示代码:

#include"mystring.h"//此处使用的是我们自己写的string,目的在于方便观察。
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:
	mystring::string  _name;
	int _age;
};


void test12()
{
	Person s1("谢灵运",22);

	Person s2 = s1;//左值
	Person s3 = std::move(s1);//右值

	Person s4;
	s4 = std::move(s2);//右值
}

在这里插入图片描述

  
  
  
  

8.2、关键字

8.2.1、default和delete、如何创建一个只在堆区的

  1)、基本说明
  强制生成默认函数的关键字default: 当需要使用某个默认成员函数,但该函数没有默认生成时,就可以使用该关键字。
  举例:当提供了拷贝构造,就不会生成移动构造,那么可以使用default关键字显示指定移动构造生成。
在这里插入图片描述

	Person(Person&& p) = default;
	Person& operator=(Person&& p) = default;
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(Person&& p) = default;
	Person& operator=(Person&& p) = default;

	~Person()//析构
	{}

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

  
  
  
  禁止生成默认函数的关键字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:
	mystring::string _name;
	int _age;
};

void test13()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
}

  
  
  
  2)、如何创建一个只在堆上的类?要求使用到delete关键字
  如下述,若不加约束条件,通常情况下一个类可以在多种内存区域创建。

class HeapOnly
{
private:
	//……

private:
	char* _str;
	//...
};

void test14()
{
	HeapOnly hp1;//在栈上
`	static HeapOnly hp2;//在静态区

	HeapOnly* ptr = new HeapOnly;//可以在堆上
}

  现在,我们需要创建一个只能在堆上申请出的类,即只有HeapOnly* ptr = new HeapOnly;可成立,该如何做?
  
  1、一种方法是将构造函数私有化,写在private类的访问限定符里,同时在public区域提供一个函数,用于外部调用,可在该函数中使用New构建类。(此法类似于之前讲述的如何设计一个只能在栈上定义的对象?详细见:类和对象(四))
  
  
  2、虽然上述方法能够解决问题, 但题目要求我们使用上delete关键字。因此,这里提供一个方法:

class HeapOnly
{
public:
	HeapOnly()
	{
		//……
	}

	~HeapOnly() = delete;


private:
	char* _str;
	//...
};

  相关说明如下:
在这里插入图片描述
  
  延伸问题:在上述基础上,类中动态申请一个空间,如何释放?
  如下,构造函数中我们动态申请了空间,由于HeapOnly类的析构被限制,无法调用,因此_str申请到的空间并不会被释放,存在内存泄漏。同理,ptr指针我们也不能直接delete ptr,因为这样实际上也是调用了析构。

class HeapOnly
{
public:
	HeapOnly()
	{
		_str = new char[10];
	}

	~HeapOnly() = delete;

private:
	char* _str;
	//...
};

void test14()
{
	HeapOnly* ptr = new HeapOnly;//在堆上创建HeapOnly类
	delete ptr;//error
}

在这里插入图片描述

  解决方案如下:

class HeapOnly
{
public:
	HeapOnly()
	{
		_str = new char[10];
	}

	~HeapOnly() = delete;

	void Destroy()
	{
		delete[] _str;

		operator delete(this);
	}

private:
	char* _str;
	//...
};

void test14()
{

	HeapOnly* ptr = new HeapOnly;

	ptr->Destroy();
	//operator delete(ptr);//此处效果等同于operator delete(this);
}

  1、可以在类中直接使用一个Destroy()函数,我们手动释放相应空间。operator delete(this);能达到释放类的作用。实际上,它是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。相关介绍与回顾:内存管理和模板初阶。

  2、如果此处不在Destory中调用operator delete(this);,也可以在类外使用,只需要将其换为operator delete(ptr);
  
  
  
  
  
  
  
  

8.2.2、final和override

  相关内容见继承与多态。
  
  
  
  
  

9、可变参数模板

9.1、基础概念介绍

  1)、为什么需要可变参数模板?
  函数中的可变参数:传递的是变量。可追溯到C语言,如下述printf中。
在这里插入图片描述

  模板中的可变参数:传递的是类型。C++98/03中,类模版和函数模版中只能含固定数量的模版参数,而C++11中引入可变参数模板,使得模板参数数量不被限制死,可根据我们的需求而定。
  
  
  2)、相关格式与示例
  相关格式如下:下述参数args前面有省略号,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。

template <class ...Args>
void ShowList(Args... args)
{

}

  1、 Args是一个模板参数包,args是一个函数形参参数包
  2、声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
  
  使用如下:用这样的类去实例化对象/使用函数时,我们能够传递不同数量的参数,且参数的类型可不同。

	string str("string");
	vector<int> v1;
	ShowList();
	ShowList(1,1.1);
	ShowList(1, 1.1, 'A', str, v1);

  
  
  
  3)、如何获取参数包大小?sizeof计算数据包个数
  以上述代码为例,我们可以通过sizeof...(args) 获取到参数包的个数,这里需要注意它的写法,中间...不能省略。

template <class ...Args>
void ShowList(Args... args)
{
	cout <<"当前参数包个数:" << sizeof...(args) << endl;
	cout << endl;
}

void test15()
{
	string str("string");
	vector<int> v1;
	ShowList();
	ShowList(1,1.1);
	ShowList(1, 1.1, 'A', str, v1);
}

  
  实际上,args参数包底层是用数组实现的,相当于使用数组接受到这些传入的参数。但需要注意以下这种写法是不允许的:

template <class ...Args>
void ShowList(Args... args)
{
	for (size_t i = 0; i < sizeof...(args); ++i)
	{
		cout << args[i] << " ";
	}
	cout << endl;
}

在这里插入图片描述

  那么,如果我们需要获取参数包中的参数,可以如何做?
  
  
  
  4)、如何展开参数包获取参数?递归、初始化列表推导

在这里插入图片描述使用递归展开参数包

  相关代码如下:ShowList(args...);注意这里的写法。

void ShowList()
{
	cout << endl;
}

template <class T,class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << "ShowList(" << val << ", " << sizeof...(args) << "参数包)" << endl;
	ShowList(args...);
}

void test16()
{
	string str("hello");
	ShowList(1, 'A', str);
	ShowList(1, 'A', str, "world",3.14);

	vector<int> v = { 1,2,3 };
	ShowList(v[0],v[1],v[2]);
}

  演示结果如下:
  以ShowList(1, 'A', str, "world",3.14);为例,每次传入的首个参数作为const T& val被当前递归层取出使用(这里是打印),后续参数包又继续参与递归。
  因此,①(1, 'A', str, "world",3.14),当val=1时,args=('A', str, "world",3.14);②递归,当val='A'时,args=(str, "world",3.14);③如此反复递归,直到递归终止函数void ShowList()

在这里插入图片描述
  
  
  

在这里插入图片描述逗号表达式展开参数包

  引入:实际上,我们查看SQL库,一些使用可变参数包的地方,并没有向上述一样需要额外提供一个参数const T& val。这里我们将介绍另一种展开参数包的方法,即使用逗号表达式。

在这里插入图片描述

  
  
  相关代码如下:

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

template <class ...Args>
void ShowList(Args... args)
{
	int a[] = { PrintArg(args)...0 };
	cout << endl;
}

void test17()
{
	string str("hello");
	ShowList(1, 'A', str);
	ShowList(1, 'A', str, "world", 3.14);

	vector<int> v = { 1,2,3 };
	ShowList(v[0], v[1], v[2]);
}

  分析:
  1、int a[] = { PrintArg(args)...,0 }中,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成[(printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... ],最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]
  2、逗号表达式会按顺序执行逗号前面的表达式,使用{ PrintArg(args)...,0 }创建数组的过程中,会先执行逗号表达式前面的部分iPrintArg(args),再执行0,即int a[]={0}。对于前者,其是一个函数调用,我们就可在PrintArg中依次获取到参数并使用(这里是打印)。
  3、通过上述方式,在构造int数组的过程中,就将参数包展开了。

  演示结果如下:
在这里插入图片描述

  
  
  
  

9.2、STL容器中的emplace相关接口函数

  1)、基本说明
  C++11中新增的两接口emplace、emplace_back,其中就使用到了可变模板参数,只是这里在其基础上增加了引用。
在这里插入图片描述

  
  
  2)、相关使用
  如下,通常情况下emplace_back和push_back用法上没什么太大的区别

	vector<int> v1;
	v1.push_back(1);
	v1.emplace_back(2);

  而如果是下述情况,对于emplace支持可变参数,拿到构建pair对象的参数后自己去创建对象,push_back则要调用make_pair先构建一个pair再返回。即emplace_back可以直接构造,push_back是先构造,再移动构造/拷贝构造。

	vector<pair<mystring::string, int>> v2;
	v2.push_back(make_pair("sort", 1));

	v2.emplace_back(make_pair("sort", 1));
	v2.emplace_back("sort", 1);

  
  
  我们再用一个日期类来演示:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}

	Date& operator=(const Date& d)
	{
		cout << "Date& operator=(const Date& d))" << endl;
		return *this;
	}

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

  
  可看到emplace_back和push_back调用如下:
在这里插入图片描述

  
  
  
  
  

10、lambda表达式

10.1、问题引入

  1)、能像函数一样使用的对象/类型有哪些?
  回答:
  1、函数指针
  2、仿函数/函数对象
  3、lambda
  
  关于仿函数的使用补充:如下两处Compare都需要传递仿函数。
  若仿函数是作为函数参数使用,则传递的是对象,需要在仿函数后加(),可用该仿函数创建一个对象传入,也可使用一个匿名对象。比如less<int>()

//std::sort
template <class RandomAccessIterator, class Compare>
  void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

  若仿函数作为模板参数使用,则传递的是类型,可以不加(),比如less<int>

//std::priority_queue
template <class T, class Container = vector<T>,
  class Compare = less<typename Container::value_type> > class priority_queue;

  
  
  一些问题说明:
  如下述,假设我们有个自定义类型,用其去定义一堆数据。比如这里的Goods商品,需要对这些数据按各种方式(名字、价格、评价、等等)进行排序。

struct Goods//货品
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价
	//……
	
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

  用上述类一定一堆商品,并对其排序:

	vector<Goods> v = { { "麻辣烤鱼", 39, 4.5 }, { "桥头排骨", 17, 4.8 }, { "千层蛋糕", 25, 4.9 } , { "牛油拌饭", 21, 4.7 } };
	for (auto& e : v)
	{
		cout << e._name <<"    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;

  假设使用std::sort排序,则需要传递仿函数,而Goods的成员类型各有不同,这就意味着我们需要写多个仿函数用于传递:

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

struct CompareEvaluateGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._evaluate > gr._evaluate;
	}
};

//……

  演示结果如下:这意味着一旦自定义类型中成员变量很多,且其类型各有不同,那么要达成各类参数排序(各方面比较评估),需要写多个仿函数。

在这里插入图片描述

void test19()
{
	vector<Goods> v = { { "麻辣烤鱼", 39, 4.5 }, { "桥头排骨", 17, 4.8 }, { "千层蛋糕", 25, 4.9 } , { "牛油拌饭", 21, 4.7 } };
	for (auto& e : v)
	{
		cout << e._name <<"    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;

	sort(v.begin(), v.end(), ComparePriceLess());
	for (auto& e : v)
	{
		cout << e._name << "    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;


	sort(v.begin(), v.end(), CompareEvaluateGreater());
	for (auto& e : v)
	{
		cout << e._name << "    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;

}

  
  因此,为了解决这类问题上仿函数的局限性,C++11提出了lambda表达式。
  
  
  
  
  

10.2、lambda表达式基本介绍

10.2.1、基本语法

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

  介绍:
  [capture-list] 捕捉列表。该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数。捕捉列表能够捕捉上下文中的变量供lambda函数使用。

  (parameters)参数列表。与普通函数的参数列表一致,若不需要参数传递,则可以连同()一起省略。

  mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

  ->return-type返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。PS:返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

  {statement}函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
  
  
  实际如何使用见下述。
  
  

10.2.2、使用举例(基本使用、捕捉列表介绍)

  1)、两数相加的lambda (如何写和如何调用)
  如何写: 如下,add1、add2等号后面的两串都是两数相加的lambda。
  如何调用: 可将lambda表达式理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。如下,add1add2是用来调用该lambda表达式的。

	//[捕捉列表](参数列表)mutable->返回值类型 {函数体};
	auto add1=[](int a, int b)->int {return a + b; };
	cout << add1(1, 2) << endl;

	auto add2 = [](double x, double y) {return x + y; };//返回值类型明确情况下,可省略返回类型由编译器自动推导
	cout << add2(1.1, 2.2) << endl;

在这里插入图片描述

  
  
  
  2)、交换变量的lambda (多行说明、无参说明)
  如下,①若无返回值,可省略,也可写为->void;②可以看到函数体比较冗杂,这时候写为一排观察起来不太方便。

	auto swap1 = [](int& val1, int& val2)->void { int tmp = val1; val1 = val2; val2 = tmp;  };
	
	int x1 = 6, x2 = 3;
	swap1(x1, x2);
	cout << x1 << " " << x2 << endl;

  因此,我们也可以如下,将函数体部分换行写:

	auto swap2 = [] (char& ch1, char& ch2)
	{
		char tmp = ch1;
		ch1 = ch2;
		ch2 = tmp;
	};

	char c1 = 'z', c2 = 'w';
	swap2(c1, c2);
	cout << c1 << " " << c2 << endl;

在这里插入图片描述

  
  
  
  
  3)、不传参数交换变量的lambda(介绍捕捉列表

  不传递参数,则可以使用捕捉列表达成:

在这里插入图片描述
  
  如下述代码:

	int x1 = 6, x2 = 3;
	cout << x1 << " " << x2 << endl;
	
	auto swap1 = [x1, x2]()mutable
	{
		x1 = 5;
		int tmp = x1;
		x1 = x2;
		x2 = tmp;
	};
	swap1();
	cout << x1 << " " << x2 << endl;

	auto swap2 = [&x1,&x2] ()mutable
	{
		int tmp = x1;
		x1 = x2;
		x2 = tmp;
	};

	swap2();
	cout << x1 << " " << x2 << endl;

  
  
  对捕捉列表的介绍:

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

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

[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

[this]:表示值传递方式捕捉当前的this指针

  
  
  2、相关注意事项:

   Ⅰ. 父作用域指包含lambda函数的语句块
  
   Ⅱ. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
   举例一:[=, &a,&b],以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。
   举例二:[&,a,this],值传递方式捕捉变量a和this,引用方式捕捉其他变量。
  
  Ⅲ.捕捉列表不允许变量重复传递,否则就会导致编译错误。
   举例一:[=, a]=已经以值传递方式捕捉了所有变量,则此处单独列出a属于重复捕捉。
  
   Ⅳ、 在块作用域以外的lambda函数捕捉列表必须为空。
  
   Ⅴ、 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  
   Ⅶ、lambda表达式之间不能相互赋值,即使看起来类型相同

  
  
  

10.2.3、示例演示

  1)、修改上述问题引入
  如下此例,之前我们使用的是仿函数,现在让我们将其修改为lambda表达式。

struct Goods//货品
{
	string _name;  // 名字
	double _price; // 价格
	double _evaluate; // 评价
	Goods(const char* str, double price, double evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};


void test19()
{
	vector<Goods> v = { { "麻辣烤鱼", 39, 4.5 }, { "桥头排骨", 17, 4.8 }, { "千层蛋糕", 25, 4.9 } , { "牛油拌饭", 21, 4.7 } };
	for (auto& e : v)
	{
		cout << e._name <<"    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;
}

  修改结果如下:

void test22()
{
	vector<Goods> v = { { "麻辣烤鱼", 39, 4.5 }, { "桥头排骨", 17, 4.8 }, { "千层蛋糕", 25, 4.9 } , { "牛油拌饭", 21, 4.7 } };
	for (auto& e : v)
	{
		cout << e._name << "    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; });
	for (auto& e : v)
	{
		cout << e._name << "    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;


	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; });
	for (auto& e : v)
	{
		cout << e._name << "    价格:" << e._price << "    评价:" << e._evaluate << endl;
	}
	cout << endl;

}

在这里插入图片描述

  
  
  
  

10.2.4、相关底层

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

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

	// lamber
	auto r2 = [=](double monty, int year)->double {return monty * rate * year;};
	r2(10000, 2);
}

  对函数对象: 可以像函数一样使用的对象。在它的类中重载了operator()运算符,使其调用方式和函数类似,因此又称为仿函数。

  对lambda表达式: 实际底层中,编译器对其处理方式与函数对象的处理方式相同。即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
  ①这个生成的类名称为lambda+uuid,用于区别;②在捕获参数时,就是把对应参数传递给operator()。

在这里插入图片描述

  
  
  
  
  

11、包装器

11.1、function

11.1.1、问题引入:为什么

  1)、问题引入
  问题:如下述这样一个代码,func可以是什么?
  回答:①可以是一个函数;②可以是一个仿函数(函数对象);③可以是一个lambda表达式。

ret = func(x);

  问题:它会存在什么问题?
  回答:效率问题,体现在模板实例化中。
  
  如下述代码,我们创建一个useF 的模板,F f传入类似于上述的func, T x是func中的参数值。

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

  现在,使用这个模板进行实例化,其第一参数分别为函数f、仿函数Functor、lambda表达式,第二参数相同。问题:模板实例化时,会实例化一份,还是不同的三份?static int count可帮助我们检验相关结果,若实例化一份,静态变量count应是同一个。

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

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

void test24()
{
	// 传入函数
	cout << useF(f, 5.20) << endl;
	cout << endl;

	// 传入仿函数
	cout << useF(Functor(), 5.20) << endl;
	cout << endl;

	// 传入lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 5.20) << endl;
	cout << endl;

}

  
  验证如下:useF函数模板实例化了三份。这种多类型调用一定程度上会带来模板的效率低下,实例化多份的问题。
在这里插入图片描述

  由此引出了下述关于包装器的内容。
  
  
  

11.1.2、基本格式:是什么

  1)、基本说明
  function包装器:也称为适配器,C++中的function本质是一个类模板。相关链接:std::function,其在头文件<functional>中。

template <class T> function;     // undefined

template <class Ret, class... Args>
class function<Ret(Args...)>;

  模板参数说明:
  Ret: 普通模板参数,作为被调用函数的返回类型。
  Args…:模板参数包,作为被调用函数的形参。
  
  
  
  2)、基本使用演示

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

struct Functor
{
public:
	double operator() (double a, double b)
	{
		return a / b;
	}
};


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

void test25()
{
	//class function<Ret(Args...)>

	//使用function包装一个函数
	function<int(int, int)> F1 = f;
	cout << F1(2, 5) << endl;

	//使用function包装一个仿函数
	function<double(double, double)> F2 = Functor();
	cout << F2(8, 2) << endl;

	//使用function包装一个类静态成员函数
	function<int(int, int)> F3 = Plus::plusi;
	cout << F3(2, 8) << endl;

	//使用function包装一个类非静态成员函数
	//function<double(double, double)> F4 = Plus::plusd();//error
	function<double(Plus,double, double)> F4 = &Plus::plusd;
	cout << F4(Plus(), 1, 3) << endl;
	//1、语法规定取非静态成员函数的地址,需要加&。
	//2、成员函数的指针不能直接调用,需要传递对象。
	//3、此处可用bind解决。


	//使用function包装一个lambda表达式
	function<int(int)> F5 = [](int i)->int {return i * i; };
	cout << F5(3) << endl;
}

在这里插入图片描述

  
  
  

  
  

11.1.3、问题解决:有什么用

  1)、针对问题引入的修改
  基于上述演示,我们将问题引入部分进行修改:
在这里插入图片描述


template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}

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

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

void test26()
{
	//包装函数
	function<double(double)> F1 = f;

	//包装仿函数
	function<double(double)> F2 = Functor();

	//包装lamber表达式
	function<double(double)> F3 = [](double d)->double { return d / 4; };


	//传入函数
	cout << useF(F1, 5.20) << endl;
	cout << endl;

	//传入仿函数
	cout << useF(F2, 5.20) << endl;
	cout << endl;

	//传入lamber表达式
	cout << useF(F3, 5.20) << endl;
	cout << endl;
}

  
  
  
  
  

11.2、bind

11.2.1、基本说明

  1)、问题引入
  根据上述,对于类的非静态成员函数,在使用function包装时,需要额外传递类。这样即使在包装后,使用类模板实例化不同类型时,就不能达到只实例化一份的效果。

	//使用function包装一个类非静态成员函数
	//function<double(double, double)> F4 = Plus::plusd();//error
	function<double(Plus,double, double)> F4 = &Plus::plusd;
	cout << F4(Plus(), 1, 3) << endl;
	//1、语法规定取非静态成员函数的地址,需要加&。
	//2、成员函数的指针不能直接调用,需要传递对象。
	//3、此处可用bind解决。

  
  
  
  2)、基本说明
在这里插入图片描述
  std::bind:定义在头文件<functional>中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象,来“适应”原对象的参数列表。相关链接:std::bind、std::placeholders。


template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);


template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

在这里插入图片描述

  一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
  
  

11.2.2、使用演示

  1)、调整参数顺序
  演示代码如下:传入的都是x,y,获取到的结果不同。

int Div(int a, int b)
{
	return a / b;
}


//using namespace placeholders;
//此处展开是方便placeholders::_1, placeholders::_2直接写为_1,_2

void test27()
{

	调整顺序 
	int x = 20, y = 10;
	cout << Div(x, y) << endl;

	//_1 _2.... 定义在placeholders命名空间中,代表绑定函数对象的形参
	bind(Div, placeholders::_1, placeholders::_2);

	auto bindFunc1 = bind(Div, _1, _2);//可以直接使用auto自动推导
	function<int(int, int)> bindFunc2 = bind(Div, _2, _1);//也可以上一层包装器

	cout << bindFunc1(x, y) << endl;
	cout << bindFunc2(x, y) << endl;
}

  演示结果如下:
  1、bind(Div, placeholders::_1, placeholders::_2);,对Div进行绑定,_1代表Div的形参a,_2代表Div的形参b。
  2、对bindFunc1(x, y),有bindFunc1 = bind(Div, _1, _2),则x对应于_1,即形参a,y对应于_2,即形参b。因此结果为x/y。
  3、对bindFunc2(x, y),有bindFunc2 = bind(Div, _2, _1),则x对应于_2,即形参b,y对应于_1,即形参a。因此结果为y/x。

在这里插入图片描述

  
  
  
  2)、调整个数:解决上述非静态成员函数与普通函数同时使用一个类实例化的问题
  相比于上述调整顺序,bind一般用于调整个数。演示代码如下:

int Div(int a, int b)
{
	return a / b;
}

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

int Mul(int a, int b, double rate)
{
	return a * b * rate;
}

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

  ①、如果此时使用funtion包装,对于非静态成员函数sub,则需要多传递一个参数,即function<int(Sub, int, int)> funcSub = &Sub::sub;
  ②、同样,这里由于Mul有三个参数,包装时为:function<int(int, int,double)> funcMul = Mul;

  ③、假设我们的需求为让上述几个函数都能使用同样的模板,比如下述的map<string, function<int(int, int)>>,那么就可用bind解决。bind使用于绑定固定参数,从而达到调整个数的效果,但其局限在与所绑定的参数在实际使用中是固定的,没有修改需求。

using namespace placeholders;

void test27()
{
	 调整个数
	function<int(int, int)> funcPlus = Plus;

	//function<int(Sub, int, int)> funcSub = &Sub::sub;
	function<int(int, int)> funcSub = bind(&Sub::sub, Sub(), _1, _2);//把第一个参数绑死,即Sub()

	function<int(int, int)> funcMul = bind(Mul, _1, _2, 1.5);//把第三个参数绑死,即rate

	map<string, function<int(int, int)>> opFuncMap =
	{
		{ "+", Plus},
		{ "-", bind(&Sub::sub, Sub(), _1, _2)}
	};

	cout << funcPlus(1, 2) << endl;
	cout << funcSub(1, 2) << endl;
	cout << funcMul(2, 2) << endl;

	cout << opFuncMap["+"](1, 2) << endl;
	cout << opFuncMap["-"](1, 2) << endl;
}

在这里插入图片描述

  
  
  
  

12、多线程

  待补。
  
  
  1)、基本说明
  
  
  
  
  
  

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

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

相关文章

一、版本控制

1、什么是版本控制 1.1、版本控制的概念 版本控制&#xff08;Revision control&#xff09;是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。 1.2、版本控制的作用…

泛型方法、Function类的函数化编程与调用

0、引言 在项目开发的过程中&#xff0c;常常需要将一些高频复用的方法封装成工具类&#xff0c;例如最近学到的Redis缓存中&#xff0c;解决缓存穿透、解决缓存击穿的方法&#xff08;例如解决缓存穿透的问题的方法queryWithPassThrough&#xff09;&#xff0c;传入一个Long型…

谷粒商城:Oss endpoint can‘t be empty.问题

商品API &#xff0c;文件上传管理的时候 出现这个问题 解决两个方向 1.springBoot、alibabaCloud、springCloud、aliyunOSS 之间的版本问题&#xff0c;我的是下面的版本可以运行了。 // springBoot版本 2.7.7 <groupId>org.springframework.boot</groupId> &l…

中关村论坛 | 金融业从增量到存量博弈背后两大原因 更重要的是……

在数字经济浪潮下&#xff0c;中国金融业正在经历数字化转型的深刻变革。为研判金融科技行业发展趋势和前景&#xff0c;探索金融创新与监管安全的边界&#xff0c;“2023中关村论坛金融科技论坛”于5月29日召开。 中电金信常务副总经理冯明刚与中国银行软件中心副总经理康钧伟…

链表:虚拟头节点你会用吗?

大家好&#xff0c;我是三叔&#xff0c;很高兴这期又和大家见面了&#xff0c;一个奋斗在互联网的打工人。 前言&#xff1a;什么是链表 什么是链表&#xff0c;链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;一个是数据域一个是指…

提高用户忠诚度的 4 种客户保留策略

什么是客户保留&#xff1f;简而言之&#xff0c;客户保留是指企业用来鼓励现有客户群重复购买和持续忠诚度的策略和战术。根据最近的研究&#xff0c;多达68%的客户在觉得公司不重视他们的业务时会转向竞争对手。 这就是为什么客户保留对各行各业的企业都如此重要的原因。与获…

《程序员面试金典(第6版)》面试题 16.25. LRU 缓存(自定义双向链表,list库函数,哈希映射)

题目描述 设计和构建一个“最近最少使用”缓存&#xff0c;该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值)&#xff0c;并在初始化时指定最大容量。当缓存被填满时&#xff0c;它应该删除最近最少使用的项目。 题目传送门&#xff1a;…

消息队列内容

问题有哪些&#xff1f; &#xff08;1&#xff09;消息队列为什么会出现&#xff1f; &#xff08;2&#xff09;消息队列能用来干什么&#xff1f; &#xff08;3&#xff09;使用消息队列存在的问题&#xff1f; &#xff08;4&#xff09;如何解决重复消费的问题&#…

PyCharm安装使用教程

简介 PyCharm是一种PythonIDE&#xff08;Integrated Development Environment&#xff0c;集成开发环境&#xff09;&#xff0c;带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#xff0c;比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单…

docker-安装redis集群

目录 1.服务器列表 2.安装docker 3.docker内网IP地址配置 4.docker安装redis集群 1.选择合适数据位置 2.循环生成redis配置目录 3.打开宿主机防火墙端口 4.循环生成redis容器 5.创建集群命令 6.命令行集群验证 1.服务器列表 服务器列表 nameip远程端口用户名/密码cen…

One2Multi Graph Autoencoder for Multi-view Graph Clustering

One2Multi Graph Autoencoder for Multi-view Graph Clustering | Proceedings of The Web Conference 2020 (acm.org) 目录 Abstract 1 Introduction 2 Model 2.1 Overview 2.2 One2Multi Graph Convolutional Autoencoder Informative graph convolutional encoder M…

Eclipse教程 Ⅸ

今天继续来学习Eclipse 快速修复、Eclipse 浏览菜单、Eclipse 查找以及Eclipse 悬浮提示的内容&#xff01;老规矩&#xff0c;废话不多说&#xff0c;开始吧。 Eclipse 快速修复 使用快速修复 在 Eclipse 编辑器中当你输入字母时&#xff0c;编辑器会对你输入的内容进行错误…

PostgreSQL FDW

一、FDW简单理解 FDW (foreign-data wrapper&#xff0c;外部数据包装器)&#xff0c;PostgreSQL FDW 是一种外部访问接口&#xff0c;它可以被用来访问存储在外部的数据&#xff0c;这些数据可以是外部的pg数据库&#xff0c;也可以oracle、mysql等数据库&#xff0c;甚至可以…

大气气溶胶期末复习笔记

大气气溶胶期末复习笔记 大气气溶胶 广义&#xff1a;指悬浮在大气中的各种固态和液态微粒与大气构成的混合体系 狭义&#xff1a;指大气中悬浮的各种固态粒子&#xff0c;简称气溶胶粒子 来源 直接注入 通过地表直接注入大气固体&#xff0c;液体物质的破碎过程中产生&…

筛质数—(埃氏筛欧拉筛)

埃氏筛&欧拉筛 埃氏筛欧拉筛 例题&#xff1a;AcWing 868. 筛质数 对欧拉筛的理解不是很深刻&#xff0c;写下自己的理解&#xff0c;加深一下理解&#xff0c;也方便后期忘记后再学习 埃氏筛 埃氏筛的主要思想是让质数x去筛掉x的所有合数&#xff0c;这个比较容易理解。…

机器学习知识经验分享之五:R语言安装

python语言用于深度学习较为广泛&#xff0c;R语言用于机器学习领域中的数据预测和数据处理算法较多&#xff0c;后续将更多分享机器学习数据预测相关知识的分享&#xff0c;有需要的朋友可持续关注&#xff0c;有疑问可以关注后私信留言。 目录 一、R语言介绍 二、R语言安装…

装饰器模式:实现类功能的动态扩展

一&#xff0c;简介 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许在不修改原有类结构的情况下&#xff0c;给一个对象动态添加额外的职责。通常情况下&#xff0c;扩展一个类的功能我们首先会想到用继承方式来实现&#xff0c…

7步搞懂手写数字识别Mnist

大家好啊&#xff0c;我是董董灿。 图像识别有很多入门项目&#xff0c;其中Mnist 手写数字识别绝对是最受欢迎的。 该项目以数据集小、神经网络简单、任务简单为优势&#xff0c;并且集合了CNN网络中该有的东西&#xff0c;可谓麻雀虽小&#xff0c;五脏俱全。 非常适合新手…

Fourier分析入门——第12章——Fourier变换的性质

目录 第12章 Fourier变换的性质 12.1 引言 12.2 Fourier变换性质的相关定理 12.2.1 线性定理(Linearity) 12.2.2 伸缩性定理(Scaling) 12.2.3 时间/空间平移定理(Shift) 12.2.4 频移定理 12.2.5 调制定理(Modulation) 12.2.6 微分定理(Differentiation) 12.2.7 积分定…

冒泡排序详解(Bubble Sort)

本文已收录于专栏 《算法合集》 目录 一、简单释义1、算法概念2、算法目的3、算法思想4、算法性质 二、核心思想构建排序 三、图形展示宏观展示微观展示 四、算法实现实现思路代码实现客户端调用构造堆的方法元素交换的方法元素比较的方法 运行结果 五、算法描述1、问题描述2、…