匿名函数和函数句柄

基本

匿名函数是 MATLAB 语言的强大工具。它们是本地存在的函数,即:在当前工作空间中。但是,它们不像常规函数那样存在于 MATLAB 路径中,例如在 m 文件中。这就是为什么它们被称为匿名,尽管它们可以在工作区中具有类似变量的名称。

@ 运算符

使用 @ 运算符创建匿名函数和函数句柄。例如,要创建 sin 函数的句柄(正弦)并将其用作 f

>> f = @sin
f = 
    @sin

现在 fsin 函数的一个句柄。就像(在现实生活中)门把手是一种使用门的方式一样,功能手柄是一种使用功能的方式。要使用 f,参数将传递给它,就像它是 sin 函数一样:

>> f(pi/2)
ans =
     1

f 接受 sin 函数接受的任何输入参数。如果 sin 是一个接受零输入参数的函数(它没有,但是其他参数,例如 peaks 函数),f() 将用于在没有输入参数的情况下调用它。

自定义匿名功能

一个变量的匿名函数

创建现有函数的句柄显然没有用,例如上面示例中的 sin。在这个例子中它是多余的。但是,创建匿名函数非常有用,这些函数可以执行自定义操作,否则需要多次重复或创建单独的函数。作为自定义匿名函数的一个示例,它接受一个变量作为其输入,求和信号的正弦和余弦平方:

>> f = @(x) sin(x)+cos(x).^2
f = 
    @(x)sin(x)+cos(x).^2

现在 f 接受一个名为 x 的输入参数。这是在 @ 运算符之后直接使用括号 (...) 指定的。f 现在是 xf(x) 的匿名函数。通过将 x 的值传递给 f 来使用它:

>> f(pi)
ans =
    1.0000

值矢量或变量也可以传递给 f,只要它们在 f 中以有效的方式使用:

>> f(1:3) % pass a vector to f
ans =
    1.1334    1.0825    1.1212
>> n = 5:7;
>> f(n) % pass n to f
ans =
   -0.8785    0.6425    1.2254

多个变量的匿名函数

以同样的方式,可以创建匿名函数以接受多个变量。一个接受三个变量的匿名函数示例:

>> f = @(x,y,z) x.^2 + y.^2 - z.^2
f = 
    @(x,y,z)x.^2+y.^2-z.^2
>> f(2,3,4)
ans =
    -3

参数化匿名函数

工作空间中的变量可以在匿名函数的定义中使用。这称为参数化。例如,要在匿名函数中使用常量 c = 2

>> c = 2;
>> f = @(x) c*x
f = 
    @(x)c*x
>> f(3)
ans =
     6

f(3) 使用变量 c 作为参数与所提供的 x 相乘。请注意,如果此时 c 的值设置为不同的值,则调用 f(3),结果不会有所不同。c 的值是创建匿名函数时的值:

>> c = 2;
>> f = @(x) c*x;
>> f(3)
ans =
     6
>> c = 3;
>> f(3)
ans =
     6

匿名函数的输入参数不引用工作空间变量

请注意,使用工作空间中的变量名称作为匿名函数的输入参数之一(即使用 @(...))将不会使用这些变量的值。相反,它们被视为匿名函数范围内的不同变量,即:匿名函数具有其私有工作空间,其中输入变量从不引用主工作空间中的变量。主工作区和匿名函数的工作区不了解彼此的内容。举例说明:

>> x = 3 % x in main workspace
x =
     3
>> f = @(x) x+1; % here x refers to a private x variable
>> f(5)
ans =
     6
>> x
x =
     3

f 中未使用来自主工作空间的 x 的值。此外,在主要工作区中,x 保持不变。在 f 的范围内,@ 运算符后括号内的变量名与主工作空间变量无关。

匿名函数存储在变量中

匿名函数(或者更确切地说,指向匿名函数的函数句柄)与当前工作空间中的任何其他值一样存储:在变量中(如上所述),在单元格数组({@(x)x.^2,@(x)x+1})中,甚至在属性(如用于交互式图形的 h.ButtonDownFcn)。这意味着匿名函数可以像任何其他值一样对待。将其存储在变量中时,它在当前工作空间中具有名称,并且可以像保存数字的变量一样进行更改和清除。

换句话说:函数句柄(无论是在 @sin 形式还是用于匿名函数)只是一个可以存储在变量中的值,就像数值矩阵一样。

高级用途

将函数句柄传递给其他函数

由于函数句柄被视为变量,因此它们可以传递给接受函数句柄作为输入参数的函数。

示例:在 m 文件中创建一个函数,该文件接受函数句柄和标量数。然后通过将 3 传递给它来调用函数句柄,然后将标量数添加到结果中。结果返回。

funHandleDemo.m 的内容:

function y = funHandleDemo(fun,x)
y = fun(3);
y = y + x;

将它保存在路径上的某个位置,例如在 MATLAB 的当前文件夹中。现在 funHandleDemo 可以如下使用,例如:

>> f = @(x) x^2; % an anonymous function
>> y = funHandleDemo(f,10) % pass f and a scalar to funHandleDemo
y =
    19

另一个现有函数的句柄可以传递给 funHandleDemo

>> y = funHandleDemo(@sin,-5)
y =
   -4.8589

请注意 @sin 如何快速访问 sin 函数,而无需先使用 f = @sin 将其存储在变量中。

使用 bsxfuncellfun 和具有匿名功能的类似功能

MATLAB 有一些内置函数可以接受匿名函数作为输入。这是一种用最少的代码行执行许多计算的方法。例如,bsxfun 执行逐元素二元运算,即:它以逐元素方式在两个向量或矩阵上应用函数。通常,这需要使用 for-loops,这通常需要预先分配速度。使用 bsxfun 加快了这个过程。以下示例使用 tictoc 来说明这一点,这两个函数可用于计算代码所需的时间。它计算矩阵列均值中每个矩阵元素的差异。

A = rand(50); % 50-by-50 matrix of random values between 0 and 1

% method 1: slow and lots of lines of code
tic
meanA = mean(A); % mean of every matrix column: a row vector
% pre-allocate result for speed, remove this for even worse performance
result = zeros(size(A));
for j = 1:size(A,1)
    result(j,:) = A(j,:) - meanA;
end
toc
clear result % make sure method 2 creates its own result

% method 2: fast and only one line of code
tic
result = bsxfun(@minus,A,mean(A));
toc

运行上面的示例会产生两个输出:

Elapsed time is 0.015153 seconds.
Elapsed time is 0.007884 seconds.

这些行来自 toc 函数,它们打印自上次调用 tic 函数以来经过的时间。

bsxfun 调用将第一个输入参数中的函数应用于其他两个输入参数。@minus 是与减号相同的操作的长名称。可以指定与任何其他函数不同的匿名函数或句柄(@),只要它接受 Amean(A) 作为输入以生成有意义的结果。

特别是对于大型矩阵中的大量数据,bsxfun 可以大大加快速度。它还使代码看起来更干净,尽管对于不了解 MATLAB 或 bsxfun 的人来说可能更难解释。 (请注意,在 MATLAB R2016a 及更高版本中,以前使用 bsxfun 的许多操作不再需要它们; A-mean(A) 直接工作,在某些情况下可能更快。)