C++11拓展语法

C++11是继C++03之后的一次大更新,出现了很多不同的语法,这里来逐一学习一下吧。

统一的列表初始化

C++11实现了初始化的大一统,无论是内置类型还是自定义类型都可以通过{}来进行初始化。

内置类型初始化

先来观测下列代码:

cpp 复制代码
int main()
{
	int x = { 10 };//列表初始化
	int y{ 15 };//忽略'='的列表初始化
	char ch{ 'c' };

	cout << x << endl << y << endl << ch << endl;

	return 0;
}

运行结果:

复制代码
10
15
c

上述结果表明,对于内置类型可以以= { 参数 } 或 { 参数 }的形式进行初始化。

自定义类型

先以STL自带的容器进行试验:

cpp 复制代码
int main()
{
	string str1{ "Love" };
	cout << str1 << endl;
	return 0;
}

输出结果为:

复制代码
Love

实际上本质是参数的隐式类型转换,不相信的话我们来以简单的自定义类型试验一下。

先定义一个简单的Point类型:

cpp 复制代码
class Point
{
public:

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

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

	void Show()
	{
		cout << _x << ' ' << _y << endl;
	}

private:
	int _x;
	int _y;
};

int main()
{
	Point x{ 1,2 };
	x.Show();
	Point y={ 11,2 };
	y.Show();

	Point z = 1;
	z.Show();

}

运行结果为:

复制代码
构造
1 2
构造
11 2
构造
1 1

可以看到是调用了对应的构造函数来隐式类型转换,然后拷贝给Point。

如果像下面这种:

cpp 复制代码
explicit Point(const int x,const int y)
	:_x(x)
	,_y(y)
{
	cout << "构造" << endl;
}
复制代码
error C3445: "Point" 的复制列表初始化不能使用显式构造函数

或者

cpp 复制代码
Point& w = 2;
复制代码
error C2440: "初始化": 无法从"int"转换为"Point &"

initializer_list初始化

之前写vector的区间构造也提到过,我们可以以下列方式来初始化vector的一堆值:

cpp 复制代码
vector<int>v = { 1,2,3 };

这种形式和上述的隐式类型转换相似又不一致,确实是调用了vector的构造函数,具体是:

没错,这里其实是传入了迭代器 来进行初始化。

C++11中将{}封装成了一个类:

cpp 复制代码
auto x = { 1,2,3 };
cout << typeid(x).name() << endl;

结果:

复制代码
class std::initializer_list<int>

也就是说所有花括号都是initializer_list这个类,上面vector的初始化,实际上就是传入initializer_list的迭代器来实现的。

具体应用

有了这些语法,我们能实现部分容器的高效初始化:

cpp 复制代码
map<string, int>m = {
	{"apple",19},
	{"banana",10},
	{"Love",0}
};

可以看到,这里其实是首先从{"apple",19}隐式类型转换为pair<string,int>,然后再根据initializer_list初始化到m上。

声明

C++11增加了许多简化声明的方式。

auto

auto关键字前面已经讲过了,可以自动推导类型。像:

cpp 复制代码
int add(int x, int y)
{
	return x + y;
}
int main()
{
	string str{ "love" };
	auto ss = str;
	auto func = add;
	cout << typeid(ss).name() << endl;
	cout << typeid(func).name() << endl;

	return 0;
}

结果:

复制代码
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
int (__cdecl*)(int,int)

除此之外,auto还可以当作返回值类型:

cpp 复制代码
auto minus(int x, int y)
{
	return x - y;
}
int main()
{
	cout << ::minus(10, 6) << endl;
	return 0;
}

结果:

复制代码
4

当然了,auto不能作参数列表。

cpp 复制代码
int times(int x,auto y)
{
	return x * y;
}
复制代码
 error C3533: 参数不能为包含"auto"的类型

事实上我们有模板功能来实现接入参数的类型随机性,也不需要auto来实现。

decltype

decltype顾名思义就是declaim type,获取类型。不同于typied.name()得到的仅是字符串,decltype得到的类型名是可以直接当作类型使用的:

cpp 复制代码
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
	decltype(t1 * t2) ret;
	cout << typeid(ret).name() << endl;
}
int main()
{
	const int x = 1;
	double y = 2.2;
	decltype(x * y) ret; // ret的类型是double
	decltype(&x) p;
	// p的类型是int*
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;
	F(1, 'a');
}

运行结果:

复制代码
double
int const * __ptr64
int

nullptr

在C语言中,NULL表示空指针,但是NULL同时又表示整数0.

考虑到程序的安全性,C++11以nullptr形式定义空指针,实际上就是(void*)0;

范围for

范围for也是C++11新增的语法,前面实现各种容器的时候已经讲过。只要容器实现了迭代器都能使用范围for。以下是一个vector的例子:

cpp 复制代码
vector<int> v = { 1,2,3,4,5 };
for (auto& e : v)
{
	cout << e << ' ';
}
cout << endl;

相当于:

cpp 复制代码
vector<int> v = { 1,2,3,4,5 };
auto it = v.begin();
while (it != v.end())
{
	cout << *it << ' ' << endl;
	it++;
}
cout << endl;

结果都是:

复制代码
1
2
3
4
5

智能指针

这部分篇幅较长,在后续文章再进行讲解。

STL新增容器

C++11在STL中增加了新的容器:array、forward_list、unordered_set、unordered_map

具体使用就不多介绍了。

右值引用

右值引用则是C++11中新增的相当重要的语法。

左值和右值

首先需要明确何为左值何为右值?

简单来说,左值就是可以取地址的数值表达式,右值就是不能取地址的数值表达式

如:

cpp 复制代码
int main()
{
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	
	cout << &p << endl << &b << endl << &c << endl;
	return 0;
}

结果:

复制代码
000000D91AEFF5C8
000000D91AEFF5E4
000000D91AEFF604

可以看到p、b、c这些都是左值。

常见的右值有:字面常量、表达式返回值、函数返回值

cpp 复制代码
int add(int x, int y)
{
	return x + y;
}

int main()
{
	cout << &10 << endl;
	cout << &(2 - 1) << endl;
	cout << &add(2, 10) << endl;
}

结果:

复制代码
 error C2101: 常量上的"&"
 error C2101: 常量上的"&"
 error C2102: "&"要求左值

其中 error C2102明确表明了,只有左值才能取地址。

不过这里要郑重申明,右值只是不能取地址,不代表没有地址

左值引用和右值引用

左值引用自然就是对左值的引用,右值引用就是对右值的引用:

左值引用

cpp 复制代码
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

右值引用

cpp 复制代码
int&& rr1 = 10;
int&& rr2 = 2 - 1;
int&& rr3 = add(2, 10);

可以看到,右值引用是类型名+"&&"

接下来我们需要考虑两件事情,那就是左值引用能否引用右值,右值引用能否引用左值?

cpp 复制代码
int& rr1 = 10+2;
int x = 20;
int&& y = x;
复制代码
error C2440: "初始化": 无法从"int"转换为"int &"
error C2440: "初始化": 无法从"int"转换为"int &&"

可以看到左值引用直接引用右值和右值引用直接引用左值都会报错,那是否没有办法了,自然不是。

cpp 复制代码
const int& rr1 = 10 + 2;
int x = 20;
int&& y = move(x);

像这种形式就可以通过编译了。也就是说const 左值引用可以引用左值也可以引用右值,右值引用只能应用右值或者move以后的左值。

这里的move函数实际上就是将左值转换成右值。

  • 还有一点比较重要的。我们都知道左值引用本身也是左值,那右值引用是左值还是右值呢?
cpp 复制代码
int main()
{
	int&& x = 10;
	cout << &x << endl;
	return 0;
}
复制代码
000000CE6D8FF614

可以看到,右值引用本身也是左值。同时也证明了右值确实也有地址。

总结:

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值。
  3. 右值引用只能右值,不能引用左值。
  4. 但是右值引用可以move以后的左值。

移动构造和移动赋值

右值其实又细分为纯右值和将亡值。纯右值是指字面常量那些,将亡值则是指临时变量或传值返回。

基于将亡值即将销毁的特性,我们可以利用其资源来完成构造和拷贝:

cpp 复制代码
void swap(string& s)
{
	::swap(_str, s._str);
	::swap(_size, s._size);
	::swap(_capacity, s._capacity);
}

string(string&& s)
	:_str(nullptr)
	,_size(0)
	,_capacity(0)
{
	cout << "移动构造" << endl;
	swap(s);
}

string& operator=(string&& s)
{
	cout << "string& operator=(string&& s)" << endl;
	swap(s);
	return *this;
}

是的我们直接将右值的资源换给了左值,右值可以安心去世了。

左右值引用使用场景

首先明确下,引用的意义是减少拷贝,提高效率。

但是对于一些函数返回值是常量拷贝时,左值引用就派不上用场了。

基于前文实现的string的实现,我们来观测一下下面情况:

cpp 复制代码
mystring::string ret1;
ret1 = mystring::string::to_string(1234);
复制代码
string(char* str) -- 构造
string(char* str) -- 构造
string& operator=(string s) -- 深拷贝

可以看到,先是对ret1进行了一次构造,然后在to_string函数里面又进行了一次构造。实际上最后应该进行两次拷贝构造,分别是to_string函数里string返回时,拷贝给一个临时变量,临时变量再拷贝给ret1.

当然两次拷贝构造被编译器优化成了一次,如果是下面这样写的话,优化更甚:

cpp 复制代码
mystring::string ret2 = mystring::string::to_string(1234);
cout << ret2.c_str() << endl;
复制代码
string(char* str) -- 构造
1234

可以看到,他竟然优化成了一次构造,不得不说现在的编译器是真的厉害啊。

回到原文,考虑一开始的情况。可以发现to_string返回的是值,对他进行深拷贝然后再深拷贝给ret1代价无疑非常大。如果我们实现了移动构造,那么代价就可以缩小:

cpp 复制代码
mystring::string ret1;
ret1 = mystring::string::to_string(1234);
复制代码
string(char* str) -- 构造
string(char* str) -- 构造
string(string&& s) -- 移动赋值

可以看到拷贝构造变成了赋值构造,代价大大降低了。

当然了,我们可不要做一个喜新厌旧的人。移动拷贝并非所有情况都是最优的。我们之所以用移动构造代替拷贝构造能提升效率,本质上拷贝构造调的是深拷贝,如果是浅拷贝的话拷贝构造效率自然会比移动构造高。

完美转发

前面提到过,右值引用本身会退化成左值,这就为某些事请埋下了伏笔。

且看下面函数:

cpp 复制代码
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;
}

首先说明下:

template

void PerfectForward(T&& t)

这里T&&可不是指T类型的右值引用,这里是万能引用模板的格式。当传入左值引用就会识别成左值引用,传入右值引用就会识别成右值引用

那么来看看代码的运行结果吧:

复制代码
左值引用
左值引用
左值引用
const 左值引用
const 左值引用

可以看到由于右值引用退化成了左值,调用Fun函数变成了全部都是左值函数了。

当然了我们可以强制类型转换一下:

cpp 复制代码
template<typename T>
void PerfectForward(T&& t)
{
	Fun((T&&)t);
}
复制代码
右值引用
左值引用
右值引用
const 左值引用
const 右值引用

结果自然正确了,但这不是C++11提供的最佳方案。C++11提供了完美转发函数forward能完美传递对应的左右值类型,使用方法如下:

cpp 复制代码
template<typename T>
void PerfectForward(T&& t)
{
	Fun(forward<T>(t));
}
复制代码
右值引用
左值引用
右值引用
const 左值引用
const 右值引用

可以看到forward其实是个仿函数,最终也实现了类型的完美对应。

lambda表达式

lambda表达式同样是C++11中新增的及其重要的语法。

使用格式

首先声明,lambda表达式本质就是一个匿名函数。它可以代替仿函数作为传入参数,节省了写仿函数的功夫。

先看具体格式:

capture-list\] (parameters) mutable -\> return-type { statement } ```cpp int main() { auto func1 = [](int x, int y)->int {return x + y; }; auto func2 = [](int x, int y){return x + y; }; auto func3 = [] {cout << "Love" << endl; }; cout << func1(10, 2) << endl << func2(20, 10) << endl; func3(); return 0; } ``` 12 30 Love 可以看到,返回值类型可以自动推导因此可以省略。如果没有参数,甚至可以省略参数列表。 ### 应用 ```cpp struct Goods { string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 // ... Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) {} }; void show(vector& v) { for (auto& e : v) { cout << e._name << ' '; } cout << endl; } int main() { vector v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } }; auto priceLess = [](const Goods& g1, const Goods& g2) { return g1._price < g2._price; }; sort(v.begin(), v.end(), priceLess); show(v); sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._price > g2._price; }); show(v); sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._evaluate < g2._evaluate; }); show(v); sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._evaluate > g2._evaluate; }); show(v); return 0; } ``` 菠萝 苹果 橙子 香蕉 香蕉 橙子 苹果 菠萝 橙子 香蕉 菠萝 苹果 苹果 香蕉 菠萝 橙子 可以看到,不需要写仿函数就能快速调用类似sort的函数,还是非常方便的。 ### 捕捉列表 捕捉列表,可以捕捉当前作用域的参数 ```cpp int main() { int a = 10; auto func = [a] {cout << a << endl; }; func(); return 0; } ``` 10 不过这里默认是不可修改捕捉值的: ```cpp auto func = [a] {a = 19; }; ``` "a": 无法在非可变 lambda 中修改通过复制捕获 需要加上mutable关键字 ```cpp int main() { int a = 10; auto func = [a]()mutable {a = 19; cout << a << endl; }; func(); cout << a << endl; return 0; } ``` 19 10 可以看到这并没有多大的意义,毕竟是传值捕捉。如果想要修改对应作用域的值,需要引用捕捉。 ```cpp int main() { int a = 10; auto func = [&a](){a = 19; cout << a << endl; }; func(); cout << a << endl; return 0; } ``` 19 19 此外\[=\]表示传值捕捉当前作用域所有变量,\[\&\]表示引用捕捉当前作用域所有变量,当然还可以混合捕捉,具体看下面代码: ```cpp int main() { int a = 1, b = 2, c = 3, d = 4, e = 5; // 传值捕捉所有对象 auto func1 = [=]() { return a + b + c * d; }; cout << func1() << endl; // 传引用捕捉所有对象 auto func2 = [&]() { a++; b++; c++; d++; e++; }; func2(); cout << a << b << c << d << e << endl; // 混合捕捉,传引用捕捉所有对象,但是d和e传值捕捉 auto func3 = [&, d, e]() { a++; b++; c++; //d++; //e++; }; func3(); cout << a << b << c << d << e << endl; // a b传引用捕捉,d和e传值捕捉 auto func4 = [&a, &b, d, e]() mutable { a++; b++; d++; e++; }; func4(); cout << a << b << c << d << e << endl; return 0; } ``` 15 23456 34556 45556 ## 类的新功能 [类和对象](https://blog.csdn.net/Fy10030629/article/details/136822990?spm=1001.2014.3001.5501)中提到,类有六个默认成员函数 * 构造函数 * 拷贝构造 * 赋值重载 * 析构函数 * \&重载 * cosnt \&重载 现在C++11新增了两个默认成员函数,分别是移动构造和移动赋值。 现在这两个默认函数有以下特点: * 当你没有实现析构函数、拷贝函数、拷贝赋值重载中的任意一个函数,编译器会生成默认的移动构造和移动赋值。 * 如果你提供了移动构造和移动赋值,那么编译器不会提供拷贝和拷贝赋值。 ## 可变参数模板 C++11允许你创建可以接受可变参数的函数模板和类模板,具体格式如下: ```cpp // Args是一个模板参数包,args是一个函数形参参数包 // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。 template void ShowList(Args... args) {} ``` 可以看到可变参数模板其实有一个相当晦涩的语法表达式。 那么接下来有个严肃的问题,既然有了参数包,要怎么获取其中的参数呢?相信很多人第一时间想到了下列方案: ```cpp template void Cpp_Printf(Args... args) { // 计算参数包的数据个数 cout << sizeof...(args) << endl; for (size_t i = 0; i < sizeof...(args); i++) { cout << args[i] << endl; } cout << endl; } int main() { Cpp_Printf(1, 2); return 0; } ``` 事实上单单计算参数包的数据个数还是能跑通的,但如果加上下面那个循环体,结果就是 error C3520: "args": 必须在此上下文中扩展参数包 事实上我也不是很理解为什么这样是不行的,但总之就是不行 正确的方法有两种 1. 以递归形式拆解拓展包 ```cpp template void Cpp_Printf(T x) { cout << x << endl; } template void Cpp_Printf(T x,Args... args) { cout << x << ' '; Cpp_Printf(args...); } int main() { Cpp_Printf(1, 2, 3, 4, 5, 6); Cpp_Printf(520, "Love", 3.14159, 'M'); return 0; } ``` 1 2 3 4 5 6 520 Love 3.14159 M 当然了,如果你觉得这种展开方式过于晦涩,我们还有更晦涩的另一种方式: 2. 逗号表达式展开 ```cpp template void Cpp_Printf(T x) { cout << x << ' '; } template < class ...Args> void Cpp_Printf( Args... args) { int arr[] = { (Cpp_Printf(args),0)... }; cout << endl; } int main() { Cpp_Printf(1, 2, 3, 4, 5, 6); Cpp_Printf(520, "Love", 3.14159, 'M'); return 0; } ``` 1 2 3 4 5 6 520 Love 3.14159 M 我们来研究一下这个是怎么做到的。首先他用到了c++11中以列表形式初始化int数组arr。 然后编译器在编译过程中就展开了参数包(Cpp_Printf(args),0)...,感觉上是以旧的参数包构建了新的参数包,然后在数组的初始化列表中展开。 随后由于展开的是逗号表达式,因此会先执行逗号前面的项,最后得到0. 因此在展开过程中实际上已经调用了函数Cpp_Printf。 此外逗号后面写其他整数也是可以的。 ### 容器的emplace接口 C++11中的容器都新增了emplace接口,例如vector: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/bd30d29f976a41a1b19ab19b10834de6.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b24ad5aa23c04eaea00363271484d57d.png) 可以看到emplace_back的参数列表是可变参数,实际上emplace_back如果传入的是vector\的T类型,那本质上和push_back没有区别。 例如: ```cpp int main() { pair p{ "Love",10 }; vector> v; v.push_back(p); v.emplace_back(p); } ``` 像这样,就是先调用p的构造,然后pushback和emplaceback时调用拷贝构造。 但是emplace_back还可以这样调用: ```cpp v.emplace_back("love u",10000); ``` 这个参数包会层层下传,最后只调用一次构造。 因此总的来说**emplace系列比push和insert系列效率是要高**的。 ## 包装器 C++11中可以起到调用函数功能的有:函数指针、仿函数、lambda表达式。类型混乱无章,无法统一调配。因此需要对他们包装一下,至少使得他们的外表是同一个类,也就是所谓的包装器。 ### function包装器 > function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。 先看使用格式: ```cpp std::function在头文件 // 类模板原型如下 template function; // undefined template class function; 模板参数说明: Ret: 被调用函数的返回类型 Args...:被调用函数的形参 ``` 可以看到,这里的模板显示实例化和一般格式不相同,但其实更方便理解,在()前的是函数返回值类型,括号中间则是返回值列表。 那么先来简单使用一下吧: ```cpp int f(int a, int b) { return a + b; } struct Functor { public: int operator() (int a, int b) { return a + b; } }; int main() { function fc2 = f; function fc3 = Functor(); function fc4 = [](int x, int y) {return x + y;}; cout << fc2(1, 2) << endl; cout << fc3(1, 2) << endl; cout << fc4(1, 2) << endl; return 0; } ``` 3 3 3 funtionc还有一些有实际意义的使用场景: ```cpp template 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; } ``` 像这种,虽然说传入函数指针、仿函数、lambda表达式都是对的,但编译器也因此实现了三份不同的代码,自然是浪费空间的。如果统一成funtion\格式自然能减少麻烦的。 ### bind包装器 除了function包装器,还有bind包装器。能够调整参数顺序和个数,具体使用格式: ```cpp // 原型如下: template /* unspecified */ bind (Fn&& fn, Args&&... args); // with return type (2) template /* unspecified */ bind (Fn&& fn, Args&&... args); ``` 具体使用: ```cpp class Sub { public: Sub(int x) :_x(x) {} int sub(int a, int b) { return (a - b) * _x; } private: int _x; }; auto f3 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3); cout << f3(Sub(1), 10, 5) << endl; Sub sub(1); cout << f3(&sub, 10, 5) << endl; // 绑定,调整参数个数 auto f4 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2); cout << f4(10, 5) << endl; auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2); cout << f5(10, 5) << endl; ``` 我们通过placeholders命名空间下的_1、_2、...、_n、...来控制参数顺序,以及想绑定的参数。 从上面的例子可以看到,正常类成员函数是要传入隐藏的this指针,因此我们可以在第一个参数的位置上绑定一个类指针或者类实例。 如果说上面的例子还不够直观,那么我们来看对于sub函数的各种绑定情况: ```cpp int main() { //无任何改变 auto f1 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3); cout << f1(Sub(1), 10, 5) << endl; //绑定类指针 auto f2 = bind(&Sub::sub,&Sub(1),placeholders::_1, placeholders::_2); cout << f2(10, 5) << endl; //绑定类实例对象 auto f3 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2); cout << f3(10, 5) << endl; //绑定被减数 auto f4 = bind(&Sub::sub, placeholders::_1, 100, placeholders::_2); cout << f4(Sub(1), 5) << endl; //调整被减数、减数顺序 auto f5 = bind(&Sub::sub, placeholders::_1, placeholders::_3, placeholders::_2); cout << f5(Sub(1), 100, 5) << endl; return 0; } ``` 5 5 5 95 -95 这下够直观显然了吧。 ## 线程库 > 在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。 使用线程库前先包含\头文件 | 函数名 | 功能 | |-----------------------------|-------------------------------------------------------------| | thread() | 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程 | | thread(fn,args1, args2,...) | 构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的参数 | | get_id() | 获取线程id | | jionable() | 线程是否还在执行,joinable代表的是一个正在执行中的线程。 | | jion() | 该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行 | | detach() | 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关 | 先来看看简单的使用: ```cpp void Print(int n, int i) { for (; i < n; i++) { cout << i << endl; } cout << endl; } int main() { thread t1(Print, 100, 0); thread t2(Print, 200, 100); cout << t1.get_id() << endl; cout << t2.get_id() << endl; t1.join(); t2.join(); cout << this_thread::get_id() << endl; return 0; } ``` 结果自然是很混乱,因为我们没有给x上锁,现在上锁改进下. ```cpp void Print(int n, int& rx, mutex& rmtx) { rmtx.lock(); for (int i = 0; i < n; i++) { // t1 t2 ++rx; } rmtx.unlock(); } int main() { int x = 0; mutex mtx; thread t1(Print, 1000000, ref(x), ref(mtx)); thread t2(Print, 2000000, ref(x), ref(mtx)); t1.join(); t2.join(); cout << x << endl; return 0; } ``` 3000000 结果很棒,但考虑到有可能跑线程时挂掉了,rmtx就一直锁着,就会导致程序死锁。 因此前辈考虑到用类的构造和析构函数形式来上锁和解锁: ```cpp class LockGuard { public: LockGuard(mutex& mtx) :_mtx(mtx) { _mtx.lock(); } ~LockGuard() { _mtx.unlock(); } private: mutex& _mtx; }; ``` 类似这般使用: ```cpp //局部域 { LockGuard lock(mtx); lock_guard lock(mtx); for (size_t i = 0; i < n; i++) { ++x; } } ``` 那么有了线程库自然能实现常见的代码:两个线程交替打印奇偶数, ```cpp void two_thread_print() { std::mutex mtx; condition_variable c; int n = 100; bool flag = true; thread t1([&]() { int i = 0; while (i < n) { unique_lock lock(mtx); c.wait(lock, [&]()->bool {return flag; }); cout << i << endl; flag = false; i += 2; // 偶数 c.notify_one(); } }); thread t2([&]() { int j = 1; while (j < n) { unique_lock lock(mtx); c.wait(lock, [&]()->bool {return !flag; }); cout << j << endl; j += 2; // 奇数 flag = true; c.notify_one(); } }); t1.join(); t2.join(); } int main() { two_thread_print(); return 0; } ``` 各种奥秘,请读者自行参悟.

相关推荐
_OP_CHEN2 小时前
从零开始的Qt开发指南:(三)信号与槽的概念与使用
开发语言·c++·qt·前端开发·qt creator·信号与槽·gui开发
乄夜2 小时前
嵌入式面试高频!!!C语言(十四) STL(嵌入式八股文)
c语言·c++·stm32·单片机·mcu·面试·51单片机
草莓熊Lotso2 小时前
《算法闯关指南:优选算法--位运算》--38.消失的两个数字
服务器·c++·算法·1024程序员节
Molesidy9 小时前
【VSCode】【Clangd】Win下的基于LLVM/Clangd+Clangd插件+MINGW+CMake的VSCode配置C/C++开发环境的详细教程
c++·ide·vscode·clangd·llvm
Mr_WangAndy11 小时前
C++_chapter13_C++并发与多线程_多线程概念,死锁,unique_lock(),lock_guard()使用
c++·lock·死锁·并发与多线程·unlock·lock_guard·unique_lock
小欣加油11 小时前
leetcode 946 验证栈序列
c++·算法·leetcode·职场和发展
神仙别闹11 小时前
基于QT(C++) 实现哈夫曼压缩(多线程)
java·c++·qt
无敌最俊朗@12 小时前
C++ 并发与同步速查笔记(整理版)
开发语言·c++·算法
神仙别闹12 小时前
基于 C++和 Python 实现计算机视觉
c++·python·计算机视觉