C++复习笔记21

1.自动类型推导

        在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎 么给,或者类型写起来特别复杂,C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方 便。将程序中cit的类型换成auto,程序可以通过编译,而且更加简洁。

void test01()
{
	map<string, string>ssmap;
	auto it = ssmap.begin();
}

2.列表初始化

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

void test02()
{
	int arr[] = { 1,2,3,4,5 };
	vector<int>iv={ 1,2,3,4,5 };//c11初始化方法
	vector<int>iv1{ 1,2,3,4,5 };
}

void test03()
{
	int* p = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };
} 

class Point
{
public:
	Point(int x = 0, int y = 0) : _x(x), _y(y)
	{}
private:
	int _x;
	int _y;
};
void test04()
{
	Point p{ 1, 2 };//标准库支持单个对象的列表初始化
}
template<class Type>
class SeqList
{
public:
	SeqList(const initializer_list<Type>& list)
		:capacity(list.size()), size(0)
	{
		base = new Type[capacity];
		for (const auto& e : list)
			base[size++] = e;
	}
private:
	Type* base;
	size_t capacity;
	size_t size;
};

void test05()
{
	SeqList<int>sq = { 1,2,3,4,5 };
}

3.decltype类型推导

       auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有 时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就 无能为力。

template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
 return left + right;
}
       如果能用 加完之后结果的实际类型作为函数的返回值类型就不会出错 ,但这需要程序运行完才能知道结果的实际类型,即 RTTI(Run-Time Type Identifification 运行时类型识别 )
       decltype 是根据表达式的实际类型推演出定义变量时所用的类型 ,比如:
(1). 推演表达式类型作为变量的定义类型
int main()
{
 int a = 10;
 int b = 20;
 
 // 用decltype推演a+b的实际类型,作为定义c的类型
 decltype(a+b) c;
 cout<<typeid(c).name()<<endl;
 return 0;
}
void test06()
{
	map<int, string>::iterator pos;
	cout << typeid(pos).name() << endl;
	auto pos1 = pos;

	decltype(pos) it;
	int a = 10;
	int b = 20;
	decltype(a) c;//省去了初始化步骤,萃取a的类型
}
(2). 推演函数返回值的类型
void* GetMemory(size_t size)
{
 return malloc(size);
}
int main()
{
 // 如果没有带参数,推导函数的类型
 cout << typeid(decltype(GetMemory)).name() << endl;
 
 // 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
 cout << typeid(decltype(GetMemory(0))).name() <<endl;
 
 return 0;
}

4.默认成员函数控制

        在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构 函数和&const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生 成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带 参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程 序员可以控制是否需要编译器生成

        在 C++11 中,可以在默认函数定义或者声明时加上 =default ,从而显式的指示编译器生成该函数的默认版本,用 =default 修饰的函数称为显式缺省函数
       如果能想要限制某些默认函数的生成, C++98 中,是该函数设置成 private ,并且不给定义 ,这样只要其他人想要调用就会报错。 C++11 中更简单,只需在该函数声明加上 =delete 即可,该语法指示编译器不生成对 应函数的默认版本,称 =delete 修饰的函数为删除函数
#include<iostream>
using namespace std;

//单例模式不是
class Test
{
public:
	Test(int d):m_a(d) {}
	Test(const Test&) = delete;
	Test& operator=(const Test&) = delete;
private:
	//Test(const Test&);
	int m_a;
};

void test01()
{
	Test t(10);
	//Test t1(t);
	Test t2(20);
	Test te(30);
}

void main()
{
	test01();
	system("pause");
}
class A
{
public:
	A(int a) : _a(a)
	{}
	// 显式缺省构造函数,由编译器生成
	//A() = default;//相当于A(){}
	A() = delete;
	A(const A&) = default;//占位符?
	// 在类中声明,在类外定义时让编译器生 成默认赋值运算符重载
	A& operator=(const A& a);
private:
	int _a;
};
A& A::operator=(const A& a) = default;
void test07()
{
	A a1(10);
	//A a2;
	A a3(a1);
	//a2 = a1;
}
class B
{
public:
	B(int a) :_a(a) {}
	B(const B& a) = delete;//一个类不能拷贝构造
private:
	//B(const A& a) { this->_a = a._a; }//一个类不能拷贝构造
	int _a;
};

void test08()
{
	B b(1);
	//B b1(b);//不能拷贝构造
}
5.右值引用
        C++98 中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通 过指针来实现的,因此使用引用,可以提高程序的可读性。为了提高程序运行效率 C++11 中引入了右值引用 ,右值引用也是别名,但其只能对右值引用。
void test09()
{
	int x = 10;
	int& b = x;
	const int& a = 10;//常量用常引用
	int&& a = 10;//对于右值的引用
}
 
#include<iostream>
using namespace std;

void test01()
{
	int a = 10;
	const int& b = a;

	a = 100;
	//int&& c = a;//右值引用只能引用右值
	const int& d = 10;
	int&& e = 10;
}

int fun(int a, int b)
{
	int value = a + b;
	return value;//无名临时空间具有常性
}

void test02()
{
   int&& ret = fun(10,20); 
   const int& ret1 = fun(10, 20);
   cout << ret << endl;
  /* cout << ret << endl;
   cout << ret << endl;*/
}

void main()
{
	test02();
	system("pause");
}

左值与右值是 C 语言中的概念,但 C 标准并没有给出严格的区分方式,一般认为: 可以放在 = 左边的,或者能够取地址的称为左值,只能放在 = 右边的,或者不能取地址的称为右值 ,但是也不一定完全正确。
       因此关于左值与右值的区分不是很好区分,一般认为:
1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。
2. const 修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址 ( 如果只是const 类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间 ) C++11 认为其是左值。
3. 如果表达式的运行结果是一个临时变量或者对象,认为是右值。
4. 如果表达式运行结果或单个变量是一个引用则认为是左值。
       总结:
1. 不能简单地通过能否放在 = 左侧右侧或者取地址来判断左值或者右值,要根据表达式结果或变量的性质判断,比如上述: c 常量
2. 能得到引用的表达式一定能够作为引用,否则就用常引用。
       C++11 对右值进行了严格的区分:
C 语言中的纯右值,比如: a+b, 100
将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。
int main()
{
 // 普通类型引用只能引用左值,不能引用右值
 int a = 10;
 int& ra1 = a; // ra为a的别名
 //int& ra2 = 10; // 编译失败,因为10是右值
 
 const int& ra3 = 10;
 const int& ra4 = a;
 return 0;
}
注意: 普通引用只能引用左值,不能引用右值,const引用既可引用左值,也可引用右值
C++11中右值引用:只能引用右值,一般情况不能直接引用左值
int fun(int a, int b)
{
	static int value = a + b;
	return value;
}

void test10()
{
    const int& ret = fun(10, 20);
	int&& ret = fun(10, 20);
}

void test11()
{
	int a = 10;
	//int&& b = a;//右值引用只能引用右值
}

6.移动语义

为了解决:浅拷贝+对象按值返回会造成空间浪费

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
			str = "";
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
	
	//右值引用 的拷贝构造函数 实现移动语义
	String(String&& s)
		: _str(s._str)
	{
		s._str = nullptr;
	}

	String& operator=(const String& s)
	{
		if (this != &s)
		{
			//异常安全
			char* pTemp = new char[strlen(s._str) + 1];
			strcpy(pTemp, s._str);
			delete[] _str;
			_str = pTemp;
		}
		return *this;
	}

	String operator+(const String& s)
	{
		char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
		strcpy(pTemp, _str);
		strcpy(pTemp + strlen(_str), s._str);
		String strRet(pTemp);
		return strRet;
	}

	~String()
	{
		if (_str) delete[] _str;
	}
private:
	char* _str;
};

void test01()
{
	String s1("hello");
	//String s2 = s1;//不能使用移动语义,s1不是右值
	String s2 = move(s1);//move将s1强转为右值,可以调用移动语义拷贝构造 s1被搬空
}

void test02()
{
	String s1("hello");
	String s2("world");
	String s3(s1 + s2);
}

void test03()
{
	String s1("hello world");
	String s2(move(s1));//s1被搬空
	String s3(s2);
}

int main()
{
	test03();
	return 0;
}
       在 operator+ 中: strRet 在按照值返回时,必须创建一个临时对象,临时对象创建好之后, strRet 就被销毁了,最后使用返回的临时对象构造 s3 s3 构造好之后,临时对象就被销毁了 。仔细观察会发现: strRet 、临 时对象、 s3 每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大。
        C++11 提出了 移动语义 概念,即: 将一个对象中资源移动到另一个对象中的方式 ,可以有效缓解该问题。
        因为 strRet 对象的生命周期在创建好临时对象后就结束了,即将亡值, C++11 认为其为右值,在用 strRet 构造 临时对象时,就会采用移动构造,即将 strRet 中资源转移到临时对象中。而临时对象也是右值,因此在用临 时对象构造 s3 时,也采用移动构造,将临时对象中资源转移到 s3 中,整个过程,只需要创建一块堆内存即 可,既省了空间,又大大提高程序运行的效率。
注意:
1. 移动构造函数的参数千万不能设置成 const 类型的右值引用,因为资源无法转移而导致移动语义失效。
2. C++11 中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理 时,用户必须显式定义自己的移动构造。
8.右值引用左值:
按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。 当需要用右值引用引用一个左值时,可以通过 move 函数将左值转化为右 C++11 中, std::move() 函数 位于 头文件中,该函数名字具有迷惑性,它 并不搬移任何东西,唯一的功 能就是将一个左值强制转化为右值引用,然后实现移动语义

 移动语义例子:

1. 被转化的左值,其生命周期并没有随着左值的转化而改变,即 std::move 转化的左值变量 lvalue 不会被销毁。
2. STL 中也有另一个 move 函数,就是将一个范围中的元素搬移到另一个位置。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
			str = "";
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	String(String&& s)
		: _str(s._str)
	{
		s._str = nullptr;
	}

	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
String& operator=(const String& s)
{
	if (this != &s)
	{
		char* pTemp = new char[strlen(s._str) + 1];
		strcpy(pTemp, s._str);
		delete[] _str;
		_str = pTemp;
	}
	return *this;
}

String operator+(const String& s)
{
	char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
	strcpy(pTemp, _str);
	strcpy(pTemp + strlen(_str), s._str);
	String strRet(pTemp);
	return strRet;
}

~String()
{
	if (_str) delete[] _str;
}
private:
	char* _str;
};

class Person
{
public:
	Person(const char* name, const char* sex, int age)
		: _name(name)
		, _sex(sex)
		, _age(age)
	{}

	Person(const Person& p)
		: _name(p._name) 
		, _sex(p._sex)
		, _age(p._age)
	{}

#if 0

	Person(Person&& p)
		: _name(p._name)
		, _sex(p._sex)
		, _age(p._age)
	{}

#else//调用String 类的移动语义
	Person(Person&& p)
		: _name(move(p._name))
		, _sex(move(p._sex))
		, _age(p._age)
	{}

#endif

private:
	String _name;
	String _sex;
	int _age;
};
Person GetTempPerson()
{
	Person p("prety", "male", 18);
	return p;
}
int main()
{
	Person p(GetTempPerson());
	return 0;
}
       注意:以下代码是 move 函数的经典的误用,因为 move s1 转化为右值后,在实现 s2 的拷贝时就会使用移动 构造,此时 s1 的资源就被转移到 s2 中, s1 就成为了无效的字符。
int main()
{
 String s1("hello world");
 String s2(move(s1));
 String s3(s2);
 return 0;
}

9.完美转发

       完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数 完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销 ,就好像转发者不存在 一样。 所谓完美: 函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相 应实参是右值,它就应该被转发为右值 。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进 行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)。
#include<iostream>
using namespace std;

void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; }
void Fun(const int& x) { cout << "const lvalue ref" << endl; }
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }
template<typename T>
void PerfectForward(T&& t) 
{
	Fun(forward<T>(t));//对t进行完美转发
	//Fun(std::forward<T>(t)); 
}

void test01()
{
    int a = 10;
	PerfectForward(a);
	PerfectForward(10);
	const int b = 20;
	PerfectForward(b);
}

int main()
{
	test01();
	return 0;
}

10lambda表达式的存在意义:提供一个匿名的函数对象(仿函数),不用整个给出类的定义,比较简便。

lambda 表达式书写格式: [capture-list] (parameters) mutable -> return-type { statement }
1. lambda 表达式各部分说明
[capture-list] : 捕捉列表 ,该列表总是出现在 lambda 函数的开始位置, 编译器根据 [] 来判断接下来
的代码是否为 lambda 函数 捕捉列表能够捕捉上下文中的变量供 lambda 函数使用 。(parameters) :参数列表。与 普通函数的参数列表一致 ,如果不需要参数传递,则可以连同 () 一起
省略mutable :默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量性。使用该修 饰符时,参数列表不可省略 ( 即使参数为空 ) ->returntype :返回值类型 。用 追踪返回类型形式声明函数的返回值类型 ,没有返回值时此部分 可省略。 返回值类型明确情况下,也可省略,由编译器对返回类型进行推导 {statement} :函数体 。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。 注意: lambda 函数定义中, 参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空 因此 C++11 最简单的 lambda 函数为: []{} ; lambda 函数不能做任何事情。
捕捉列表描述了上下文中那些数据可以被 lambda 使用 ,以及 使用的方式传值还是传引用
[var] :表示值传递方式捕捉变量 var
[=] :表示值传递方式捕获所有父作用域中的变量 ( 包括 this)
[&var] :表示引用传递捕捉变量 var
[&] :表示引用传递捕捉所有父作用域中的变量 ( 包括 this)
[this] :表示值传递方式捕捉当前的 this 指针
注意:
a. 父作用域指包含 lambda 函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
比如: [=, &a, &b] :以引用传递的方式捕捉变量 a b ,值传递方式捕捉其他所有变量 [& a, this] :值传递方式捕捉变量 a this ,引用方式捕捉其他变量 c. 捕捉列表不允许变量重复传递,否则就会导致编 译错误 。 比如: [=, a] = 已经以值传递方式捕捉了所有变量,捕捉 a 重复
d. 在块作用域以外的 lambda 函数捕捉列表必须为空
e. 在块作用域中的 lambda 函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f. lambda 表达式之间不能相互赋值 ,即使看起来类型相同
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;

void test01()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };

	int n = sizeof(array) / sizeof(array[0]);
	// 默认按照小于比较,排出来结果是升序
	sort(array, array + sizeof(array) / sizeof(array[0]));

	//qsort();
	for (int i = 0; i < n; ++i)
		cout << array[i] <<" ";
	cout << endl;

	// 如果需要降序,需要改变元素的比较规则
	sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());

	for (int i = 0; i < n; ++i)
		cout << array[i] <<" ";
}

struct Goods
{
	string _name;
	double _price;
};

struct Goods_Less
{
	bool operator()(const struct Goods& g1, const struct Goods& g2)
	{
		return g1._price < g2._price;
	}
};

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

int main()
{
	Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
	int n = sizeof(gds) / sizeof(gds[0]);
	//sort(gds, gds + n, Goods_Greater());//谓词这里传函数指针或者仿函数都可以
	
	sort(gds, gds + n, [](const struct Goods& g1,
		const struct Goods& g2)->bool
		{return g1._price > g2._price; });//lambda表达式就是一个无名的仿函数对象,底层就是仿函数对象实现的
	return 0;
}

示例:

lambda 表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要
直接调用,可借助 auto 将其赋值给一个变量。
#include<iostream>
using namespace std;

void test01()//lambda表达式就是用在函数对象(谓词)那里使用的,省略了类的给出)
{
	[]()->void {};//最基本的lanbda //匿名函数对象
	//[]捕获块
	//()参数列表
	//->返回值
	//{}函数体
}

void test02()
{
	auto f1 = [](int a, int b)->int {return a + b; };
	cout << f1(10, 20) << endl;
	cout << typeid(f1).name() << endl;
}

void test03()
{
	auto f1 = []{cout << "hello lambda" << endl; };//没有参数可省略(),没有返回值->可省略
	f1();
}

void main()
{
	test03();
	system("pause");
}
#include<iostream>
using namespace std;

int a = 1;
int b = 2;

int fun()
{
	return a + b;
}

void test01()
{
	cout << fun() << endl;
	int x = 10;
	int y = 20;
	auto f1 = [x,y/*,d*/] {cout << a + b+x+y << endl; };//局部变量需要捕获 lambda前面是父作用域,后面是子作用域,一般对父作用域进行捕获
    
	int d = 30;
	f1();
}

void test02()
{
	int x = 10;
	int y = 20;

	auto f1 = [=](int p, int q)mutable->int//实参不会改变,仅在函数内部改变形参 mutable不可以省略
	auto f1 = [&](int p, int q)->int//实参会改变
	{
		p = 100;
		q = 200;
		x = 300;
		y = 500;
		return p + q + x + y;
	};
	cout << f1(10,20) << endl;
	cout << "x=" << x << " y=" << y << endl;
}

class Test
{
public:
	int fun(int x, int y)
	{
		auto f=[this](int x, int y)->int
		{
			return x + y + m_a + m_b;
		};
		return f(x,y);
	}
private:
	int m_a=1;
	int m_b = 2;
};

void test03()
{
	Test t;
	cout << t.fun(10, 20) << endl;
}

void main()
{
	test03();
	system("pause");
}

C11线程库操作:

1. 线程是操作系统中的一个概念, 线程对象可以关联一个线程,用来控制线程以及获取线程的状态
2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
3. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一
般情况下可按照以下三种方式提供:
函数指针
lambda 表达式
函数对象
4. thread 类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关 联线程的状态转移给其他线程对象,转移期间不影响线程的执行。
5. 可以通过 jionable() 函数判断线程是否是有效的,如果是以下任意情况,则线程无效
采用无参构造函数构造的线程对象
线程对象的状态已经转移给其他线程对象
线程已经调用 jion 或者 detach 结束
       线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的 ,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为 其实际引用的是线程栈中的拷贝,而不是外部实参.
       注意:如果是类成员函数作为线程参数时,必须将 this 作为线程函数参数。

        启动了一个线程后,当这个线程结束的时候,如何去回收线程所使用的资源呢? thread 库给我们两种选择:join() 方式join() :主线程被阻塞,当新线程终止时, join() 会清理相关的线程资源,然后返回,主线程再继续向下 执行,然后销毁线程对象 。由于 join() 清理了线程的相关资源, thread 对象与已销毁的线程就没有关系 了,因此 一个线程对象只能使用一次 join() ,否则程序会崩溃
detach() 方式
        detach() :该函数被调用后,新线程与线程对象分离,不再被线程对象所表达,就不能通过线程对象控 制线程了,新线程会在后台运行,其所有权和控制权将会交给 c++ 运行库。同时, C++ 运行库保证,当 线程退出时,其相关资源的能够正确的回收
因此: 线程对象销毁前,要么以 jion() 的方式等待线程结束,要么以 detach() 的方式将线程与线程对分离。
#include<thread>
#include<iostream>
using namespace std;

struct Test
{
	int a;
	double b;
	char c;
};

struct Student
{
	char name[10];
	int age;
};

struct Data
{
	int data;
	Test test_data;
	Student student_data;
};

//多线程程序
void thread_fun(Data* pdt)
{
	for (int i = 0; i < pdt->data; ++i)
	{
		cout << "This is Child Thread." << endl;
	}
	cout << pdt->student_data.name << endl;
	cout << pdt->student_data.age << endl;
};

void test01()
{
	int n = 5;
	Test t={ 10,12.34,'A' };
	Student s = { "hym",18 };
	Data dt = { n,t,s };
	thread td(thread_fun,&dt);
	//cout << td.get_id() << endl;
	for (int i = 0; i < 10; ++i)
	{
		cout << "This is Main Thread." << endl;
	}
	
	//td.detach();
	td.join();
}

void main()
{
	test01();
	system("pause");
}
#include<iostream>
#include<thread>
using namespace std;

class Test 
{
public:
	void fun()
	{
		cout << "class  Test.fun()" << endl;
	}
	int m_a;
};

struct Threadobj
{
	void operator()()
	{
		cout << "Hello Threadobj" << endl;
	}
};

void thread_fun()
{
	cout << "thread id" << this_thread::get_id() << endl;
}

void thread_fun1(int& a)
{
	a += 10;
	cout << "int fun a="<<a << endl;
}

void thread_fun2(int* a)
{
	*a += 10;
	cout << "int fun *a=" << *a << endl;
}

void thread_fun4(int a)
{
	a += 10;
}

void thread_fun3(Test* t)
{
	t->fun();
}

void test01()
{
	cout << "Main thread id" << this_thread::get_id() << endl;
	thread th(thread_fun);
	cout << "Child id" << th.get_id() << endl;
	th.join();
}

void test02()
{
	thread th[10];
	/*for (int i = 0; i < 10; ++i)
		th[i](thread_fun1);*/
}

void test03()
{
	cout << "Main thread id" << this_thread::get_id() << endl;
	thread th(thread_fun);
	cout << "thread id" << th.get_id() << endl;
	th.detach();
	cout << "thread id" << th.get_id() << endl;
}

void test04()
{
	thread th;
	thread::id id = th.get_id();
	cout << "id=" << id << endl;
}

void test05()
{
	thread th(thread_fun);//函数指针
	th.join();

	thread th1([] {cout << "hello lambda thread" << endl; });//lambda表达式
	th1.join();

	Threadobj thobj;
	thread th2(thobj);
	//thread th2(Threadobj());//不能创建线程 临时对象不行 必须先创建出对象再放进来
	th2.join();
}

void test06()
{
	thread th(thread_fun);

	//thread th1 = th;//不能拷贝构造
    
	thread th1 = move(th);
}

void test07()
{
	//thread th;
	thread th(thread_fun);
	th.detach();
	if (th.joinable())
	{
		cout << "th join able." << endl;
	}

	else
	{
		cout << "th join unable" << endl;
	}
	//th.join();
}

//void test08()//传值不能改变实参
//{
//	int a = 10;
//	thread th(thread_fun1, a);
//	th.join();
//	cout << "int test08 a=" << a << endl;
//}

void test09()
{
	int a = 10;
	thread th(thread_fun2, &a);//传地址
	th.join();
	cout << "int test08 a=" << a << endl;
}

void test10()
{
	int a = 10;
	thread th(thread_fun1, ref(a));//传引用
	th.join();
	cout << "int test08 a=" << a << endl;
}

void test11()//仅传成员变量不需要传this指针
{
	Test t;
	thread th(thread_fun4,t.m_a);
	th.join();
}

void test12()//传对象的成员函数需要传this指针
{
	Test t;
	thread th(&Test::fun,&t);//使t对象调用fun函数 不加&也可以
	th.join();
}

void main()
{
	test12();
	system("pause");
}
#include<iostream>
#include<thread>
using namespace std;
// jion()的误用一
void ThreadFunc() { cout << "ThreadFunc()" << endl; }
bool DoSomething() { return false; }
int main()
{
	std::thread t(ThreadFunc);
	if (!DoSomething())
		return -1;

	t.join();
	return 0;
}
#include<iostream>
#include<thread>
using namespace std;
void ThreadFunc() { cout << "ThreadFunc()" << endl; }
void Test1() { throw 1; }
void Test2()
{
	int* p = new int[10];
	std::thread t(ThreadFunc);
	try
	{
		Test1();
	}
	catch (...)
	{
		delete[] p;
		throw;
	}

	//t.jion();//执行不到
}

以上两段代码中join的调用位置不对,导致线程资源无法正常回收。采用jion()方式结束线程时,jion()的调用位置非常关键。为了避免该问题,可以采用RAII的方式 对线程对象进行封装,比如:

#include<iostream>
#include<thread>
using namespace std;
class mythread
{
public:
	explicit mythread(std::thread& t) :m_t(t) {}
	~mythread()
	{
		if (m_t.joinable())
			m_t.join();
	}
	mythread(mythread const&) = delete;
	mythread& operator=(const mythread&) = delete;
private:
	std::thread& m_t;
};
void ThreadFunc() { cout << "ThreadFunc()" << endl; }
bool DoSomething() { return false; }
int main()
{
	thread t(ThreadFunc);
	mythread q(t);
	if (DoSomething())
		return -1;
	//t.join();
	return 0;
}

detech例子:

#include<iostream>
#include<thread>
using namespace std;

void thread_fun()
{
	for(;;)
	cout << "thread_fun()" << endl;
}

void test01()
{
	thread th(thread_fun);
	th.detach();
	
	//th.join();
}

void main()
{
	test01();
	system("pause");
}

按数组创建线程:

#include<iostream>
#include<thread>
using namespace std;

void thread_fun()
{
	cout << "thread_fun" <<endl;
}

void test01()
{
	/*thread th1(thread_fun);
	thread th2(thread_fun);
	thread th3(thread_fun);

	th1.join();
	th2.join();
	th3.join();*/

	thread* thar[3];
	for (int i = 0; i < 3; ++i)
	{
		thar[i] = new thread(thread_fun);
	}

	for (int i = 0; i < 3; ++i)
		thar[i]->join();
}

void main()
{
	test01();
	system("pause");
}

线程安全:

        多线程最主要的问题是共享数据带来的问题 ( 即线程安全 ) 。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是, 当一个或多 个线程要修改共享数据时,就会产生很多潜在的麻烦 。比如:
#include <iostream>
#include<mutex>
using namespace std;
#include <thread>
//多线程 临界(共享资源) 创建线程的目的是并发性,效率高
unsigned long sum = 0L;
mutex t;
void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		t.lock();
		sum++;
		t.unlock();
	}
}
int main()
{
	cout << "Before joining,sum = " << sum << std::endl;
	thread t1(fun, 10000000);
	thread t2(fun, 10000000);
	t1.join();
	t2.join();
	cout << "After joining,sum = " << sum << std::endl;
	return 0;
}
        虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对 sum++ 时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。
因此 C++11 中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作, C++11 引入的原子操作类型,使得线程间数据的同步变得非常高效。

使用原子操作可以解决:

#include <iostream>
using namespace std;
#include <thread>
#include <atomic>
atomic_long sum={0};//初始化列表
void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		sum++; // 原子操作
		//printf("sum=%d\n", sum); 
		//cout << "sum=" << sum << endl;
	}
}
int main()
{
	cout << "Before joining, sum = " << sum << std::endl;
	thread t1(fun, 1000000);
	thread t2(fun, 1000000);
	t1.join();
	t2.join();

	cout << "After joining, sum = " << sum << std::endl;
	return 0;
}
       在 C++11 中, 程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问 注意:原子类型通常属于 " 资源型 " 数据,多个线程只能访问单个原子类型的拷贝,因此 C++11 中,原子类 型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator= 等,为了防止意 外,标准库已经将 atmoic 模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。
锁机制:std::mutex lock()锁已经被占用则当前线程阻塞,trylock()锁已经被占用则当前线程退出。
try_lock_for(),try——lock_until 是两种定时用法,不到时间会阻塞,如果锁被其他线程释放,这是当前线程占用并加锁,超时则返回false;
       lock_guard 类模板主要是通过 RAII 的方式,对其管理的互斥量进行了封装 ,在需要 加锁的地方,只需要用上述介绍的 任意互斥体实例化一个 lock_guard ,调用构造函数成功上锁,出作用域前, lock_guard 对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题
       与 lock_gard 类似, unique_lock 类模板也是采用 RAII 的方式对锁进行了封装,并且也是以独占所有权的方式管理 mutex 对象的上锁和解锁操作,即其对象之间不能发生拷贝 。在构造 ( 或移动 (move) 赋值 ) 时, unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的 unique_lock 对象负责传入的 Mutex 对象的上锁和解锁操作。 使用以上类型互斥量实例化 unique_lock 的对象时,自动调用构造函数上锁, unique_lock 对象销毁时自动调用析构函数解锁,可以很方便的防止死锁问题
        与 lock_guard 不同的是, unique_lock 更加的灵活,提供了更多的成员函数:
        上锁 / 解锁操作 lock try_lock try_lock_for try_lock_until unlock修改操作 :移动赋值、交换 (swap :与另一个 unique_lock 对象互换所管理的互斥量所有权 ) 、释放 (release :返回它所管理的互斥量对象的指针,并释放所有权 ) 获取属性 owns_lock( 返回当前对象是否上了锁 ) operator bool()( owns_lock() 的功能相同 ) mutex( 返回当前 unique_lock 所管理的互斥量的指针 )
#include<iostream>
using namespace std;
#include <thread>
#include <mutex>

int number = 0;
mutex g_lock;
int ThreadProc1()
{
	for (int i = 0; i < 1000000; i++)
	{
		//g_lock.lock();
		//lock_guard<mutex> lock(g_lock);
		unique_lock<mutex> lock(g_lock);
		++number;
		//cout << "thread 1[] :" << number << endl;
		//printf("thread 1[%d] :%d\n", i, number);
		//g_lock.unlock();
	}
	return 0;
}
int ThreadProc2()
{
	for (int i = 0; i < 1000000; i++)
	{
		//g_lock.lock();
		//lock_guard<mutex> lock(g_lock);
		unique_lock<mutex> lock(g_lock);
		--number;
		//cout << "thread 2 :" << number << endl;
		//printf("thread 2[%d] :%d\n", i, number);
		//g_lock.unlock();
	}
	return 0;
}
int main()
{
	thread t1(ThreadProc1);
	thread t2(ThreadProc2);
	t1.join();
	t2.join();
	cout << "number:" << number << endl;
	system("pause");
	return 0;
}
#include<iostream>
using namespace std;
template<class _Mutex>
class lock_guard
{
public:
	// 在构造lock_gard时,_Mtx还没有被上锁
	explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
	{
		_MyMutex.lock();
	}
	// 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁
	lock_guard(_Mutex& _Mtx, adopt_lock_t)
		: _MyMutex(_Mtx)
	{}
	~lock_guard() _NOEXCEPT
	{
		_MyMutex.unlock();
	}
	lock_guard(const lock_guard&) = delete;
	lock_guard& operator=(const lock_guard&) = delete;
private:
	_Mutex& _MyMutex;
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值