MATLAB 学习笔记——1. 基础
基本语法
注释:MATLAB 使用百分号%进行单行注释,使用%{...%}进行多行注释(不常见),使用%%开头的内容不仅是注释,而且会被视作节标题。(在字符数组中%仍然被用于占位符)
分号:; MATLAB 并不需要强制使用分号作为语句的结尾,以空格或回车结尾即可。不使用分号结尾时会把计算结果显示出来,使用分号结尾则会抑制计算结果的显示。
续行符:在第一行的结尾使用...可以把命令接续到第二行。(如果直接把...接在数字后面会报错,可以加空格)
命令补全:Tab 键。
对命令行窗口清屏:clc
MATLAB 几乎所有数据都是矩阵:行向量是行数为 1 的矩阵,数是行数列数均为 1 的矩阵,甚至一个无效命令,它的返回值[]都是一个 0 行 0 列的空矩阵。
和其他现代编程语言类似:
- MATLAB 对大小写敏感,这与它所参考的 FORTRAN 不同。
- MATLAB 对表达式中的空格不敏感,表达式中的空格通常被忽略。(只有bash这些异类才会对空格敏感)
和大部分现代编程语言不同:
- MATLAB 的索引都从 1 开始,而不是更常见的从 0 开始;
- 在 MATLAB 中,不等号是
~=,而不是 C 语言风格的!=; - MATLAB 的矩阵在内存中采用 FORTRAN 风格的列主序,而不是 C 语言风格的行主序。(这会影响很多矩阵操作的默认行为,以及数据读写的效率问题)
- MATLAB 的范围操作通常是闭区间,而其它语言普遍采用的是左闭右开区间。
- MATLAB 不支持连续比较的语法糖,
a < b && b < c不可以简写为a < b < c。
在MATLAB控制台中数据显示格式可以使用format命令修改,这个命令的效果是持续性的,但只是影响呈现效果,不会影响数据的储存形式和计算精度。
format默认格式(恢复为默认格式)format short5 字长定点数,显示 5 位(scaled fixed point format with 5 digits)format long15 字长定点数,显示 15 位双精度,7 位单精度(scaled fixed point)format short e5 字长浮点数format long e15 字长浮点数format rat对小数使用近似的有理数表示
变量
MATLAB 的变量名允许使用字母,数字和下划线组成,只允许字母开头。变量名严格区分大小写,长度不超过 namelengthmax=63 个字母。
和Python类似,MATLAB 也属于动态类型的语言,变量是无类型的(相对于指针的效果),必须先对变量进行赋值(数组或矩阵等),然后才能使用。
与 Python 不同的是,MATLAB不需要关注深拷贝和浅拷贝的细节,
MATLAB 在函数传参等情况下默认采用传值方式,但是在底层处理时普遍采用了懒拷贝(Lazy Copy)的优化:如果没有进行修改,那么总是对原件的引用;如果产生了修改,那么就会对原件进行拷贝,在拷贝的基础上进行修改。
实际上在面向对象的部分,我们还是需要关注深拷贝和浅拷贝的机制,区别体现在自定义类型是否继承自内置的
handle基类。
特殊变量:
ans没有指定变量接收时,上次的计算结果会默认保存在这个变量里;eps浮点数误差;inf,Inf无穷大,NaN,nan非法值;pi圆周率;i,j虚数单位,两者相等。
特别注意:i 和 j 默认是虚数单位,但是它们非常可能在代码中被作为循环变量赋值使用,MATLAB 允许这样的用法,但是此时它们的值就不再是虚数单位了,如果需要使用虚数,可以重新赋值,例如
1 | i = complex(0, 1); |
更可靠的做法是使用 1i,1j 这种安全的虚数字面量形式。(在涉及复数操作时,不要直接假定 MATLAB 的 i 和 j 仍然是虚数单位)
在命令行中:
- 执行
x=1会把结果赋值给x变量,返回
1 | x= |
- 执行
1*2会把结果赋值给ans变量,返回
1 | ans= |
当前定义和使用的所有变量会处于当前的工作区中:
- 查询变量:
- 使用
who命令,可以查询 MATLAB 当前工作区使用的所有变量名称; - 使用
whos会显示更详细的结果,包括所有变量的内存尺寸;(也可以单独在工作区窗口查看)
- 使用
1 | >> who |
- 删除变量:
- 使用
clear x可以删除x变量; - 使用
clear可以删除所有变量;
- 使用
- 变量加载与保存:
- 使用
save myfile命令可以把当前工作区的所有变量保存到文件myfile.mat; - 使用
load myfile命令可以把myfile.mat里面的所有变量重新加载到当前的工作区中。
- 使用
MATLAB提供了全局变量,可以使用global x来声明一个全局变量(声明不是定义,仍然需要单独定义),在任何的函数作用域中只需要再次声明这个全局变量,就可以使用它,例如
1 | % 主脚本 |
不建议使用全局变量,尤其是自定义类型作为全局变量,可能有很多意外的行为。
MATLAB 提供了与 shell 类似的函数查找命令 which,可以用来查找函数或文件的位置,查找的逻辑与下文中的搜索路径有关,使用例如
1 | which item |
MATLAB 会返回对应名称文件的完整位置,例如
1 | % which doc |
MATLAB 的一些基本命令并不是通过 M 文件实现的,而是直接内置实现的,例如
1 | % which svd |
找到的这个文件并不会包括具体实现,只是一段注释
1 | %SVD Singular value decomposition. |
which 默认只会输出最先找到的一个匹配项,加上 -all 选项则会展示所有的匹配项,例如
1 | which fopen -all |
输出例如
1 | built-in (path\to\MATLAB\R2023b\toolbox\matlab\iofun\fopen) |
MATLAB 会根据搜索路径的顺序查找文件。实际执行时,只会调用第一个匹配到的文件,而排在后面的同名文件则会被忽略(即被遮蔽)。
数据类型
浮点数与整数
在MATLAB中,数和矩阵的元素等默认都是double浮点数,虽然MATLAB也支持int64等数据类型,但是通常是不需要使用的!!!(除非考虑与C语言等的接口对接等情况,此时才需要特别关注数值类型)
1 | x = 1; % double |
因为针对int类型的所有运算仍然会得到int,并不会自动转换为double,这很可能会导致程序bug
1 | a = int32(2); |
在通常的使用中直接用字面量2或2.0即可,常见的操作包括索引等也不需要专门使用int类型,即使有取整的需求,使用floor()也是足够的,并且返回值仍然是double类型
1 | a = floor(1.2) |
字符与字符串
MATLAB 支持字符和字符数组,全部使用单引号 '' 包裹。用下面的语句可以创建和修改字符数组:
1 | % 创建字符 |
注意,这里由字符组成的字符数组会默认显示为连续的形式,而不是类似于矩阵的形式
1 | hello, MATLAB! is great! |
在较新的版本中,MATLAB 额外提供了字符串类型,需要使用双引号 "" 包裹。用下面的语句可以创建和修改字符串和字符串数组,与字符数组不同,字符串数组的使用则更像是字符串的“矩阵”
1 | % 创建字符串 |
字符、字符数组与字符串分别采用了单引号和双引号,它们也需要特殊处理引号:
- 在字符数组中,使用连续的单引号
''表示一个单引号,对于双引号不需要额外处理; - 在字符串中,对于单引号不需要额外处理,使用连续的双引号
""表示一个双引号。
虽然 MATLAB 肯定会保证大部分支持字符数组的接口也会正常支持字符串参数,但是两者毕竟不是同一个类型,混合使用可能出现一些麻烦。对于需要兼容低版本的代码,最好避免使用字符串;对于新项目,则可以主要使用字符串。
布尔类型
布尔值可以直接使用 true 和 false 关键字创建,或通过逻辑表达式生成。
1 | % 创建布尔值 |
需要注意的是,向控制台输出布尔值时会自动转换为1或0。
多个布尔值可以组成数组,在使用逻辑判断作用于矩阵时也会得到布尔数组
1 | % 创建布尔数组 |
布尔值和布尔数组都支持常见的逻辑运算:
- 逻辑与(AND):
& - 逻辑或(OR):
| - 逻辑非(NOT):
~ - 逻辑异或(XOR):
xor
它们对于布尔数组是逐个元素运算的,例如
1 | vec1 = [true, false, true]; |
容易混淆的运算是
&&和||,它们常用于条件表达式中,只支持标量间运算,并且具有短路逻辑,MATLAB不支持!作为否定。
运算符
MATLAB 的运算符对矩阵非常友好,但是和其它语言有很多差异。
算术运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
+ |
加法 | a + b |
- |
减法 | a - b |
+ |
正号 | + a |
- |
负号 | - a |
.* |
按元素乘法 | A .* B |
.\ |
按元素左除 | A .\ B |
./ |
按元素右除 | A ./ B |
.^ |
按元素幂运算 | A .^ n |
* |
乘法 (矩阵乘法) | A * B |
\ |
左除 (矩阵) | A \ B |
/ |
右除 (矩阵乘法) | A / B |
^ |
幂运算 (矩阵) | A ^ n |
逐元素运算符具有和原运算符相同的运算优先级。
部分运算符需要进行更详细的说明。
矩阵的左除 A\B 被设计用于求解如下方程组,(在 A 可逆时)相当于 inv(A) * B,
1 | A * x = B -> x = A\B |
矩阵的右除 B/A 被设计用于求解如下方程组,(在 A 可逆时)相当于 B * inv(A)
1 | x * A = B -> x = B/A |
在 A 不可逆时通常会返回最小二乘解。
/在这里作为右除,但是和通常的除法在某些表达式中可能有语法二义性,例如1/[1;2;3]会被视作右除而不是逐元素除法,这可能导致非常隐蔽的 BUG!在使用标量逐元素除以列向量时必须注意。
左除和右除具有如下转换关系('代表矩阵转置)
1 | B/A = (A'\B')' |
MATLAB 为了降低使用难度,通过这两个运算符隐藏了各种常见的方程组数值求解算法,在实际计算时涉及一套复杂的判定流程,并根据不同情况调用合适的数值算法,对于病态的方程组在求解过程中会发出警告。
垃圾 MATLAB,根本就不应该设计左除和右除的所谓语法糖。
按元素左除和按元素右除
相比于矩阵的左除和右除,按元素左除和按元素右除就直观太多:按元素右除自动对每一位元素进行对应的除法,按元素左除就是先把除数和被除数反过来。(尺寸不相同的情况下也会自动扩容)
右除例如
1 | >> 1 / [2;3;4] |
1 | >> 1 .\ [2;3;4] |
关系运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
== |
等于 | A == B |
~= |
不等于 | A ~= B |
> |
大于 | A > B |
< |
小于 | A < B |
>= |
大于等于 | A >= B |
<= |
小于等于 | A <= B |
注意:
- MATLAB 使用的不等号并不是常见的
!=,而是~=。 <在面向对象中用于表示继承关系。
逻辑运算符
| 运算符 | 说明 | 示例 | 等价函数 |
|---|---|---|---|
& |
按元素与 | A & B |
and(A, B) |
| ` | ` | 按元素或 | `A |
~ |
取反 | ~A |
not(A) |
&& |
短路与 | A && B |
|
| ` | ` | 短路或 |
注意:
and、or、not在 MATLAB 中作为函数提供,用法与 C++ 或 Python 中的关键字不同。&和|不会进行短路运算。- 短路逻辑运算
&&和||仅适用于标量逻辑运算,不适用于矩阵或向量,并且没有直接的函数替代。 ~也被用于占位符。
特殊运算符
| 运算符 | 说明 | 示例 | 等价函数 |
|---|---|---|---|
' |
共轭转置 | A' |
ctranspose(A) |
.' |
仅转置 | A.' |
transpose(A) |
: |
闭区间运算 | 1:5 -> [1 2 3 4 5] |
colon(1, 5) |
end |
末尾索引 | A(end) |
这里的转置和共轭转置也是很容易出错的地方,这也是 MATLAB 的奇葩设计之一,虽然默认共轭转置的操作对复矩阵的处理更加友好,对实值矩阵也没什么影响,大部分算法从实矩阵推广到复矩阵时,都需要把转置替换为共轭转置,
但是 .' 和 ' 这两个符号实在没有体现出转置和共轭转置的语义区别。
补充
MATLAB 没有以 &、|、~ 等形式提供位运算符,所有按位运算均需要使用专门的 bit* 函数(例如bitand(A, B)代表按位与),而且针对int等数据类型进行位运算才有意义。
1 | A = uint8([0 1; 0 1]); |
常用片段/脚本
运行前清理
下面几个清理性质的命令在主脚本的开头很常见
1 | clc; |
作用依次为:
clc: 清除命令行窗口的内容(不建议使用,会丢失上一次的输出结果)clear: 清除工作空间中的所有变量close all: 关闭所有打开的图形窗口format short e: 对浮点数的输出采用科学计数法format compact: 使命令行的输出更加紧凑,去除不必要的空行
其中 clear会清理所有的工作区变量,如果工作区中有存储重要数据的变量,建议立刻保存到 .mat 文件中,在脚本中再重新加载。补充几个可选命令:
clear all:比clear更彻底的清理(不含下面的类定义)clear classes:清理所有的类定义,确保MATLAB加载最新的类定义fclose all:关闭所有正在打开的文件
调用 clear all、clear classes 和 clear functions 会降低代码执行的性能,而且通常没有必要。建议的用法如下:
- 要从当前工作区中清除一个或多个特定变量,可以逐个指定变量,使用
clear name1 ... nameN。 - 要清除当前工作区中的所有变量,可以使用
clear或clearvars。 - 要清除所有全局变量,可以使用
clear global或clearvars –global。 - 要清除特定类,可以使用
clear myClass。 - 要清除特定函数,可以使用
clear functionName。
工作目录
MATLAB 始终是在它提供的模拟终端中运行脚本或命令,存在当前工作目录的概念,可以使用 pwd() 查看。(不仅限于 MATLAB,所有程序实际上都有当前工作目录的概念)
工作目录对 MATLAB 命令的执行非常重要,GUI 层面的操作也会与其产生关联:
- 使用 GUI 提供的按钮运行脚本,实质是在当前工作目录下执行当前打开的脚本;
- 在 GUI 的目录栏进行目录切换,会立刻影响到终端的工作目录。
直接执行脚本时,脚本中所有的相对路径都是相对于当前工作目录的,而不是相对于脚本自身所在目录。(这是几乎所有脚本语言都采取的做法)
如果通过 run 命令来执行脚本,情况就有点复杂,按照 官方文档 来解释:
- 绝大多数情况下,
run命令在执行时会先切换工作目录到脚本所在的文件夹,执行脚本,然后切换回原始工作目录。 - 脚本默认应该是在脚本所在的文件夹中执行,如果脚本(实质性地)切换了工作目录,那么在脚本执行完成之后,
run命令就不会自动切换回原始工作目录。
在 MATLAB 中可以使用 mfilename('fullpath') 可以获取当前脚本/函数文件所在的绝对路径,返回一个字符数组,如果在命令行调用则会返回空的字符数组。
在主脚本中,可以使用如下命令将当前的工作目录切换到脚本实际所在的目录
1 | cd(fileparts(mfilename('fullpath'))); |
这样在脚本中的所有路径都可以使用基于当前脚本位置的相对路径。
使用
cd仍然可能存在一些潜在的风险(尤其在并行时),更健壮的方式是获取当前脚本路径,使用基于当前路径的相对路径,但是不进行工作目录的切换。
在函数文件中,有时也需要读取数据文件,不建议使用绝对路径,此时可以通过获取当前函数文件的路径以及相对路径来定位数据文件
1 | root_dir = fileparts(mfilename('fullpath')); |
在 R2019b 版本中其实提供了完整的项目管理功能,类似 VS 的 project,基于 xml 文件进行项目管理配置,可以设置启动时和退出时的自动运行脚本,并进行搜索路径配置,但是这部分的功能过于依赖 MATLAB 自身,不建议使用。
搜索路径配置
MATLAB 经常会出现找不到函数或文件的情况,此时需要配置搜索路径,可以通过GUI或命令行操作。
基于 addpath 函数可以将指定路径添加到搜索路径中,例如
1 | addpath('./base'); |
如果传递的是相对路径,那么仍然是相对于当前工作目录的。
注意:
- 对 MATLAB 搜索路径的修改在 MATLAB 整个会话期间会持续生效;
- MATLAB 会对搜索路径集合自动去重,因此重复添加同一个路径不会产生任何副作用。
- 默认情况下会把新路径添加到搜索路径列表的开头,可以加上参数
-end将其加到结尾,搜索路径的顺序显然会影响搜索结果:如果存在两个相同名称的文件,那么 MATLAB 会调用先找到的文件,这可能导致潜在的问题。
一种简单的做法是在合适的位置放置一个 setup.m 脚本,在其中添加所有需要的搜索路径(相对于 setup.m 所在路径),例如
1 | addpath('./base'); |
还可以在其中加上一些必要的初始化配置(注意要保证幂等性:多次重复执行的效果和单次执行一样)
然后在主脚本中使用 run('/path/to/setup.m') 来添加所有路径,如果使用的是 setup.m 的相对路径,需要先切换工作目录到当前脚本所在路径
1 | cd(fileparts(mfilename('fullpath'))); |
在项目中可能存在多级目录,使用 genpath 可以生成指定路径以及它的所有子文件夹组成的字符串
1 | >> genpath('myfolder') |
可以用如下命令将其全部添加到搜索路径中
1 | addpath(genpath('myfolder')) |
也可以同时添加多个,例如
1 | addpath(genpath('myfolder'), genpath('utils')) |
除此之外,MATLAB 还提供了持久化地修改搜索路径的方式:savepath,在后续的启动过程中会自动添加对应路径,但是并不建议使用,因为这会破坏程序的可移植性。
MATLAB 在处理路径时需要考虑文件夹分隔符和路径分隔符的表示,这是和平台相关的,可以使用
filesep和pathsep来获取。(在 Windows 中为\和;,在 Linux 中则为/和;)
运行外部命令
MATLAB 毕竟只是一个大号的计算器,有一些工作并不适合用 MATLAB 完成,
使用外部命令会更加方便(例如 Python 脚本),可以使用 system 函数来运行外部命令,标准方式如下
1 | cmd = sprintf('python %s %s', python_script, args); |
注意这里不能直接使用 fprintf(msg),因为 msg 可能含有一些被 fprintf 用来格式化占位的特殊字符。
时间相关
获取时间信息
1 | >> datetime("now", "Format", "yyyyMMdd-HHmmss") |
计时并记录(物理时间)
1 | t_start = tic(); |
也可以更简单地直接写
1 | tic; |
此时会自动向控制台输出
1 | Elapsed time is x seconds. |
性能检查
如果只是粗略检查一段代码的总耗时,使用 tic/toc 就足够了;如果需要定位具体是哪个函数或哪一行代码耗时较多,应该使用 MATLAB 的性能探查器 Profiler。
在 MATLAB GUI 中,可以在编辑器里点击 Run and Time 来运行代码并打开性能分析结果的窗口。
性能调试机制在底层对应的是 profile 命令,在无 GUI 环境可以直接使用这个命令。
profile 常用控制命令如下:
profile on:启动探查器,并自动清除以前记录的探查统计信息;profile off:停止探查器;profile clear:停止探查器并清空已经记录的统计信息;profile viewer:停止探查器并在探查器窗口中显示结果;结果中默认包括函数调用次数、总耗时,以及具体代码行的执行次数和耗时。
profile默认记录物理时间而不是 CPU 时间。
在 GUI 环境下的基本使用示例如下:
1 | profile on; |
无 GUI 环境下的基本使用示例如下:
1 | result_dir = fullfile(pwd, 'profile_results'); |
其中 profsave 会把探查结果保存为一组 HTML 文件。第二个参数用于指定保存的目录;如果不指定,默认会保存到当前工作目录下的 profile_results 子目录中。
注:
- 对于 Linux 服务器等无 GUI 环境中的性能探查,通常只需要把结果保存成 HTML,然后在本地下载读取即可,当然也可以直接在 MATLAB 脚本中解析
profile('info')。 - Profiler 机制本身会在代码运行时收集统计数据,因此并不是无影响的,它本身就会引入额外的运行时开销,因此它给出的耗时指标更适合用来比较不同函数或不同代码行之间的相对性能,不应直接当作程序真实运行时间的精确值。
代码问题检查
MATLAB 提供了 codeIssues 用来检查代码中潜在的问题,例如语法错误、可能的性能问题、不推荐的写法等。codeIssues 对象可以保存 MATLAB Code Analyzer 找到的问题,并且这些问题可以在命令行中排序、筛选,也可以在 Code Analyzer App 中交互查看。
codeIssues在 R2022b 中才被引入,官方推荐用于替代旧的接口checkcode。
检查单个 M 文件
1 | issues = codeIssues('myfile.m') |
检查指定文件夹下的所有 M 文件
1 | issues = codeIssues('src') |
对于传入目录的情况,默认会包含它的子目录,如果不想递归检查子目录,可以显示指定参数
1 | issues = codeIssues('src', IncludeSubfolders = false) |
缺省参数会默认检查当前工作目录,因此自动包括当前目录下的所有子目录
1 | issues = codeIssues() |
返回结果示例
1 | ans = |
其中的 Files 属性记录了它实际检查的所有文件,Issues 属性是检查结果表格。
判断返回结果是否检查出问题,最简单的方式是检查 Issues 表格是否为空
1 | issues = codeIssues(); |
杂项
命名风格
从语法的角度,MATLAB 的变量、函数名以及自定义类型名都只允许使用大小写字母,数字和下划线 _ 组成,不允许使用连字符 -,只能使用字母开头。
下面是一些建议的命名风格:
- MATLAB 在函数和自定义类型名称中通常不使用下划线,更偏向于使用大小写来分隔单词。
- 函数名可以使用小驼峰,小写,或者小写下划线的形式,例如
myFunc,myfunc,my_func。官方文档中似乎混用了前两种方式,为了与内置函数区分,可以使用第三种方式。 - 自定义类型建议使用大驼峰命名法,例如
CarModel,DataProcessor。 - 自定义类型的方法函数命名与普通函数相同。
- 对于约定的常量,通常使用全大写加下划线的风格,例如
MAX_VALUE。 - 对于变量的命名比较自由,但是通常只在循环指标中使用单个小写字母作为变量,并且注意最好避免使用
i和j,可以使用ii和jj或者其它字母替代。
调试控制
MATLAB 除了支持和其它语言一样的最基本的断点调试功能之外,还有一套更强大的调试机制,主要是 MATLAB 的运行时环境实在太强大了。
在 MATLAB 代码中支持如下的中断命令:
pause:程序暂停,按任意键继续;keyboard:手动加入断点,程序执行到这里时会暂停,可以在命令行中输入指令进行调试,点击继续后才会继续执行。
我们可以在命令行或者脚本开头使用下面的特殊命令(等效于在 MATLAB GUI 中勾选 Pause on Errors 选项)
1 | dbstop if error; |
在错误发生时,MATLAB 会暂停并自动进入调试模式,停留在出错的位置,完整保留出错时的调用栈。(显然这样的做法也会降低程序性能)
这个命令的效果是持续性的,可以使用下面的命令清除这个效果(等效于在 MATLAB GUI 中取消勾选 Pause on Errors 选项)
1 | dbclear if error; |
除了遇到错误暂停,MATLAB 还提供了遇到警告或遇到 NaN/Inf 时暂停的选项,对应的命令为
1 | dbclear if warning; |
但是并不建议使用,因为有些操作(包括 MATLAB 内置函数)会在正常运算流程中使用 NaN/Inf。
打断点的操作也是可以完全通过命令完成,例如在进入函数时暂停
1 | dbstop in buggy % buggy.m 函数文件 |
例如在指定函数的指定行数,满足指定条件时暂停
1 | dbstop in myprogram at 4 if n>=4 |
可以使用 dbstatus 命令查看当前调试状态
1 | dbstatus |
如果没有进行任何调试设置,则会返回空。
可以使用 dbclear 命令来清除调试设置,例如
1 | dbclear all |
在调试状态下,MATLAB 的提示符会修改为 K>>,可以使用 dbstep 单步执行,可以用下面的命令在函数调用栈之间跳转
1 | dbup |
显然通过 GUI 操作进行调试更加方便。(在服务器终端运行 MATLAB 脚本时,不要开启调试模式,否则一旦遇到错误中断,整个脚本就会卡死,而且无法从外部进入调试)
避免在程序暂停时修改代码文件,因为MATLAB在运行时会整体加载所需要的代码文件,如果尝试在程序暂停修改代码文件,在暂停恢复后这些修改可能并不会生效,但是下次重新运行程序时会生效。
终端运行 MATLAB
在 Linux 服务器上使用 MATLAB 有一些不同点,最典型的不同是没有 GUI 界面,只有终端界面,不支持显示图像等,
最基础的使用方式和 Python 一样,输入 matlab 就可以进入 MATLAB 提供的终端界面。
在启动时可以附加如下选项
-nodisplay:不启动 GUI 显示。-nosplash:不显示启动画面。-nodesktop:不启动桌面环境。-batch "...":以非交互方式运行指定的命令或脚本
在 bash 脚本中调用 MATLAB 运行指定脚本,可以使用上述参数,例如
1 | matlab -nodisplay -nosplash -nodesktop -batch "test" # run test.m |
更健壮的调用方式如下
1 | export SCRIPT_PATH='/path/to/my_script.m' |
这里的 run() 命令必须传入字符数组,通常是脚本的文件名,可以是完整路径或相对路径,也可以是不带后缀的文件名。
老版本不支持 -batch 选项,需要使用 -r 选项,并且建议使用 try-catch 包裹执行脚本的语句,在脚本出错时保留一些错误信息,并自行提供返回值。
1 | export SCRIPT_PATH='/path/to/my_script.m' |
环境变量操作
MATLAB 提供了一些函数可以操作当前 MATLAB 会话中的环境变量,包括 getenv / setenv。
- 读取环境变量:
value = getenv('VAR_NAME'),未设置时返回空字符'' - 修改环境变量:
setenv('VAR_NAME', 'value');设为空视作删除:setenv('VAR_NAME', '')
例如
1 | setenv('MY_MODE', 'debug'); |
版本兼容性
MATLAB 在不同版本之间并不保证兼容,主要的语法差异包括:
- 低版本不允许在 M 文件内部定义局部函数,只允许在一个单独的函数文件中定义一个函数,不允许脚本文件中定义函数,在 R2016b 之后允许定义局部函数;
- 在低版本中不同尺寸的矩阵运算不会自动扩展,必须保持矩阵尺寸一致,在 R2016b 之后支持维数隐式扩展;
- 在低版本中只有字符数组,没有字符串类型,在 R2016b 之后提供了字符串类型。
- 实时交互式脚本(
.mlx文件)大约在 R2019b 之后在各个平台上才被完整支持。 - 基于
arguments的函数参数约束语法在 R2019b 提出,在后续版本中不断完善,在 R2023a 中支持键值对参数。
除此之外,MATLAB 提供的海量内置函数的接口也可能随着版本而变化。
编码与换行符问题
MATLAB 对编码和换行符并没有一套完整的支持:
- 关于源文件编码:
- 可以使用
feature('locale')命令查看当前的本地编码; - 新版本的 MATLAB 文件默认都采用 UTF-8 编码,但是老版本可能采用 GBK 编码,在保存文件时也可以指定 GBK 编码;
- 终端可能采用和文件不一样的编码,例如在文件中使用 UTF8,但是在终端使用 GBK,非常奇怪。
- 可以使用
- 关于源文件回车:没有在设置中找到任何关于换行符切换的选项,实测发现,在 Windows 下的 MATLAB 创建并编辑的新文件始终采用的是 CRLF,但是对于 LF 文件进行编辑改动,则会保持与文件一致的换行符,并不会产生换行符混用的异常。
总体来说,在终端和绘图窗口中,都很可能会出现不支持中文字符输入和显示的问题,尽量避免使用中文。
底层实现
尽管 MATLAB 是闭源软件,但从官方文档等资料的分析可以推测:
- 底层核心计算主要由 C/C++ 和 Fortran 实现。
- 用户界面(GUI)使用 Java。
- 核心数学库调用 BLAS/LAPACK 进行优化。
- MATLAB 内部数据结构基于 mxArray,并使用 Copy-on-Write 机制。
- MATLAB 解释器本质上是一个基于栈的虚拟机,MATLAB 代码会被解析成字节码(类似 Python 的 .pyc 文件),然后由解释器执行。
- 高版本开始使用 JIT 和解释执行结合的方式运行 MATLAB 代码。
- 高版本提供了 Python 接口支持。
锐评
MATLAB 经常被程序员鄙视,普遍认为这不是一个设计良好的编程语言,我也这么认为,因为和 C++,Java,Python 这些典型的编程语言相比,MATLAB 有很多对编程不友好的地方:
- MATLAB 本身不是开源的,我们不清楚它的具体底层实现,无法进行深度优化;
- MATLAB 和自身提供的 IDE 绑定太深了,尤其是当下其它语言的 LSP 流行之时,MATLAB 摆脱自身的 IDE 仍然很难使用;
- 还是 IDE 的问题,作为代码编辑器显得太垃圾,连黑暗模式都没有,智能提示也与其他语言差距甚远,鼠标放在函数和变量上面居然没有自动的智能提示!!!在各种主流代码编辑器已经通过插件集成 AI 功能的当下,MATLAB 的 IDE 就像一个老古董;
- 语法设计并不是完全符合编程思想的,而是基于实用主义,和其他正经编程语言的成体系的语法规则截然不同,有些语法规则简直反人类,例如允许用命令式语法进行函数调用;
- 内置函数太多,而且命名和接口等非常不规范,虽然MATLAB的官方文档是非常详细的,但是我们仍然很可能误用这些内置函数,某些参数选项的设置甚至是非常违背逻辑的,一个函数有 N 种重载,稍不注意就会产生意想不到的错误,而且有些在官方文档中没有指出的内容也只能自己摸索;
- MATLAB 只允许一个函数文件对外暴露一个函数接口,这导致我们必须拆分出很多的小函数文件,维护非常不方便;(可以使用类的静态方法绕开这个限制)
- MATLAB 的结构体过于灵活(可以随时增减数据成员),自定义类型又过于笨重(必须使用一个专门的类文件来定义),面向对象的机制不够完善;
- MATLAB 不支持局部作用域,除非在函数中,否则变量都是所谓的工作区变量;
- 教程不是面向程序员的,市面上的绝大部分 MATLAB 教程对语法层面的介绍仅仅是入门级的,关注的重点多半是某些工具箱的使用;
- 大部分使用者不是专业的程序员,代码并没有普遍采用面向对象等编程思想,对于大型项目的维护非常困难;
- MATLAB 对第三方社区不够友好,没有成熟开源的第三方模块开发社区,只有官方的工具箱机制;
- …
就MATLAB这破语法,我还能吐槽很多很多条,但是没办法还得用,其它语言的矩阵操作远没有MATLAB方便高效,例如 Python 的 Numpy 在涉及矩阵的操作就有很多反直觉的地方,毕竟矩阵操作相关的语法设计在 MATLAB 中是第一位的,但是在 Python 中并不是。
不应该使用的垃圾语法:
global全局变量- 直接通过名称执行脚本
- 脚本化编程,不使用函数封装
- 通过
run直接执行其它脚本(除了调用脚本配置环境并进行初始化) - 使用命令式语法进行函数调用,包括对无参数的函数调用省略括号
- 在使用结构体时,不使用显式的
struct()函数来创建结构体 - …
