2.2 函数Functions:接口与复用的进化


2.2 函数 Functions:接口与复用的进化

本节摘要:函数为计算提供了命名的接口与隔离的工作区,是 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 里可用但要克制:默认递归上限五百层,深递归改写成循环几乎总是更快;真正适合递归的是分形绘制、树结构遍历这类天然分形的结构,此时记得给终止条件留足余量,别卡在上限附近。

本节要点回顾

  • 函数 = 命名接口 + 隔离工作区,值传递、状态不外泄;
  • 多输出 + nargin 默认值是经典接口配方,arguments 块是现代替代;
  • 子函数藏辅助逻辑,单文件即小模块;
  • 函数头注释即文档help 直接可读;
  • 重构方向:脚本渐薄、函数渐厚。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U