Delphi 知识 彻底搞懂Delphi中的匿名方法

  

    

 

前言:

        顾名思义,匿名方法是一个没有与之相关的名字的过程或函数。一个匿名方法将一个代码块视为一个实体,可以分配给一个变量或作为一个方法的参数使用。此外,匿名方法可以引用变量,并在定义该方法的上下文中为变量绑定值。匿名方法可以用简单的语法进行定义和使用。它们类似于其他语言中定义的闭包结构。

目录

一、语法

二、使用匿名方法

三、匿名方法的变量绑定

1. 可变的装订图示

2. 作为事件的匿名方法

3. 可变的绑定机制

三、 匿名方法的效用

1. 变量的绑定

2. 使用的便利性

3. 使用参数的代码


一、语法

匿名方法的定义与普通的过程或函数类似,但没有名称。例如,下面这个函数返回一个被定义为匿名方法的函数:

function MakeAdder(y: Integer): TFuncOfInt;
begin
  Result := { start anonymous method } function(x: Integer) : Integer
  begin
    Result := x + y;
  end; { end anonymous method }
end;

函数 MakeAdder 返回一个它声明的没有名字的函数:一个匿名方法。

注意,MakeAdder 返回一个 TFuncOfInt 类型的值。一个匿名方法类型被声明为对一个方法的引用:

type
  TFuncOfInt = reference to function(x: Integer): Integer;

这个声明表示匿名方法: 

  1. 是一个函数
  2. 接受一个整数参数
  3. 返回一个整数值。

一般来说,匿名函数类型被声明为过程或函数: 

type
  TType1 = reference to procedure (parameterlist);
  TType2 = reference to function (parameterlist): returntype;

其中 parameterlist(参数表)是可选的。

下面是几个类型的例子: 

type
  TSimpleProcedure = reference to procedure;
  TSimpleFunction = reference to function(x: string): Integer;

一个匿名方法被声明为一个没有名字的过程或函数: 

// Procedure
procedure (parameters)
begin
  { statement block }
end;
// Function
function (parameters): returntype
begin
  { statement block }
end;

其中(参数)是可选的。

二、使用匿名方法

匿名方法通常被分配给某个事情,如这些例子中:

myFunc := function(x: Integer): string
begin
  Result := IntToStr(x);
end;

myProc := procedure(x: Integer)
begin
  Writeln(x);
end;

匿名方法也可以由函数返回或在调用方法时作为参数值传递。例如,使用上面刚刚定义的匿名方法变量myFunc: 

type
  TFuncOfIntToString = reference to function(x: Integer): string;

procedure AnalyzeFunction(proc: TFuncOfIntToString);
begin
  { some code }
end;

// Call procedure with anonymous method as parameter
// Using variable:
AnalyzeFunction(myFunc);

// Use anonymous method directly:
AnalyzeFunction(function(x: Integer): string
begin
  Result := IntToStr(x);
end;)

方法引用也可以被分配给方法以及匿名方法。比如说: 

type
  TMethRef = reference to procedure(x: Integer);
TMyClass = class
  procedure Method(x: Integer);
end;

var
  m: TMethRef;
  i: TMyClass;
begin
  // ...
  m := i.Method;   //assigning to method reference
end;

然而,反之亦然:你不能将一个匿名方法分配给一个普通的方法指针。方法引用是可管理的类型,但方法指针是不可管理的类型。因此,出于类型安全的考虑,不支持将方法引用分配给方法指针。例如,事件是方法指针值的属性,所以你不能为一个事件使用匿名方法。

三、匿名方法的变量绑定

匿名方法的一个关键特征是,它们可以引用变量,这些变量在它们被定义的地方对它们是可见的。此外,这些变量可以被绑定到值上,并与对匿名方法的引用一起被包装起来。这可以捕获状态并延长变量的寿命。

1. 可变的装订图示

再考虑一下上面定义的函数: 

function MakeAdder(y: Integer): TFuncOfInt;
begin
  Result := function(x: Integer): Integer
  begin
    Result := x + y;
  end;
end;

我们可以创建一个这个函数的实例,绑定一个变量值:

var
  adder: TFuncOfInt;
begin
  adder := MakeAdder(20);
  Writeln(adder(22)); // prints 42
end.

变量 adder 包含一个匿名方法,它将值20绑定到匿名方法代码块中引用的变量y上。即使该值超出了范围,这种绑定也会持续存在。

2. 作为事件的匿名方法

使用方法引用的一个动机是有一个可以包含约束变量的类型,也被称为闭包值。由于闭包在其定义环境中关闭,包括在定义点引用的任何局部变量,它们有必须被释放的状态。方法引用是被管理的类型(它们是引用计数的),所以它们可以跟踪这种状态,并在必要时释放它。如果一个方法引用或闭包可以自由地分配给一个方法指针,比如一个事件,那么就很容易产生具有悬空指针或内存泄漏的错误类型的程序。

Delphi的事件是属性的一种约定。除了类型的不同,事件和属性之间没有任何区别。如果一个属性是方法指针类型的,那么它就是一个事件。

如果一个属性是方法引用类型,那么它在逻辑上也应该被视为一个事件。然而,IDE并不把它当作一个事件。这对作为组件和自定义控件安装在IDE中的类来说很重要。

因此,要在一个组件或自定义控件上有一个可以使用方法引用或闭合值分配的事件,该属性必须是方法引用类型。然而,这很不方便,因为IDE并不承认它是一个事件。

下面是一个使用方法引用类型的属性的例子,所以它可以作为一个事件操作:

type
  TProc = reference to procedure;
  TMyComponent = class(TComponent)
  private
    FMyEvent: TProc;
  public
    // MyEvent property serves as an event:
    property MyEvent: TProc read FMyEvent write FMyEvent;
    // some other code invokes FMyEvent as usual pattern for events
  end;

// …

var
  c: TMyComponent;
begin
  c := TMyComponent.Create(Self);
  c.MyEvent := procedure
  begin
    ShowMessage('Hello World!'); // shown when TMyComponent invokes MyEvent
  end;
end;

3. 可变的绑定机制

为了避免产生内存泄漏,更详细地了解变量绑定过程是很有用的。

在程序、函数或方法(以下简称 "例程")开始时定义的局部变量,通常只在该例程处于活动状态时存在。匿名方法可以延长这些变量的生存期。

如果一个匿名方法在其主体中引用了一个外部局部变量,那么这个变量就被 "捕获 "了。捕获意味着延长了变量的寿命,因此它的寿命与匿名方法的值一样长,而不是与它的声明例程一起死亡。注意,变量捕获是指捕获变量--而不是值。如果一个变量的值在被匿名方法捕获后发生了变化,那么匿名方法捕获的变量的值也会发生变化,因为它们是具有相同存储空间的同一个变量。捕获的变量存储在堆中,而不是堆栈中。

匿名方法的值是方法引用类型的,并且是引用计数的。当给定的匿名方法值的最后一个方法引用超出范围,或被清除(初始化为nil)或最终确定,它所捕获的变量最终也会超出范围。

在多个匿名方法捕获同一个局部变量的情况下,这种情况会更加复杂。为了理解在所有情况下这是如何工作的,有必要对实现的机制进行更精确的说明。

每当一个局部变量被捕获,它就会被添加到一个与其声明的例程相关的 "框架对象 "中。每个在例程中声明的匿名方法都会被转换成与其包含的例程相关的框架对象上的一个方法。最后,任何由于匿名方法值被构建或变量被捕获而创建的框架对象都会通过另一个引用链接到它的父框架上--如果有这样的框架存在的话,并且如果有必要的话,可以访问一个捕获的外部变量。这些从一个框架对象到它的父框架的链接也是参考计算的。在一个嵌套的本地例程中声明的匿名方法,从其父例程中捕获变量,使该父框架对象保持活力,直到它自己超出范围。

例如,考虑这种情况:
 

type
  TProc = reference to procedure;
procedure Call(proc: TProc);
// ...
procedure Use(x: Integer);
// ...

procedure L1; // frame F1
var
  v1: Integer;

  procedure L2; // frame F1_1
  begin
    Call(procedure // frame F1_1_1
    begin
      Use(v1);
    end);
  end;

begin
  Call(procedure // frame F1_2
  var
    v2: Integer;
  begin
    Use(v1);
    Call(procedure // frame F1_2_1
    begin
      Use(v2);
    end);
  end);
end;

每个例程和匿名方法都有一个框架标识符,以便更容易识别哪个框架对象链接到哪个:

  1. v1是F1中的一个变量
  2. v2是F1_2中的一个变量(由F1_2_1捕获)。
  3. F1_1_1的匿名方法是F1_1中的一个方法。
  4. F1_1链接到F1(F1_1_1使用v1)。
  5. F1_2的匿名方法是F1中的一个方法。
  6. F1_2_1的匿名方法是F1_2中的一个方法。

框架F1_2_1和F1_1_1不需要框架对象,因为它们既没有声明匿名方法也没有被捕获的变量。它们也不在嵌套的匿名方法和外部捕获变量之间的任何父子关系路径上。(它们有隐含的框架存储在栈上)。

仅仅给了匿名方法F1_2_1一个引用,变量v1和v2就被保留了下来。相反,如果唯一超过F1调用时间的引用是F1_1_1,那么只有变量v1被保留下来。

有可能在方法引用/框架链接链中创建一个循环,导致内存泄漏。例如,将匿名方法直接或间接地存储在匿名方法本身捕获的变量中,就会产生一个循环,导致内存泄漏。

三、 匿名方法的效用

匿名方法提供的不仅仅是一个简单的指向可调用事物的指针。它们提供了几个优点: 

  1. 绑定变量值
  2. 定义和使用方法的简单方法
  3. 易于使用代码进行参数化

1. 变量的绑定

匿名方法提供了一个代码块和变量绑定到它们所定义的环境中,即使该环境不在范围内。一个指向函数或过程的指针不能做到这一点。

例如,上面的代码样本中的语句adder := MakeAdder(20);产生了一个变量adder,封装了一个变量与数值20的绑定。

其他一些实现这种结构的语言把它们称为闭包。从历史上看,我们的想法是,对adder := MakeAdder(20);这样的表达式进行评估会产生一个闭包。它代表了一个对象,其中包含了对函数中引用的、在函数之外定义的所有变量的绑定的引用,从而通过捕获变量的值来关闭它。

2. 使用的便利性

下面的例子显示了一个典型的类定义,定义一些简单的方法,然后调用它们: 

type
  TMethodPointer = procedure of object; // delegate void TMethodPointer();
  TStringToInt = function(x: string): Integer of object;

TObj = class
  procedure HelloWorld;
  function GetLength(x: string): Integer;
end;

procedure TObj.HelloWorld;
begin
  Writeln('Hello World');
end;

function TObj.GetLength(x: string): Integer;
begin
  Result := Length(x);
end;

var
  x: TMethodPointer;
  y: TStringToInt;
  obj: TObj;

begin
  obj := TObj.Create;

  x := obj.HelloWorld;
  x;
  y := obj.GetLength;
  Writeln(y('foo'));
end.

这与使用匿名方法定义和调用的相同方法形成了对比: 

type
  TSimpleProcedure = reference to procedure;
  TSimpleFunction = reference to function(x: string): Integer;

var
  x1: TSimpleProcedure;
  y1: TSimpleFunction;

begin
  x1 := procedure
    begin
      Writeln('Hello World');
    end;
  x1;   //invoke anonymous method just defined

  y1 := function(x: string): Integer
    begin
      Result := Length(x);
    end;
  Writeln(y1('bar'));
end.

注意到使用匿名方法的代码是多么的简单和简短。如果你想明确而简单地定义这些方法并立即使用它们,而不需要为创建一个可能永远不会在其他地方使用的类而付出开销和努力,这就是理想的做法。这样的代码更容易理解。

3. 使用参数的代码

匿名方法使得编写以代码为参数的函数和结构更加容易,而不仅仅是数值。

多线程是匿名方法的一个很好的应用。如果你想并行地执行一些代码,你可能有一个parallel-for函数,看起来像这样: 

type
  TProcOfInteger = reference to procedure(x: Integer);

procedure ParallelFor(start, finish: Integer; proc: TProcOfInteger);

ParallelFor过程在不同的线程上迭代一个过程。假设这个过程使用线程或线程池正确而有效地实现,那么它就可以很容易地用于利用多处理器的优势: 

procedure CalculateExpensiveThings;
var
  results: array of Integer;
begin
  SetLength(results, 100);
  ParallelFor(Low(results), High(results),
    procedure(i: Integer)                           // \
    begin                                           //  \ code block
      results[i] := ExpensiveCalculation(i);        //  /  used as parameter
    end                                             // /
    );
  // use results
  end;

这与没有匿名方法的情况下需要做的事情形成对比:可能是一个带有虚拟抽象方法的 "任务 "类,以及ExpensiveCalculation的具体后裔,然后将所有的任务添加到队列中--几乎没有那么自然或整合。

在这里,"并行换 "算法是被代码参数化的抽象概念。在过去,实现这种模式的常见方法是使用一个具有一个或多个抽象方法的虚拟基类;考虑TThread类和它的抽象Execute方法。然而,匿名方法使这种模式--使用代码对算法和数据结构进行参数化--变得容易得多。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Unity入门】19.定时调用Invoke

【Unity入门】定时调用Invoke 大家好,我是Lampard~~ 欢迎来到Unity入门系列博客,所学知识来自B站阿发老师~感谢 (一)计时器 (1)Invoke 单词调用 计时器我们并不陌生,在cocos上有着schedule类是…

056:cesium 七种方法设置颜色

第056个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置颜色,这里用到了7种方法,查看API,还有很多种方法 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共115行)相关API参考:专栏目标示例效果 配置…

【C++】map和set的模拟实现

一、思路 1. 改造RBTree 现在我们有一棵 R B T r e e RBTree RBTree,那么如何用它实现 m a p map map和 s e t set set?我们知道 m a p map map是 KV 结构, s e t set set是 K 结构,传统思路是两份 R B T r e e RBTree RBTree的代…

【MATLAB图像处理实用案例详解(12)】——利用BP神经网络实现图像压缩

目录 一、图像压缩二、BP神经网络实现图像压缩原理三、算法步骤3.1 图像块划分3.2 归一化3.3 建立BP神经网络3.4 保存结果 四、效果演示 一、图像压缩 常见的文件压缩软件如WinZip、WinRAR等采用的是无损压缩,能够完全恢复原文件内容。多媒体信息具有信息量大、冗余…

STM32F4 HAL库使用DMA进行ADC采样实时发送波形到串口显示(包含傅里叶变换)

1.总体逻辑 按下STM32F4的KEY0按键,通过外部中断的方式对按键进行检测,然后开启一次带DMA的固定点数的ADC采集,采集完成后在DMA的中断发送采集到的数据,然后清空数据区准备下一次的按键中断。电脑接受到串口数据后对数据进行简单…

【JavaEE】SpringBoot的日志

目录 日志作用 SpringBoot日志框架 日志打印 日志级别 类型 作用 修改级别 日志永久化 配置日志文件目录 配置日志文件名 简化日志打印和永久化——lombok 日志作用 问题定位:可以帮助开发人员快速找到问题出现的位置系统监控:可以把系统的运…

跳跃游戏类题目 总结篇

一.跳跃游戏类题目简单介绍 跳跃游戏是一种典型的算法题目,经常是给定一数组arr,从数组的某一位置i出发,根据一定的跳跃规则,比如从i位置能跳arr[i]步,或者小于arr[i]步,或者固定步数,直到到达某…

结构型模式-组合模式

组合模式 概述 ​ 对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。…

计算机组成原理4.2.2汉明码

编码的最小距离 奇校验和偶校验 看1的个数是奇数 还是偶数 汉明码 汉明码的配置 根据不等式,确定增添几位,根据指数放置增添位 汉明码的检错 分不同检测小组 分组规则:哪位为’1‘就是哪组元素。 1号位为‘1’的都是第一组元素&#…

基于COM组件实现C#调用C++类对象过程中的注意事项

目录 一、基于COM的调用原理二、注意事项如何在C ATL中有效添加方法与属性如何让C#调用C中的属性(.idl中声明属性)如何对变量类型进行转换C#如何获取C类中的参数变量 一、基于COM的调用原理 调用原理:首先基于C ATL模板类,实现需…

【网络进阶】服务器模型Reactor与Proactor

文章目录 1. Reactor模型2. Proactor模型3. 同步IO模拟Proactor模型 在高并发编程和网络连接的消息处理中,通常可分为两个阶段:等待消息就绪和消息处理。当使用默认的阻塞套接字时(例如每个线程专门处理一个连接),这两…

【redis】redis分布式锁(二)可重入锁+设计模式

【redis】redis分布式锁(二)可重入锁 文章目录 【redis】redis分布式锁(二)可重入锁前言一、可重入锁(又名递归锁)1、说明:2、分开解释:3、可重入锁的种类隐式锁(即synch…

【软件测试】测试用例的设计

文章目录 一. 针对没有需求的案例来设计测试用例二. 针对有需求的案例来设计测试用例1. 穷举法2. 等价类3. 边界值4. 判定表法5. 场景设计法5.1 简介5.2 基本设计步骤5.3 基本流和备选流5.4 使用场景5.5 优缺点5.6 实例 6. 错误猜测法 一. 针对没有需求的案例来设计测试用例 针…

深度强化学习——蒙特卡洛算法(6)

注:本章的内容作为补充插曲,大家可以选看,不过还是建议把最后一个使用蒙特卡洛近似求期望稍微看一下 蒙特卡洛是一大堆随机算法,通过随机样本来估算真实值 使用随机样本来近似Π 1、在[a,b]做随机均匀抽样,抽出n个样…

YOLO物体检测系列1.经典方法概述及评价指标体现

1. 深度学习经典检测方法: two-stage(两阶段): Faster-rcnn Mask-RCNN系列 one-stage(单阶段):Yolo系列 两阶段:一阶段实现RPN候选区域预选 二阶段基于候选区域再进行检测回归分类任务 单阶段:一个CNN卷积网络实现检测…

C++线程的简单学习及了解

此篇文章只是线程的简单了解。 文章目录 前言一、线程的优缺点二、C线程库 1.thread类的简单介绍2.线程函数参数总结 前言 什么是线程? 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控…

day3 TCP/IP协议与五层体系结构

TCP / IP 四层体系结构 TCP / IP工作流程: 现在互联网使用的 TCP/IP 体系结构已经发生了演变,即某些应用程序可以直接使用 IP 层,或甚至直接使用最下面的网络接口层。 沙漏型展示: 五层体系结构 各层的主要功能 应用层&#xff1…

搭建外网minecraft服务器方案

很多minecraft服务器主都想自己搭建一个外网可以访问的minecraft服务器,在没有外网IP的情况下,一般都是使用Logmein Hamachi方案。这种方案有它的弊端,需要客户机安装Hamachi,十分不方便。另外,免费版只支持5人&#x…

mysql如何加行锁

一、概述 InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁,所以后面的内容都是基于 InnoDB 引擎的。当我们使用delete、update进行数据库删除、更新的时候,数据库会自动加上行锁。但是,行锁有时也会失效。 数据库版本&a…

笔记:计算机网络体系结构(OSI七层模型、TCP/IP五层协议)

计算机网络体系结构 计算机网络是一个复杂的、具有综合性技术的系统,它由计算机系统、通信处理机、通信线路和通信设备、操作系统以及网络协议等组成。为了更好地描述计算机网络结构,使计算机网络系统有条不紊地处理工作,需要定义一种较好的…