MATLAB 编写函数

编写程序的时候会经常用到相似的代码片段,为了使代码整洁,可以将这些代码片段封装为函数使用。本文介绍 MATLAB 编写函数的一般方法。

匿名函数适用于一些简短操作的封装,常用于对一些较长的数学表达式进行封装。下面给出了定义匿名函数的一般方法。

matlab

f = @(x) exp(-x.^2/2)/sqrt(2*pi);

可以看到,匿名函数的定义与 $y=f(x)$ 这种形式相似,我们只需要用 @ 和括号给定自变量,然后给出表达式,即可定义名为 f 的匿名函数。匿名函数必须在使用前进行定义。应当注意的是,有时为了向量化计算,在平方或乘除运算中通常采用加点的运算。

匿名函数还支持多元函数,这种情况下只需要在 @(x) 中增加变量即可,例如 @(x1,x2,x3)

在老版本中,与匿名函数非常相似的是内联函数,然而得知它在 MATLAB 以后的版本中将被移除,所以就不介绍了。

MATLAB 内联函数

由于内联函数过于简单,且只在脚本内生效,即无法调用另一个独立脚本中定义的匿名函数。为了使函数能够进行更复杂的操作,且能够被多个脚本所调用,我们需要用 function 开头定义一个独立的 .m 文件。

函数的格式一般如下所示,例子中 <> 里边的内容仅为说明,不是代码。

matlab

% 函数作用的简单说明
% 输入输出变量的基本说明

% 作者和修改日期

%% 主函数
function [y1,y2,y3] = simplefcn(x1,x2,x3)
    <某些操作>
   y1 = <某变量>;
   y2 = <某变量>;
   y3 = <某变量>;
end

由于函数的通用性,共同工作的小伙伴们会经常分享一些好用的函数。因此,用 % 写好注释是一件非常重要的事情。MATLAB官方模板中将函数说明写在 function 紧接着的下一行,然而更实用的情况是写在文件的最前面,同时我们可以用 %% 对代码进行分节,使可读性更强。

注释是不参与代码计算的,因此上面这个例子中,函数真正开始于 function。然后分别给出输出、函数名和输入参数。当有多个输出时,只需用中括号和逗号构成输出向量即可。

函数编写完成后,将其另存为单独的m文件,这时需要文件名与函数名相同。在上面的例子中,对应的函数文件名为应当为 simplefcn.m

同各种其他编程一样,在未申明全局变量的情况下,函数体内的变量与函数外的变量相互独立。这就是说函数内的变量如果与脚本中的变量名字相同,他们并不会相互影响。同时,函数在计算结束后,其内部的变量都会在内存中清除,所以在函数内部计算过程中任何有用的变量都应当考虑将其输出。

有时候我们确实需要像函数一样封装一个较为复杂的代码,但是我们只需要这段代码对当前文件有用,其他文件无法对它进行调用。这时我们只需要将函数写在脚本的内部,更多情况下是写在脚本的末尾。一个简单的例子如下所示。

matlab

% 脚本说明
% 作者和修改日期

%% 主代码
<某些操作>

%% 子函数1
% 函数说明
% 变量说明
function y = subfcn1(x)
    <某些操作>
    y = <某变量>;
end

%% 子函数2
% 函数说明
% 变量说明
function y = subfcn2(x)
    <某些操作>
    y = <某变量>;
end

同样地,建议将注释写完整并放在前面显眼的位置。例子中,subfcn1subfcn2 是两个子函数,其只在脚本内有效。子函数不仅适用于脚本文件,在大型函数的定义中也可将一些小函数编写成子函数,从而提高代码的可读性。

注解
MATLAB 中函数文件以 function 开头的m文件,除此以外的m文件均可视为脚本。两者的不同之处在于函数可以有输入输出变量,且函数内的变量具有独立的变量空间。而脚本虽然能被其他脚本所调用,但不能提供输入参数,且脚本内的变量共享工作空间。例如两个脚本中都有 x 这个变量,那么 x 的值就会受两个脚本控制,有时会因为变量被覆盖而使计算与预期不符。这也是为什么建议将通用的算法写成函数而非脚本。

从前面的介绍中可以看到,定义函数时需要给定输入、输出变量的数目,那么问题就来了:如果不确定有多少输入输出,允许函数内使用一定的默认值,或者当给定不同数量的输入、输出时函数进行不同的操作,要怎么做呢?可能有人会说,重新定义相应的函数就可以了。这确实是一种办法,但会使函数文件过多而显得冗余。这时就需要用到函数的重载。

在 MATLAB 中可利用关键字 varargin 作为输入,用 varargout 作为输出来代替可变数量的输入输出(variable-length input/output argument list)。同时,用 narginnargout 来获取输入和输出变量的数量。结合 ifswitch 语句即可实现函数的重载。进一步,可使用函数 narginchknargoutchk 来限制输入输出变量的数量,但是这两个函数在较老的MATLAB版本中不存在。

这样一来,我们可以给出更加通用的函数定义模板:

matlab

% 函数说明
% 变量说明

% 作者和修改日期

%% 主函数
function varargout = fun(varargin)
    % 解析输入变量
    switch nargin
        case 0
            <导入输入变量默认值>
        case 1
            <变量1> = varargin{1};
            <其他变量取默认值>
        otherwise
            error('无效输入')
    end

    % 函数主体
    <某些操作>

    % 解析输出变量
    switch nargout
        <定义不同数量输出时的操作>
    end
end

%% 子函数1
% 函数说明
% 变量说明
function y = subfcn1(x)
    <某些操作>
    y = <某变量>;
end

%% 子函数2
% 函数说明
% 变量说明
function y = subfcn2(x)
    <某些操作>
    y = <某变量>;
end

一般来说,主函数采用可变输入输出以增强通用性,而子函数由于只在文件内部有效,可采用简单的模式。上面以存在两个子函数的情况为例,实际使用时并不一定完全如此。

MATLAB 的函数中还有一些其他的"高级"用法,这里稍作提醒,有兴趣的伙伴们可以自行学习。

为了与函数内的变量进行交互,常用的方式是在函数内外采用 global 申明全局变量,从而实现参数传递。

如果仅仅想获取工作区变量而不对其进行操作的话,可采用 evalin 函数,例如,为了在函数内部获取工作区名为 x 变量的值,可使用以下代码:

matlab

x = evalin('base','x');

这样就实现了在不定义全局变量时,从工作空间向函数内部单向传递参数的功能。应当注意,虽然函数内的变量名为 x,但由于不是全局变量,其与外部工作空间的 x 相互独立,除利用该函数传递数值外,互不干扰。

evalin 类似但相反,assignin 能够将函数内的局部变量输出到工作空间。例如,将函数内计算的 x 输出到工作空间,并命名为 xout,相应的代码为

matlab

assignin('base','xout',x)
危险
需要特别注意,这种方法会直接覆盖掉工作空间内的同名变量,因此不满足一般的编程规范。除非在特定情况下,否则不建议使用这个功能。

熟悉MATLAB的小伙伴或许知道有些函数可以像面向对象编程那样输入"属性值",最经典的例子莫过于绘图函数的调用:

matlab

plot(x,y,'LineWidth',2,'LineStyle','--')

上面在调用绘图函数时还额外指定了线宽和线型。对于这样的输入,我们应当如何定义函数呢? 有兴趣的小伙伴可以参考 MATLAB 解析输入参数