lambda强转成函数空指针引用 内存崩溃,运行时崩溃,为什么

C++中的函数对象与Lambda表达式
C++中的函数对象与Lambda表达式
函数对象是C++中以参数形式传递函数的一个很好的方法,我们将函数包装成类,并且利用()运算符重载实现。123456typedef class hello {public:&&&&void operator()(double x) {&&&&&&&&cout && x &&&&&&}}
  这时候hello是一个类,我们可以实例化一个对象,然后通过h(3.14)的方式来调用这个类的成员函数,如果某个函数需要这个函数作为回调函数,则可以将这个hello类的对象传入即可。
  因为这是一个类的定义,因此我们完全可以在其中定义一些包含额外信息的成员和一些构造函数,让这个函数对象可以做更多不同的可定制的任务,最终的行为实际上只是调用了这个()运算符重载函数。这种做法比C++函数指针要容易理解得多,也不容易写错。
  而Lambda表达式则是C++中的新语法,实现了许多程序员渴望的部分闭包特性。C++中Lambda表达式可以被视为一种匿名函数,这样,对于一些非常短,而且不太可能被其他地方的复用的小函数,可以通过Lambda表达式提高代码的可读性。
  在Lambda表达式中对于变量生命期的控制还是与完全支持闭包的JavaScript非常不同,总而言之,C++对于变量声明期的控制在新标准中完全向前兼容,也就是局部变量一定在退出代码块时被销毁,而不是观察其是否被引用。因此,尽管C++的Lambda表达式中允许引用其代码上下文中的值,但是实际上并不能够保证引用的对象一定没有被销毁。
  Lambda表达式对于上下文变量的引用有值传递和引用传递两种方式,实际上,无论是哪种方式,在产生Lambda表达式对象时,这些上下文值就已经从属于Lambda表达式对象了,也就是说,代码运行至定义Lambda表达式处时,通过值传递方式访问的上下文变量值已经被写入Lambda表达式的栈中,而引用方式传递的上下文变量地址被写入Lambda表达式的栈中。因此,调用Lambda表达式时得到的上下文变量值就是定义Lambda表达式时这些变量的值,而引用的上下文变量,如果已经被销毁,则会出现运行时异常。
  Lambda表达式的基本语法是:
  [上下文变量说明](Lambda表达式参数表) -& 返回类型 { 语句块 }
  上下文变量说明部分就是说明对于上下文变量的引用方式,=表示值传递,&表示引用传递,例如,&s就表示s变量采用引用传递,不同的说明项之间用逗号分隔,可以为空,但是方括号不能够省略。第一项可以是单独的一个=或者&,表示,所有上下文变量若无特殊说明一律采用值传递/引用传递,什么都不写默认为值传递。
  Lambda表达式和TR1标准对应的function&返回类型 (参数表)&对象是可以互相类型转换的,这样,我们也可以将Lambda表达式作为参数进行传递,也可以作为返回值返回。
  下面看一个Lambda表达式各种使用方法的完整例子:123456789101112131415161718192021222324252627282930313233343536#include &iostream&#include &string&#include &functional& //这是TR1的头文件,定义了function类模板using namespace &typedef class hello {public:&&&&void operator()(double x) {&&&&&&&&cout && x &&&&&&}} &void callhello(string s, hello func) {&&&&cout &&&&&&func(3.14);} &void callhello(string s, const function&void (double x)&& func) {&&&&cout &&&&&&func(3.14);} &void callhello(string s, double d) {&&&&[=] (double x) {&&&&&&&&cout && s && x &&&&&&}(d);} &function&void (double x)& returnLambda(string s) {&&&&cout && s &&&&&&function&void (double x)& f = ([=] (double x) {&&&&&&&&cout && s && x &&&&&&});&&&&s = "changed"; &&&&return f;} &function&void (double x)& returnLambda2(string& s) {&&&&cout && s &&&&&&function&void (double x)& f = ([&s] (double x) {&&&&&&&&cout && s && x &&&&&&});&&&&s = "changed"; &&&&return f;} &int main() {&&&&&&&&callhello("hello:", h); &&&&callhello("hello lambda:", -3.14); &&&&int temp = 6;&&&&callhello("hello lambda2:", [&] (double x) -& void {&&&&&&&&cout && x &&&&&&&&&&cout && temp++ &&&&&&}); &&&&cout && temp &&&&&&&function&void (double x)& f = returnLambda("lambda string"); &&&&f(3.3);&&&&string lambdastring2 = "lambda string2"; &&&&f = returnLambda2(lambdastring2); &&&&f(6.6);&&&&&&&&&system("pause");}
* 本文在(署名-相同方式共享)协议下发布。
TA的推荐TA的最新馆藏[转]&[转]&[转]&[转]&Boost(13)
原来lambda还可以这么用。
//用lambda作为函数的指针:
//程序参考自:Boost程序库完全开发指南(第三版)Page545
#include &iostream&
int main1()
auto lambda_fun =
if (n &= 0)
std::cout && &n&=0& && std::
std::cout && &positive number:& && n && std::
for (int i = -2; i & 3; ++i)
lambda_fun(i);
int main2()
int positive_number = 0;
auto lambda_fun =
[&](int n)//使用[&]捕获外部变量
if (n &= 0)
std::cout && &0&=n & && n && std::
std::cout && &positive number count is & && ++positive_number && std::
for (int i = -2; i & 3; ++i)
lambda_fun(i);
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:70879次
积分:1157
积分:1157
排名:千里之外
原创:31篇
转载:93篇
评论:10条
(2)(2)(15)(10)(7)(1)(12)(5)(3)(6)(8)(8)(5)(3)(21)(9)(1)(6)2652人阅读
#include &iostream&
#include &functional& //std::function 头文件
//传统的函数指针
typedef int(*fun0)(int n);
int testfun(int n)
//Lambda表达式
class CBase&
class CA : public CBase
& & CA(int n) : m_id(n) {}
& & int getid() { return m_ }
& & int m_
class CB : public CBase
& & CB(int n) : m_id(n) {}
& & int getid() { return m_ }
& & int m_
#define CL(__className__) [](int n){ return new __className__(n); }
int main()
& & //函数指针
& & fun0 pfun0 =
& & cout && pfun0(888) &&
& & //lambda表达式
& & std::function&CBase*(int)& funA = CL(CA);
& & CBase* pA = funA(1);
& & cout && ((CA*)pA)-&getid() &&
& & std::function&CBase*(int)& funB = CL(CB);
& & CBase* pB = funB(2);
& & cout && ((CB*)pB)-&getid() &&
& & system(&pause&);
& & return 0;
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:95797次
积分:1360
积分:1360
排名:千里之外
原创:35篇
转载:37篇
(3)(2)(1)(2)(1)(4)(3)(2)(11)(31)(1)(2)(1)(2)(3)(1)(1)(1)16411人阅读
C++ AMP(74)
这篇文章是根据维基百科整理来的,原文请看:  C++11提供了对匿名函数的支持,称为Lambda函数(也叫Lambda表达式). Lambda表达式具体形式如下:    [capture](parameters)-&return-type{body}  如果没有参数,空的圆括号()可以省略.返回值也可以省略,如果函数体只由一条return语句组成或返回类型为void的话.形如:  &  [capture](parameters){body}  下面举了几个Lambda函数的例子:&&    [](int x, int y) { return x + } // 隐式返回类型
[](int& x) { ++x; }
// 没有return语句 -& lambda 函数的返回类型是'void'
[]() { ++global_x; }
// 没有参数,仅访问某个全局变量
[]{ ++global_x; }
// 与上一个相同,省略了()  可以像下面这样显示指定返回类型:&&[](int x, int y) -& int { int z = x + return }  在这个例子中创建了一个临时变量z来存储中间值. 和普通函数一样,这个中间值不会保存到下次调用. 什么也不返回的Lambda函数可以省略返回类型, 而不需要使用 -& void 形式.  Lambda函数可以引用在它之外声明的变量. 这些变量的集合叫做一个闭包. 闭包被定义在Lambda表达式声明中的方括号[]内. 这个机制允许这些变量被按值或按引用捕获.下面这些例子就是:&&[]
//未定义变量.试图在Lambda内使用任何外部变量都是错误的.
//x 按值捕获, y 按引用捕获.
//用到的任何外部变量都隐式按引用捕获
//用到的任何外部变量都隐式按值捕获
//x显式地按值捕获. 其它变量按引用捕获
//z按引用捕获. 其它变量按值捕获  接下来的两个例子演示了Lambda表达式的用法.&&std::vector&int& some_
int total = 0;
for (int i=0;i&5;++i) some_list.push_back(i);
std::for_each(begin(some_list), end(some_list), [&total](int x)
});  此例计算list中所有元素的总和. 变量total被存为lambda函数闭包的一部分. 因为它是栈变量(局部变量)total的引用,所以可以改变它的值.&&std::vector&int& some_
int total = 0;
int value = 5;
std::for_each(begin(some_list), end(some_list), [&, value, this](int x)
total += x * value * this-&some_func();
});  此例中total会存为引用, value则会存一份值拷贝. 对this的捕获比较特殊, 它只能按值捕获. this只有当包含它的最靠近它的函数不是静态成员函数时才能被捕获.对protect和priviate成员来说, 这个lambda函数与创建它的成员函数有相同的访问控制. 如果this被捕获了,不管是显式还隐式的,那么它的类的作用域对Lambda函数就是可见的. 访问this的成员不必使用this-&语法,可以直接访问.  不同编译器的具体实现可以有所不同,但期望的结果是:按引用捕获的任何变量,lambda函数实际存储的应该是这些变量在创建这个lambda函数的函数的栈指针,而不是lambda函数本身栈变量的引用. 不管怎样, 因为大数lambda函数都很小且在局部作用中, 与候选的内联函数很类似, 所以按引用捕获的那些变量不需要额外的存储空间.  如果一个闭包含有局部变量的引用,在超出创建它的作用域之外的地方被使用的话,这种行为是未定义的!  lambda函数是一个依赖于实现的函数对象类型,这个类型的名字只有编译器知道. 如果用户想把lambda函数做为一个参数来传递, 那么形参的类型必须是模板类型或者必须能创建一个std::function类似的对象去捕获lambda函数.使用 auto关键字可以帮助存储lambda函数,&&auto my_lambda_func = [&](int x) { /*...*/ };
auto my_onheap_lambda_func = new auto([=](int x) { /*...*/ });  这里有一个例子, 把匿名函数存储在变量,数组或vector中,并把它们当做命名参数来传递&#include&functional&
#include&vector&
#include&iostream&
double eval(std::function&double(double)& f, double x = 2.0){return f(x);}
int main()
std::function&double(double)& f0
= [](double x){return 1;};
= [](double x){return};
decltype(f0)
fa[3] = {f0,f1,[](double x){return x*x;}};
std::vector&decltype(f0)&
= {f0,f1};
fv.push_back
([](double x){return x*x;});
for(int i=0;i&fv.size();i++)
std::cout && fv[i](2.0) && &\n&;
for(int i=0;i&3;i++)
std::cout && fa[i](2.0) && &\n&;
for(auto &f : fv)
std::cout && f(2.0) && &\n&;
for(auto &f : fa)
std::cout && f(2.0) && &\n&;
std::cout && eval(f0) && &\n&;
std::cout && eval(f1) && &\n&;
}  一个没有指定任何捕获的lambda函数,可以显式转换成一个具有相同声明形式函数指针.所以,像下面这样做是合法的:&auto a_lambda_func = [](int x) { /*...*/ };
void(*func_ptr)(int) = a_lambda_
func_ptr(4); //calls the lambda.&C++QQ群:,欢迎对C++感兴趣的朋友一起来交流学习.
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:8561773次
积分:96757
积分:96757
排名:第8名
原创:1132篇
转载:2969篇
评论:1369条
(6)(2)(4)(10)(2)(3)(13)(13)(4)(9)(62)(16)(8)(23)(9)(37)(73)(34)(31)(120)(128)(183)(23)(69)(75)(1)(171)(33)(148)(168)(145)(27)(144)(139)(208)(61)(59)(10)(10)(32)(2)(7)(34)(24)(9)(39)(25)(32)(46)(20)(44)(8)(21)(43)(49)(100)(113)(136)(35)(55)(15)(29)(41)(15)(50)(17)(20)(182)(206)(43)(27)(19)(17)(13)(1)(40)(5)(3)(4)(21)(71)(73)(19)(2)(2)(1)(1)(1)(6)(3)12460人阅读
Java(47)
Lambda(12)
Java 8(13)
Java 8中同时存在面向对象编程(OOP)和函数式编程(FP, Functional Programming)这两种编程范式。实际上,这两种范式并不矛盾,只是着重点不同。在OOP中,着重于通过丰富的类型系统对需要解决的问题进行建模;而FP中则着重于通过高阶函数和Lambda表达式来完成计算。所以我们完全可以将这两者融合在一起,对问题提出更加优雅的解决方案。
在这篇文章中,会介绍如何通过函数组合(Function Composition)来将若干个函数单元组合成一个Map-Reduce模式的应用。同时,还会介绍如何将整个计算过程并行化。
使用函数组合
在使用函数式编程的时候,函数是组成程序的单元。通过将函数以高阶函数的形式组织,可以有效地提高不变性(Immutability),从而减少程序的状态变化,最终让并行化更加容易。
下面这张图反映了,纯粹的面向对象设计和混合式设计(面向对象和函数式)的风格。
在OOP中,对象的状态会随着程序的进行而不断发生变化,但是对象始终只有一个。 而在FP中,对象每次被一个函数处理之后,都会得到一个新的对象,而原来的对象并不会发生变化。
下面是一个小例子,让你对这种混合式的编程范式有一个初步的了解。假设我们有一些股票的代码,需要得到股票价格大于100美元的股票并对它们进行排序:
public class Tickers {
public static final List&String& symbols = Arrays.asList(
&AMD&, &HPQ&, &IBM&, &TXN&, &VMW&, &XRX&, &AAPL&, &ADBE&,
&AMZN&, &CRAY&, &CSCO&, &DELL&, &GOOG&, &INTC&, &INTU&,
&MSFT&, &ORCL&, &TIBX&, &VRSN&, &YHOO&);
对于每只股票代码,可以通过调用下面这段程序借助Yahoo提供的Web Service来得到对应的股价:
public class YahooFinance {
public static BigDecimal getPrice(final String ticker) {
final URL url = new URL(&http://ichart./table.csv?s=& + ticker);
final BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
final String data = reader.lines().skip(1).findFirst().get();
final String[] dataItems = data.split(&,&);
return new BigDecimal(dataItems[dataItems.length - 1]);
} catch(Exception ex) {
throw new RuntimeException(ex);
最后,通过一串操作来得到我们需要的答案:
final BigDecimal HUNDRED = new BigDecimal(&100&);
System.out.println(&Stocks priced over $100 are & +
Tickers.symbols
.filter(symbol -& YahooFinance.getPrice(symbol).compareTo(HUNDRED) & 0)
.collect(joining(&, &)));
这就是一个混合范式的应用,将主要的计算逻辑通过方法进行封装,然后将这些函数根据其所属的类型进行面向对象建模,比如getPrice方法属于类型YahooFinance。最后使用Stream类型和Lambda表达式完成需要执行的计算逻辑,得到最终结果。
将计算逻辑封装成一个函数调用链的好处在于:
更简洁,代码量会少很多,从而代码也更容易被理解提高了对象的不变性(Immutability),从而更加容易并行化调用链中的每一环都很容易被复用,如filter,sorted等
使用Map-Reduce
顾名思义,Map-Reduce实际上分为了两个步骤:
Map阶段:对集合中的元素进行操作Reduce阶段:将上一步得到的结果进行合并得到最终的结果
正是因为这个模式十分简单,同时它也能够最大限度的利用多核处理器的能力,所以它得到了广泛关注。
比如,当我们需要得到股票价格小于500美元的最高价格的股票时,应该如何做呢? 首先我们还是从最熟悉的命令式代码开始。
首先,我们需要对这个问题进行一个基础的建模,这个步骤就是面向对象设计的过程。很容易地,可以得到下面的实体类型:
public class StockInfo {
public final String
public final BigDecimal
public StockInfo(final String symbol, final BigDecimal thePrice) {
price = theP
public String toString() {
return String.format(&ticker: %s price: %g&, ticker, price);
同时,也需要一些工具方法来帮助我们解决这个问题:
通过股票代码得到对应的实体信息。我们可以使用前面介绍的YahooFinance中定义的getPrice方法来完成这一任务。判断股票的价格是否小于某个值,可以通过Predicate函数接口实现,它是一个高阶函数,会将传入的price信息作为阈值来生成一个Lambda表达式并返回。用来比较取得两个股价实体对象中股价较高的对象的方法。
分别实现如下:
public class StockUtil {
public static StockInfo getPrice(final String ticker) {
return new StockInfo(ticker, YahooFinance.getPrice(ticker));
public static Predicate&StockInfo& isPriceLessThan(final int price) {
return stockInfo -& stockInfo.price.compareTo(BigDecimal.valueOf(price)) & 0;
public static StockInfo pickHigh(
final StockInfo stockInfo1, final StockInfo stockInfo2) {
return stockInfo1.price.compareTo(stockInfo2.price) & 0 ? stockInfo1 : stockInfo2;
命令式风格
有了以上的准备工作,我们就可以着手实现了。首先是命令式风格的代码,这也是最熟悉的方式:
final List&StockInfo& stocks = new ArrayList&&();
for(String symbol : Tickers.symbols) {
stocks.add(StockUtil.getPrice(symbol));
final List&StockInfo& stocksPricedUnder500 = new ArrayList&&();
final Predicate&StockInfo& isPriceLessThan500 = StockUtil.isPriceLessThan(500);
for(StockInfo stock : stocks) {
if(isPriceLessThan500.test(stock))
stocksPricedUnder500.add(stock);
StockInfo highPriced = new StockInfo(&&, BigDecimal.ZERO);
for(StockInfo stock : stocksPricedUnder500) {
highPriced = StockUtil.pickHigh(highPriced, stock);
System.out.println(&High priced under $500 is & + highPriced);
上述代码完成了以下几个工作:
首先是根据股票代码得到股价信息,然后将股价实体放到一个列表对象中。然后对集合进行一次遍历,得到所有价格低于500美元的股价实体。对步骤2中的结果进行遍历,得到其中拥有最高股价的实体。
当然,如果觉得循环的次数太多了,我们也可以将它们合并到一个循环中:
StockInfo highPriced = new StockInfo(&&, BigDecimal.ZERO);
final Predicate&StockInfo& isPriceLessThan500 = StockUtil.isPriceLessThan(500);
for(String symbol : Tickers.symbols) {
StockInfo stockInfo = StockUtil.getPrice(symbol);
if(isPriceLessThan500.test(stockInfo))
highPriced = StockUtil.pickHigh(highPriced, stockInfo);
System.out.println(&High priced under $500 is & + highPriced);
可以发现,只是使用了一个Predicate类型的Lambda表达式就可以将代码的篇幅大大的较少。 只不过,以上的代码仍然是命令式风格,仍然会通过对变量进行修改来实现计算逻辑。更重要的是,以上的代码复用性比较差,当我们需要更改过滤条件的时候,就需要对它进行修改。
更好的办法是将所有会发生变化的代码封装成一个个单独的小模块,然后使用函数式风格的代码将它们联系起来。
函数式风格
使用函数式风格后,代码中看不到for循环的踪影了:
public static void findHighPriced(final Stream&String& symbols) {
final StockInfo highPriced = symbols
.map(StockUtil::getPrice)
.filter(StockUtil.isPriceLessThan(500))
.reduce(StockUtil::pickHigh)
System.out.println(&High priced under $500 is & + highPriced);
map,filter和reduce方法分别替代了三个for循环,而且代码也变的异常简洁。除了简洁之外,更重要的是这段代码随时可以被并行化。
以上的计算逻辑可以使用下图进行表达:
在实施并行化之前,让我们看看上面的几个操作:map,filter和reduce。
显然,map方法的速度是最慢的,因为它依赖于外部的Web Service。但是同时也可以注意到,对于每个股票代码,获取它们对应的股价信息是完全独立的,故而可以考虑将这部分并行化。
当需要让一段代码以并行的方式运行时,需要考虑两个方面:
如何完成?如何以合适的方式完成?
对于第一个方面,我们可以使用JDK中提供的各种并发相关的库来完成。 对于第二个方面,就需要我们根据这段代码的特点进行考虑了。对于并发程序,首先需要避免的是竞态条件(Race Condition),当多个线程试图去更新一个对象或者一个变量时,就有可能发生。所以对于这类更新,我们需要小心翼翼地维护其线程安全性。反过来,如果对象的状态是不可变的(状态变量被修饰为final),那么滋生竞态条件的土壤也就不复存在了,而这一点正是函数式编程所一再强调和标榜的。
因此,在严格遵守函数式编程的最佳实践后,并行化只不过是临门一脚的功夫而已:
// 串行执行的调用方式
findHighPriced(Tickers.symbols.stream());
// 并行执行的调用方式
findHighPriced(Tickers.symbols.parallelStream());
只不过是把stream方法替换成了parallelStream方法,就给代码插上了并行的翅膀。不需要考虑如何完成,也不需要考虑如竞态条件那样的各种风险。
关于这两个方法的定义,可以在Collection接口中找到,这也意味着不仅仅对于List类型可以很方便的实现并行,对其它实现了Collection接口的类型也非常方便:
default Stream&E& stream() {
return StreamSupport.stream(spliterator(), false);
default Stream&E& parallelStream() {
return StreamSupport.stream(spliterator(), true);
这里不打算对深层的实现原理进行剖析,但是当使用parallelStream时,意味着像map,filter这样的方法都会以并行的方式被运行,而工作线程则是来自于底层的一个线程池。这些细节都已经被封装的相当好,作为开发人员只需要保证你的代码确实遵守了游戏规则。
串行方式和并行方式的性能比较如下:
通过简单地改变一个方法,就将性能提高了接近5倍!这也许就是函数式编程的魅力之一吧。
那么,在从stream和parallelStream方法中进行选择时,需要考虑以下几个问题:
是否需要并行?任务之间是否是独立的?是否会引起任何竞态条件?结果是否取决于任务的调用顺序?
对于问题2,如果任务之间是独立的,并且代码中不涉及到对同一个对象的某个状态或者某个变量的更新操作,那么就表明代码是可以被并行化的。
对于问题3,由于在并行环境中任务的执行顺序是不确定的,因此对于依赖于顺序的任务而言,并行化也许不能给出正确的结果。
对于问题1,在回答这个问题之前,你需要弄清楚你要解决的问题是什么,数据量有多大,计算的特点是什么?并不是所有的问题都适合使用并发程序来求解,比如当数据量不大时,顺序执行往往比并行执行更快。毕竟,准备线程池和其它相关资源也是需要时间的。
但是,当任务涉及到I/O操作并且任务之间不互相依赖时,那么并行化就是一个不错的选择。通常而言,将这类程序并行化之后,执行速度会提升好几个等级,正如上面的例子那样。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1517243次
积分:12631
积分:12631
排名:第873名
原创:135篇
译文:44篇
评论:268条
(2)(2)(1)(2)(2)(5)(10)(8)(4)(3)(1)(2)(1)(3)(3)(1)(3)(1)(7)(25)(29)(22)(25)(4)(1)(7)(1)(2)(5)}

我要回帖

更多关于 java lambda 函数指针 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信