当前位置: 首页 > news >正文

C++11拓展语法

  • 统一的列表初始化
    • 内置类型初始化
    • 自定义类型
    • initializer_list初始化
    • 具体应用
  • 声明
    • auto
    • decltype
    • nullptr
  • 范围for
  • 智能指针
  • STL新增容器
  • 右值引用
    • 左值和右值
    • 左值引用和右值引用
    • 移动构造和移动赋值
    • 左右值引用使用场景
    • 完美转发
  • lambda表达式
    • 使用格式
    • 应用
    • 捕捉列表
  • 类的新功能
  • 可变参数模板
    • 容器的emplace接口
  • 包装器
    • function包装器
    • bind包装器
  • 线程库

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

统一的列表初始化

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

内置类型初始化

先来观测下列代码:

int main()
{int x = { 10 };//列表初始化int y{ 15 };//忽略‘=’的列表初始化char ch{ 'c' };cout << x << endl << y << endl << ch << endl;return 0;
}

运行结果:

10
15
c

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

自定义类型

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

int main()
{string str1{ "Love" };cout << str1 << endl;return 0;
}

输出结果为:

Love

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

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。
如果像下面这种:

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

或者

Point& w = 2;
error C2440: “初始化”: 无法从“int”转换为“Point &”

initializer_list初始化

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

vector<int>v = { 1,2,3 };

这种形式和上述的隐式类型转换相似又不一致,确实是调用了vector的构造函数,具体是:
在这里插入图片描述
没错,这里其实是传入了迭代器来进行初始化。
C++11中将{}封装成了一个类:

auto x = { 1,2,3 };
cout << typeid(x).name() << endl;

结果:

class std::initializer_list<int>

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

具体应用

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

map<string, int>m = {{"apple",19},{"banana",10},{"Love",0}
};

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

声明

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

auto

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

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还可以当作返回值类型:

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

结果:

4

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

int times(int x,auto y)
{return x * y;
}
 error C3533: 参数不能为包含“auto”的类型

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

decltype

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

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的类型是doubledecltype(&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的例子:

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

相当于:

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中新增的相当重要的语法。

左值和右值

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

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

如:

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这些都是左值。

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

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明确表明了,只有左值才能取地址。

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

左值引用和右值引用

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

int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

右值引用

int&& rr1 = 10;
int&& rr2 = 2 - 1;
int&& rr3 = add(2, 10);

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

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

int& rr1 = 10+2;
int x = 20;
int&& y = x;
error C2440: “初始化”: 无法从“int”转换为“int &”
error C2440: “初始化”: 无法从“int”转换为“int &&”

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

const int& rr1 = 10 + 2;
int x = 20;
int&& y = move(x);

像这种形式就可以通过编译了。也就是说const 左值引用可以引用左值也可以引用右值,右值引用只能应用右值或者move以后的左值。
这里的move函数实际上就是将左值转换成右值。

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

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

总结:

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

移动构造和移动赋值

右值其实又细分为纯右值和将亡值。纯右值是指字面常量那些,将亡值则是指临时变量或传值返回。
基于将亡值即将销毁的特性,我们可以利用其资源来完成构造和拷贝:

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的实现,我们来观测一下下面情况:

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.
当然两次拷贝构造被编译器优化成了一次,如果是下面这样写的话,优化更甚:

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

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

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

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

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

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

完美转发

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

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函数变成了全部都是左值函数了。

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

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

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

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 }

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

可以看到,返回值类型可以自动推导因此可以省略。如果没有参数,甚至可以省略参数列表。

应用

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<Goods>& v)
{for (auto& e : v){cout << e._name << ' ';}cout << endl;
}int main()
{vector<Goods> 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的函数,还是非常方便的。

捕捉列表

捕捉列表,可以捕捉当前作用域的参数

int main()
{int a = 10;auto func = [a] {cout << a << endl; };func();return 0;
}
10

不过这里默认是不可修改捕捉值的:

auto func = [a] {a = 19; };
“a”: 无法在非可变 lambda 中修改通过复制捕获

需要加上mutable关键字

int main()
{int a = 10;auto func = [a]()mutable {a = 19; cout << a << endl; };func();cout << a << endl;return 0;
}
19
10

可以看到这并没有多大的意义,毕竟是传值捕捉。如果想要修改对应作用域的值,需要引用捕捉。

int main()
{int a = 10;auto func = [&a](){a = 19; cout << a << endl; };func();cout << a << endl;return 0;
}
19
19

此外[=]表示传值捕捉当前作用域所有变量,[&]表示引用捕捉当前作用域所有变量,当然还可以混合捕捉,具体看下面代码:

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

类的新功能

类和对象中提到,类有六个默认成员函数

  • 构造函数
  • 拷贝构造
  • 赋值重载
  • 析构函数
  • &重载
  • cosnt &重载

现在C++11新增了两个默认成员函数,分别是移动构造和移动赋值。
现在这两个默认函数有以下特点:

  • 当你没有实现析构函数、拷贝函数、拷贝赋值重载中的任意一个函数,编译器会生成默认的移动构造和移动赋值。
  • 如果你提供了移动构造和移动赋值,那么编译器不会提供拷贝和拷贝赋值。

可变参数模板

C++11允许你创建可以接受可变参数的函数模板和类模板,具体格式如下:

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

可以看到可变参数模板其实有一个相当晦涩的语法表达式。

那么接下来有个严肃的问题,既然有了参数包,要怎么获取其中的参数呢?相信很多人第一时间想到了下列方案:

template <class ...Args>
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. 以递归形式拆解拓展包
template <class T>
void Cpp_Printf(T x)
{cout << x << endl;
}template <class T, class ...Args>
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

当然了,如果你觉得这种展开方式过于晦涩,我们还有更晦涩的另一种方式:

  1. 逗号表达式展开
template <class T>
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:
在这里插入图片描述
在这里插入图片描述

可以看到emplace_back的参数列表是可变参数,实际上emplace_back如果传入的是vector<T>的T类型,那本质上和push_back没有区别。
例如:

int main()
{pair<string, int> p{ "Love",10 };vector<pair<string, int>> v;v.push_back(p);v.emplace_back(p);
}

像这样,就是先调用p的构造,然后pushback和emplaceback时调用拷贝构造。
但是emplace_back还可以这样调用:

v.emplace_back("love u",10000);

这个参数包会层层下传,最后只调用一次构造。

因此总的来说emplace系列比push和insert系列效率是要高的。

包装器

C++11中可以起到调用函数功能的有:函数指针、仿函数、lambda表达式。类型混乱无章,无法统一调配。因此需要对他们包装一下,至少使得他们的外表是同一个类,也就是所谓的包装器。

function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

先看使用格式:

std::function在头文件<functional>// 类模板原型如下
template <class T> function;     
// undefinedtemplate <class Ret, class... Args>class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

可以看到,这里的模板显示实例化和一般格式不相同,但其实更方便理解,在()前的是函数返回值类型,括号中间则是返回值列表。
那么先来简单使用一下吧:

int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator() (int a, int b){return a + b;}
};int main()
{function<int(int, int)> fc2 = f;function<int(int, int)> fc3 = Functor();function<int(int, int)> 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还有一些有实际意义的使用场景:

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

像这种,虽然说传入函数指针、仿函数、lambda表达式都是对的,但编译器也因此实现了三份不同的代码,自然是浪费空间的。如果统一成funtion<double(double)>格式自然能减少麻烦的。

bind包装器

除了function包装器,还有bind包装器。能够调整参数顺序和个数,具体使用格式:

// 原型如下:
template <class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);// with return type (2) 
template <class Ret, class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);

具体使用:

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函数的各种绑定情况:

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()构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
thread(fn,args1, args2,…)构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
get_id()获取线程id
jionable()线程是否还在执行,joinable代表的是一个正在执行中的线程。
jion()该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关

先来看看简单的使用:

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上锁,现在上锁改进下.

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就一直锁着,就会导致程序死锁。
因此前辈考虑到用类的构造和析构函数形式来上锁和解锁:

class LockGuard
{
public:LockGuard(mutex& mtx):_mtx(mtx){_mtx.lock();}~LockGuard(){_mtx.unlock();}
private:mutex& _mtx;
};

类似这般使用:

 //局部域
{LockGuard lock(mtx);lock_guard<mutex> lock(mtx);for (size_t i = 0; i < n; i++){++x;}
}

那么有了线程库自然能实现常见的代码:两个线程交替打印奇偶数,

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

各种奥秘,请读者自行参悟.

http://www.dtcms.com/a/600788.html

相关文章:

  • 智慧医疗:FHIR R5、联邦学习与MLOps三位一体的AI产品化实战指南(下)
  • 创建一个达梦库需要多大空间
  • Redis_11_类型补充+命令补充+RESP
  • 网站设计哪家便宜seo网站做推广公司
  • 用于感知图像超分辨率的自编码监督(易于理解版本)
  • 地图可视化实践录:空间分析库Turf.js的学习
  • 长沙制作网站公司哪家好广州seo推广营销
  • 11、prometheus-PromQL-5-聚合计算函数
  • (114页PPT)上海x友集团管理咨询及IT规划项目第一期报告管理诊断分析咨询报告(附下载方式)
  • C语言编译器 Visual Studio:实现高效编程与调试环境优化
  • 王树森深度强化学习 DRL(六)连续控制 DDPG + 随机策略
  • 【SatWetCH4 第一期】全球湿地甲烷排放通量估算过程模型 SatWetCH4 介绍
  • Opencv(十一) : 图像缩放
  • 开源 Objective-C IOS 应用开发(四)Xcode工程文件结构
  • 儿童网站 源码html5网站开发教学
  • 编译类语言的特点与应用
  • Python 数组使用方法总结
  • 网站风格变化免费logo在线制作头像
  • 第三章深度解析:智能体“大脑”的底层逻辑——大语言模型技术基石全拆解
  • 100个云计算基础知识
  • 对比 DeepSeek(MLA)、Qwen 和 Llama 系列大模型在 Attention 架构/算法层面的核心设计及理解它们的本质区别。
  • 【C++】List容器模拟实现(超详细)
  • 湖南火电建设有限公司网站龙采哈尔滨建站公司
  • 【PHP反序列化】css夺旗赛
  • ServletLess架构简介
  • 安卓C语言编译器的选择与使用技巧 | 优化C语言编程体验,提升开发效率
  • (三)自然语言处理笔记——Transformer
  • iOS性能分析工具,有UI卡顿、app启动、内存、webview等性能优化解析
  • 电商网站建设 数商云招商码头无忧查询系统
  • 开源 Objective-C IOS 应用开发(三)第一个iPhone的APP