C++11

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

struct Point
{
 int _x;
 int _y;
};
int main()
{
 Date d1 = {2025,5,2};
Date d1{2025,5,2};//花括号调用构造函数
 int x1 = 1;
 int x2{ 2 };//花括号初始化内置类型
 int array1[]{ 1, 2, 3, 4, 5 };
 int array2[5]{ 0 };
 Point p{ 1, 2 };//花括号初始化自定义类型
 // C++11中列表初始化也可以适用于new表达式中
 int* pa = new int[4]{ 0 };
 return 0;
}

int main()
{
    auto i1 = { 10,20,30,1,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,2 };//花括号还可能被识别成为initializer_list<int>的类型
	auto i2 = { 10,20,30 };
    initializer_list<int> i3 = { 10,20,30 };
	cout << typeid(i1).name() << endl;//initializer_list<int>类型
	cout << typeid(i2).name() << endl;
	initializer_list<int>::iterator it1 = i1.begin();
	initializer_list<int>::iterator it2 = i2.begin();
	cout << it1 << endl;//DB14256F
	cout << it2 << endl;
{

 

	Date d1(2023,5,20);
	Date d2(2023,5,21);
	vector<Date> vd1 = {d1, d2};
	vector<Date> vd2 = { Date(2023,5,20), Date(2023,5,21) };
	vector<Date> vd3 = { {2023,5,20}, {2023,5,20} };//两层花括号的意义是不一样的,外面一层是initializer_list<Date>的构造,里面一层是Date的构造。
    map<string, string> dict = { {"sort", "排序"},{"string", "字符串"},{"Date", "日期"} };//map也可以采用initializer_list来进行构造
	pair<string, string> kv1 = { "Date", "日期" };
	pair<string, string> kv2 { "Date", "日期" };

 关键字decltype将变量的类型声明为表达式指定的类型。

int main()
{
	const int x = 1;
	double y = 2.2;

	cout << typeid(x*y).name() << endl;

	decltype(x * y) ret; // x*y是上面类型ret就是什么类型,double
	decltype(&x) p;      // p的类型是const int*
	cout << typeid(ret).name() << endl;
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;

	// vector存储的类型跟x*y表达式返回值类型一致,此时必须传一个类型,auto是不能帮忙解决的。
	// decltype推导表达式类型,用这个类型实例化模板参数或者定义对象
	vector<decltype(x* y)> v;

	return 0;
}

范围for的底层就是迭代器

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

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

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

nt main()
{
// 以下的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& pvalue = *p;
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);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
//取别名的一个目的是减少拷贝 
// 左值引用只能引用左值,不能引用右值。
int& ref1 = a;
// 但是const左值引用既可引用左值,也可引用右值。
// int& ref2 = (a + b);
const int& ref2 = (a + b);
// 右值引用给右值取别名
int&& ref3 = (a + b);
// 右值引用不能给左值去别名,但是可以给move后左值取别名
//int&& ref4 = a;
int&& ref4 = move(a);
void func(int& a)
{
	cout << "void func(int& a)" << endl;
}

void func(int&& a)
{
	cout << "void func(int&& a)" << endl;
}

int main()
{
	int a = 0;
	int b = 1;
	func(a);
	func(a + b);//这里调用的是右值引用void func(int&& a),如果没有右值引用void func(int&& a),还有一个方法是采用void func(const int& a)
}

移动构造

void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
// 拷贝构造
string(const string& s)//const的左值引用时可以引用右值的
	:_str(nullptr)
{
	cout << "string(const string& s) -- 深拷贝" << endl;
	string tmp(s._str);
	swap(tmp);//虽然这里的深拷贝也是交换,但是这和移动构造的交换时不一样的,这是深拷贝的现代写法,传统写法就是开空间拷贝数据。
}

// 移动构造
string(string&& s)//右值的增加首先我们可以在语法上进行区分,如果是左值我们进行深拷贝,如果是右值我们采用移动拷贝,移动拷贝的代价一定比深拷贝的代价要低。右值引用也可以引用move之后的左值。
	:_str(nullptr)
{
	cout << "string(string&& s) -- 移动拷贝" << endl;
	swap(s);
}

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

	return *this;
}
wjj::string to_string(int value)//为什么这个函数的返回值不能采用引用(不管是左值还是右值)?因为返回的是局部对象str,除了作用域就销毁了。对象都没有了,还谈什么引用,右值引用也要求对象存在。
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

		bit::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()
{
	wjj::string s1("hello world");

	wjj::string ret1 = s1;
	wjj::string ret2 = (s1+'!');

	wjj::string ret3 = move(s1);//如果你想让左值s1也被转走,就得用move函数赋予变量ret3权限转移走你的资源。move了之后是s1就变成右值了,这里用的就是移动构造了。

	return 0;
}

 上图可知,对于左值是s1,ret1是不敢动的,只能进行深拷贝,而对于右值(将亡值),你都要走了你把这个资源带走不如直接给ret2

// 左值引用:直接减少拷贝。1、左值引用传参  2、传引用返回。(函数内的局部对象,不能用引用返回)

c++98会用一个临时对象(也是右值)通过深拷贝to_string函数的tmp变量,然后valstr有对这个临时对象再进行一次拷贝构造,当然编译器可能会优化成为一次拷贝构造。

再c++11当中,由于str是一个左值,所以从str到临时对象一定是拷贝构造,而临时对象是一个将亡值也就是我们所说的右值,所以从临时对象到valstr是一个移动构造,当然编译器可能会优化成为一次移动构造(优化成一次拷贝构造也就是少了一次移动构造的收益是很小的,因为移动构造的代价是很小的)。

int main()
{
	list<bit::string> lt;

	string s1("hello world");
	lt.push_back(s1);//调用string的拷贝构造,也就是重新开空间,拷贝s1的数据,然后插入到list当中。
	lt.push_back(move(s1));//调用string的移动构造,

	lt.push_back(string("hello world"));//匿名对象是右值,所以这边经历的也是一次string的移动构造。
	lt.push_back("hello world");//先通过"hello world"构造一个临时对象,这里是单参数的构造函数属于隐式类型转换,然后再进行移动构造。如果没有移动构造,这里会调用拷贝构造。

	return 0;
}

 

左值引用是直接减少拷贝,提高效率。而右值引用时间接减少拷贝,识别出时左值还是右值,如果时右值,则不再深拷贝,直接移动拷贝(也就是直接移动资源,而不是采用深拷贝的方式),提高效率。

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
int main()
{
 double x = 1.1, y = 2.2;
 int&& rr1 = 10;
 const double&& rr2 = x + y;
 rr1 = 20;
 rr2 = 5.5;  // 报错
 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)//右值引用引用之后的t的属性是左值,这样才可以实现转移资源。
{
	Fun(t);
}

int main()
{
	PerfectForward(10);           // 右值->左值引用

	int a;
	PerfectForward(a);            // 左值->左值引用
	PerfectForward(std::move(a)); // 右值->左值引用

	const int b = 8;
	PerfectForward(b);		      // const 左值->const左值引用
	PerfectForward(std::move(b)); // const 右值->const左值引用
}
//右值引用的一个重要的特点就是需要借助移动拷贝去转移它的资源,右值具有常性,不能修改,现在我们通过对右值取引用可以修改其属性,但是我们不是再第一层对资源进行修改,所以变量再向下传递的过程中属性就对不上,这时候我们需要一个完美转发Fun(forward<T>(t)),继续保持右值的属性匹配。

引用都是给对象取别名,减少拷贝。左值引用解决了大多数的问题,但是左值引用无法解决一是局部对象返回问题,其次插入接口对象拷贝的问题。右值引用需要借助移动语义(也就是移动赋值和移动拷贝)来间接减少拷贝。

T是自定义类型,当T是浅拷贝的类,这里就是拷贝构造,因为对于浅拷贝的类,移动构造是没有意义的;深拷贝的类,这里就是移动构造,移动构造可以转移右值(将亡值)的资源(将亡值得资源返回要释放的不如直接给我用行了,右值引用延长了资源而不是对象的生命周期),没有了拷贝,效率得到了提高 。

void push_back(const T& X)//左值引用是因为既想接收左值又想接收右值所以加了const
{
    mewnode->val=x;//拷贝赋值,对于左值没有问题,右值太浪费了。
}
void push_back(T&& X)//右值引用本身就是接收右值的,不用加const,加了const反倒是不能改变了。
{
    mewnode->val=x;//对于右值,移动赋值。
}
//对于编译器,会走最符合的哪一个函数接口。如果是左值走上面,右值走下面。

// s1 = 将亡值
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动赋值" << endl;
	swap(s);//将将亡值的资源转移给我不再需要进行深拷贝,并且还将我不要的资源交给将亡值,让将亡值帮我析构释放。效率很好,只是进行单纯的资源移动。
	return *this;
}

 

 default关键字

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}

	Person(const Person& p)=delete//如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁 已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即 可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。    
	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;//因为赋值拷贝是返回对象本身
    //假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了析构,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
	~Person()
	{
		cout << "~Person()" << endl;
	}
}
//如何解析出可变参数包呢?
//递归推导思维
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
void ShowList()
{
	cout << endl;
}
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << val << " ";
    ShowList(args...);
}

int main()
{
	ShowList();//编译器会选择最匹配的,所以这里调用的是第一个无参的函数接口。
	howList(1);//接下来的这几个会调用带参数包的函数接口,但是最后都会调用一次无参的函数接口,这也是打印的时候会换行的原因。
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));

	return 0;
}

/ STL容器的插入接口都有一个emplace系列
int main()
{
	
	std::list<string> mylist;
	
	string s1("1111");
	mylist.push_back(s1);
	mylist.emplace_back(s1);

	cout << endl;
	bit::string s2("2222");
	mylist.push_back(move(s1));
	mylist.emplace_back(move(s2));
    // 对于上面的左值和右值,emplace_back和push_back并没有什么区别
	// 下面开始有区别
	//cout << endl;
	//mylist.push_back("3333");   // 借助string(const char* str="")构造匿名对象 + 匿名对象传给右值引用string(string&&s)进行移动构造
	//mylist.emplace_back("3333");// 由于void emplace_back (Args&&... args)可以传递参数包,所以可以直接构造,在这种场景下如果传的是直接构造对象的参数,这里是可以做到直接构造的。
    //上面对于深拷贝的类差别不大,因为push_back多出来的移动构造的牺牲并不大。
	cout << "=========================" << endl;
	list2.push_back(Date(2023, 5, 28));
	list2.push_back({ 2023, 5, 28 });//这里是列表初始化的隐式类型转换,但是对于emplace_back的参数包并不支持。
	cout << endl;
	list2.emplace_back(Date(2023, 5, 28)); // 构造+移动构造
	list2.emplace_back(2023, 5, 28);       // 但是这种写法是支持的,通过可变参数包一直像下面进行传递,直接传构造日期对象的参数,是一个直接构造。


	return 0;
}

  Imabda

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

int main()
{
	auto add1 = [](int x, int y)->int {return x + y; };//左边是一个对象,类似于仿函数对象。

	cout << [](int x, int y)->int {return x + y; }(1, 2) << endl;//可以这样调用,但是可读性不强。
	cout << add1(1, 2) << endl;//3

	auto add2 = [](int x, int y) //这种写法也没有问题
	{
	return x + y;
	};

	cout << add2(1, 2) << endl;

	[] {};//最简单的imabda表达式
	return 0;
}
struct Goods
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };

	auto priceLess = [](const Goods& g1, const Goods& g2)->bool {return g1._price < g2._price; };
	sort(v.begin(), v.end(), priceLess);//价格升序

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {
		return g1._price > g2._price; 
		});//价格降序

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {
		return g1._evaluate < g2._evaluate;
		});

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {
		return g1._evaluate > g2._evaluate;
		});
}
int main()
{
	int x = 0, y = 1;
	int m = 0, n = 1;

	auto swap1 = [](int& rx, int& ry)
	{
		int tmp = rx;
		rx = ry;
		ry = tmp;
	};
	swap1(x, y);
	cout << x << " "<< y << endl;

	// 传值捕捉
	auto swap2 = [x, y]() mutable//捕捉列表里面有了x和y就可以不需要参数列表了,但是捕捉列表是类似于传值传参的拷贝,并且默认用const修饰, 不让修改。这时候就需要用mutable进行强行修改,但由于捕捉列表里面的对象是外面对象的拷贝,所以再函数体里面做了改变并不会影响外面对象。 
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap2();
	cout << x << " " << y << endl;
	// 引用捕捉
	auto swap2 = [&x, &y]()//这样才可以成功改变外部对象,但是这和传统的引用又不一样,传统引用是int& x,直接写成&x是取地址。
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap2();
	cout << x << " " << y << endl;

	// 混合捕捉
	auto func1 = [&x, y]()
	{
		//...
	};

	// 全部引用捕捉
	auto func2 = [&]()//需要捕捉的外部对象太多了,就可以采用这种全部引用捕捉
	{
		//...
	};

	// 全部传值捕捉
	auto func3 = [=]()
	{
		//...
	};

	// x传值捕捉,剩下全部引用捕捉
	auto func4 = [&, x]()
	{
		//...
	};

	return 0;
}
int main()
{
    // C++98,想让代码在linux和windows下都可以支持的多线程程序。-- 采用条件编译
    #ifdef _WIN32
	    CreateThread        
    #else
	    pthread_create
    #endif

	// C++11,linux和windows下都可以支持的多线程程序。-- 直接采用thread库,条件编译的工作由库来代替完成。

}

void Func(int n, int num)
{
	for (int i = 0; i < n; i++)
	{
		cout <<num<<":" << i << endl;
	}
	cout << endl;
}
//传统的线程创建
int main()
{
	int n1, n2;
	cin >> n1>>n2;
	thread t1(Func, n1, 1);
	thread t2(Func, n2, 2);  
	t1.join();
	t2.join();
	return 0;
}
//采用imabda进行线程创建
int main()
{
	int n1, n2;
	cin >> n1 >> n2;
	thread t1([n1](int num)
		{
			for (int i = 0; i < n1; i++)
			{
				cout <<num<<":" << i << endl;
			}
			cout << endl;
		}, 1);

	thread t2([n2](int num)
		{
			for (int i = 0; i < n2; i++)
			{
				cout << num << ":" << i << endl;
			}
			cout << endl;
		}, 2);

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

int main()
{

	size_t m;
	cin >> m;
	vector<thread> vthds(m);//线程这个类也有默认构造

	// 要求m个线程分别打印n
	for (size_t i = 0; i < m; i++)
	{
		size_t n;
		cin >> n;

		vthds[i] = thread([i, n, m]() {
			for (int j = 0; j < n; j++)
			{
				cout << i << ":" << j << endl;
			}
			cout << endl;
			});//=左边是一个匿名对象也就是一个右值(将亡值),采用移动赋值。
	}

	for (auto& t : vthds)//这里auto&必须采用引用,因为thread类不支持拷贝构造。
	{
		t.join();
	}

	return 0;
}
class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}

	double operator()(double money, int year)
	{
		return money * _rate * year;
	}

private:
	double _rate;
};

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

	// lambda只是表层的玩法,底层依旧采用的是类+operator()
	auto r2 = [=](double monty, int year)->double {return monty * rate * year; };
	r2(10000, 2);
	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };
	//f1 = f2;//所以lambda对象并不支持赋值,因为f1和f2是两个不同类实例化的对象。

	return 0;
}

多线程

#include<mutex>
int x = 0;
mutex mtx;
void Func(int n)
{
	cout << &n << endl;//不同线程打印是不一样的,因为不同线程有自己独立的栈区
	cout << &x << endl;//不同线程打印的是一样的,因为这是在全局区域
	// 并行
	for (int i = 0; i < n; i++)
	{
		mtx.lock();//
		++x;
		mtx.unlock();
	}

	// 下面的更快,虽然是串行,但是不像上面一样在加锁和解锁这个过程消耗大量时间。并且线程切换上下文的过程也会有消耗。
	// 这也可以看出串行不一定比并行慢
	//mtx.lock();
	//for (int i = 0; i < n; i++)
	//{
	//	++x;
	//}
	//mtx.unlock();
}

int main()
{
	int n = 10000000;
	size_t begin = clock();

	thread t1(Func, n);
	thread t2(Func, n);

	t1.join();
	t2.join();
	size_t end = clock();

	cout << x << endl;
	cout << end - begin << endl;

	return 0;
}
//配合上Lambda表达式的加锁方式,偏c++11的写法。
#include<mutex>

int main()
{
	int n = 100000;
	int x = 0;
	mutex mtx;
	size_t begin = clock();

	thread t1([&, n](){
			mtx.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			mtx.unlock();
		});//对于锁和x我们需要改变所以采用引用捕捉,而n我们不想去改变它,所以采用值捕捉。

	thread t2([&, n]() {
			mtx.lock();
			for (int i = 0; i < n; i++)
			{
				++x;
			}
			mtx.unlock();
		});

	t1.join();
	t2.join();
	size_t end = clock();

	cout << x << endl;
	cout << end - begin << endl;

	return 0;
}
#include<mutex>
int x = 0;
recursive_mutex mtx;//递归互斥锁,如果这里采用普通锁的话会造成死锁的问题。
void Func(int n)
{
	if (n == 0)
		return;
	mtx.lock();//如果没有加锁和解锁操作会导致debug模式代码挂掉,其原因就是栈爆了,因为debug下面栈保存了很多非必须信息。
	++x;
	Func(n - 1);
	mtx.unlock();//如果采用普通锁,会导致死锁问题。
}

int main()
{
	thread t1(Func, 10000);
	thread t2(Func, 20000);
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

#include<mutex>

template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lk)
		:_lk(lk)
	{
		_lk.lock();
	}
	~LockGuard()
	{
		_lk.unlock();//析构的时候我们是没有锁对象的,所以这里我们将锁做为私有成员保存起来。
	}

private:
	Lock& _lk;//Lock没有拷贝构造,这里必须采用引用。引用、const对象、没有默认构造的成员变量必须要在初始化列表中初始化。
};

//int x = 0;
mutex mtx;

void Func(int n)
{
	for (int i = 0; i < n; i++)
	{
		try
		{
			//mtx.lock();//这种加锁解锁的方式无法面对抛异常的情况,一旦抛异常,程序跳到catch部分,导致未能解锁。当然我们也可以在catch部分的代码进行解锁但是不推荐这种写法。
			LockGuard<mutex> lock(mtx);//采用类的构造函数和析构函数来解决问题
			//lock_guard<mutex> lock(mtx);//这是库里面提供的,和我们自定义的LockGuard<mutex> 效果一样。
			unique_lock<mutex> lock(mtx);//也是库里面提供的,相比上面两个采用构造和析构加锁和解锁,这个类提供了成员函数进行手动的加锁和解锁。像下面的第二行和第四行所示。
			++x;
			//lock.unlock();
			...
			//lock.lock();
			// .... 抛异常
			if (rand() % 3 == 0)
			{
				throw exception("抛异常");		}
			//mtx.unlock();
		}
		catch (const exception& e)
		{
			cout << e.what() << endl;
		}
	}
	
}

int main()
{
	
	thread t1(Func, 10);
	thread t2(Func, 10);

	t1.join();
	t2.join();

	cout << x << endl;

	return 0;
}
//atomic类,原子操作。
#include<mutex>
void Func(int x)
{
	cout << x << endl;
}

int main()
{
	int n = 100000;
	atomic<int> x = 0;//这三种写法都可以,让x是原子的,这样对其进行加加的时候就可以不用锁了。
	//atomic<int> x = {0};
	//atomic<int> x{0};
	//int x = 0;

	mutex mtx;
	size_t begin = clock();

	thread t1([&, n](){
			for (int i = 0; i < n; i++)
			{
				++x;//由于X是atomic封装的整型对象,此时加加不用加锁也是安全的。
			}
		});

	thread t2([&, n]() {
			for (int i = 0; i < n; i++)
			{
				++x;
			}
		});

	t1.join();
	t2.join();
	size_t end = clock();

	cout << x << endl;
	cout << end - begin << endl;
	
	Func(x);
    printf("%d\n", x.load());//打印的时候为了防止出现类型不匹配,采用load成员函数返回其封装的底层变量。

	return 0;
}
#include<mutex>
#include<condition_variable>

支持两个线程交替打印,t1打印奇数,t2一个打印偶数
int main()
{
	mutex mtx;
	condition_variable cv;//定义一个条件变量

	int n = 100;
	int x = 1;

	// 问题1:如何保证t1先运行,t2阻塞?1、假如t1先抢到锁,t2后抢到锁,t1先运行,t2阻塞在锁上面。2、t2先抢到锁,t1后抢到锁。t2先运行,t1阻塞在锁上面,但是此时t2会执行下一步wait会阻塞诸,并且wait时会解锁保证t1先运行。
	// 问题2:如何防止一个线程不断运行?
	thread t1([&, n]() {
		while (1)
		{
			unique_lock<mutex> lock(mtx);//先创建一个锁,条件变量在wait之前必须要先竞争到锁,竞争到锁了才有资格去wait。
			if (x >= 100)
				break;

			if (x % 2 == 0) // 增加条件控制,偶数就阻塞。
			{
				cv.wait(lock);//wait就是当前执行的线程会阻塞,知道被notify唤醒。并且在阻塞的这一刻会执行lock.unlock(),也就是允许线程继续加锁。当被唤醒的时候,这个函数会去call lock.lock().
			}
			cout << this_thread::get_id() << ":" << x << endl;
			++x;

			cv.notify_one();//只通知一个线程,在这里就是t2,但是我通知你了并不代表t2可以马上回来,因为所有的线程执行都要去排时间片的,所以此时t1并没有停止下来,进行下一次循环,开始重新竞争锁。
		}
		});

	thread t2([&, n]() {
		while (1)
		{
			unique_lock<mutex> lock(mtx);
			if (x > 100)
				break;

			if (x % 2 != 0) // 增加条件控制,奇数就阻塞
			{
				cv.wait(lock);
			}
			cout << this_thread::get_id() << ":" << x << endl;
			++x;

			cv.notify_one();
		}
		});

	t1.join();
	t2.join();

	return 0;
}

 包装器

std::function在头文件<functional>
// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:可变参数包是被调用函数的形参
// function包装器 -- 是一个类模板,由于可调用对象太多了,可调用对象类型进行再封装适配。
// 函数指针
// 仿函数
// lambda,底层还是仿函数
#include<map>
#include<functional>

int f(int a, int b)
{
	cout << "int f(int a, int b)" << endl;
	return a + b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		cout << "int operator() (int a, int b)" << endl;

		return a + b;
	}
};

class Plus
{
public:
	Plus(int rate = 2)
		:_rate(rate)
	{}

	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return (a + b)* _rate;
	}

private:
	int _rate = 2;
};

int main()
{
	int(*pf1)(int,int) = f;//函数指针的类型非常不好声明,
	function<int(int, int)> f1 = f;//可以用f进行初始化
	function<int(int, int)> f2 = Functor();//也可以用Functor进行初始化
	function<int(int, int)> f3 = [](int a, int b) {
		cout << "[](int a, int b) {return a + b;}" << endl;
		return a + b;
	};//lambda表达式进行初始化,也就是说function<int(int, int)>可以对这三种调用方式进行包装,包装之后类型是一样的,但是下面调用的时候是各自不同的。
	cout << f1(1, 2) << endl;
	cout << f2(10, 20) << endl;
	cout << f3(100, 200) << endl;

	map<string, function<int(int, int)>> opFuncMap;//现在声明函数指针、仿函数和lambda表达式的类型的话就比较方便了,采用包装器,虽然不是原生类型,你传下面的三种都可以。
	opFuncMap["函数指针"] = f;
	opFuncMap["仿函数"] = Functor();
	opFuncMap["lambda"] = [](int a, int b) {
	cout << "[](int a, int b) {return a + b;}" << endl;
		return a + b;};
	cout << opFuncMap["lambda"](1, 2) << endl;//使用场景:包装器可以更好的控制调用对象的类型,指令对应函数
	return 0
}
class Plus
{
public:
	Plus(int rate = 2)
		:_rate(rate)
	{}

	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return (a + b)* _rate;
	}

private:
	int _rate = 2;
};

int main()
{
	
	function<int(int, int)> f1 = Plus::plusi;//静态成员函数名就是函数指针
    //function<int(int, int)> f1 = &Plus::plusi;//静态成员函数名就是函数指针,当然加上&也是没有问题的。
    cout << f1(1, 2) << endl;
    function<double(Plus, double, double)> f2 = &Plus::plusd;//非静态成员函数需要加上&
    cout << f2(Plus(), 20, 20) << endl;//匿名对象可以
    Plus pl(3);
	cout << f2(pl, 20, 20) << endl;//命名对象也可以
    function<double(Plus*, double, double)> f2 = &Plus::plusd;//非静态成员函数需要加上&,这种写法匿名对象就没办法调用了,因为匿名对象是右值是一个常量
	Plus p2(3);
	cout << f2(&p2, 20, 20) << endl;

	function<double(Plus, double, double)> f2 = &Plus::plusd;

	cout << f1(1, 2) << endl;
	cout << f2(Plus(), 20, 20) << endl;

	Plus pl(3);
	cout << f2(pl, 20, 20) << endl;

	return 0;
}

 bind

// 使用举例
#include <functional>
int Plus(int a, int b)
{
    return a + b;
}
class Sub
{
public:
    int sub(int a, int b)//有三个参数,还有一个指针
    {
        return a - b;
    }
};
int main()
{
    
    //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
    std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, 
placeholders::_2);
    //auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
    //func2的类型为 function<void(int, int, int)> 与func1类型一样
    //表示绑定函数 plus 的第一,二为: 1, 2
    auto  func2 = std::bind(Plus, 1, 2);   
    cout << func1(1, 2) << endl;
    cout << func2() << endl;
    Sub s;
    // 修改成员函数参数个数
    function<int(Sub,int,int)> fSub=&Sub::sub;
    fSub(Sub(),10,20);//传统写法调用成员函数还有传一个对象
    std::function<int(int, int)> func3 = std::bind(&Sub::sub,s,placeholders::_1, placeholders::_2);//本来有三个参数,其中一个参数s"显示"传递过去,也就是绑定死了,placeholders::_2是一个占位符
    std::function<int(Sub, int)> func4 = std::bind(&Sub::sub,placeholders::_1,100, placeholders::_2);//总共三个参数,现在是将第二个参数绑定死。
    // 参数调换顺序
    std::function<int(int, int)> func5 = std::bind(&Sub::sub,s,placeholders::_2, placeholders::_1);
    cout << func3(1, 2) << endl; 
    cout << func4(s 2) << endl;
    cout << func5(1, 2) << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值