98中的一个例子

如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法

#include <algorithm>
#include <functional>
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>());return 0;
}

排序一个单链表
如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

struct Goods
{string _name;double _price;
};
struct Compare
{bool operator()(const Goods& gl, const Goods& gr){return gl._price <= gr._price;}
};
int main()
{Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, { "菠萝", 1.5 } };sort(gds, gds + sizeof(gds) / sizeof(gds[0]), Compare());return 0;
}

每次为了实现一个algorithm算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便

lambda表达式

函数中声明函数

sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods&left, const Goods& right){return left._price < right._price; });

lambda表达式语法

lambda表达式书写格式:

  • [capture-list] (parameters) mutable -> return-type { statement }
  1. lambda表达式各部分说明

    • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
    • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
    • ->return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
    • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
[捕捉列表](参数列表)mutable->返回值类型{//函数体};
[]{}; //最简单的lambda表达式
  1. 捕获列表说明
    捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

    • [var]:表示值传递方式捕捉变量var
    • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
    • [&var]:表示引用传递捕捉变量var
    • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
    • [this]:表示值传递方式捕捉当前的this指针

注意:

  1. 父作用域指包含lambda函数的语句块
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量 c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  3. 在块作用域以外的lambda函数捕捉列表必须为空。
  4. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。(主函数只能捕获主函数声明得变量)
int a = 3, b = 4;
[=,&g_a]{return a + 3; }; //验证4
  1. lambda表达式之间不能相互赋值,即使看起来类型相同,找不到operator=()。
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
//f1 = f2; // 编译失败--->提示找不到operator=()
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
f3();
// 可以将lambda表达式赋值给相同类型的函数指针
PF = f2;
PF();//执行相应得表达式
 // 最简单的lambda表达式, 该lambda表达式没有任何意义[]{};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=]{return a + 3; };  //想要在a的基础上+3返回。//但是这个lambda表达式没有用//因为没有取名字// 省略了返回值类型,无返回值类型//不知道lambda表达式的类型就用auto//[&]以引用的方式捕获当前主函数的变量  a=3;b=13;//[=]以值得方式进行捕获            a=3;b=4;auto fun1 = [=](int c)mutable{b = a + c; };fun1(10);cout << a << " " << b << endl;// 各部分都很完善的lambda函数// [=] 以值的方式捕获所有的变量// [&] 以引用的方式捕获所有的变量// [=,&b] 对于b变量以引用得方式捕获,对于其它变量用值的方式auto fun2 = [=, &b](int c)->int{return b += a + c; };cout << fun2(10) << endl;// 复制捕捉x// 以值得方式捕获x,函数内修改不会影响外部int x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0;

仿函数与lambda表达式的联系

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象

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);  //对象调用自身的方法,跟函数调用比较像都是 名字()// 仿函数//=捕获rateauto r2 = [=](double monty, int year)->double{return monty*rate*year; };r2(10000, 2);return 0;
}

函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载operator()。

lambda表达式的应用

int array[] = { 1, 2, 3, 4, 5 };
for_each(array, array + 5, [](int&c){c *= 2; });
for_each(array, array + 5, [](int c){cout << c<<" "; });

线程库

#include<thread>
void ThreadFunc(int a)
{cout << "Thread1" << a << endl;
}
class TF
{public:void operator()(){cout << "Thread3" << endl;}
};int main()
{TF tf;//线程函数尾函数指针thread t1(ThreadFunc, 10);//线程函数为lambda表达式thread t2([]{cout << "Thread2" << endl; });//线程函数为函数对象thread t3(tf);t1.join();t2.join();t3.join();cout << "Main thread!" << endl;system("pause");return 0;
}

线程之间不能互相赋值,也不能拷贝

线程的启动

C++线程库通过构造一个线程对象来启动一个线程,该线程对象中就包含了线程运行时的上下文环境,比如:线程函数、线程栈、线程起始状态等以及线程ID等,所有操作全部封装在一起,最后在底层统一传递给_beginthreadex() 创建线程函数来实现 (注意_beginthreadex是windows中创建线程的底层c函数)。std::thread()创建一个新的线程可以接受任意的可调对象类型(带参数或者不带参数),包括lambda表达式(带变量捕获或者不带),函数,函数对象,以及函数指针。

#include<thread>
void ThreadFunc1(int& x)
{cout << &x << " " << x << endl;x += 10;
}void ThreadFunc2(int*x)
{*x += 10;
}int main()
{int a = 10;//在线程函数中对a修改,不会影响外部实参,因为:线程函数虽然是引用方式,但其实际引用的是线程栈中的拷贝thread t1(ThreadFunc1, a);t1.join();cout << &a <<" "<< a << endl;//地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;system("pause");return 0;
}

线程的结束

1. join()方式

join():会主动地等待线程的终止。在调用进程中join(),当新的线程终止时,join()会清理相关的资源,然后返回,调用线程再继续向下执行。由于join()清理了线程的相关资源,thread对象与已销毁的线程就没有关系了,因此一个线程的对象每次你只能使用一次join(),当你调用的join()之后joinable()就将返回false了。主线程会阻塞

2. detach()

detach:会从调用线程中分理出新的线程,之后不能再与新线程交互。就像是你和你女朋友分手,那之后你们就不会再有联系(交互)了,而她的之后消费的各种资源也就不需要你去埋单了(清理资源)。此时调用joinable()必然是返回false。分离的线程会在后台运行,其所有权和控制权将会交给c++运行库。同时,C++运行库保证,当线程退出时,其相关资源的能够正确的回收

原子性操作

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。比如:

#include <iostream>
using namespace std;
#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);//两个线程每个每回都循环10000000次,每次加1//如果没问题应该是20000000t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;system("pause");return 0;
}

通过加锁保证线程安全

#include <iostream>
using namespace std;
#include <thread>
#include<mutex>
unsigned long sum = 0L;mutex m;
void fun(size_t num)
{for (size_t i = 0; i < num; ++i){m.lock();sum++;m.unlock();}
}
int main()
{size_t begin = clock();cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);//两个线程每个每回都循环10000000次,每次加1//如果没问题应该是20000000t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;size_t end = clock();cout << end - begin << endl; //计算时间system("pause");return 0;
}

加锁后,能够保证线程安全,但是耗费的时间就比较多,而且有可能导致死锁

原子操作

原子操作:一但开始,不能被打断

对于内置类型

对于自定义类型

使用atomic模板,定义出需要的任意原子类型

atomic<T> t;

注意事项

原子类型通常属于“资源型”数据,多个线程只能访问单个原子类型的拷贝,因此在c++11中原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造,移动构造以及operator=等,于是,标准库已经将atmoic模板类中的拷贝构造,移动构造,赋值运算符的重载默认删除了

#include <iostream>
using namespace std;
#include <thread>
#include<atomic>//unsigned long sum = 0L;
atomic_long sum{0}; //定义原子类型变量void fun(size_t num)
{for (size_t i = 0; i < num; ++i){sum++;}
}
int main()
{size_t begin = clock();cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);//两个线程每个每回都循环10000000次,每次加1//如果没问题应该是20000000t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;size_t end = clock();cout << end - begin << endl; //计算时间system("pause");return 0;
}


时间更短,也能保证线程安全

C++中的lambda表达式和线程库相关推荐

  1. 什么是C ++ 11中的lambda表达式?

    本文翻译自:What is a lambda expression in C++11? What is a lambda expression in C++11? 什么是C ++ 11中的lambda ...

  2. C++中的Lambda表达式详解

    函数对象与Lambdas 你编写代码时,尤其是使用 STL 算法时,可能会使用函数指针和函数对象来解决问题和执行计算.函数指针和函数对象各有利弊.例如,函数指针具有最低的语法开销,但不保持范围内的状态 ...

  3. nashorn预编译_Java 8:在新的Nashorn JS引擎中编译Lambda表达式

    nashorn预编译 在最近的一篇文章中,我了解了Java 8和Scala如何实现Lambda表达式. 众所周知,Java 8不仅引入了对Javac编译器的改进,而且还引入了全新的解决方案-Nasho ...

  4. Java 8:在新的Nashorn JS引擎中编译Lambda表达式

    在最近的一篇文章中,我了解了Java 8和Scala如何实现Lambda表达式. 众所周知,Java 8不仅引入了对Javac编译器的改进,而且还引入了全新的解决方案-Nashorn. 这个新引擎旨在 ...

  5. java8中的lambda表达式实用详解

    java8中的lambda表达式实用详解 1. lambda简介 ​ Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中 ...

  6. Qt5中使用lambda表达式

    c11新特性中加入了lambda表达式,所以Qt 也支持 需在.pro文件中加入 CONFIG += c++11例子: 1 QString program = "C:/Windows/Sys ...

  7. C++ 11中的Lambda表达式

    1. 概述 C++ 11 中的 Lambda 表达式用于定义匿名类(anonymous class).创建匿名类对象,以简化编程工作.编译器为该类添加操作符重载函数void operator()(ar ...

  8. Python中的Lambda表达式

    Lambda表达式 (Lambda Expressions) Lambda Expressions are ideally used when we need to do something simp ...

  9. android studio lambda插件,在Android Studio中使用Lambda表达式(retrolambda)

    在Android Studio中使用Lambda表达式 要在Android Studio中使用Lambda表达式,需要借助一个gradle插件来完成. A gradle plugin for gett ...

最新文章

  1. 【java的socket编程】结合多线程Thread实现通信(使用线程池和非线程池对比)、java开发UDP/IP网络程序
  2. boost::gregorian模块实现使用周期来计算日期信息的测试程序
  3. LDAP命令介绍---ldappasswordmodify口令修改操作
  4. C++学习笔记系列三
  5. Docker平台的基本使用方法
  6. 为qt程序添加ico图标
  7. 图书馆占座系统(四)
  8. Python电影推荐系统
  9. Frida系列--自动化生成脚本
  10. word2013和wps使合并后的内容上下对齐
  11. 毕设 疲劳驾驶检测系统 python
  12. 手游还能这么玩?电脑控制手机鼠标键盘大屏玩手游了解一下
  13. 已经快失传的面诊、舌诊宝典,终于找齐了(建议收藏)
  14. 照片美妆---卷积“换脸”
  15. 什么人工智能、云计算和大数据?发展趋势怎么样
  16. Not in GZIP format异常
  17. L1-039 古风排版-java
  18. 电票系统(ECDS)常用名词解释
  19. 股票基金模拟交易日志5
  20. sas options有用的全局设置

热门文章

  1. Vs Code 配置C/C++ 开发环境
  2. echarts折线图相关
  3. python 多文件知识
  4. Ubuntu中输入输出重定向及管道技术简述
  5. Javascript 基础—变量 运算符
  6. 设计模式读书笔记-----备忘录模式
  7. 《Pro ASP.NET MVC 3 Framework》学习笔记之四【领域模型介绍】
  8. mysql怎么按年份分组_mysql - MYSQL按ID分组,但根据最近的年份进行拉取 - SO中文参考 - www.soinside.com...
  9. 华为鸿蒙不再孤,华为鸿蒙OS系统不再孤单!又一款国产系统启动内测:再掀国产替代化...
  10. 宝塔添加多占点_宝塔面板启用WordPress多站点子域名、子目录