MATLAB 编写函数
编写程序的时候会经常用到相似的代码片段,为了使代码整洁,可以将这些代码片段封装为函数使用。本文介绍 MATLAB 编写函数的一般方法。
匿名函数
匿名函数适用于一些简短操作的封装,常用于对一些较长的数学表达式进行封装。下面给出了定义匿名函数的一般方法。
f = @(x) exp(-x.^2/2)/sqrt(2*pi);
可以看到,匿名函数的定义与 $y=f(x)$ 这种形式相似,我们只需要用 @
和括号给定自变量,然后给出表达式,即可定义名为 f
的匿名函数。匿名函数必须在使用前进行定义。应当注意的是,有时为了向量化计算,在平方或乘除运算中通常采用加点的运算。
匿名函数还支持多元函数,这种情况下只需要在 @(x)
中增加变量即可,例如
@(x1,x2,x3)
。
在老版本中,与匿名函数非常相似的是内联函数,然而得知它在 MATLAB 以后的版本中将被移除,所以就不介绍了。
自定义函数
由于内联函数过于简单,且只在脚本内生效,即无法调用另一个独立脚本中定义的匿名函数。为了使函数能够进行更复杂的操作,且能够被多个脚本所调用,我们需要用
function
开头定义一个独立的 .m
文件。
函数的格式一般如下所示,例子中 <>
里边的内容仅为说明,不是代码。
% 函数作用的简单说明
% 输入输出变量的基本说明
% 作者和修改日期
%% 主函数
function [y1,y2,y3] = simplefcn(x1,x2,x3)
<某些操作>
y1 = <某变量>;
y2 = <某变量>;
y3 = <某变量>;
end
由于函数的通用性,共同工作的小伙伴们会经常分享一些好用的函数。因此,用 %
写好注释是一件非常重要的事情。MATLAB官方模板中将函数说明写在 function
紧接着的下一行,然而更实用的情况是写在文件的最前面,同时我们可以用 %%
对代码进行分节,使可读性更强。
注释是不参与代码计算的,因此上面这个例子中,函数真正开始于 function
。然后分别给出输出、函数名和输入参数。当有多个输出时,只需用中括号和逗号构成输出向量即可。
函数编写完成后,将其另存为单独的m文件,这时需要文件名与函数名相同。在上面的例子中,对应的函数文件名为应当为 simplefcn.m
。
同各种其他编程一样,在未申明全局变量的情况下,函数体内的变量与函数外的变量相互独立。这就是说函数内的变量如果与脚本中的变量名字相同,他们并不会相互影响。同时,函数在计算结束后,其内部的变量都会在内存中清除,所以在函数内部计算过程中任何有用的变量都应当考虑将其输出。
有时候我们确实需要像函数一样封装一个较为复杂的代码,但是我们只需要这段代码对当前文件有用,其他文件无法对它进行调用。这时我们只需要将函数写在脚本的内部,更多情况下是写在脚本的末尾。一个简单的例子如下所示。
% 脚本说明
% 作者和修改日期
%% 主代码
<某些操作>
%% 子函数1
% 函数说明
% 变量说明
function y = subfcn1(x)
<某些操作>
y = <某变量>;
end
%% 子函数2
% 函数说明
% 变量说明
function y = subfcn2(x)
<某些操作>
y = <某变量>;
end
同样地,建议将注释写完整并放在前面显眼的位置。例子中,subfcn1
和 subfcn2
是两个子函数,其只在脚本内有效。子函数不仅适用于脚本文件,在大型函数的定义中也可将一些小函数编写成子函数,从而提高代码的可读性。
function
开头的m文件,除此以外的m文件均可视为脚本。两者的不同之处在于函数可以有输入输出变量,且函数内的变量具有独立的变量空间。而脚本虽然能被其他脚本所调用,但不能提供输入参数,且脚本内的变量共享工作空间。例如两个脚本中都有 x
这个变量,那么 x
的值就会受两个脚本控制,有时会因为变量被覆盖而使计算与预期不符。这也是为什么建议将通用的算法写成函数而非脚本。函数的重载
从前面的介绍中可以看到,定义函数时需要给定输入、输出变量的数目,那么问题就来了:如果不确定有多少输入输出,允许函数内使用一定的默认值,或者当给定不同数量的输入、输出时函数进行不同的操作,要怎么做呢?可能有人会说,重新定义相应的函数就可以了。这确实是一种办法,但会使函数文件过多而显得冗余。这时就需要用到函数的重载。
在 MATLAB 中可利用关键字 varargin
作为输入,用 varargout
作为输出来代替可变数量的输入输出(variable-length input/output argument list)。同时,用 nargin
和 nargout
来获取输入和输出变量的数量。结合 if
或 switch
语句即可实现函数的重载。进一步,可使用函数 narginchk
和 nargoutchk
来限制输入输出变量的数量,但是这两个函数在较老的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
变量的值,可使用以下代码:
x = evalin('base','x');
这样就实现了在不定义全局变量时,从工作空间向函数内部单向传递参数的功能。应当注意,虽然函数内的变量名为 x
,但由于不是全局变量,其与外部工作空间的 x
相互独立,除利用该函数传递数值外,互不干扰。
直接输出到工作空间
与 evalin
类似但相反,assignin
能够将函数内的局部变量输出到工作空间。例如,将函数内计算的 x
输出到工作空间,并命名为 xout
,相应的代码为
assignin('base','xout',x)
带参数的输入
熟悉MATLAB的小伙伴或许知道有些函数可以像面向对象编程那样输入"属性值",最经典的例子莫过于绘图函数的调用:
plot(x,y,'LineWidth',2,'LineStyle','--')
上面在调用绘图函数时还额外指定了线宽和线型。对于这样的输入,我们应当如何定义函数呢? 有兴趣的小伙伴可以参考 MATLAB 解析输入参数。