Lambda表达式的前世今生

Lambda 表达式

早在 C# 1.0 时,C#中就引入了委托(delegate)类型的概念。通过使用这个类型,我们可以将函数作为参数进行传递。在某种意义上,委托可理解为一种托管的强类型的函数指针。

通常情况下,使用委托来传递函数需要一定的步骤:

  1. 定义一个委托,包含指定的参数类型和返回值类型。
  2. 在需要接收函数参数的方法中,使用该委托类型定义方法的参数签名。
  3. 为指定的被传递的函数创建一个委托实例。

可能这听起来有些复杂,不过本质上说确实是这样。上面的第 3 步通常不是必须的,C# 编译器能够完成这个步骤,但步骤 1 和 2 仍然是必须的。

幸运的是,在 C# 2.0 中引入了泛型。现在我们能够编写泛型类、泛型方法和最重要的:泛型委托。尽管如此,直到 .NET 3.5,微软才意识到实际上仅通过两种泛型委托就可以满足 99% 的需求:

  • Action :无输入参数,无返回值
  • Action<T1, ..., T16> :支持1-16个输入参数,无返回值
  • Func<T1, ..., T16, Tout> :支持1-16个输入参数,有返回值

Action 委托返回 void 类型,Func 委托返回指定类型的值。通过使用这两种委托,在绝大多数情况下,上述的步骤 1 可以省略了。但是步骤 2 仍然是必需的,但仅是需要使用 Action 和 Func。

那么,如果我只是想执行一些代码该怎么办?在 C# 2.0 中提供了一种方式,创建匿名函数。但可惜的是,这种语法并没有流行起来。下面是一个简单的匿名函数的示例:

      Func<double, double> square = delegate(double x){return x * x;};

为了改进这些语法,在 .NET 3.5 框架和 C# 3.0 中引入了Lambda 表达式。

首先我们先了解下 Lambda 表达式名字的由来。实际上这个名字来自微积分数学中的 λ,其涵义是声明为了表达一个函数具体需要什么。更确切的说,它描述了一个数学逻辑系统,通过变量结合和替换来表达计算。所以,基本上我们有 0-n 个输入参数和一个返回值。而在编程语言中,我们也提供了无返回值的 void 支持。

让我们来看一些 Lambda 表达式的示例:

复制代码
 1   // The compiler cannot resolve this, which makes the usage of var impossible! 
 2   // Therefore we need to specify the type.
 3   Action dummyLambda = () =>
 4   {
 5     Console.WriteLine("Hello World from a Lambda expression!");
 6   };
 7 
 8   // Can be used as with double y = square(25);
 9   Func<double, double> square = x => x * x;
10 
11   // Can be used as with double z = product(9, 5);
12   Func<double, double, double> product = (x, y) => x * y;
13 
14   // Can be used as with printProduct(9, 5);
15   Action<double, double> printProduct = (x, y) => { Console.WriteLine(x * y); };
16 
17   // Can be used as with 
18   // var sum = dotProduct(new double[] { 1, 2, 3 }, new double[] { 4, 5, 6 });
19   Func<double[], double[], double> dotProduct = (x, y) =>
20   {
21     var dim = Math.Min(x.Length, y.Length);
22     var sum = 0.0;
23     for (var i = 0; i != dim; i++)
24       sum += x[i] + y[i];
25     return sum;
26   };
27 
28   // Can be used as with var result = matrixVectorProductAsync(...);
29   Func<double[,], double[], Task<double[]>> matrixVectorProductAsync =
30     async (x, y) =>
31     {
32       var sum = 0.0;
33       /* do some stuff using await ... */
34       return sum;
35     };
复制代码

从这些语句中我们可以直接地了解到:

  • 如果仅有一个入参,则可省略圆括号。
  • 如果仅有一行语句,并且在该语句中返回,则可省略大括号,并且也可以省略 return 关键字。
  • 通过使用 async 关键字,可以将 Lambda 表达式声明为异步执行。
  • 大多数情况下,var 声明可能无法使用,仅在一些特殊的情况下可以使用。

在使用 var 时,如果编译器通过参数类型和返回值类型推断无法得出委托类型,将会抛出 “Cannot assign lambda expression to an implicitly-typed local variable.” 的错误提示。来看下如下这些示例:

现在我们已经了解了大部分基础知识,但一些 Lambda 表达式特别酷的部分还没提及。

我们来看下这段代码:

1   var a = 5;
2   Func<int, int> multiplyWith = x => x * a;
3 
4   var result1 = multiplyWith(10); // 50
5   a = 10;
6   var result2 = multiplyWith(10); // 100

可以看到,在 Lambda 表达式中可以使用外围的变量,也就是闭包。

复制代码
 1     static void DoSomeStuff()
 2     {
 3       var coeff = 10;
 4       Func<int, int> compute = x => coeff * x;
 5       Action modifier = () =>
 6       {
 7         coeff = 5;
 8       };
 9 
10       var result1 = DoMoreStuff(compute); // 50
11 
12       ModifyStuff(modifier);
13 
14       var result2 = DoMoreStuff(compute); // 25
15     }
16 
17     static int DoMoreStuff(Func<int, int> computer)
18     {
19       return computer(5);
20     }
21 
22     static void ModifyStuff(Action modifier)
23     {
24       modifier();
25     }
复制代码

这里发生了什么呢?首先我们创建了一个局部变量和两个 Lambda 表达式。第一个 Lambda 表达式展示了其可以在其他作用域中访问该局部变量,实际上这已经展现了强大的能力了。这意味着我们可以保护一个变量,但仍然可以在其他方法中访问它,而不用关心那个方法是定义在当前类或者其他类中。

第二个 Lambda 表达式展示了在 Lambda 表达式中能够修改外围变量的能力。这就意味着通过在函数间传递 Lambda 表达式,我们能够在其他方法中修改其他作用域中的局部变量。因此,我认为闭包是一种特别强大的功能,但有时也可能引入一些非期望的结果。

复制代码
 1       var buttons = new Button[10];
 2 
 3       for (var i = 0; i < buttons.Length; i++)
 4       {
 5         var button = new Button();
 6         button.Text = (i + 1) + ". Button - Click for Index!";
 7         button.OnClick += (s, e) => { Messagebox.Show(i.ToString()); };
 8         buttons[i] = button;
 9       }
10 
11       //What happens if we click ANY button?!
复制代码

这个诡异的问题的结果是什么呢?是 Button 0 显示 0, Button 1 显示 1 吗?答案是:所有的 Button 都显示 10!

因为随着 for 循环的遍历,局部变量 i 的值已经被更改为 buttons 的长度 10。一个简单的解决办法类似于:

1       var button = new Button();
2       var index = i;
3       button.Text = (i + 1) + ". Button - Click for Index!";
4       button.OnClick += (s, e) => { Messagebox.Show(index.ToString()); };
5       buttons[i] = button;

通过定义变量 index 来拷贝变量 i 中的值。

注:如果你使用 Visual Studio 2012 以上的版本进行测试,因为使用的编译器与 Visual Studio 2010 的不同,此处测试的结果可能不同。可参考:Visual C# Breaking Changes in Visual Studio 2012

表达式树

在使用 Lambda 表达式时,一个重要的问题是目标方法是怎么知道如下这些信息的:

  1. 我们传递的变量的名字是什么?
  2. 我们使用的表达式体的结构是什么?
  3. 在表达式体内我们用了哪些类型?

现在,表达式树帮我们解决了问题。它允许我们深究具体编译器是如何生成的表达式。此外,我们也可以执行给定的函数,就像使用 Func 和 Action 委托一样。其也允许我们在运行时解析 Lambda 表达式。

我们来看一个示例,描述如何使用 Expression 类型:

1       Expression<Func<MyModel, int>> expr = model => model.MyProperty;
2       var member = expr.Body as MemberExpression;
3       var propertyName = memberExpression.Member.Name; //only execute if member != null

上面是关于 Expression 用法的一个最简单的示例。其中的原理非常直接:通过形成一个 Expression 类型的对象,编译器会根据表达式树的解析生成元数据信息。解析树中包含了所有相关的信息,例如参数和方法体等。

方法体包含了整个解析树。通过它我们可以访问操作符、操作对象以及完整的语句,最重要的是能访问返回值的名称和类型。当然,返回变量的名称可能为 null。尽管如此,大多数情况下我们仍然对表达式的内容很感兴趣。对于开发人员的益处在于,我们不再会拼错属性的名称,因为每个拼写错误都会导致编译错误。

如果程序员只是想知道调用属性的名称,有一个更简单优雅的办法。通过使用特殊的参数属性 CallerMemberName 可以获取到被调用方法或属性的名称。编译器会自动记录这些名称。所以,如果我们仅是需要获知这些名称,而无需更多的类型信息,则我们可以参考如下的代码写法:

1 string WhatsMyName([CallerMemberName] string callingName = null)
2 {
3     return callingName;
4 }

Lambda 表达式的性能

有一个大问题是:Lambda 表达式到底有多快?当然,我们期待其应该与常规的函数一样快,因为 Lambda 表达式也同样是由编译器生成的。在下一节中,我们会看到为 Lambda 表达式生成的 MSIL 与常规的函数并没有太大的不同。

一个非常有趣的讨论是关于在 Lambda 表达式中的闭包是否要比使用全局变量更快,而其中最有趣的地方就是是否当可用的变量都在本地作用域时是否会有性能影响。

让我们来看一些代码,用于衡量各种性能基准。通过这 4 种不同的基准测试,我们应该有足够的证据来说明常规函数与 Lambda 表达式之间的不同了。

复制代码
 1   class StandardBenchmark : Benchmark
 2   {
 3     static double[] A;
 4     static double[] B;
 5 
 6     public static void Test()
 7     {
 8       var me = new StandardBenchmark();
 9 
10       Init();
11 
12       for (var i = 0; i < 10; i++)
13       {
14         var lambda = LambdaBenchmark();
15         var normal = NormalBenchmark();
16         me.lambdaResults.Add(lambda);
17         me.normalResults.Add(normal);
18       }
19 
20       me.PrintTable();
21     }
22 
23     static void Init()
24     {
25       var r = new Random();
26       A = new double[LENGTH];
27       B = new double[LENGTH];
28 
29       for (var i = 0; i < LENGTH; i++)
30       {
31         A[i] = r.NextDouble();
32         B[i] = r.NextDouble();
33       }
34     }
35 
36     static long LambdaBenchmark()
37     {
38       Func<double> Perform = () =>
39       {
40         var sum = 0.0;
41 
42         for (var i = 0; i < LENGTH; i++)
43           sum += A[i] * B[i];
44 
45         return sum;
46       };
47       var iterations = new double[100];
48       var timing = new Stopwatch();
49       timing.Start();
50 
51       for (var j = 0; j < iterations.Length; j++)
52         iterations[j] = Perform();
53 
54       timing.Stop();
55       Console.WriteLine("Time for Lambda-Benchmark: \t {0}ms", 
56         timing.ElapsedMilliseconds);
57       return timing.ElapsedMilliseconds;
58     }
59 
60     static long NormalBenchmark()
61     {
62       var iterations = new double[100];
63       var timing = new Stopwatch();
64       timing.Start();
65 
66       for (var j = 0; j < iterations.Length; j++)
67         iterations[j] = NormalPerform();
68 
69       timing.Stop();
70       Console.WriteLine("Time for Normal-Benchmark: \t {0}ms", 
71         timing.ElapsedMilliseconds);
72       return timing.ElapsedMilliseconds;
73     }
74 
75     static double NormalPerform()
76     {
77       var sum = 0.0;
78 
79       for (var i = 0; i < LENGTH; i++)
80         sum += A[i] * B[i];
81 
82       return sum;
83     }
84   }
复制代码

当然,利用 Lambda 表达式,我们可以把上面的代码写的更优雅一些,这么写的原因是防止干扰最终的结果。所以我们仅提供了 3 个必要的方法,其中一个负责执行 Lambda 测试,一个负责常规函数测试,第三个方法则是在常规函数。而缺少的第四个方法就是我们的 Lambda 表达式,其已经在第一个方法中内嵌了。使用的计算方法并不重要,我们使用了随机数,进而避免了编译器的优化。最后,我们最感兴趣的就是常规函数与 Lambda 表达式的不同。

在运行这些测试后,我们会发现,在通常情况下 Lambda 表达式不会表现的比常规函数更差。而其中的一个很奇怪的结果就是,Lambda 表达式实际上在某些情况下表现的要比常规方法还要好些。当然,如果是在使用闭包的条件下,结果就不一样了。这个结果告诉我们,使用 Lambda 表达式无需再犹豫。但是我们仍然需要仔细的考虑当我们使用闭包时所丢失的性能。在这种情景下,我们通常会丢失一点性能,但或许仍然还能接受。关于性能丢失的原因将在下一节中揭开。

下面的表格中显示了基准测试的结果:

  • 无入参无闭包比较

  • 含入参比较

  • 含闭包比较

  • 含入参含闭包比较

TestLambda [ms]Normal [ms]
045+-146+-1
144+-146+-2
249+-345+-2
348+-245+-2

注:测试结果根据机器硬件配置有所不同

下面的图表中同样展现了测试结果。我们可以看到,常规函数与 Lambda 表达式会有相同的限制。使用 Lambda 表达式并没有显著的性能损失。

MSIL揭秘Lambda表达式

使用著名的工具 LINQPad 我们可以查看 MSIL。

我们来看下第一个示例:

1 void Main()
2 {
3     DoSomethingLambda("some example");
4     DoSomethingNormal("some example");
5 }

Lambda 表达式:

1 Action<string> DoSomethingLambda = (s) =>
2 {
3     Console.WriteLine(s);// + local
4 };

相应的方法的代码:

1 void DoSomethingNormal(string s)
2 {
3     Console.WriteLine(s);
4 }

两段代码的 MSIL 代码:

复制代码
 1 IL_0001:  ldarg.0     
 2 IL_0002:  ldfld       UserQuery.DoSomethingLambda
 3 IL_0007:  ldstr       "some example"
 4 IL_000C:  callvirt    System.Action<System.String>.Invoke
 5 IL_0011:  nop         
 6 IL_0012:  ldarg.0     
 7 IL_0013:  ldstr       "some example"
 8 IL_0018:  call        UserQuery.DoSomethingNormal
 9 
10 DoSomethingNormal:
11 IL_0000:  nop         
12 IL_0001:  ldarg.1     
13 IL_0002:  call        System.Console.WriteLine
14 IL_0007:  nop         
15 IL_0008:  ret         
16 
17 <.ctor>b__0:
18 IL_0000:  nop         
19 IL_0001:  ldarg.0     
20 IL_0002:  call        System.Console.WriteLine
21 IL_0007:  nop         
22 IL_0008:  ret  
复制代码

此处最大的不同就是函数的命名和用法,而不是声明方式,实际上声明方式是相同的。编译器会在当前类中创建一个新的方法,然后推断该方法的用法。这没什么特别的,只是使用 Lambda 表达式方便了许多。从 MSIL 的角度来看,我们做了相同的事,也就是在当前的对象上调用了一个方法。

我们可以将这些分析放到一张图中,来展现编译器所做的更改。在下面这张图中我们可以看到编译器将 Lambda 表达式移到了一个单独的方法中。

在第二个示例中,我们将展现 Lambda 表达式真正神奇的地方。在这个例子中,我们使用了一个常规的方法来访问全局变量,然后用一个 Lambda 表达式来捕获局部变量。代码如下:

复制代码
 1 void Main()
 2 {
 3     int local = 5;
 4 
 5     Action<string> DoSomethingLambda = (s) => {
 6         Console.WriteLine(s + local);
 7     };
 8     
 9     global = local;
10     
11     DoSomethingLambda("Test 1");
12     DoSomethingNormal("Test 2");
13 }
14 
15 int global;
16 
17 void DoSomethingNormal(string s)
18 {
19     Console.WriteLine(s + global);
20 }
复制代码

目前看来没什么特殊的。关键的问题是:编译器是如何处理 Lambda 表达式的?

复制代码
 1 IL_0000:  newobj      UserQuery+<>c__DisplayClass1..ctor
 2 IL_0005:  stloc.1     // CS$<>8__locals2
 3 IL_0006:  nop         
 4 IL_0007:  ldloc.1     // CS$<>8__locals2
 5 IL_0008:  ldc.i4.5    
 6 IL_0009:  stfld       UserQuery+<>c__DisplayClass1.local
 7 IL_000E:  ldloc.1     // CS$<>8__locals2
 8 IL_000F:  ldftn       UserQuery+<>c__DisplayClass1.<Main>b__0
 9 IL_0015:  newobj      System.Action<System.String>..ctor
10 IL_001A:  stloc.0     // DoSomethingLambda
11 IL_001B:  ldarg.0     
12 IL_001C:  ldloc.1     // CS$<>8__locals2
13 IL_001D:  ldfld       UserQuery+<>c__DisplayClass1.local
14 IL_0022:  stfld       UserQuery.global
15 IL_0027:  ldloc.0     // DoSomethingLambda
16 IL_0028:  ldstr       "Test 1"
17 IL_002D:  callvirt    System.Action<System.String>.Invoke
18 IL_0032:  nop         
19 IL_0033:  ldarg.0     
20 IL_0034:  ldstr       "Test 2"
21 IL_0039:  call        UserQuery.DoSomethingNormal
22 IL_003E:  nop         
23 
24 DoSomethingNormal:
25 IL_0000:  nop         
26 IL_0001:  ldarg.1     
27 IL_0002:  ldarg.0     
28 IL_0003:  ldfld       UserQuery.global
29 IL_0008:  box         System.Int32
30 IL_000D:  call        System.String.Concat
31 IL_0012:  call        System.Console.WriteLine
32 IL_0017:  nop         
33 IL_0018:  ret         
34 
35 <>c__DisplayClass1.<Main>b__0:
36 IL_0000:  nop         
37 IL_0001:  ldarg.1     
38 IL_0002:  ldarg.0     
39 IL_0003:  ldfld       UserQuery+<>c__DisplayClass1.local
40 IL_0008:  box         System.Int32
41 IL_000D:  call        System.String.Concat
42 IL_0012:  call        System.Console.WriteLine
43 IL_0017:  nop         
44 IL_0018:  ret         
45 
46 <>c__DisplayClass1..ctor:
47 IL_0000:  ldarg.0     
48 IL_0001:  call        System.Object..ctor
49 IL_0006:  ret         
复制代码

还是一样,两个函数从调用语句上看是相同的,还是应用了与之前相同的机制。也就是说,编译器为该函数生成了一个名字,并把它替换到代码中。而此处最大的区别在于,编译器同时生成了一个类,而编译器生成的函数就被放到了这个类中。那么,创建这个类的目的是什么呢?它使变量具有了全局作用域范围,而此之前其已被用于捕获变量。通过这种方式,Lambda 表达式有能力访问局部作用域的变量(因为从 MSIL 的观点来看,其仅是类实例中的一个全局变量而已)。

然后,通过这个新生成的类的实例,所有的变量都从这个实例分配和读取。这解决了变量间存在引用的问题(会对类添加一个额外的引用 - 确实是这样)。编译器已经足够的聪明,可以将那些被捕获变量放到这个类中。所以,我们可能会期待使用 Lambda 表达式并不会存在性能问题。然而,这里我们必须提出一个警告,就是这种行为可能会引起内存泄漏,因为对象仍然被 Lambda 表达式引用着。只要这个函数还在,其作用范围仍然有效(之前我们已经了解了这些,但现在我们知道了原因)。

像之前一样,我们把这些分析放入一张图中。从图中我们可以看到,闭包并不是仅有的被移动的方法,被捕获变量也被移动了。所有被移动的对象都会被放入一个编译器生成的类中。最后,我们从一个未知的类实例化了一个对象。







本文转自匠心十年博客园博客,原文链接:http://www.cnblogs.com/gaochundong/p/way_to_lambda.html,如需转载请自行联系原作者

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/391840.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matplotlib柱状图、面积图、直方图、散点图、极坐标图、箱型图

一、柱状图 1.通过obj.plot() 柱状图用bar表示&#xff0c;可通过obj.plot(kindbar)或者obj.plot.bar()生成&#xff1b;在柱状图中添加参数stackedTrue&#xff0c;会形成堆叠图。 fig,axes plt.subplots(2,2,figsize(10,6)) s pd.Series(np.random.randint(0,10,15),index …

微信支付商业版 结算周期_了解商业周期

微信支付商业版 结算周期Economics is an inexact science, finance and investing even more so (some would call them art). But if there’s one thing in economics that you can consistently count on over the long run, it’s the tendency of things to mean revert …

leetcode 448. 找到所有数组中消失的数字

给定一个范围在 1 ≤ a[i] ≤ n ( n 数组大小 ) 的 整型数组&#xff0c;数组中的元素一些出现了两次&#xff0c;另一些只出现一次。 找到所有在 [1, n] 范围之间没有出现在数组中的数字。 您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回…

前端初学者开发学习视频_初学者学习前端开发的实用指南

前端初学者开发学习视频by Nikita Rudenko通过尼基塔鲁登科(Nikita Rudenko) 初学者学习前端开发的实用指南 (A practical guide to learning front end development for beginners) I started my coding journey in spring 2018, a bit less than one year ago. I earned som…

weblogic启动失败案例(root启动引起的权限问题)

weblogic的一个domain启动失败&#xff0c;在日志中有如下信息提示&#xff1a; **************************************************** To start WebLogic Server, use a username and ** password assigned to an admin-level user. For ** server administration, us…

HTTP请求示例

HTTP请求格式当浏览器向Web服务器发出请求时&#xff0c;它向服务器传递了一个数据块&#xff0c;也就是请求信息&#xff0c;HTTP请求信息由3部分组成&#xff1a;l 请求方法URI协议/版本l 请求头(Request Header)l 请求正文下面是一个HTTP请求的例子&#xff1a;GET/sa…

Bootstrap——可拖动模态框(Model)

还是上一个小项目&#xff0c;o(╥﹏╥)o&#xff0c;要实现点击一个div或者button或者一个东西然后可以弹出一个浮在最上面的弹框。网上找了找&#xff0c;发现Bootstrap的Model弹出框可以实现该功能&#xff0c;因此学习了一下&#xff0c;实现了基本弹框功能&#xff08;可拖…

mfcc中的fft操作_简化音频数据:FFT,STFT和MFCC

mfcc中的fft操作What we should know about sound. Sound is produced when there’s an object that vibrates and those vibrations determine the oscillation of air molecules which creates an alternation of air pressure and this high pressure alternated with low …

leetcode 765. 情侣牵手(并查集)

N 对情侣坐在连续排列的 2N 个座位上&#xff0c;想要牵到对方的手。 计算最少交换座位的次数&#xff0c;以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人&#xff0c;让他们站起来交换座位。 人和座位用 0 到 2N-1 的整数表示&#xff0c;情侣们按顺序编号&#xff…

ariel字体_播客第58集:软件开发人员和freeCodeCamp超级巨星Ariel Leslie

ariel字体On this weeks episode of the freeCodeCamp.org podcast, Abbey interviews Ariel Leslie, a software developer and avid contributor to the freeCodeCamp community.在本周的freeCodeCamp.org播客节目中&#xff0c;Abbey采访了Ariel Leslie&#xff0c;他是free…

PHP绘制3D图形

PEAR提供了Image_3D Package来创建3D图像。图像或光线在3D空间中按照X、Y 、Z 坐标定位。生成的图像将呈现在2D空间中&#xff0c;可以存储为 PNG、SVG 格式&#xff0c;或输出到Shell。通过Image_3D可以很方便生成一些简单的3D对象&#xff0c;例如立方体、锥体、球体、文本和…

清除日志的sql

SET NOCOUNT ONDECLARE LogicalFileName sysname,MaxMinutes INT,NewSize INTUSE cms -- 要操作的数据库名SELECT LogicalFileName cms_log, -- 日志文件名MaxMinutes 10, -- Limit on time allowed to wrap log.NewSize 100 -- 你想设定的日志文件的大小(M)-- Setup / init…

r语言怎么以第二列绘制线图_用卫星图像绘制世界海岸线图-第二部分

r语言怎么以第二列绘制线图Part I of this blog series is here.本博客系列的第一部分 在这里 。 At the UKHO we are interested in the oceans, the seabed and the coastline — not to mention everything in and on them! In our previous blog, we (the UKHO Data Scien…

javascript创建类_如何在10分钟内使用JavaScript创建费用管理器

javascript创建类by Per Harald Borgen通过Per Harald Borgen 如何在10分钟内使用JavaScript创建费用管理器 (How to create an expense organizer with JavaScript in 10 minutes) 让我们使用ES6和Dropbox API来防止收据变得混乱。 (Let’s use ES6 and the Dropbox API to k…

豆瓣API

Api V2 索引 图书Api V2 电影Api V2 音乐Api V2 同城Api V2 广播Api V2 用户Api V2 日记Api V2 相册Api V2 线上活动Api V2 论坛Api V2 回复Api V2 我去Api V2 https://developers.douban.com/wiki/?titleapi_v2 搜索图书 GET https://api.douban.com/v2/book/search参数意义…

leetcode 485. 最大连续1的个数

给定一个二进制数组&#xff0c; 计算其中最大连续1的个数。 示例 1: 输入: [1,1,0,1,1,1] 输出: 3 解释: 开头的两位和最后的三位都是连续1&#xff0c;所以最大连续1的个数是 3. 解题思路 遇到0时&#xff0c;将连续1的长度归零。遇到1时&#xff0c;累加进长度 代码 c…

HDU Today

经过锦囊相助&#xff0c;海东集团终于度过了危机&#xff0c;从此&#xff0c;HDU的发展就一直顺风顺水&#xff0c;到了2050年&#xff0c;集团已经相当规模了&#xff0c;据说进入了钱江肉丝经济开发区500强。这时候&#xff0c;XHD夫妇也退居了二线&#xff0c;并在风景秀美…

JSP基础--动作标签

JSP基础--动作标签 JSP动作标签 1 JSP动作标签概述 动作标签的作用是用来简化Java脚本的&#xff01; JSP动作标签是JavaWeb内置的动作标签&#xff0c;它们是已经定义好的动作标签&#xff0c;我们可以拿来直接使用。 如果JSP动作标签不够用时&#xff0c;还可以使用自定义标…

整数存储怎么转化为浮点数_非整数值如何存储在浮点数中(以及为什么要浮点数)...

整数存储怎么转化为浮点数by Shukant Pal通过Shukant Pal 非整数值如何存储在浮点数中(以及为什么要浮点数) (How non-integer values are stored in a float (and why it floats)) Did you ever think how computers work on floating-point numbers? I mean — where does …

rcp rapido_Rapido使用数据改善乘车调度

rcp rapidoGiven our last blog post of the series, which can be found here :鉴于我们在该系列中的最后一篇博客文章&#xff0c;可以在这里找到&#xff1a; We thought it would be helpful to explain how we implemented all of the above into an on-ground experimen…