本节摘要:函数为计算提供了命名的接口与隔离的工作区,是 Matlab 程序复用的基本单位。本节讲函数定义语法、多输入多输出、
nargin默认参数、子函数与私有函数,以及从脚本抽取函数的重构手法。
脚本时代处理重复的办法是复制代码块改参数。三个副本时尚可忍受,第十个副本出现时,原始逻辑发现一个 bug,你要改十处——漏一处就是潜伏的错。函数解决的就是这个问题:逻辑写一次,参数换着调。Moler 最初没打算做语言,但用户规模把接口与封装的需求压了过来,函数于 1990 年代初成为一等公民。
function [rms, peak] = statFeat(x, fs) % STATFEAT 计算信号的基本统计特征 % [rms, peak] = statFeat(x, fs) 返回均方根与峰值 if nargin < 2 fs = 1000; % 默认采样率 end x = x(:); % 强制列向量,防御式写法 rms = sqrt(mean(x.^2)); peak = max(abs(x)); end
要点逐条看:函数名与文件名一致;返回值放在左侧方括号;nargin 记录实际传入参数个数,用于默认值——这是 Matlab 没有"默认参数语法"时代的标准解法(R2019b 之后可用 arguments 块,是同一需求的现代演化);注释紧贴函数头的部分会进帮助系统,help statFeat 能看到。
调用:
[r, p] = statFeat(randn(1, 2000)); r2 = statFeat(randn(1, 2000), 500); % 指定采样率
函数内部变量与外界完全隔离:外界传值进来,函数返回值出去,中间状态不泄漏。好处是函数内部的 x 不会覆盖你工作区的 x;代价是只返回写进函数头的东西,函数里画了图可以,但改了输入矩阵外界是看不见的(值传递语义)。
一个文件里可以排多个函数:第一个是主函数(对外可见),后面的子函数只能被同文件调用——小型工具模块常用这招,把辅助计算藏进同一文件,不污染搜索路径。
💡 关键直觉:判断一段代码该不该抽成函数,看两件事——它会不会被第二处调用,它有没有独立到值得一个名字。二者占其一就抽。

第 2.1 节的谱分析脚本,重构后拆成"参数脚本 + 信号生成函数":
function x = genNoisySine(f0, fs, T, noiseAmp) t = 0 : 1/fs : T - 1/fs; x = sin(2*pi*f0*t) + noiseAmp * randn(size(t)); end
脚本里只剩 x = genNoisySine(50, 1000, 2, 0.3); 一行。生成逻辑从此只有一份,改噪声模型只动一处——这就是复用压力推动演化的最小完整样本。
同一个"加权平均"功能,三种接口风格摆在一起看,取舍会具体很多。第一种无校验,调用方全责;第二种 nargin 默认值,日常够用;第三种 arguments 块,校验与默认值一体化,R2019b 起的推荐写法:
function y = wavg3(x, w) % 第三种:arguments 块,参数即契约 arguments x (:,:) double w (1,:) double {mustBeEqualSize(x, size(w))} end y = sum(x .* w, 2) ./ sum(w); end
function y = wavg1(x, w) % 第一种:裸接口,两行完事 y = sum(x .* w) / sum(w); end
| 风格 | 行数 | 错误提示质量 | 版本要求 | 适用 |
|---|---|---|---|---|
| 裸接口 | 最少 | 报错在计算深处,难懂 | 无 | 私人快速工具 |
| nargin 默认值 | 中 | 自己写的 error,清楚 | 无 | 团队内部库 |
| arguments 块 | 稍多 | 进函数前就拦截,最清楚 | R2019b | 对外发布的库 |
我的倾向是对外接口一律上 arguments:多写五行,换来的是调用方传错参数时在门口就收到人话报错,而不是在第三层内部调用里炸出一串栈。nargin 写法在老代码里仍将长期存在,读懂它的能力不能丢。
函数相关的报错集中在三处。一是"未定义函数或变量"但函数文件明明在——九成是文件名与函数名不一致,或者文件不在当前文件夹和搜索路径上;which statFeat 能直接告诉你 Matlab 到底解析到了哪个同名函数,排查同名冲突时是第一命令。二是输出数量不匹配:函数头声明了两个输出,调用时一个变量接不住会报错,用 nargout 在函数内适配可变输出数量是正统解法。三是匿名函数与句柄的混淆:f = @(x) x^2 定义的句柄捕获的是定义时刻的工作区快照,之后在外面改同名变量并不影响它——这个"快照语义"与直觉相悖,闭包场景下要特意留意。
varargin 处理不定长输入,聚合统计类函数常需要它:
function s = sumAll(varargin) s = 0; for k = 1:numel(varargin) s = s + sum(varargin{k}); end end % sumAll([1 2], 5, rand(2)) 输出 12.70 左右(rand 部分随种子变化)
递归在 Matlab 里可用但要克制:默认递归上限五百层,深递归改写成循环几乎总是更快;真正适合递归的是分形绘制、树结构遍历这类天然分形的结构,此时记得给终止条件留足余量,别卡在上限附近。
arguments 块是现代替代;help 直接可读;