📁 MATLAB M文件与函数
M 文件是 MATLAB 编程的核心载体,掌握其创建、函数定义与调试技巧,是精通 MATLAB 的关键一步。
📁 M文件基础
到目前为止,我们已经使用 MATLAB 环境作为计算器。但是,MATLAB 还是一种强大的编程语言,也是一种交互式计算环境。在前面的章节中,您学习了如何从 MATLAB 命令提示符下输入命令。MATLAB 还允许您将一系列命令写入文件并以完整的单元形式执行文件,例如编写函数和调用它。
✏️ 使用 edit 命令编辑 m 文件
编辑 m 文件 forexp.m:
edit forexp.m
如果 m 文件不存在,MATLAB 会提示用户是否要创建一个新文件。如果用户选择创建,MATLAB 会自动创建一个新文件。如果 m 文件存在,会直接打开该文件。
将下列代码复制到 MATLAB 打开的 m 文件编译窗口中,并保存文件。
for i = 1:1:10
if i == 5
continue
end
disp("i = " + num2str(i))
end
回到 MATLAB 命令提示符下,输入 forexp 来调用:
>> forexp
💻 运行结果
i = 2 i = 3 i = 4 i = 6 i = 7 i = 8 i = 9 i = 10
📜 脚本文件与函数文件
M 文件分为两种类型:脚本文件(Script) 和 函数文件(Function)。理解两者的区别是 MATLAB 编程的基础。
| 类型 | 是否有输入输出 | 工作区 | 文件开头 | 典型用途 |
|---|---|---|---|---|
| 脚本文件 | 无输入输出 | 共享基础工作区 | 直接写命令 | 执行一系列操作、数据处理流程 |
| 函数文件 | 有输入输出 | 独立工作区 | function 关键字 | 封装可复用的计算逻辑 |
% 脚本文件 scriptDemo.m —— 直接写命令,无 function 关键字
x = 1:10;
y = x.^2;
plot(x, y)
% 函数文件 funcDemo.m —— 以 function 关键字开头
function y = funcDemo(x)
y = x.^2;
end
💡 提示:脚本文件共享基础工作区,脚本中创建的变量在脚本运行后仍然存在;而函数文件拥有独立的工作区,函数内部的变量不会污染基础工作区。
▶️ M 文件的执行方式
M 文件有多种执行方式,可以根据需要选择:
| 执行方式 | 说明 |
|---|---|
| 直接输入文件名 | 在命令窗口输入文件名(不带 .m 后缀)即可运行 |
run 命令 | 使用 run('文件名.m') 运行脚本文件 |
| 编辑器运行 | 在编辑器中点击"运行"按钮或按 F5 快捷键 |
eval 命令 | 使用 eval('文件名') 动态执行 |
>> forexp % 直接输入文件名运行
>> run('forexp.m') % 使用 run 命令运行
🗂️ 文件路径与搜索路径
MATLAB 通过搜索路径(Search Path) 来查找函数和脚本文件。当你在命令窗口输入一个函数名时,MATLAB 会按照搜索路径的顺序查找该文件。
| 命令 | 功能 |
|---|---|
path | 显示当前搜索路径 |
addpath | 添加文件夹到搜索路径,例如 addpath('D:\myfolder') |
rmpath | 从搜索路径中移除文件夹 |
which | 显示函数或文件的完整路径,例如 which forexp |
cd | 更改当前工作目录 |
pwd | 显示当前工作目录 |
>> path % 查看搜索路径
>> addpath('D:\myfolder') % 添加文件夹到搜索路径
>> which forexp % 查看 forexp 的完整路径
>> pwd % 查看当前工作目录
也可以使用可视化界面来管理文件路径。点击 MATLAB 编辑器的"路径管理器"按钮,即可打开路径管理器窗口。
⚠️ 警告:如果 M 文件不在当前工作目录或搜索路径中,MATLAB 将无法找到该文件并报错。建议将常用的自定义函数文件夹添加到搜索路径中。
💡 M 文件的注释与帮助
在函数文件开头使用 % 注释,可以生成函数的帮助文档。使用 help 命令可以查看这些注释。
function y = mySquare(x)
% mySquare 计算输入值的平方
% 输入:
% x - 数值或数组
% 输出:
% y - x 的平方
y = x.^2;
end
>> help mySquare
💻 运行结果
>> help mySquare mySquare 计算输入值的平方 输入: x - 数值或数组 输出: y - x 的平方
💡 提示:在函数文件开头紧跟在
function声明之后的注释行,会被 MATLAB 自动识别为帮助文本,可通过help命令查看。
🔧 函数
📝 函数定义
函数是一起执行任务的一组语句。在 MATLAB 中,函数是在单独的文件中定义的。文件名和函数名应该相同。
函数语句的语法是:
function [out1, out2, ..., outN] = myfun(in1, in2, in3, ..., inN)
🧩 示例函数
编辑 m 文件 a2b3.m:
edit a2b3.m
将下列代码复制到 a2b3.m 文件中,并保存文件。
function [c, d] = a2b3(a, b)
c = a * 2;
d = b * 3;
end
回到 MATLAB 命令提示符下,输入 a2b3 来调用:
>> [c, d] = a2b3(2, 3)
💻 运行结果
c = 4 d = 9
🔄 函数的参数与返回值
- 函数的参数:函数调用时传递给函数的值。参数可以是标量、向量、矩阵等。
- 函数的返回值:函数执行完毕时返回的值。返回值可以是标量、向量、矩阵。当函数返回多个值时,这些值会被赋值给多个变量。
% 获取所有的函数返回值:
>> [c, d] = a2b3(2, 3)
💻 运行结果
c = 4 d = 9
% 只获取第一个返回值,其他返回值会被忽略:
>> c = a2b3(2, 3)
💻 运行结果
c = 4
% 只获取第二个返回值,其他返回值会被忽略:
>> [~, d] = a2b3(2, 3)
💻 运行结果
d = 9
⚡ 匿名函数
匿名函数就像传统编程语言中的内联函数一样,是在单个 MATLAB 语句中定义的。
>> power = @(x, n) x.^n;
>> result1 = power(7, 3)
>> result2 = power(49, 0.5)
>> result3 = power(10, -10)
>> result4 = power(4.5, 1.5)
💻 运行结果
result1 = 343 result2 = 7 result3 = 1.0000e-10 result4 = 9.5459
🏗️ 主和子函数
除匿名函数外,所有函数都必须在文件中定义。每个函数文件包含一个必需的主函数(必须首先出现且与 m 文件同名的函数),以及任意数量的可选子函数(位于主函数之后,供主函数调用)。
文件 a4b5.m 的函数定义如下:
function [c, d] = a4b5(a, b)
c = a4(a);
d = b5(b);
end
function c = a4(a)
c = a * 4;
end
function d = b5(b)
d = b * 5;
end
调用函数:
[c, d] = a4b5(2, 3) % 主函数与m文件同名,可以在外部直接调用
% c = a4(2) % 错误:子函数不能在外部直接调用
% d = b5(3) % 错误:子函数不能在外部直接调用
💻 运行结果
c = 8 d = 15
⚠️ 警告:在一个文件中定义多个函数,一定要注意只有主函数(与 m 文件同名的函数)可以在外部直接调用,其他子函数只能在主函数中调用。
🔢 可变参数 varargin 与 varargout
有时函数需要处理不定数量的输入或输出参数,此时可以使用 varargin 和 varargout:
varargin:可变数量的输入参数(Variable-length input argument list),以元胞数组的形式保存所有额外传入的参数。varargout:可变数量的输出参数(Variable-length output argument list),同样以元胞数组的形式保存。
function result = sumAll(varargin)
% sumAll 计算任意数量输入参数的总和
result = 0;
for i = 1:nargin
result = result + varargin{i};
end
end
调用:
>> sumAll(1, 2, 3)
💻 运行结果
ans = 6
>> sumAll(1, 2, 3, 4, 5)
💻 运行结果
ans = 15
varargout 示例:
function varargout = minMax(x)
% minMax 返回向量中的最小值和最大值
varargout{1} = min(x);
varargout{2} = max(x);
end
调用:
>> [mn, mx] = minMax([3 1 4 1 5])
💻 运行结果
mn = 1 mx = 5
💡 提示:
nargin表示函数实际接收的输入参数个数,nargout表示函数实际请求的输出参数个数,二者常与varargin、varargout配合使用。
🎛️ 函数默认参数
MATLAB 本身不提供像其他语言那样直接的默认参数语法,但可以通过以下方式实现:
1. 使用 nargin 判断(最基础)
function greet(name, greeting)
if nargin < 2
greeting = '你好';
end
if nargin < 1
name = '世界';
end
disp([greeting ',' name '!'])
end
2. 使用 arguments 块(R2019b 起,推荐)
function greet(name, greeting)
arguments
name (1, 1) string = "世界"
greeting (1, 1) string = "你好"
end
disp(greeting + "," + name + "!")
end
调用:
>> greet() % 全部使用默认参数
💻 运行结果
你好,世界!
>> greet("小明") % 只传第一个参数
💻 运行结果
你好,小明!
>> greet("小明", "晚上好") % 传全部参数
💻 运行结果
晚上好,小明!
🐞 M文件调试
调试是程序开发过程中非常重要的一环。当 M 文件运行出错或结果不符合预期时,我们需要借助 MATLAB 提供的调试工具来定位并修复问题。MATLAB 提供了多种调试方式,包括命令行调试命令、编辑器断点调试以及错误信息分析。
⚠️ 常见的错误类型
在调试之前,先了解 MATLAB 中常见的错误类型,有助于快速定位问题:
| 错误类型 | 说明 | 发现时机 |
|---|---|---|
| 语法错误 | 代码不符合 MATLAB 语法规则,例如缺少 end、括号不匹配、拼写错误等 | 运行前 |
| 运行时错误 | 代码语法正确,但在运行时出现问题,例如数组索引越界、除数为零、变量未定义 | 运行时 |
| 逻辑错误 | 代码可以正常运行,但计算结果不符合预期 | 运行后 |
💡 提示:逻辑错误最难发现,通常需要借助断点逐步检查。
🎯 使用编辑器断点调试
MATLAB 编辑器提供了图形化的断点调试功能,是最直观的调试方式。
设置断点
在编辑器窗口中,点击代码行号左侧的灰色横线区域,即可在该行设置一个红色圆断点。
当程序运行到断点处时会暂停执行,此时可以查看和修改当前工作区的变量值。
调试工具栏
设置断点后,运行 M 文件,程序会在断点处暂停。此时编辑器上方会出现调试工具栏,常用的按钮包括:
| 按钮 | 说明 |
|---|---|
| 继续 | 继续运行程序,直到下一个断点或程序结束 |
| 步进 | 逐行执行代码,遇到函数调用时会进入函数内部 |
| 步入 | 进入当前行调用的函数内部 |
| 步出 | 跳出当前函数,返回到调用它的上一层 |
| 停止 | 结束调试会话 |
查看变量值
程序在断点处暂停时,可以将鼠标悬停在变量名上查看其当前值:
也可以在"工作区"窗口中查看所有变量的值:
还可以在命令行窗口输入变量名来查看其值:
🛡️ 使用 try-catch 捕获错误
对于可能出错的代码,可以使用 try-catch 结构捕获错误并给出友好提示,避免程序直接崩溃:
try
% 可能出错的代码
result = 1 / x;
catch ME
% 出错时执行的代码
disp("发生错误:" + ME.message)
end
其中 ME 是错误对象,ME.message 保存了错误的具体信息。这样即使发生错误,程序也能继续执行后续代码。
💻 运行结果
forexp 发生错误:函数或变量 'x' 无法识别
📢 使用 warning 和 error 提示
在编写函数时,可以使用 warning 和 error 主动提示用户:
function result = safeDivide(a, b)
if b == 0
error("除数不能为零!");
end
if abs(b) < 1e-6
warning("除数接近零,结果可能不准确。");
end
result = a / b;
end
| 函数 | 说明 |
|---|---|
error | 会抛出错误并终止程序执行 |
warning | 会显示警告信息,但程序会继续执行 |
💻 运行结果
>> safeDivide(1, 0) 错误使用 safeDivide (第 3 行)除数不能为零! >> safeDivide(1, 0.000000001) 警告: 除数接近零,结果可能不准确。 位置:forexp (第 6 行) ans = 1000000000
📝 调试技巧总结
| 技巧 | 说明 |
|---|---|
| 分步验证 | 将复杂问题拆分成小步骤,逐步验证每一步的结果 |
| 善用断点 | 在关键计算处设置断点,检查中间变量的值是否符合预期 |
| 检查工作区 | 程序暂停时,仔细检查工作区中所有变量的值 |
使用 disp 输出 | 在关键位置添加 disp 语句输出中间结果,帮助定位逻辑错误 |
| 阅读错误信息 | MATLAB 的错误信息通常会指出出错的文件、行号和具体原因 |
✨ MATLAB编程风格
良好的编程风格不仅能提高代码的可读性,还能减少出错的可能性,方便他人理解和维护你的代码。下面介绍一些 MATLAB 编程中常用的规范和最佳实践。
🏷️ 命名规范
变量命名
- 变量名应具有描述性,能够清晰表达其含义。例如,用
totalCost而不是tc,用studentScore而不是s。 - 变量名通常使用小写字母开头,后续单词首字母大写(驼峰命名法),例如
maxValue、numStudents。 - 常量通常使用全大写字母,例如
PI、MAX_ITER。 - 避免使用 MATLAB 内置函数名或关键字作为变量名,例如不要用
sum、mean、if作为变量名。
% 好的命名
totalCost = 100;
maxIterations = 50;
% 不好的命名
tc = 100;
mi = 50;
函数命名
- 函数名应与文件名保持一致,且通常使用小写字母开头。
- 函数名应能反映函数的功能,例如
calculateAverage、plotData。
💬 代码注释
注释是代码的重要组成部分,良好的注释能帮助他人(以及未来的自己)理解代码逻辑。
注释规范
- 在关键代码段前添加注释,说明该段代码的功能。
- 使用
%进行单行注释,使用%{ ... %}进行块注释。 - 函数文件开头应包含注释,说明函数的功能、输入参数和返回值。
function [avg] = calculateAverage(data)
% calculateAverage 计算数据的平均值
% 输入:
% data - 一维数值数组
% 输出:
% avg - 数据的平均值
avg = sum(data) / length(data);
end
注释示例
% 计算圆的面积
radius = 5;
area = pi * radius^2; % 面积公式:πr²
📐 代码布局与缩进
- 使用一致的缩进(通常为4个空格)来体现代码的层级结构。
- 在运算符两侧添加空格,提高可读性,例如
a + b而不是a+b。 - 在逗号后添加空格,例如
[1, 2, 3]而不是[1,2,3]。 - 使用空行分隔逻辑上独立的代码块。
for i = 1:10
if mod(i, 2) == 0
disp(i) % 输出偶数
end
end
🔇 使用分号控制输出
- 在语句末尾添加分号
;可以抑制命令窗口中的输出,避免大量中间结果刷屏。 - 在调试时,可以暂时去掉分号来查看中间变量的值。
x = 1:100; % 不显示结果
y = x.^2; % 不显示结果
z = sum(y) % 显示结果
⚡ 向量化编程
MATLAB 擅长矩阵运算,应尽量使用向量化操作代替循环,以提高代码运行效率。
% 不好的做法:使用循环
y = zeros(1, 100);
for i = 1:100
y(i) = i^2;
end
% 好的做法:向量化
x = 1:100;
y = x.^2;
📚 使用帮助文档
- 使用
help命令查看函数的使用说明,例如help sum。 - 使用
doc命令打开函数的详细文档,例如doc plot。 - 使用
lookfor命令根据关键词搜索相关函数,例如lookfor average。
>> help sum
>> doc plot
>> lookfor average
🚀 代码性能优化
当程序运行效率成为瓶颈时,可以从以下几个层面进行优化:
| 优化手段 | 说明 |
|---|---|
| 预分配内存 | 使用 zeros 或 NaN 预先分配数组大小,避免循环中动态扩容 |
| 向量化 | 使用矩阵运算代替循环,充分利用 MATLAB 的矩阵计算优势 |
| 逻辑索引 | 使用 data(data > 5) 代替 find + 循环 |
| 减少重复计算 | 将循环内不变的计算提取到循环外 |
使用 tic/toc | 测量代码运行时间,评估优化效果 |
避免 eval | eval 会降低运行速度且难以调试,应尽量使用函数句柄代替 |
预分配内存示例:
% 不好的做法:循环中动态扩容
for i = 1:10000
y(i) = i^2;
end
% 好的做法:预分配内存
y = zeros(1, 10000);
for i = 1:10000
y(i) = i^2;
end
逻辑索引示例:
data = randn(1, 1e6);
% 方式一:使用 find + 循环(较慢)
idx = find(data > 0);
positive1 = data(idx);
% 方式二:逻辑索引(更快、更简洁)
positive2 = data(data > 0);
使用 tic/toc 测量时间:
tic;
result = sum(1:1e6);
toc
💻 运行结果
经过的时间是 0.001234 秒。
📝 编程风格总结
| 规范 | 说明 |
|---|---|
| 命名清晰 | 使用有意义的变量名和函数名 |
| 注释充分 | 在关键位置添加注释,说明代码意图 |
| 布局规范 | 保持一致的缩进和空格,提高可读性 |
| 控制输出 | 使用分号抑制不必要的输出 |
| 向量化 | 尽量使用矩阵运算代替循环 |
| 善用帮助 | 熟练使用 help、doc、lookfor 命令 |
作者:majorArcs