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