IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11 -> 正文阅读

[C++知识库]C++11

列表初始化

使用{}的初始化

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加

#include<iostream>
using namespace std;

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

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

int main()
{
	//如下形式都是可以
	int x1 = 1;
	int x2{ 2 };

	int* p3 = new int[4]{0};
	int* p4 = new int[4]{1,2,3,4};

	//自定义类型初始化
	Date d1;
	Date d2(2022, 1, 17);
	
	Date d3{2022, 1, 18};
	Date d4 = { 2022, 1, 18 };
	return 0;
}

initializer_list

对于initializer_list
1.initializer_list主要用于容器的初始化
2.initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size()
3.该类型用于访问c++初始化列表中的值,这种类型的对象由编译器根据初始化列表声明自动构造,列表声明是一个用花括号括起来的逗号分隔元素列表

#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;

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

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

int main()
{
	auto in = { 10,20,30 };//in的类型是initializer_list<int>
	//初始化容器
	vector<int> v1 = { 1,2,3,4,5,6 };
	vector<Date> v2 = { Date(2022,2,21),Date{2022,2,21},{2022,2,22} };
	map<string, int> m = { make_pair("hello",1),{"world",2} };
	return 0;
}

在这里插入图片描述
模拟实现initializer_list

#include <initializer_list>
template<class T>
class Vector 
{
public: 
	Vector(initializer_list<T> l)
		: _capacity(l.size())
		, _size(0)
	{ 
		_array = new T[_capacity];
		//利用initializer_list的迭代器遍历
		for(auto e : l) 
			_array[_size++] = e; 
	}  
	Vector<T>& operator=(initializer_list<T> l) 
	{ 
		delete[] _array; 
		_array = new T[l._capacity];
		size_t i = 0; 
		for (auto e : l) 
			_array[i++] = e;
		return *this; 
	} 
	// ...private: T* _array; size_t _capacity; size_t _size;};

auto关键字

C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便,比如容器迭代器的推导

auto使用的前提是:必须要对auto声明的类型进行初始化

decltype关键字

运行时,将变量的类型声明为表达式推出的类型,decltype的结果类型与表达式的类型息息相关 ,同时运行时类型识别的缺陷是降低程序运行的效率

示例:

#include<iostream>
using namespace std;
template<class T1, class T2>
int F(T1 t1, T2 t2)
{		//将t1*t2表达式的类型给ret
	decltype(t1 * t2) ret;
	cout << typeid(ret).name() << endl;//ret的类型为double向上转型
	cout << "调用F函数" << endl;
	return 1;
}
int main()
{
	int x = 0;
	double y = 0;
	//1.编译器只分析表达式的值并不会实际计算或者调用相关函数
	decltype (F(x, y)) z;
	cout << "z的类型为" << typeid(z).name() << endl;
	//2.如果表达式的内容是解引用操作或者给变量加了1个或多个括号,那么会被识别为引用类型
	int* p = &x;
	int b = 0;
	decltype(*p) a=b;
	decltype((b)) c =b;
	cout <<"a的类型为" << typeid(a).name() << endl;
	cout << "c的类型为" << typeid(c).name() << endl;

	return 0;
}

在这里插入图片描述
a,c变量是引用类型,所以a,c变量需要初始化,而z不需要
在这里插入图片描述

右值引用和移动语义

①左值VS右值

左值右值
是什么数据表达式数据表达式
包括变量名和解引用的指针纯右值:字面常量、表达式返回值以及将亡值:传值返回的函数返回值这样的即将销毁临时对象
特点能够被取地址,一般能修改(除const)不能被取地址,不能出现在赋值符号的左边
#include<iostream>
using namespace std;
int mymin(int x, int y)
{
	return x + y;
}
int main() 
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;//字面常量
	x + y;//表达式返回值的临时变量
	mymin(x, y);//传值返回函数的返回值的临时变量 
	//像上面三个都不会存储在内存中,直接赋值,所以不能取地址

	int&& rr1 = 10;//右值引用后,会开辟空间,这是就可以取地址,可以修改了
	const double&& rr2 = x + y;//不让修改
	double&& rr3 = mymin(x, y);
	rr1 = 20;//修改的是开辟空间里的值
	cout << rr1 << endl;
	cout <<&rr1 << endl;
	
	return 0;
}

②左值引用VS右值引用

左值引用右值引用
用&表示左值引用用&&表示右值引用
左值引用只能引用左值不能引用右值右值引用只能右值,不能引用左值
特别地const左值引用既可引用左值,也可引用右值特别地右值引用可以move以后的左值

注意:左值move之后的转移会修改原对象
在这里插入图片描述

③右值引用使用场景和意义

右值引用是为了补左值引用的短板:
在深拷贝返回值场景中,如果函数返回的对象是临时对象,出了作用域就会销毁,那么只能返回该对象的拷贝,而深拷贝需要重新开辟大量空间

场景一:在string类中对于to_string方法我们需要在类中创建临时对象,而只能传值返回,返回时调用拷贝构造,发生深拷贝
在这里插入图片描述

解决:在类中添加移动构造函数
移动构造本质是将参数右值的资源直接转移,右值引用只能绑定到临时对象,所以可以接管它的资源,那么就不用做深拷贝了,所以它叫做移动构造
在这里插入图片描述
深入分析右值引用的好处:
在这里插入图片描述
在这里插入图片描述
同理对于移动赋值:
在这里插入图片描述
优化后在这里插入图片描述

完整代码:

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

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

			return *this;
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			string tmp(s);
			swap(tmp);

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		tzc::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;
	}
};
int main()
{
	tzc::string tmp=tzc::to_string(1234);
	return 0;
}

总结:
右值引用并不是直接使用右值引用来减少拷贝,提供效率。而是在深拷贝的类中,提供移动赋值和移动构造,这是在传值返回和参数是右值时,就可以直接转移右值的资源,避免深拷贝

完美转发

模板中的&&

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

右值引用的对象在作为实参传递时,属性会退化为左值,只能匹配左值引用,因为传递时,右值引用已经能够取到地址了

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

int main()
{
	PerfectForward(10);           // 右值
	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

在这里插入图片描述

为了保持对象的属性,就需要使用完美转发,forward(t)

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};

template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = (Node*)malloc(sizeof(Node));
		_head->_next = _head;
		_head->_prev = _head;
	}

	void PushBack(const T& x)
	{
		Insert(_head, x);
	}

	void PushBack(T&& x)// 这里x属性退化为左值,其他对象再来引用x,x会识别为左值
	{	
		//能取地址
		//cout << &x << endl;

		// 这里就要用完美转发,让x保持他的右值属性,匹配到右值的函数
		Insert(_head, std::forward<T>(x));
	}

	void PushFront(T&& x)
	{
		Insert(_head->_next, std::forward<T>(x));
	}

	void Insert(Node* pos, T&& x)//退化
	{
		Node* prev = pos->_prev;
		Node* newnode = (Node*)malloc(sizeof(Node));
		new(&newnode->_data)T(std::forward<T>(x));//模板中,每次右值引用作为参数都会退化,都需要完美转发

		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}

	void Insert(Node* pos, const T& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = (Node*)malloc(sizeof(Node));
		new(&newnode->_data)T(x);

		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};
int main()
{
	List<tzc::string> lt;
	tzc::string s1("1111");

	lt.PushBack(s1);

	lt.PushBack("1111");
	lt.PushFront("2222");

	return 0;
}

类的新特性

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

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

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

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

如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

①关键字default

作用:强制生成默认成员函数
比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显式指定移动构造生成

②关键字delete

作用:强制删除默认成员函数
比如:我们想防拷贝,或者不想让别人使用该函数,就可以delete删除禁用掉

③final与override关键字

final修饰的类表示该类不能被继承,final修饰的虚函数表示不能被重写
override修饰子类重写的虚函数,会检查是否完成重写, 如果没有就报错

可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
template <class ...Args>
void ShowList(Args... args){}

比如下面就是简单的可变参数模板,但是最核心的问题是:
虽然支持了能够计算参数的个数,但是不支持直接像数组那样拿到参数

template <class ...Args>
void ShowList(Args... args)//可变参数
{
	cout << sizeof...(args) << endl;//总共有多少个参数
	//不支持
	//for (int i = 0; i < sizeof...(args); ++i)
	//
	//	cout<<args[i]<<" "; 
	//}
}

int main()
{
	//下面都是支持的
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', “Hello");

	return 0;
}

那怎么样才能拿到传入的参数呢?

递归函数方式展开参数包

既然是递归展开那么关键就应该考虑递归出口问题
plan A:
在这里插入图片描述

plan B:
在这里插入图片描述
plan C:
这样只好捕获只有一个参数的情景,但是就不支持空参

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

plan D:适用于所有场景

void ShowListArgs()//递归出口
{
	cout << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowListArgs(T value, Args... args)
{
	cout << value << " ";
	ShowListArgs(args...);
}
//接收函数
template <class ...Args>
void ShowList( Args... args)
{
	ShowListArgs(args...);
}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', "Hello world");
	return 0;
}

逗号表达式展开参数包

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void ShowList()
{
	cout << endl;
}
template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}
int PrintArg()
{
	cout <<endl;
	return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
	// 列表初始化	先执行前面的函数,再用0初始化arr
	// {(printarg(args), 0)...}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... )
	int arr[] = { (PrintArg(args), 0)... };//生成int arr[sizeof...(args)] 
	cout << endl;
}
int main()
{
	ShowList();//匹配空参
	//匹配可变参数包
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', "Hello world");
	return 0;
}

STL中emplace_back场景:

对于list:
在这里插入图片描述
可以看出emplace_back比push_back高效一些:
体现在当且仅当传入可变参数列表时,能够直接构造
而在其余两种情况下,都要在外面实例化出对象,在传入调用深拷贝或移动构造,这时与push_back一样

lambda表达式

在C++98中,如果需要对自定义类型按不同的要求排序,可以用sort+仿函数的方法

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Date
{
public:
	int _year;
	int _month;
	int _day;
public:
	Date(int year=2022,int month=2,int day=23)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}
};
class DesCmp
{
public:
	bool operator()(Date& d1, Date& d2)
	{
		if (d1._year != d2._year)
		{
			return d1._year > d2._year;
		}
		if (d1._month != d2._month)
		{
			return d1._month > d2._month;
		}
		return d1._day > d2._day;
	}
};
class AesCmp
{
public:
	bool operator()(Date& d1, Date& d2)
	{
		if (d1._year != d2._year)
		{
			return d1._year < d2._year;
		}
		if (d1._month != d2._month)
		{
			return d1._month < d2._month;
		}
		return d1._day < d2._day;
	}
};
int main()
{ 
	vector<Date> v = { {2022,1,1},{1000,5,6},{1949,10,1},{2022,1,1} };
	sort(v.begin(), v.end(), DesCmp());
	cout << "降序" << endl;
	for (auto& x : v)
	{
		cout << x._year<<"年"<<x._month<<"月"<<x._day<<"日" << endl;
	}
	cout << "升序" << endl;
	sort(v.begin(), v.end(), AesCmp());
	for (auto& x : v)
	{
		cout << x._year << "年" << x._month << "月" << x._day << "日" << endl;
	}
	return 0;
}

在这里插入图片描述
因此,在C++11语法中就诞生了Lambda表达式

lambda表达式语法

lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement}
[capture-list] : 捕捉列表,捕捉列表能够捕捉上下文中的变量供lambda函数使用
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略
->returntype:返回值类型,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
{statement}:函数体

所以可以用lambda表达式改进上面的代码

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Date
{
public:
	int _year;
	int _month;
	int _day;
public:
	Date(int year=2022,int month=2,int day=23)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}
};

int main()
{ 
	vector<Date> v = { {2022,1,1},{1000,5,6},{1949,10,1},{2022,1,1} };
	//auto f1=[]{...} 返回值f1是可调用的对象
	//简洁了许多
	sort(v.begin(), v.end(), [](Date& d1, Date& d2) {
		if (d1._year != d2._year)
		{
			return d1._year > d2._year;
		}
		if (d1._month != d2._month)
		{
			return d1._month > d2._month;
		}
		return d1._day > d2._day; });
	cout << "降序" << endl;
	for (auto& x : v)
	{
		cout << x._year<<"年"<<x._month<<"月"<<x._day<<"日" << endl;
	}
	cout << "升序" << endl;
	sort(v.begin(), v.end(), [](Date& d1, Date& d2)
		{
			if (d1._year != d2._year)
			{
				return d1._year < d2._year;
			}
			if (d1._month != d2._month)
			{
				return d1._month < d2._month;
			}
			return d1._day < d2._day; });
	for (auto& x : v)
	{
		cout << x._year << "年" << x._month << "月" << x._day << "日" << endl;
	}
	return 0;
}

捕捉列表说明:
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(成员函数包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(成员函数包括this)

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

lambda底层原理:
在这里插入图片描述

function包装器

上面提到可调用对象:包括函数指针,仿函数,lambda表达式

对于任意一个Func(i);该Func可以是函数指针,也可以是仿函数,也可以是lambda,如果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);
}

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

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

int main()
{
	// 函数指针
	cout << useF(func, 11.11) << endl;

	// 仿函数
	cout << useF(Functor(), 11.11) << endl;

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

	return 0;
}

而我们可以通过包装器来解决这个问题

template <class Ret, class… Args>
class function<Ret(Args…)>
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

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 func(double i)
{
	return i / 2;
}

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

int main()
{
	// 函数指针
	std::function<double(double)> f1 = func;
	cout << useF(f1, 11.11) << endl;

	// 仿函数
	std::function<double(double)> f2 = Functor();
	cout << useF(f2, 11.11) << endl;
	
	// lamber表达式
	std::function<double(double)> f3 = [](double d)->double{ return d / 4; };
	cout << useF(f3, 11.11) << endl;
	//三者的参数和返回值一致才能统一为同一个包装器
	return 0;
}

通过包装器将三个可调用对象类型统一,在传入模板时,只生成一份类,提高了模板效率
包装器也是可调用对象

包装器使用的其他场景

#include<iostream>
#include<functional>
using namespace std;
class A
{
public:
	static	void printf1(int b=1)
	{
		cout << b+1<<endl;
	}
	void  printf2(int b = 1)
	{
		cout <<  b+2 << endl;
	}
	
};
class func
{
public:
	void operator()(int b = 1)
	{
		cout << b + 3 << endl;
	}
};
int main()
{
	int b = 0;
	//包装类的静态函数
	function<void(int)> f1 = &A::printf1;
	f1(b);
	//包装类的非静态函数
	function<void(A,int)> f2 = &A::printf2;//拿到成员函数地址
	f2(A(),b);//非静态函数要通过类对象调用
	//包装仿函数
	function<void(int)> f3 = func();
	f3(b);
	//包装lambda
	function<void(int)> f4 = [](int b) {cout << b + 4 << endl; };
	f4(b);
	return 0;
}

总结:
function用于包装各种可调用的对象,统一对象的类型,并且明确指定返回值和参数类型

function的出现巧妙地补了可调用对象的短板:
1.对于函数指针类型复杂,并且不易让人理解
2.对于仿函数类型是类名,只能通过看operator()才能看到参数与返回值
3.lambda类型是更加复杂

bind

是一个函数模板,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,也就是调整可调用类型参数
bind的使用场景:

#include<iostream>
#include<functional>
using namespace std;
class A
{
public:
	int  printf2(int b = 1)
	{
		return  b+2 ;
	}
};
int main()
{
	// 想把plus绑定成一个值+10														//表示传入的第一个参数			
	std::function<int(int)> f2 = bind([](int a, int b) { return a + b; }, 10, placeholders::_1);
	cout << f2(5) << endl;

	// 绑定固定的可调用对象 
	//在成员函数中需要传入对象,我们就绑定该对象,每次调用时就不用传入了
	std::function<int(int)> f3 = bind(&A::printf2, A(), placeholders::_1);
	cout << f3(10) << endl;
	//调整参数顺序
	std::function<int(int, int)> f4 = bind([](int a, int b) { return a - b; }, placeholders::_2, placeholders::_1);
	cout << f4(1, 2) << endl;//1
	return 0;
}

线程库

在C++11中引入的最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库

相关函数:
对于构造函数和赋值重载
在这里插入图片描述
thread类成员函数

函数名功能
get_id()获取线程id
joinable()判断线程是否还在执行
join该等待线程
detach()分离线程
swap(thread& x))交换两个线程

Linux线程更多细节
互斥锁mutex:

成员函数介绍
lock()加锁
unlock()解锁
try_lock尝试获取锁,成功锁住返回true,否则返回fasle

创建两个线程对i++通过加锁保证线程安全

#include<iostream>
#include<vector>
#include<thread>
using namespace std;
#include<mutex>
mutex mtx;//定义互斥锁
int main()
{
	vector<thread> tv;
	int i = 0;
	tv.resize(2);
	for (auto& t : tv)
	{
		t = thread([&i]
			{
				//保护临界资源
				mtx.lock();
				for (int j = 0; j < 1000000; j++)
				{
					i++;
				}
				mtx.unlock();
			});
	}
	for (auto& t : tv)
	{
		t.join();
	}
	cout << i<<endl;
	

也可以使用atomic类来保证操作的原子性,这样没有线程切换,线程自旋检测

#include<iostream>
#include<vector>
#include<thread>
using namespace std;
#include<mutex>
mutex mtx;//定义互斥锁
int main()
{
	vector<thread> tv;
	atomic<int> i = 0;
	tv.resize(3);
	for (auto& t : tv)
	{
		t = thread([&i]
			{
				for (int j = 0; j < 1000000; j++)
				{
					i++;//原子++
				}
			});
	}
	for (auto& t : tv)
	{
		t.join();
	}
	cout << i<<endl;
	return 0;
}

lock_guard与unique_lock

lock_guard主要解决在锁中间代码直接返回,或者在锁的范围内抛异常而忘记释放锁造成死锁的问题

lock_guard主要是通过RAII的方式,对其管理的互斥量进行了封装,在构造函数时上锁,出作用域后,调用析构函数时释放锁,有效规避死锁问题

#include<iostream>
using namespace std;
#include<mutex>
mutex mtx;//定义互斥锁
template<class Lock>
class mylock_guard
{
public:
	mylock_guard(Lock& mtx)
		:_mtx(mtx)
	{
		_mtx.lock();
	}
	~mylock_guard()
	{
		_mtx.unlock();
		cout << "解锁" << endl;
	}
	mylock_guard(const Lock& l) = delete;
private:
	mutex& _mtx;//锁不能拷贝
};
int main()
{
	FILE* f = fopen("test.cpp", "r");
	mylock_guard<mutex> lg(mtx);//相当于把锁托管给lock_guard让它管理
	if (f == NULL)
	{
		return 0;
	}
	//...
	return 0;
}

无论是怎么样退出,局部对象的生命周期都会结束,调用析构解锁
lock_guard并不支持中途解锁与上锁,而unique_lock支持

线程同步:
实现一个程序,支持两个线程交替打印,一个打印奇数,一个打印偶数

#include<iostream>
using namespace std;
#include<mutex>
mutex mtx;//定义互斥锁
int main()
{
	int n = 1;
	thread t1([&]
		{
			int i = 1;
			for (; i <= 100; i += 2)
			{
				this_thread::sleep_for(chrono::seconds(1));
				unique_lock<mutex> m(mtx);
				cout << this_thread::get_id() << ":" << i << endl;
			}
		}
	);
	thread t2([&]
		{
			int i = 2;
			for (; i <= 100; i += 2)
			{
				this_thread::sleep_for(chrono::seconds(1));
				unique_lock<mutex> m(mtx);
				cout << this_thread::get_id() << ":" << i << endl;
				
			}
		}
	);
	t1.join();
	t2.join();
	return 0;
}

在这里插入图片描述

在这里插入图片描述
这种情况通过控制竞争能力强的线程竞争锁后休眠,让另一个线程成功得到锁,好像成功了,但是还是存在第一个线程还没拿到锁前OS调度到线程二,线程二先打印的场景

所以此方案不行,应该使用同步机制才能根本解决
condition_variable类:
在这里插入图片描述

#include<iostream>
#include<condition_variable>
using namespace std;
#include<mutex>
mutex mtx;//定义互斥锁
int main()
{
	int n = 1;
	condition_variable cv;
	bool flag = true;
	thread t1([&]
		{
			int i = 1;
			for (; i <= 100; i += 2)
			{
				unique_lock<mutex> m(mtx);
				cv.wait(m, [&]{return flag; });
				cout << this_thread::get_id() << ":" << i << endl;
				flag = false;
				cv.notify_one();
			}
		}
	);
	thread t2([&]
		{
			int i = 2;
			for (; i <= 100; i += 2)
			{
				unique_lock<mutex> m(mtx);
				cv.wait(m, [&] {return !flag; });
				cout << this_thread::get_id() << ":" << i << endl;
				flag = true;
				cv.notify_one();
			}
		}
	);
	t1.join();
	t2.join();
	return 0;
}

在这里插入图片描述

在这里插入图片描述
本质是t1线程只有在flag为true的时候才能被真正唤醒,而t2线程只有在flag为false的时候唤醒,flag的交替变换导致线程的不断切换

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章           查看所有文章
加:2022-03-10 22:13:22  更:2022-03-10 22:17:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:48:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码