C++11简单介绍

目录

  • 1.列表初始化
    • {}
    • initializer_list
  • 声明
    • auto
    • decltype
    • nullptr
  • STL中的一些变化
    • 1.新增了一些容器
      • array,
      • unorganized_XXX,
      • forward_list
    • 对已有容器,增加一些接口,
  • 右值引用和移动语义
    • 左值引用
    • 右值引用
    • 交叉引用问题
    • 左值引用的使用场景
    • 右值引用解决问题
    • 完美转发
      • 模板中的&& 万能引用(引入)
      • std::forward 完美转发在传参的过程中保留对象原生类型属性(解决)
  • 新的类功能
    • 强制生成默认函数的关键字default:
    • 禁止生成默认函数的关键字delete:
    • 移动构造函数和移动赋值运算符重载
  • 可变参数模板
    • 可变参数模板
    • emplace
  • lambda表达式
    • 引入:
    • lambda------C++11
    • 捕捉列表
    • lambda 类型
    • lambda本质
  • 包装器
    • 引入 function:
    • 解决 引入function
    • 使用 function:
    • 应用:150. 逆波兰表达式求值
      • 常规写法:
      • 运用包装器之后:
    • bind
  • 线程库
    • 1.问题描述:两个线程对同一个变量x++
      • 解决问题(加锁),讨论问题(里外加锁问题)
      • 改进讨论问题 原子操作
    • 2.问题描述:thread可执行函数对象参数不能是左值引用
      • 解决方案一:采用指针
      • 解决方案二:std::ref()
    • 3.lock_guard与unique_lock
      • 问题引入:
      • 解决方案一:加锁
      • 解决(解决方案一引申出的问题):
        • 初步解决:
        • 使用lock_guard 和 unique_guard解决问题
    • 4.支持两个线程交替打印,一个打印奇数,一个打印偶数
      • 错误代码展示:
      • 解决:

1.列表初始化

{}

//多参数
struct Point
{
	int _x;
	int _y;

	Point(int x,int y)
		:_x(x)
		,_y(y)
	{
		cout << "Point(int x,int y)" << endl;
	}
};

class A
{
public:
	//explicit A(int a)//不让他进行隐式类型转化
	//	:_a(a)
	//{}

	A(int a)
		:_a(a)
	{}
private:
	int _a;
};

int main()
{
	Point p={ 1,2 };
	int a = 1;
	int b = { 2 };
	int c{ 3 };
	int array1[]{ 1,2,3,4 };
	int array2[4]{ 0 };
	Point pp{ 3,4 };

	//C++98需要先new,然后再初始化
	int* ptr1 = new int[5];
	//C++11
	int* ptr2 = new int[5]{ 1,2,3 };//1,2,3,0,0
	Point* ptr2 = new Point[2]{ {1,1},{2,2} };
	//Point* ptr3=new Point[2{1,1,1,2};//这样不行,可以编译通过,但是达不到目的
	//new是C++的特性,如果需要支持那么需要自己提供构造函数

	A aa1(1);
	A aa2 = 2;//会发生隐式类型转化

	//隐式类型转化支持
	string s1("hello");
	string s2 = "world";
	vector<string> v;
	v.push_back(s1);
	v.push_back("sakeww");

	return 0;
}

initializer_list

引入:

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

	vector<int> v2 = { 1,2,3,4 }; 
	v2={2,3,6};//重新初始化成2,3,6。

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

	list<int> lt2 = { 1,2,3,4,5,6 };
	pair<string, string> pr("mid", "中间");
	map<string, string> mss = { {"left","左边"},pr,make_pair("right","右边")};

	return 0;
}

这里有一份我们自己实现的list代码,只不过这个代码不支持:initializer_list,可以用它来看效果。
链接: List的模拟实现和实现中遇见的一些问题C++

	void test1()
	{
		list<int> lt = { 1,2,3,4 };
		for (auto e : lt)
		{
			cout << e << endl;
		}
		cout << endl;
	}

C++11简单介绍_第1张图片
增添:

		list(initializer_list<T> ilt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;

			for (auto e : ilt)
			{
				push_back(e);
			}
		}

或者

		list(initializer_list<T> ilt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			
			list<T> temp(ilt.begin(), ilt.end());
			std::swap(_head, temp._head);
		}

或者

		list(initializer_list<T> ilt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;

			typename initializer_list<T>::iterator it = ilt.begin();
			while (it != ilt.end())
			{
				push_back(*it);
				++it;
			}
		}

声明

auto

会根据右边的值推到左边的对象的类型,(自动推导)
便利用处:
1.迭代器
2.范围for,不要形成思维定势,范围for只能用auto

decltype

int main()
{
	int x = 1;
	double y = 0.2;
	cout << typeid(x).name() << endl;//int

	decltype(x) z = 3;
	cout << typeid(z).name() << endl;//int

	decltype(x * y) xy;
	cout << typeid(x*y).name() << endl;//double

	decltype(&x) p;
	cout << typeid(p).name() << endl;//int*
	return 0;
}

用处:
vector<这里不能用auto>
可以用decltype

nullptr

主要解决
C++将空定义为0,和特殊情况下有点混

STL中的一些变化

1.新增了一些容器

array,

固定大小的数组,静态数组,
array价值:
1.支持迭代器,更好兼容STL容器的玩法
2.对于越界的检查(最核心的地方)
C/C++对于越界的检查是一种抽查的行为(在OJ上面跑不过,在vs可以过)

unorganized_XXX,

forward_list

单向链表
支持头插头删,不支持尾插尾删,
不支持在当前位置前面插入和当前位置的删除,支持在当前位置后面插入和删除当前位置后面的
价值:每个节点省了一个指针

对已有容器,增加一些接口,

1.列表初始化initializer,
2.类似cbegin,cend
3.移动构造,移动赋值
4.右值引用版本的插入接口

右值引用和移动语义

左值引用

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取他的地址+可以对他赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义const修饰符后的左值,不能给他赋值,但是可以取它的地址,左值引用就是给左值的引用,给左值取别名。

价值:
减少拷贝,做一些输出性参数,修改返回值

//可以取地址对象,就是左值
int main()
{
	int a = 10;
	int& r1 = a;
	int* p = &a;
	int& r2 = *p;

	const int b = 10;
	const int& r3 = b;
	int b=a;
	return 0;
}

右值引用

右值也是一个表示数据的表达式,如:字面常量,表达式返回值。传值返回函数的返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号左边,右值不能取地址,右值引用就是对右值的引用,给右值取别名

//不能取地址对象,就是右值
int main()
{
	double x = 1.1, y = 2.2;

	//右值
	10;
	x + y;
	fmin(x, y);

	//下面是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);

	return 0;
}

交叉引用问题

//交叉引用
//左值引用能否引用右值
//右值引用能否引用左值
int main()
{
	//右值
	double x = 1.1, y = 2.2;
	10;
	x + y;
	fmin(x, y);

	//以下*p,b,c都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;

	//左值引用能否引用右值->不能直接引用
	//int& r1 = 10;
	//double& r2 = x + y;
	//double& r3 = fmin(x, y);
	//但是可以加const
	const int& r1 = 10;
	const double& r2 = x + y;
	const double& r3 = fmin(x, y); 

	//右值引用能否引用左值->不能,
	//int*&& rr1 = p;
	//int&& rr2 = *p;
	//int&& rr3 = b;
	//int&& rr4 = c;
	//但是右值引用可以引用move以后左值
	int*&& rr1 = move(p);
	int&& rr2 = move(*p);
	int&& rr3 = move(b);
	const int&& rr4 = move(c);//不行

	return 0;
}

举个栗子,左值引用需要引用右值
void push_back(const T& x)
我们传递的可能是 左值,也可能是右值


注意:
需要注意的是右值不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1,如果不想rr1被修改,可以用const int& rr1去引用,

int main()
{
	double x = 1.1, y = 2.2;
	int&& rr1 = 10;
	double&& rr2 = x + y;
	const double&& rr2 = x + y;

	cout << &rr1 << endl;
	cout << &rr2 << endl;
	//都可以取地址

	rr1 = 10;//还可以赋值
	rr2 = 3;
	//rr3不能被赋值
	return 0;
}

左值引用的使用场景

右值引用的产生是要弥补左值引用的不足
引出问题:

namespace sakeww
{
	class string
	{
	public:

		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)
			, _size(0)
			, _capacity(0)
		{
			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()
		{
			//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];
				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;
		}

		//只能使用传值返回
		string operator+(char ch)
		{
			string tmp(*this);
			push_back(ch);

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

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

//场景1
//左值引用做参数,基本完美解决所有问题
void func1(sakeww::string s)
{}

void func2(const sakeww::string& s)
{}

// 场景2
// 左值引用做返回值,只能解决部分问题
// string& operator+=(char ch) 解决了
// string operator+(char ch)   没有解决

int main()
{
	sakeww::string str("hello world");
	func1(str);
	func2(str);

	str += '!';
	str + '!';//会多两次深拷贝
	return 0;
}

解决问题:

namespace sakeww
{
	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)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}
//
		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 资源转移" << endl;

			this->swap(s);
		}
//

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

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

		string operator+(char ch)
		{
			string tmp(*this);
			push_back(ch);

			return tmp;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

// 场景1
// 左值引用做参数,基本完美的解决所有问题
void func1(sakeww::string s)
{}

void func2(const sakeww::string& s)
{}

// 场景2
// 左值引用做返回值,只能解决部分问题
// string& operator+=(char ch) 解决了
// string operator+(char ch)   没有解决

// 右值引用,如何解决operator+传值返回存在拷贝的问题呢?
// C++11 将右值分为:纯右值,将亡值
sakeww::string func3()
{
	sakeww::string str("hello world!");

	return str;
}

int main()
{
	sakeww::string str("hello world");

	func3();

	return 0;
}

右值引用解决问题

无论是自己写的还是库里面的都是这样的
C++11简单介绍_第2张图片
右值引用延长了对象的生命周期
右值对象并没有延长了对象的生命周期
右值引用只不过是作为一个属性匹配上移动构造,把资源转移走了,

完美转发

模板中的&& 万能引用(引入)

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;
}
//左值引用
//左值引用
//左值引用
//const 左值引用
//const 左值引用

右值引用之后,全部变成了左值

std::forward 完美转发在传参的过程中保留对象原生类型属性(解决)

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; }
// std::forward(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t));
}
int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}
右值引用
左值引用
右值引用
const 左值引用
const 右值引用

新的类功能

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

类成员变量初始化:C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化

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

class Person
{
public:
	Person() = default;
	Person(const char* name, int age)
		:_name(name)
		, _age(age)
	{}
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
private:
	sakeww::string _name;
	int _age=0;
};
int main()
{
	Person s1;
	return 0;
}

禁止生成默认函数的关键字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:
	sakeww::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	return 0;
}

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

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

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载
    最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。

C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

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

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

可变参数模板

可变参数模板


//template 
//void ShowList(Args... args)
//{
//  打印个数
//	cout << sizeof...(args) << endl;
//	cout << sizeof...(Args) << endl;
//}
//int main()
//{
//	ShowList(1);
//	ShowList(1, 'A');
//	ShowList(1, 'A', std::string("sort"));
//	return 0;
//}

//递归函数方式展开参数包
//template
//void ShowList(T t)
//{
//	cout << t << endl;
//}
//template 
//void ShowList(T val, Args... args)
//{
//	cout << typeid(val).name() <<":"<
//	ShowList(args...);
//}
//int main()
//{ 
//	ShowList(1);
//	ShowList(1, 'A');
//	ShowList(1, 'A', std::string("sort"));
//	return 0;
//}

//逗号表达式展开参数包
template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}

template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

//改进版本
template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}

template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

emplace

template <class... Args>
void emplace_back (Args&&... args);
int main()
{
	std::list< std::pair<int, char> > mylist;
	// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
	// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
	mylist.emplace_back(10, 'a');
	mylist.emplace_back(20, 'b');
	mylist.emplace_back(make_pair(30, 'c'));
	mylist.push_back(make_pair(40, 'd'));
	mylist.push_back({ 50, 'e' });
	return 0;
}

lambda表达式

引入:

可调用对象:
1.函数指针,qsort--------------C
2.仿函数,sort------------------C++98

排序的数据有很多项,我们需要根据其中一种数据进行排序(C++98)

#include
//lambda
struct Goods
{
	string _name;//名字
	double _price;//价格
	Goods(const char* str, double price)
		:_name(str)
		,_price(price)
	{}
};
struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};
struct ComparePriceLess;
struct ComparePriceGreater;

int main()
{
	vector<Goods> v = { { "苹果", 2.1 }, { "香蕉", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
	return 0;
}

需要写很多仿函数

lambda------C++11

匿名函数

struct Goods
{
	string _name;//名字
	double _price;//价格
	Goods(const char* str, double price)
		:_name(str)
		,_price(price)
	{}
};
int main()
{
	Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝",1.5} };
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, const Goods& r)->bool
		{
			return l._price < r._price;
		});
	return 0;
}
  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推 导。
  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
  6. 注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

捕捉列表

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(成员函数中包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(成员函数中包括this

普通实现交换两个数

int main()
{
	int a = 1, b = 2;
	auto swap1 = [](int a, int b)->void {int c = a; a = b; b = c; };
	
	swap(a, b);
	cout << a << " " << b << endl;

	swap1(a, b);
	cout << a << " " << b << endl;

	cout << " -------------------------" << endl;
	auto swap2 = [](int& a, int& b)->void {int c = a; a = b; b = c; };

	swap2(a, b);
	cout << a << " " << b << endl;
	return 0;
}

使用捕捉

int main()
{
	int a = 1, b = 2;
	auto swap1 = [&a, &b]()mutable{int c = a; a = b; b = c; };
	swap1();
	cout << a << " " << b << endl;

	auto swap2 = [=, &b](int a)mutable {int c = a; a = b; b = c; };
	swap2(a);
	cout << a << " " << b << endl;
	return 0;
}

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

解决引入的那个问题:

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

int main()
{
	vector<Goods> gds = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝",1.5} };
	auto compare1 = [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price;
	};
	
	sort(gds.begin(), gds.end(), compare1);
	sort(gds.begin(), gds.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price;});
	return 0;
}

lambda 类型

int main()
{
	auto swap1 = []() {};

	cout << typeid(swap1).name() << endl;
	return 0;
}
class <lambda_e135540ea225bb5a54d182ae61f85d7a>

lambda的类型是:class
class的名称是:lambda_+字符串
这个字符串是uid,
uid是有人通过算法生成的一段字符串,在当前程序中不会重复

lambda本质

仿函数

底层原理:
被处理成一个匿名仿函数,lambda+uid的一个反函数类

包装器

引入 function:

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;
	}
};
int main()
{
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

C++11简单介绍_第4张图片
通过上面的程序验证,我们会发现useF函数模板实例化了三份。

解决 引入function

#include 
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;
	}
};
int main()
{
	// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double { return d /
		4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

C++11简单介绍_第5张图片

使用 function:

#include 
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int 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;
	}
};
int main()
{
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函数对象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lamber表达式
	std::function<int(int, int)> func3 = [](const int a, const int b)
	{return a + b; };
	cout << func3(1, 2) << endl;
	// 类的成员函数
	std::function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

应用:150. 逆波兰表达式求值

链接: 150. 逆波兰表达式求值

常规写法:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;

        for(int i = 0;i<tokens.size();i++)
        {
            string& str = tokens[i];

            if(!(str=="+" || str=="-"||str=="*"||str=="/"))
                s.push(stoi(str));
            else
            {
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();

                if(str=="+")
                    s.push(right+left);
                else if(str == "-")
                    s.push(left-right);
                else if(str == "*")
                    s.push(left*right);
                else 
                    s.push(left/right);
            }
        }
        return s.top();
    }
};

运用包装器之后:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;
        map<string, function<int(int, int)>> opFuncMap =
            {
            { "+", [](int i, int j) {return i + j; } },
            { "-", [](int i, int j) {return i - j; } },
            { "*", [](int i, int j) {return i * j; } },
            { "/", [](int i, int j) {return i / j; } }
            };

        for(int i = 0;i<tokens.size();i++)
        {
            string& str = tokens[i];

            if(!(str=="+" || str=="-"||str=="*"||str=="/"))
                s.push(stoi(str));
            else
            {
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();

                s.push(opFuncMap[str](left, right));
            }
        }
        return s.top();
    }
};

bind

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

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

int main()
{
	function<int(int, int)> f1 = SubFunc;
	cout << f1(10, 3) << endl;

	function<int(int, int)> f2 = bind(SubFunc, placeholders::_1, placeholders::_2);
	cout << f2(10, 3) << endl;

	// 通过bind调整参数顺序
	function<int(int, int)> f3 = bind(SubFunc, placeholders::_2, placeholders::_1);
	cout << f3(10, 3) << endl;

	// 通过bind调整参数个数
	function<int(Sub, int, int)> f4 = &Sub::sub;
	cout << f4(Sub(), 10, 3) << endl;

	function<int(int, int)> f5 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << f5(10, 3) << endl;

	function<int(int)> f6 = bind(&Sub::sub, Sub(), 100, placeholders::_1);
	cout << f6(10) << endl;
	
	auto f7 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << f7(10, 3) << endl;

	return 0;
}

线程库

优势:

  1. C++11 的thread库可以跨平台,运用条件编译,先判断是那个系统,然后调用对应的库
  2. 面向对象的方式写的

1.问题描述:两个线程对同一个变量x++

C++11简单介绍_第6张图片

解决问题(加锁),讨论问题(里外加锁问题)

C++11简单介绍_第7张图片

C++11简单介绍_第8张图片
加锁加在外面,t1和t2就变成串行运行
加锁加在里面,t1和t2能交替并行运行
理论而言,加在里面更好,但是这个题的++x太快,所以为了 安全 只能加在外面
也就是说当++x处的内容比较多时,加在里面更好

改进加在里面的锁:如果要加锁在里面,不要使用互斥锁,使用自旋锁更好
C++11 没有自旋锁

改进讨论问题 原子操作

C++11简单介绍_第9张图片

2.问题描述:thread可执行函数对象参数不能是左值引用

void func(int& x)
{
	cout << &x << endl;
	x += 10;
}
int main()
{
	int n = 1;
	cout << &n << endl;
	thread t1(func, n);
	t1.join();
	cout << n << endl;
	return 0;
}

vs2013:
C++11简单介绍_第10张图片
vs2019:
C++11简单介绍_第11张图片
我们想象中的结果:
C++11简单介绍_第12张图片
问题解释:
这是vs2013支持C++11的bug

解决方案一:采用指针

C++11简单介绍_第13张图片

解决方案二:std::ref()

C++11简单介绍_第14张图片

3.lock_guard与unique_lock

问题引入:

vs2013下
C++11简单介绍_第15张图片

vs2019下:
在这里插入图片描述
即这段代码存现线程安全问题, 会出现交替打印的情况,两个线程同时插入,会出现覆盖的情况

解决方案一:加锁

void func(vector<int>& v, int n, int j, mutex& mtx)
{
	for (int i = 0; i < n; i++)
	{
		mtx.lock();
		v.push_back(i + j);
		mtx.unlock();
	}
}

int main()
{
	vector<int> v;
	mutex mtx;
	thread t1(func, std::ref(v), 10000, 100, std::ref(mtx));
	thread t2(func, std::ref(v), 10000, 200, std::ref(mtx));
	t1.join();
	t2.join();

	//for (auto e : v)
	//{
	//	cout << e << " ";
	//}
	//cout << endl;
	cout << v.size() << endl;
	return 0;
}

出现问题:加锁和解锁的过程中,中间代码出现问题该如何?

解决(解决方案一引申出的问题):

初步解决:

void func(vector<int>& v, int n, int j, mutex& mtx)
{
	try {
		for (int i = 0; i < n; i++)
		{
			mtx.lock();
			v.push_back(i + j);
			mtx.unlock();
		}
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		mtx.unlock();
	}

}

int main()
{
	vector<int> v;
	mutex mtx;
	thread t1, t2;
	try {
		t1 = thread(func, std::ref(v), 10000, 100, std::ref(mtx));
		t2 = thread(func, std::ref(v), 10000, 200, std::ref(mtx));
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	t1.join();
	t2.join();
	//for (auto e : v)
	//{
	//	cout << e << " ";
	//}
	//cout << endl;
	cout << v.size() << endl;
	return 0;
}

延伸问题:当加锁中间的代码出现问题的时候,锁可以用try catch回收,但是如果中间的 new delete出现问题呢?

使用lock_guard 和 unique_guard解决问题

手写 lock_guard


template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lock)
		:_lock(lock)
	{
		_lock.lock();
	}
	~LockGuard()
	{
		_lock.unlock();
	}
private:
	Lock& _lock;
};

void func(vector<int>& v, int n, int j, mutex& mtx)
{
	try {
		for (int i = 0; i < n; i++)
		{
			LockGuard<mutex> lgm(mtx);
			v.push_back(i + j);

			if (j == 1000 && i == 200)
				throw bad_alloc();
		}
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		mtx.unlock();
	}

}

int main()
{
	vector<int> v;
	mutex mtx;
	thread t1, t2;
	try {
		t1 = thread(func, std::ref(v), 10000, 100, std::ref(mtx));
		t2 = thread(func, std::ref(v), 10000, 200, std::ref(mtx));
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	t1.join();
	t2.join();
	//for (auto e : v)
	//{
	//	cout << e << " ";
	//}
	//cout << endl;
	cout << v.size() << endl;
	return 0;
}

调用库函数lock_guard

void func(vector<int>& v, int n, int j, mutex& mtx)
{
	try {
		for (int i = 0; i < n; i++)
		{
			lock_guard<mutex> lock(mtx);
			v.push_back(i + j);

			if (j == 1000 && i == 200)
				throw bad_alloc();
		}
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

}

int main()
{
	vector<int> v;
	mutex mtx;
	thread t1, t2;
	try {
		t1 = thread(func, std::ref(v), 10000, 100, std::ref(mtx));
		t2 = thread(func, std::ref(v), 10000, 200, std::ref(mtx));
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	t1.join();
	t2.join();
	//for (auto e : v)
	//{
	//	cout << e << " ";
	//}
	//cout << endl;
	cout << v.size() << endl;
	return 0;
}

unique_guard 也可以达到相同的目的
lock_guard 只是加锁和解锁
unique_guarf 在加锁的过程,也可以进行一些其他操作
例如:
中途解一下锁,然后去执行一些别的事情,然后再把锁加回来

4.支持两个线程交替打印,一个打印奇数,一个打印偶数

错误代码展示:

(这段代码看起来没什么问题,在vs2013下面是正确的,但是在vs2019就会出现问题,并且这段代码本身就有问题)

int main()
{
	int n = 100;
	int i = 0;
	mutex mtx;

	//t1 打印偶数
	thread t1([n,&i,&mtx] {
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			cout << this_thread::get_id() << ":" << i << endl;
			++i;
		}
	});

	//t2 打印奇数
	thread t2([n,&i,&mtx] {
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			cout << this_thread::get_id() << ":" << i << endl;
			++i;
		}
	});

	t1.join();
	t2.join();
	return 0;
}

错误分析:
因为时间片的原因,t1在执行一次加锁解锁的时候没有用一个时间片的时间,那么他会接着继续执行加锁和解锁并打印

解决:


#include
int main()
{
	mutex mtx;
	condition_variable c;
	int n = 100;
	bool flag = true;
	thread t1([&]() {
		int i = 0;
		while (i < n)
		{
			unique_lock<mutex> lock(mtx);
			//最开始flag=true,那么这里将会首先运行
			c.wait(lock, [&flag]()->bool {return flag; });
			cout << this_thread::get_id() << ": " << i << endl;
			flag = false;
			i += 2; // 偶数
			c.notify_one();
		}
		});
	thread t2([&]() {
		int j = 1;
		while (j < n)
		{
			unique_lock<mutex> lock(mtx);
			c.wait(lock, [&flag]()->bool {return !flag; });
			cout << this_thread::get_id() << ": " << j << endl;
			j += 2; // 奇数
			flag = true;
			c.notify_one();
		}
		});
	t1.join();
	t2.join();
	return 0;
}

你可能感兴趣的:(C++日常笔记,c++,开发语言)