列表初始化
使用{}的初始化
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 };
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];
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;
}
auto关键字
C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便,比如容器迭代器的推导
auto使用的前提是:必须要对auto声明的类型进行初始化
decltype关键字
运行时,将变量的类型声明为表达式推出的类型,decltype的结果类型与表达式的类型息息相关 ,同时运行时类型识别的缺陷是降低程序运行的效率
示例:
#include<iostream>
using namespace std;
template<class T1, class T2>
int F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
cout << "调用F函数" << endl;
return 1;
}
int main()
{
int x = 0;
double y = 0;
decltype (F(x, y)) z;
cout << "z的类型为" << typeid(z).name() << endl;
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)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
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)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
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);
PerfectForward(std::move(b));
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)
{
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->_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->_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个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
C++11 新增了两个:移动构造函数和移动赋值运算符重载
| 移动构造函数 | 移动赋值运算重载 |
---|
编译器默认生成条件 | 没有实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个 | 没有实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个 | 编译器默认生成 | 对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造 | 对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值 |
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
①关键字default
作用:强制生成默认成员函数 比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显式指定移动构造生成
②关键字delete
作用:强制删除默认成员函数 比如:我们想防拷贝,或者不想让别人使用该函数,就可以delete删除禁用掉
③final与override关键字
final修饰的类表示该类不能被继承,final修饰的虚函数表示不能被重写 override修饰子类重写的虚函数,会检查是否完成重写, 如果没有就报错
可变参数模板
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板
template <class ...Args>
void ShowList(Args... args){}
比如下面就是简单的可变参数模板,但是最核心的问题是: 虽然支持了能够计算参数的个数,但是不支持直接像数组那样拿到参数
template <class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
}
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)
{
int arr[] = { (PrintArg(args), 0)... };
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} };
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;
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;
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);
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()
{
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;
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);
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的交替变换导致线程的不断切换
|