MATLAB编程-简介
据我所知,目前几乎没有什么资料可以帮助人们理解 MATLAB 的错误信息。大多数语法错误其实不难修复,只要你知道它们的成因。因此,这篇指南的目的是帮助识别并修复 MATLAB 代码中的错误。
这里还列出了警告信息,因为它们常常会在后续导致错误。
算术错误
通常这些错误都比较直观。以下是一些无法执行的常见运算,以及 MATLAB 的返回结果(并伴有警告):
-
a / 0:若 a > 0,则返回 Inf;若 a < 0,则返回 -Inf;若 a = 0,则返回 NaN。
-
log(0):返回 -Inf。
-
MATLAB 将 0^0 定义为 1。
-
NaN 通常会导致错误或无效结果,除非你采取措施防止其传播。
错误示例:
??? Error using ==> minus
Matrix dimensions must agree.
这表示矩阵维度不一致。检查表达式中所有项的维度是否一致。通常是索引错误导致了维度不一致。如果你使用的是幂运算,可能需要在参数后加一个点(.
),比如:
y = x.^2 % 正确
y = x^2 % 错误(x 为数组时)
矩阵乘法要求第一个矩阵的列数等于第二个矩阵的行数,否则会报错:
??? Error using ==> mtimes
Inner matrix dimensions must agree.
与前一个错误不同,这个错误也可能是你本来想做元素乘法(点乘)但忘了加 .
。
对奇异矩阵求逆会得到一个警告,并返回一个包含 Inf 的矩阵。建议先计算行列式,或者更好的是使用不需求逆的数值稳定方法。
对非方阵进行幂运算会报错:
??? Error using ==> mpower
Matrix must be square.
这通常是因为你本来想用逐元素幂运算,却忘了加点 .^
。
数组索引错误
数组索引是 MATLAB 的关键特性之一。注意变量和函数名是区分大小写的,且变量可以覆盖内置函数名或用户自定义函数名。例如,如果你定义了一个数组 abs
,然后调用 abs(1)
,MATLAB 会返回数组 abs
的第一个元素,而不是绝对值函数的结果。MATLAB 不会报错,因为它无法判断你是否是故意覆盖函数名的。所以千万不要使用已有函数名作为变量名。
MATLAB 提供的函数非常多,无法全部记住,因此在定义变量或函数前,如果不确定一个名字是否已被使用,可以使用 which name
检查。新版 MATLAB 支持命令补全,在输入左括号或 Tab 时会显示简要说明,有助于避免命名冲突。
常见错误还包括:
访问未定义元素
A = [1, 3];
A(3)
错误信息:
??? Index exceeds matrix dimensions.
如果你在循环中根据索引访问数组,但循环超过了数组范围,或者某个操作结果为空数组,再访问其元素,就会出现这个错误。
使用非法索引
A(-1)
A(i)
A(1.5)
A(0)
错误信息:
??? Subscript indices must either be real positive integers or logicals.
MATLAB 是从 1 开始索引的,不支持 0 或负数。索引必须为实数正整数或逻辑值。
表达式中如果涉及非法索引,MATLAB 可能会告诉你具体哪一个非法:
y = 3 * A(-1)
错误信息:
Attempted to access A(-1); index must be a positive integer or logical.
如果你使用逻辑索引,比如 A(0)
,会被解析为逻辑下标。逻辑 0 表示 false,对应为空返回:
A(A == 3)
是合法并有意义的表达方式。
使用错误语法
A(2::, 2)
错误信息:
??? A(2::, 2)
|
Error: Unexpected MATLAB operator.
正确做法应为:
A(2:end, 2)
赋值错误
赋值是使用 =
给变量或数组元素赋值。
常见错误:
a = 2;
if a = 3
错误信息:
Error: The expression to the left of the equals sign is not a valid target for an assignment.
这是因为你想比较 a 是否等于 3,但写成了赋值。应改为:
if a == 3
end
你不能用不同数据类型混合定义普通数组。例如:
A = @(T) (1+T);
A(2) = 3;
错误信息:
??? Conversion to function_handle from double is not possible.
如果你需要混合数据类型,应使用 cell 数组或 struct 数组。
这是个容易出错的地方。来看下面这段代码:
>> A = [1,2,3;4,5,6;7,8,9];
>> A(2,:) = [3,5];
??? Subscripted assignment dimension mismatch.
>> A(2,:) = [1,4,5,6];
??? Subscripted assignment dimension mismatch.
>> A(1:2, 1:2) = [1,2,3,4];
??? Subscripted assignment dimension mismatch.
这里到底发生了什么?在这三种情况中,仔细比较左右两边的维度。
-
第一个例子中,左侧是一个 1x3 的数组,而右侧是 1x2。
-
第二个例子中,左侧还是 1x3,右侧是 1x4。
-
第三个例子中,左侧是 2x2,而右侧是 1x4。
三种情况中,维度不一致。如果你想替换已有变量中的一部分,左右两侧的维度必须一致。数据点数量是否一致无关紧要(第三个例子就说明了这一点);维度必须一致。唯一的例外是,当一边是 1×n 向量,另一边是 n×1 向量时,MATLAB 会自动转置并进行替换:
>> A(2,:) = [1;2;3]
A = 1 2 3
1 2 3
7 8 9
如果你不想发生自动转置,要特别留意!
结构体数组错误(Struct Array Errors)
结构体数组较为复杂,MATLAB 对它们的操作有严格规定。先来看结构体数组的索引。
假设你定义了变量 cube
,并希望记录两个立方体的边长和体积,可以这样写:
>> cube(1).side = 1;
>> cube(1).volume = 1;
>> cube(2).side = 2;
>> cube(2).volume = 8;
这是一种常用的数据存储方式。但如果你想提取所有 volume
并存入一个数组,下面这种做法是不行的:
>> volumes = cube.volume
??? Illegal right hand side in assignment. Too many elements.
你会注意到,若你直接输入 cube.volume
,MATLAB 会显示所有值,但会分别赋值给 ans
,因为它被视为多个独立值。要避免这个错误,你必须在赋值时把 cube.volume
包装成数组:
>> volumes = {cube.volume}
当然你也可以为每个元素单独赋值,但这种写法更适合处理大量结构体。
就像提取数据一样,向结构体中添加字段时,即便内容相同,也需要逐个添加。例如:
>> cube.volForm = @(S) (S^3)
??? Incorrect number of right hand side elements in dot name assignment. Missing [] around left hand side is a likely cause.
>> cube(:).volForm = @(S) (S^3)
??? Insufficient outputs from right hand side to satisfy comma separated list expansion on left hand side. Missing [] are the most likely cause.
实际上错误并不是因为缺少 []
,而是因为你不能一次性将相同值赋给所有结构体字段,必须逐个赋值:
>> for ii = 1:2
>> cube(ii).volForm = @(S) (S^3);
>> end
结果:
>> cube
ans = 1x2 struct array with fields:
volume
side
volForm
此时两个结构体中都包含同一个体积计算公式。为了避免这种必须用循环的情况,建议不要拆分结构体根字段。比如可以这样写:
>> shapes.cubeVol = @(S) (S^3);
>> shapes.cube(1).vol = 1;
>> shapes.cube(2).vol = 8;
这样就不需要为每个子结构体单独设置公式。
语法错误(Syntax Errors)
括号错误(Parenthesis Errors)
与 C++ 不同,MATLAB 中不需要以特殊符号结束每一行,只要有换行即可。但你仍需遵循语法规则,尤其是括号的位置要特别注意。
下面是一个常见错误:
>> A(1
??? A(1
|
Error: Expression or statement is incorrect--possibly unbalanced (, {, or [.
意思是你缺少一个括号或多加了一个。
另一个类似错误是:
>> A(1))
??? A(1))
|
Error: Unbalanced or misused parentheses or brackets.
MATLAB 会试图指出缺失括号的位置,但未必准确。复杂表达式中你需要仔细检查。一个实用技巧是:在出错行之后设置断点,直到那行断点变红说明语法修正正确了。当然,修正后还要确保括号使用逻辑正确,否则可能又会报索引或函数调用相关的错误。
字符串错误(String Errors)
你可以通过两种方式创建字符串:使用 '字符串'
的语法,或者输入仅用空格分隔的多个词(不包括换行),例如:
>> save file.txt variable
在这行代码中,file.txt
和 variable
被作为字符串传给 save
函数。但有时你可能会忘记括号,导致错误地将字符串传给一个不接受字符串的函数:
>> eye 5
??? Error using ==> eye
Only input must be numeric or a valid numeric class name.
这类错误通常很容易发现,因为字符串在 MATLAB 中是紫色高亮的。这种问题经常发生在你取消注释一行文本后却忘记修改它。
如果在字符串另一种语法中忘记闭合引号 '
,会产生明显的错误:
>> A = 'hi
??? A = 'hi
|
Error: A MATLAB string constant is not terminated properly.
未闭合的字符串会被标记为红色,提示你字符串未被正确结束,否则很容易忽略这个错误。
一个常见的字符串错误是试图使用 ==
来比较字符串。如果两个字符串长度不同,这将不起作用,因为字符串在 MATLAB 中是字符数组,使用 ==
比较数组时要求尺寸一致。正确的方式是使用 strcmp
函数:
>> 'AA' == 'AaA'
??? Error using ==> eq
Matrix dimensions must agree.
>> strcmp('AA', 'AaA')
ans = 0
>> strcmp('A', 'a')
ans = 0
>> strcmp('AA', 'AA')
ans = 1
注意:MATLAB 字符串是区分大小写的,'A' 和 'a' 被视为不同字符串。
还需注意的是,字符串的起止符 '
与转置操作符是同一个字符。如果你关闭了一个字符串却没开启新字符串,MATLAB 可能会认为你在进行转置操作,如果变量未定义就会报错,或者导致其他不可预期的行为。
其他杂项错误
你不能在语句结尾留下未完成的函数或表达式,例如:
>> A = 1+3+
??? A = 1+3+
|
Error: Expression or statement is incomplete or incorrect.
这种错误通常很容易发现,往往是因为忘了添加续行符 ...
。
::
并不是唯一的“意外 MATLAB 运算符”,还有 ..
、....
等打字错误也会导致此类错误。
如果不小心打了反引号 `
会报错:
>> `
Error: The input character is not valid in MATLAB statements or expressions.
这通常是因为你想打数字 1 却按错了键。还有一种情况是你给 .m
文件取了计算机不支持的名字,比如德语的 "ä, ü, ö"。请使用常规字母命名文件,并避免使用大写字母。
函数调用错误
你可能会试图调用不存在的函数,比如:
>> samplemat = [1 2; 1 4];
>> A = eigen(samplemat);
??? Undefined command/function 'eigen'.
这可能是因为你不知道该功能的正确函数名。例如,如果你想计算矩阵的特征值,应使用 eig
而不是 eigen
。此时使用 MATLAB 帮助文档(菜单:Help → Product Help 或命令行输入 doc
)来搜索你想要的功能非常有用。
如果你调用的是你自己写的函数而出错,可能有以下原因:
-
.m
文件不在当前目录或 MATLAB 路径下(检查 File → Set Path); -
.m
文件名必须与函数定义中的名字一致。如果你改了函数名,也必须改文件名,否则 MATLAB 无法正确找到该函数。
MATLAB 找到函数后会尝试运行,但调用函数时还可能遇到以下问题:
-
必须清楚函数需要的输入输出参数数量和类型。内置函数的说明可以通过如下方式查看:
>> help functionname
你也可以为自己写的函数添加注释,让 help
命令能读取它们。注意:help
只会读取函数定义下方的连续注释块:
function outvars = myfunc(invars)
% function outvars = myfunc(invars)
% Outputs outvars
% All of this is outputted when you type >> help myfunc
% 这一行不会显示在 help 中
保存为 myfunc.m
后,运行:
>> help myfunc
将输出你写的注释说明。
大多数函数都至少需要一个输入参数,输入不足会报错:
>> A = ode45()
??? Error using ==> ode45
Not enough input arguments. See ODE45.
输入过多也会报错:
>> A = plus(1,2,3)
??? Error using ==> plus
Too many input arguments.
输入参数格式必须符合函数预期。比如 ode45
和其他微分方程求解器的第一个参数必须是函数句柄,顺序错误也会报错。
函数输出的变量数量也要注意:
>> A = [1,2;3,4];
>> D = eig(A); % 一个输出
>> [V,D] = eig(A); % 两个输出
>> [V,D,Mistake] = eig(A);
??? Error using ==> eig
Too many output arguments.
输出变量数量不能超过函数定义的最大返回数。如果你用这些结果去替换已有数组中的部分元素,还需要注意输出变量的类型是否匹配(详见赋值相关内容)。
控制流错误
最常见的控制流错误是漏写 end
。在 M 文件函数中尤其常见,MATLAB 会提示你“至少缺少一个 END”,并试图指出缺失位置。
如果你写了太多的 end
,并且一个 M 文件中有多个函数,MATLAB 会报一些莫名其妙的格式错误。这是因为一个 M 文件中的所有函数要么都用 end
结尾,要么都不用。如果某个函数多写了一个 end
,MATLAB 会误以为函数提前结束,从而在下一个函数处找不到正确结尾,导致混乱。
如果你在发布(比如生成 HTML 文件)时遇到格式混乱的提示,可能是代码缩进层级不一致。可尝试全选代码后按 Ctrl+I 自动缩进修复。
在 switch
语句中多写了一个 end
,MATLAB 会报“非法使用 case”,因为它以为 switch
已经结束,而 case
在外部是无效的。
其他错误
还有很多不被编译器立即识别的错误,比如调用错误的函数、使用错误的变量、操作错误、进入死循环等。这些错误最难发现,但可以使用 MATLAB 的调试器(Debugger)来排查。参见调试 M 文件(Debugging M Files)部分。
错误预判与规划
无论代码多么严谨,总会有出错的可能。调试技巧非常有用,而在某些情况下预判可能的错误也非常重要。例如:
if x < 5
% 做某件事
elseif x > 5
% 做另一件事
end
在循环中加入 if mod(ii, 5) == 0
之类的条件,只在特定迭代中执行某些操作,也有助于提前发现潜在错误。
有些错误直到循环较晚阶段才出现,例如 x
比 y
少一个元素,导致 x .* y
报错。这种错误通常发生在最后一个元素,很难察觉。可以使用 try-catch
结构来处理:
try
% 执行代码
catch me
disp(me.getReport)
end
如果错误不是致命的,代码甚至可以继续执行,并通过信息提示或转换为警告。
有用的 MATLAB 工具/函数:
-
warning
:生成警告; -
lastwarn
:查看最近警告; -
disp
:打印信息; -
try-catch
:异常处理; -
dbstack
:查看调用栈; -
rethrow
、throwAsCaller
:重新抛出异常; -
MATLAB 帮助文档可查看每个函数的使用方式与优劣。