目录
1、lambda表达式
1.1 lambda表达式的引入
1.2 lambda表达式的语法
1.3 lambda表达式的原理
2、线程库
2.1thread类的介绍
2.2 线程函数参数
2.3 原子性操作库(atomic)
2.4 使用场景
应用场景1:
应用场景2:
应用场景3:
应用场景4:
本文为C++【下】
C++11【上】链接:【C++】C++11【上】列表初始化|声明|新容器|右值引用|完美转发|新的类功能-CSDN博客
1、lambda表达式
1.1 lambda表达式的引入
在C++98中,如果想要对一个数据集合中的元素进行排序,可利用sort
下面利用sort的使用来引出lambda表达式,因为sort是个函数模板,故第三个参数可以接受函数指针,函数对象(仿函数),lambda表达式,这里先利用前两个来实现
#include <algorithm>
#include <functional>//当需要用到仿函数(函数对象)时用,但你不写也能编译运行成功template<class T>
struct Greater
{bool operator()(const T& x1, const T& x2){return x1 > x2;}
};bool g2(const int& x1, const int& x2)
{return x1 > x2;
}
int main()
{int array[] = { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较,排出来结果是升序std::sort(array, array + sizeof(array) / sizeof(array[0]));// 如果需要降序,需要改变元素的比较规则//std::sort(array, array + sizeof(array) / sizeof(array[0]), Greater<int>());Greater<int> g1;g1(1, 2); //g1是一个对象,调用它的operator()实现的g2(1, 2); //g2是一个函数指针,调用它指向的函数//他们是完全不同的对象,但用起来是一样的,均可排序std::sort(array, array + sizeof(array) / sizeof(array[0]), g1);std::sort(array, array + sizeof(array) / sizeof(array[0]), g2);return 0;
}
如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());
}
随着 C++ 语法的发展, 人们觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去写一个类(仿函数或函数),如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 一般需要根据功能来命名(你瞎命名别人很难看懂) 。因此,在 C++11 语法中出现了 Lambda 表达式。
1.2 lambda表达式的语法
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函数不能做任何事情
int main() {// 最简单的lambda表达式, 该lambda表达式没有任何意义[] {};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=] {return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c) {b = a + c; };fun1(10);cout << a << " " << b << endl; //3 13// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int {return b += a + c; };cout << fun2(10) << endl; //26// 捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl; //30return 0; }
2. 捕获列表说明:
传引用捕捉:[&a]捕捉a [&a,&b]捕捉a,b [&]捕捉同一作用域中的所有对象
- [var]: 表示值传递方式捕捉变量var
- [=]: 表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]: 表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]: 表示值传递方式捕捉当前的this指针(其实=和&就已经包含this指针了)
下面利用lambda表达式实现相加和交换两数操作,了解lambda表达式的使用
int add1(int a, int b)
{return a + b;
}int main()
{int a = 0, b = 1;//实现一个a+b的lambda表达式auto add1 = [](int x1, int x2)->int{return x1 + x2;};//返回值可不写,编译器会自动推(但不建议)cout << add1(a, b) << endl;cout << ::add1(a, b) << endl;//调用全局域的add1,即主函数之外的//auto add1 = [](int x1, int x2)->int {return x1 + x2 + a; };//没捕捉a导致无法使用auto add1 = [a](int x1, int x2)->int {return x1 + x2 + a; };//捕捉a后可以正常使用auto add1 = [a,b]()->int {return a + b; };//捕捉a,b,只用于a + b//实现a和b交换auto swap1 = [](int& x, int& y){ int z = x;x = y;y = z; };swap(a, b);//不正确的用法,即使加上mutable可以修改传值捕捉了,但传值捕捉的//是a和b的拷贝,只是把a和b的拷贝交换了而已,所以这里是没意义的auto swapab = [a, b]()mutable{int c = a;a = b;b = c;};swapab();return 0;
}
void (*PF)();
int main()
{auto f1 = [] {cout << "hello world" << endl; };auto f2 = [] {cout << "hello world" << endl; };// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了//f1 = f2; // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF = f2;PF();return 0;
}
3.使用场景:
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 } };//因为sort是个函数模板,故第三个参数可以传仿函数,函数指针和lambda表达式sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate < g2._evaluate; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; });//一般我们不会像下面这么用,我们一般都是构造lambda表达式的匿名对象/*auto price_greater = [](const Goods& g1, const Goods& g2)->bool {return g1._price <g2._price; };sort(v.begin(), v.end(), price_greater);*/
}
1.3 lambda表达式的原理
先看以下用仿函数和lambda表达式实现的代码:
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);//仿函数// lambdaauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);//lambda表达式return 0;
}
int main()
{int a = 1, b = 2;// 对象 = 对象(编译器生成的lambda_uuid仿函数的对象)auto add = [](int x, int y)->int {return x + y; };add(a,b); //call lambda_uuid仿函数的operator()//底层还是靠仿函数来实现,也就是说你定义了一个lambda表达式,实际上//编译器会全局域生成一个叫lambda_uuid类,仿函数的operator()的参数和实现//就是我们写的lambda表达式的参数和实现//仿函数和lambda表达式的关系如同迭代器和范围for的关系,也就是lambda表达式存在即合理return 0;
}
2、线程库
2.1thread类的介绍
在 C++11 之前,涉及到多线程问题,都是和平台相关的,比如 windows 和 linux 下各有自己的接 口,这使得代码的可移植性比较差 。 C++11 中最重要的特性就是对线程进行支持了,使得 C++ 在 并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的 线程,必须包含 < thread > 头文件。C++11 线程库
windows 自己的一套API 如:CreateThread
Linux 使用posix的pthread 如:pthread_create
C++98中,若你想写多线程的程序,既想在windows下跑,也想在Linux下跑,怎么办?
用条件编译
#ifdef _WIN32
CreateThread(...)
#else
pthread_create(...)
#endifC++11 线程库
特点:跨平台、面向对象封装的类(每个线程是一个类对象)
实现原理:封装库时使用了条件编译,也就是他的底层还是分别调用了不同平台的线程API扩展:C++缺点之一:有用的东西更新的太慢了,比如线程库C++11(2011)才更新的,且到现在也没有更新一个官方的封装好的靠谱网络库,其次一堆使用意义不大的语法一堆,学习成本高
- thread()构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
- thread(fn,args1,args2,参数…)构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
- get_id()获取线程id
- jionable()线程是否还在执行,joinable代表的是一个正在执行中的线程。
- jion()该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
- detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关
#include <thread>
int main()
{std::thread t1;cout << t1.get_id() << endl;return 0;
}
// vs下查看
typedef struct
{ /* thread identifier for Win32 */void *_Hnd; /* Win32 HANDLE */unsigned int _Id;
} _Thrd_imp_t;
- 函数指针
- lambda表达式
- 函数对象
#include <iostream>
using namespace std;
#include <thread>
void ThreadFunc(int a)
{cout << "Thread1" << a << endl;
}
class TF
{
public:void operator()(){cout << "Thread3" << endl;}
};
int main()
{// 线程函数为函数指针thread t1(ThreadFunc, 10);// 线程函数为lambda表达式thread t2([]{cout << "Thread2" << endl; });// 线程函数为函数对象TF tf;thread t3(tf);t1.join();t2.join();t3.join();cout << "Main thread!" << endl;return 0;
}
- 采用无参构造函数构造的线程对象
- 线程对象的状态已经转移给其他线程对象
- 线程已经调用jion或者detach结束
2.2 线程函数参数
#include <thread>
void ThreadFunc1(int& x)
{x += 10;
}
void ThreadFunc2(int* x)
{*x += 10;
}
int main()
{int a = 10;//在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际//引用的是线程栈中的拷贝thread t1(ThreadFunc1, a);t1.join();cout << a << endl;// 如果想要通过形参改变外部实参时,必须借助std::ref()函数thread t2(ThreadFunc1, std::ref(a));t2.join();cout << a << endl;// 地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}
2.3 原子性操作库(atomic)
多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问 题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数 据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。比如:
#include <thread>
unsigned long sum = 0L;void fun(size_t num)
{for (size_t i = 0; i < num; ++i)sum++;
}
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;
}
#include <thread>
#include <mutex>
std::mutex m;
unsigned long sum = 0L;
void fun(size_t num)
{for (size_t i = 0; i < num; ++i){m.lock();sum++;m.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;
}
mutex mtx;
int x = 0;//两个线程一起对x加n次
void Add(int n)
{ //串行:一个线程跑完了,另一个线程接着跑mtx.lock();for (int i = 0; i < n; ++i){++x;}mtx.unlock();//并行:两个线程同时跑/*for (int i = 0; i < n; ++i){mtx.lock();// 比如t2刚切出去,t1就解锁了。++后马上又把t2切回来++x;mtx.unlock();}*///实际上串行更快。为什么?因为这里锁的力度太小了,时间都花到切换上下文了。
}
atmoic<T> t; // 声明一个类型为T的原子类型变量t
注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11 中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算 符重载默认删除掉了。
#include <atomic>
int main()
{atomic<int> a1(0);//atomic<int> a2(a1); // 编译失败atomic<int> a2(0);//a2 = a1; // 编译失败return 0;
}
2.4 使用场景
应用场景1:
atomic<int> x = 0; //支持整形/浮点的原子++,--
//扩展学习:atomic支持CAS->无锁编程//两个线程一起对x加n次
void Add(int n)
{for (int i = 0; i < n; ++i){++x;}
}int main()
{//利用函数指针配合threadthread t1(Add, 1000000);//加锁之后每次计算的出的数据就准确了thread t2(Add, 1000000);//要在主线程结束前对两个线程join一下才行t1.join();t2.join();cout << x << endl;return 0;
}
②、仿函数
atomic<int> x = 0;
struct Add
{int operator()(int n){for (int i = 0; i < n; ++i){++x;}return x;}
};
int main()
{Add add;//利用仿函数对象配合threadthread t1(add, 1000000);thread t2(add, 1000000);//也可以写为Add(),即用匿名对象//要在join前获取线程idcout << t1.get_id() << endl;//每次获取到的ID不一cout << t2.get_id() << endl;//每次获取到的ID不一t1.join();t2.join();cout << x << endl;//2000000return 0;
}
③、lambda表达式
int main()
{atomic<int> x = 0;auto add = [&x](int n) {for (int i = 0; i < n; ++i){++x;}};thread t1(add, 1000000);thread t2(add, 1000000);//也可以写为Add(),即用匿名对象//要在join前获取线程idcout << t1.get_id() << endl;//每次获取的ID不一cout << t2.get_id() << endl;//每次获取的ID不一t1.join();t2.join();cout << x << endl;//2000000return 0;
}
应用场景2:
m个线程对x加n次
int main()
{atomic<int> x = 0;//m个线程对x加n次int m, n;cin >> m >> n;vector<thread> vthreads;for (int i = 0; i < m; ++i){vthreads.push_back(thread([&x](int count) {for (int i = 0; i < count; ++i){++x;}}, n));}for (auto& t : vthreads){cout << t.get_id() << "join" << endl;t.join();}cout << x << endl;return 0;
}//写法二
int main()
{atomic<int> x = 0;//m个线程对x加n次int m, n;cin >> m >> n;//thread支持移动赋值和移动拷贝,不支持深拷贝的拷贝构造和拷贝赋值vector<thread> vthreads(m);for (int i = 0; i < m; ++i){//移动赋值【thread构造的是匿名对象,返回右值】vthreads[i] = thread([&x](int count) {for (int i = 0; i < count; ++i){++x;}}, n);}for (auto& t : vthreads){cout << t.get_id() << "join" << endl;t.join();}cout << x << endl;return 0;
}
应用场景3:
//以下代码存在一个问题:竞争终端,每次结果都不一样,且都不对
int main()
{int n = 100;thread t1([n](){for (int i = 0; i < n; ++i){//偶数if (i % 2 == 0)cout << this_thread::get_id() << ":" << i << endl;}});thread t2([n](){for (int i = 0; i < n; ++i){//奇数if (i % 2)cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到id}});t1.join();t2.join();return 0;
}
改善后:
#include<mutex>
#include<condition_variable>//偶数和奇数依次打印的过程类似于生产者消费者模型
//互斥锁和条件变量同时使用
int main()
{int n = 100;mutex mtx1, mtx2;condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用thread t1([&](){for (int i = 0; i < n; i+=2){ if (i != 0)cv1.wait(unique_lock<mutex>(mtx1));//偶数cout << this_thread::get_id() << ":" << i << endl;cv2.notify_one(); //t1打印偶数以后,通知t2}});thread t2([&](){for (int i = 0; i < n; i+=2){cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量//奇数cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到idcv1.notify_one(); //t2}});t1.join();t2.join();return 0;
}//法二
int main()
{int n = 100;mutex mtx1, mtx2;condition_variable cv1, cv2;//条件变量(一个是不行的,要让偶数先走),必须配合锁用thread t1([&](){for (int i = 0; i < n; i += 2){//偶数cout << this_thread::get_id() << ":" << i << endl;cv2.notify_one(); //t1打印偶数以后,通知t2cv1.wait(unique_lock<mutex>(mtx1));}});thread t2([&](){for (int i = 0; i < n; i += 2){cv2.wait(unique_lock<mutex>(mtx2));//要传互斥锁来保护条件变量//奇数cout << this_thread::get_id() << ":" << i << endl;//利用this_thread可以不需要线程对象就获取到idcv1.notify_one(); //t2}});t1.join();t2.join();return 0;
}
应用场景4:
线程池
//扩展:线程池
struct tack
{template<class fn>tack(fn){}
};class thread_poo1
{
public:thread_poo1(int n = 8):vthreads(n){}private:vector<thread> vthreads;//queue<task> _q;
};