MATLAB M文件与函数

0 阅读14分钟

📁 MATLAB M文件与函数

M 文件是 MATLAB 编程的核心载体,掌握其创建、函数定义与调试技巧,是精通 MATLAB 的关键一步。


📁 M文件基础

到目前为止,我们已经使用 MATLAB 环境作为计算器。但是,MATLAB 还是一种强大的编程语言,也是一种交互式计算环境。在前面的章节中,您学习了如何从 MATLAB 命令提示符下输入命令。MATLAB 还允许您将一系列命令写入文件并以完整的单元形式执行文件,例如编写函数和调用它。

✏️ 使用 edit 命令编辑 m 文件

编辑 m 文件 forexp.m

edit forexp.m

如果 m 文件不存在,MATLAB 会提示用户是否要创建一个新文件。如果用户选择创建,MATLAB 会自动创建一个新文件。如果 m 文件存在,会直接打开该文件。

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 编辑器的"路径管理器"按钮,即可打开路径管理器窗口。

matalb_path

⚠️ 警告如果 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

有时函数需要处理不定数量的输入或输出参数,此时可以使用 vararginvarargout

  • 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 表示函数实际请求的输出参数个数,二者常与 vararginvarargout 配合使用。

🎛️ 函数默认参数

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 编辑器提供了图形化的断点调试功能,是最直观的调试方式。

设置断点

在编辑器窗口中,点击代码行号左侧的灰色横线区域,即可在该行设置一个红色圆断点。

matalb_debug1

当程序运行到断点处时会暂停执行,此时可以查看和修改当前工作区的变量值。

matalb_debug2

调试工具栏

设置断点后,运行 M 文件,程序会在断点处暂停。此时编辑器上方会出现调试工具栏,常用的按钮包括:

matalb_debug3

按钮说明
继续继续运行程序,直到下一个断点或程序结束
步进逐行执行代码,遇到函数调用时会进入函数内部
步入进入当前行调用的函数内部
步出跳出当前函数,返回到调用它的上一层
停止结束调试会话
查看变量值

程序在断点处暂停时,可以将鼠标悬停在变量名上查看其当前值:

matalb_debug4

也可以在"工作区"窗口中查看所有变量的值:

matalb_debug5

还可以在命令行窗口输入变量名来查看其值:

matalb_debug6

🛡️ 使用 try-catch 捕获错误

对于可能出错的代码,可以使用 try-catch 结构捕获错误并给出友好提示,避免程序直接崩溃:

try
    % 可能出错的代码
    result = 1 / x;
catch ME
    % 出错时执行的代码
    disp("发生错误:" + ME.message)
end

其中 ME 是错误对象,ME.message 保存了错误的具体信息。这样即使发生错误,程序也能继续执行后续代码。

💻 运行结果

forexp
发生错误:函数或变量 'x' 无法识别

📢 使用 warning 和 error 提示

在编写函数时,可以使用 warningerror 主动提示用户:

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
  • 变量名通常使用小写字母开头,后续单词首字母大写(驼峰命名法),例如 maxValuenumStudents
  • 常量通常使用全大写字母,例如 PIMAX_ITER
  • 避免使用 MATLAB 内置函数名或关键字作为变量名,例如不要用 summeanif 作为变量名。
% 好的命名
totalCost = 100;
maxIterations = 50;

% 不好的命名
tc = 100;
mi = 50;
函数命名
  • 函数名应与文件名保持一致,且通常使用小写字母开头。
  • 函数名应能反映函数的功能,例如 calculateAverageplotData

💬 代码注释

注释是代码的重要组成部分,良好的注释能帮助他人(以及未来的自己)理解代码逻辑。

注释规范
  • 在关键代码段前添加注释,说明该段代码的功能。
  • 使用 % 进行单行注释,使用 %{ ... %} 进行块注释。
  • 函数文件开头应包含注释,说明函数的功能、输入参数和返回值。
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

🚀 代码性能优化

当程序运行效率成为瓶颈时,可以从以下几个层面进行优化:

优化手段说明
预分配内存使用 zerosNaN 预先分配数组大小,避免循环中动态扩容
向量化使用矩阵运算代替循环,充分利用 MATLAB 的矩阵计算优势
逻辑索引使用 data(data > 5) 代替 find + 循环
减少重复计算将循环内不变的计算提取到循环外
使用 tic/toc测量代码运行时间,评估优化效果
避免 evaleval 会降低运行速度且难以调试,应尽量使用函数句柄代替

预分配内存示例:

% 不好的做法:循环中动态扩容
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 秒。

📝 编程风格总结

规范说明
命名清晰使用有意义的变量名和函数名
注释充分在关键位置添加注释,说明代码意图
布局规范保持一致的缩进和空格,提高可读性
控制输出使用分号抑制不必要的输出
向量化尽量使用矩阵运算代替循环
善用帮助熟练使用 helpdoclookfor 命令

作者:majorArcs

原文链接:k-hengzhou.github.io/posts/MATLA…