之前对 MATLAB 的使用都是轻量级的,因此总是不太熟,趁着必须要用 MATLAB 的机会,小结一下 MATLAB 的基本语法,便于查阅。(2022年9月)

之前的MATLAB笔记还是太简陋了,必须加上面向对象等部分组成更完整的系列笔记。因为现在手上的代码还是太乱了,必须彻底重构一下,先把笔记补上。(2024年6月)

基本语法

注释:MATLAB 使用百分号%进行单行注释,使用%{...%}进行多行注释。

分号:; MATLAB 并不需要强制使用分号作为语句的结尾,以空格或回车结尾即可。不使用分号结尾时会把计算结果显示出来,使用分号结尾则会抑制计算结果的显示。

续行符:在第一行的结尾使用...可以把命令接续到第二行。(如果直接把...接在数字后面会报错,可以加空格)

命令补全:Tab 键。

对命令行窗口清屏:clc

MATLAB 的一切数据都是矩阵:行向量是行数为 1 的矩阵,数是行数列数均为 1 的矩阵,甚至一个无效命令,它的返回值[]都是一个 0 行 0 列的空矩阵。

和其他现代编程语言类似:

  • MATLAB 对大小写敏感,这与它所参考的 FORTRAN 不同。
  • MATLAB 对表达式中的空格不敏感,表达式中的空格通常被忽略。(只有bash这些异类才会对空格敏感)

和大部分现代编程语言不同:

  • 在 MATLAB 中,不等号是~=,而不是 C 语言风格的!=
  • MATLAB的矩阵在内存中采用FORTRAN风格的列主序,而不是C语言风格的行主序。

在MATLAB控制台中数据显示格式可以使用format命令修改,这个命令的效果是持续性的,但只是影响呈现效果,不会影响数据的储存形式和计算精度。

  • format 默认格式(恢复为默认格式)
  • format short 5 字长定点数,显示 5 位(scaled fixed point format with 5 digits)
  • format long 15 字长定点数,显示 15 位双精度,7 位单精度(scaled fixed point)
  • format short e 5 字长浮点数
  • format long e 15 字长浮点数
  • format rat 对小数使用近似的有理数表示

变量

MATLAB 的变量名允许使用字母,数字和下划线组成,只允许字母开头。变量名严格区分大小写,长度不超过 namelengthmax=63 个字母。

和Python类似,MATLAB 也属于动态类型的语言,变量是无类型的(相对于指针的效果),必须先对变量进行赋值(数组或矩阵等),然后才能使用。

与 Python 不同的是,MATLAB不需要关注深拷贝和浅拷贝的细节, MATLAB 在函数传参等情况下默认采用传值方式,但是在底层处理时普遍采用了懒拷贝(Lazy Copy)的优化:如果没有进行修改,那么总是对原件的引用;如果产生了修改,那么就会对原件进行拷贝,在拷贝的基础上进行修改。

实际上在面向对象的部分,我们还是需要关注深拷贝和浅拷贝的机制,区别体现在自定义类型是否继承自内置的handle基类。

特殊变量:

  • ans 没有指定变量接收时,上次的计算结果会默认保存在这个变量里;
  • eps 浮点数误差;
  • inf,Inf 无穷大,NaN,nan 非法值;
  • pi 圆周率;
  • i,j 虚数单位,两者相等。

特别注意:ij 默认是虚数单位,但是作为循环变量赋值之后就不再是虚数单位了,此时如何需要使用虚数,需要重新赋值,例如

1
i = complex(0, 1);

在命令行中:

  • 执行 x=1 会把结果赋值给x变量,返回
1
2
x=
1
  • 执行 1*2 会把结果赋值给ans变量,返回
1
2
ans=
2

当前定义和使用的所有变量会处于当前的工作区中:

  • 查询变量:
    • 使用who命令,可以查询 MATLAB 当前工作区使用的所有变量名称;
    • 使用whos会显示更详细的结果,包括所有变量的内存尺寸;(也可以单独在工作区窗口查看)
1
2
3
4
5
6
7
8
9
10
11
12
>> who

Your variables are:

a ans x

>> whos
Name Size Bytes Class Attributes

a 1x1 8 double
ans 1x1 8 double
x 1x1 8 double
  • 删除变量:
    • 使用clear x可以删除x变量;
    • 使用clear可以删除所有变量;
  • 变量加载与保存:
    • 使用save myfile命令可以把当前工作区的所有变量保存到文件myfile.mat
    • 使用load myfile命令可以把myfile.mat里面的所有变量重新加载到当前的工作区中。

MATLAB提供了全局变量,可以使用global x来声明一个全局变量(声明不是定义,仍然需要单独定义),在任何的函数作用域中只需要再次声明这个全局变量,就可以使用它,例如

1
2
3
4
5
6
7
8
9
10
11
12
% 主脚本
global x; % 声明全局变量
x = 10;

% 调用函数
displayGlobalVariable();

% 函数文件
function displayGlobalVariable()
global x; % 再次声明全局变量
disp(x); % 10
end

数据类型

浮点数与整数

在MATLAB中,数和矩阵的元素等默认都是double浮点数,虽然MATLAB也支持int64等数据类型,但是通常是不需要使用的!!!(除非考虑与C语言等的接口对接等情况,此时才需要特别关注数值类型)

1
2
3
x = 1; % double

x = int64(1); % int64

因为针对int类型的所有运算仍然会得到int,并不会自动转换为double,这很可能会导致程序bug

1
2
3
a = int32(2);

b = a * 1.2 % int32 2

在通常的使用中直接用字面量22.0即可,常见的操作包括索引等也不需要专门使用int类型,即使有取整的需求,使用floor()也是足够的,并且返回值仍然是double类型

1
2
3
a = floor(1.2)

b = a * 1.2 % 1.2000

字符与字符串

MATLAB 支持字符和字符数组,全部使用单引号 '' 包裹。用下面的语句可以创建和修改字符数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
% 创建字符
singleChar = 'A';

% 创建字符数组
charArray = 'Hello, MATLAB!';

% 获取字符数组的元素(字符)
firstChar = charArray(1); % 'H'

% 修改字符数组的元素(字符)
charArray(1) = 'h'; % 'hello, MATLAB!'

% 合并字符数组
newCharArray = [charArray, ' is great!']; % 'hello, MATLAB! is great!'

注意,这里由字符组成的字符数组会默认显示为连续的形式,而不是类似于矩阵的形式

1
hello, MATLAB! is great!

在较新的版本中,MATLAB 额外提供了字符串类型,需要使用双引号 "" 包裹。用下面的语句可以创建和修改字符串和字符串数组,与字符数组不同,字符串数组的使用则更像是字符串的“矩阵”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
% 创建字符串
singleString = "Hello, MATLAB!";

% 创建字符串数组
stringArray = ["Hello", "MATLAB", "!"];

% 获取字符串数组中的元素(字符串)
firstString = stringArray(1); % "Hello"

% 修改字符串数组中的元素(字符串)
stringArray(1) = "Hi"; % ["Hi", "MATLAB", "!"]

% 合并字符串数组为字符串
newString = join(stringArray, " "); % "Hi MATLAB !"

% 拆分字符串为字符串数组
splitStrings = split(singleString, ", "); % ["Hello", "MATLAB!"]

% 替换字符串
replacedString = replace(singleString, "MATLAB", "World"); % "Hello, World!"

字符、字符数组与字符串分别采用了单引号和双引号,它们也需要特殊处理引号:

  • 在字符数组中,使用连续的单引号''表示一个单引号,对于双引号不需要额外处理;
  • 在字符串中,对于单引号不需要额外处理,使用连续的双引号""表示一个双引号。

布尔类型

布尔值可以直接使用 truefalse 关键字创建,或通过逻辑表达式生成。

1
2
3
4
5
6
7
% 创建布尔值
a = true;
b = false;

% 通过逻辑表达式生成布尔值
c = (5 > 3);
d = (2 == 3);

需要注意的是,向控制台输出布尔值时会自动转换为10

多个布尔值可以组成数组,在使用逻辑判断作用于矩阵时也会得到布尔数组

1
2
3
4
5
6
% 创建布尔数组
boolArray = [true, false, true];

% 通过逻辑表达式生成布尔数组
numbers = [1, 2, 3, 4, 5];
boolArray = (numbers > 3); % [false, false, false, true, true]

布尔值和布尔数组都支持常见的逻辑运算:

  • 逻辑与(AND):&
  • 逻辑或(OR):|
  • 逻辑非(NOT):~
  • 逻辑异或(XOR):xor

它们对于布尔数组是逐个元素运算的,例如

1
2
3
vec1 = [true, false, true];
vec2 = [true, true, false];
result = vec1 | vec2; % 结果是 [true, true, true]

容易混淆的运算是&&||,它们常用于条件表达式中,只支持标量间运算,并且具有短路逻辑,MATLAB不支持!作为否定。

常用片段/脚本

运行前清理

下面几个清理性质的命令在主脚本的开头很常见

1
2
3
4
5
6
clc;
clear;
close all;

format short e
format compact

作用依次为:

  • clc: 清除命令行窗口的内容
  • clear: 清除工作空间中的所有变量
  • close all: 关闭所有打开的图形窗口
  • format short e: 对浮点数的输出采用科学计数法
  • format compact: 使命令行的输出更加紧凑,去除不必要的空行

注意:clear会清理所有的工作区变量,如果工作区中有存储重要数据的变量,建议立刻保存到.mat文件中,在脚本中再重新加载。

补充几个可选命令:

  • clear all:比clear更彻底的清理,通常没有必要(不含下面的类定义)
  • clear classes:清理所有的类定义,确保MATLAB加载最新的类定义,通常没有必要
  • fclose('all'):关闭所有正在打开的文件

搜索路径配置

对于一个含有多级目录的项目,可以使用下面的setup_path.m脚本将其中的子文件夹添加到搜索路径中

setup_path.m
1
2
3
4
5
6
7
8
9
10
11
12
% set up the project environment

disp('call setup.m')

projectRoot = fileparts(mfilename('fullpath'));
fprintf('projectRoot: %s\n', projectRoot);

foldersToAdd = {'src', 'utils'};
for i = 1:length(foldersToAdd)
folderPath = fullfile(projectRoot, foldersToAdd{i});
addpath(genpath(folderPath));
end

在脚本中定义了项目根目录projectRoot,添加了src/, utils/这两个子目录,可以根据需要修改。 将setup_path.m脚本放置在项目根目录下,在主脚本的开头使用run('/path/to/setup_path.m')即可,在涉及到存储路径时使用projectRoot组成绝对路径。

关于MATLAB搜索路径的特点:

  • 对MATLAB搜索路径的修改在MATLAB整个会话期间会持续生效;
  • MATLAB会对搜索路径集合自动去重,因此重复执行没有任何副作用。

并行池配置

对于一个需要并行的程序,虽然MATLAB在遇到parfor语句时会自动开启并行,但是我们也可以使用下面的命令提前开启

1
gcp();

可以用下面的命令关闭并行池

1
delete(gcp);

因为MATLAB在会话结束之后自动关闭并行池,我们通常不需要手动进行关闭。

资源与性能检测

我们关注MATLAB进行大规模科学计算所需要的典型资源:

  • MATLAB版本
  • 系统内存大小
  • 系统核心数(支持的并行数目)

下面的脚本可用于检测这些关键信息

check.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
%% Check MATLAB Environment

% Get current MATLAB version
matlabVersion = version('-release');
disp(['MATLAB Version: ', matlabVersion]);

% memory
if ispc
% Windows
[~, cmdout] = system('systeminfo | findstr "Total Physical Memory"');
disp('Memory information (Windows):');
disp(cmdout);
elseif isunix
% Unix-like
[status, cmdout] = system('free -h');
disp('Memory information (Linux):');
disp(cmdout);
else
disp('System memory information not available on this platform.');
end

% Get maximum supported workers
maxWorkers = feature('numcores'); % Get maximum number of cores

% Check current parallel pool status, open if not already open, and execute test task
pool = gcp('nocreate'); % If no pool exists, do not create new
if isempty(pool)
disp(['No parallel pool found. Opening a new one with ', num2str(maxWorkers), ' workers...']);
parpool('local', maxWorkers); % Open local pool based on maximum supported cores
disp('Parallel pool opened successfully.');
else
disp(['Current parallel pool size: ', num2str(pool.NumWorkers)]);
end

% Execute parallel task to output "Hello, world!" using maximum supported workers
parfor idx = 1:maxWorkers
fprintf('Hello, world! (from %d)\n', idx);
end

% close parallel pool
delete(gcp);

杂项

调试控制

MATLAB除了支持和其它语言一样的断点调试功能之外,还有一些更方便的调试机,MATLAB的运行时环境实在是太强大了。

在MATLAB代码中支持如下的中断命令:

  • pause:程序暂停,按任意键继续;
  • keyboard:手动加入断点,程序执行到这里时会暂停,可以在命令行中输入指令进行调试,点击继续后才会继续执行。

我们可以在命令行或者脚本开头使用下面的特殊命令

1
dbstop if error;

这样在错误发生时,MATLAB 会自动进入调试模式,保留出错的现场,显然这样的做法会导致程序性能的降低。 这个命令的效果是持续性的,可以使用下面的命令清除这个效果

1
dbclear if error;

这两个命令在新版本的MATLAB是可以直接在GUI界面上设置的,在run的下拉菜单中有pause on errors选项,勾选或取消就对应了这两个命令。

在调试状态下,提示符为K>>,可以用下面的命令在函数调用栈之间跳转

1
2
dbup
dbdown

我们要避免在程序暂停时修改代码文件,因为MATLAB在运行时会整体加载所需要的代码文件,如果尝试在程序暂停修改代码文件,在暂停恢复后这些修改并不会生效,但是下次重新运行程序时会生效。

服务器运行

在Linux服务器上使用MATLAB有一些不同点,最典型的不同是没有GUI界面,不支持显示图像等,可以使用如下选项

  • -nodisplay:不启动GUI显示。
  • -nosplash:不显示启动画面。
  • -nodesktop:不启动桌面环境。
  • -r "...":运行指定的命令或脚本(运行结束不会自动退出MATLAB)

例如

1
matlab -nodisplay -nosplash -nodesktop -r "run('path/to/myscript.m'); exit;"

这里使用exit;在脚本正常完成后自动退出MATLAB。

注意:

  • run()需要使用字符数组作为参数,表示执行的脚本名称(可以含路径),如果改成直接输入脚本名称,则不能带.m后缀;
  • 在运行遇到错误时可能被卡死,建议使用下面脚本中的try-catch语句,在我们的脚本出错时显示一些错误信息并主动退出。
run_matlab
1
2
3
4
5
6
7
8
9
10
#!/bin/bash

if [ "$#" -ne 1 ]; then
echo "Usage: $0 <script_path>"
exit 1
fi

SCRIPT_PATH=$1

matlab -nodisplay -nosplash -nodesktop -r "try, run('$SCRIPT_PATH'); catch ME, disp(ME.message); exit(1); end; exit;"

版本兼容性

MATLAB在不同版本之间并不保证兼容,主要的差异包括:

  • 低版本不允许在M文件内部定义局部函数,只允许在一个单独的函数文件中定义一个函数,不允许脚本文件中定义函数,在R2016b之后允许定义局部函数;
  • 在低版本中不同尺寸的矩阵运算不会自动扩展,必须保持矩阵尺寸一致,在R2016b之后支持维数隐式扩展;
  • 在低版本中只有字符数组,没有字符串类型,在R2016b之后提供了字符串类型。
  • 实时交互式脚本(.mlx文件)大约在R2019b之后在各个平台上才被完整支持。

除此之外,MATLAB提供的海量内置函数的接口也不是稳定的,可能随着版本的变化而变化。

在高版本的MATLAB环境下,写出一套对低版本兼容的代码还是非常困难的,例如笔记本上的是R2023b,但是服务器上安装的版本只有R2015b, 有很多内置函数都不一样:新版本提供了很多新的内置函数,并且对很多旧的内置函数提供了更好的替代实现。

编码与换行符问题

MATLAB对编码和换行符并没有一套完整的支持:

  • 关于源文件编码:新版本的MATLAB文件默认都采用UTF-8编码,但是老版本可能采用GBK编码,在保存时也可以指定GBK编码;
  • 关于源文件回车:Windows下的MATLAB创建并编辑的新文件始终采用的是CRLF,但是对于LF文件进行编辑改动,则会保持与文件一致的换行符,并不会产生异常。

在命令行窗口和绘图窗口中,都很可能会出现不支持中文字符输入和显示的问题。

评价

MATLAB经常被程序员鄙视,普遍认为这不是一个设计良好的编程语言,我也这么认为, 因为和C++,Java,Python这些典型的编程语言相比,MATLAB有很多对编程不友好的地方:

  • MATLAB本身不是开源的,我们不清楚它的具体底层实现,无法进行深度优化;
  • IDE太垃圾,连黑暗模式都没有,智能提示与其他语言差距甚远;
  • 语法设计并不是完全符合编程思想的,而是基于实用主义,和其他正经编程语言的成体系的语法规则截然不同;
  • 内置函数太多,而且命名和接口等非常不规范,虽然MATLAB的官方文档是非常详细的,但是我们仍然很可能误用这些内置函数;
  • MATLAB只允许一个函数文件对外暴露一个函数接口,这导致我们必须拆分出很多的小函数文件,维护非常不方便;(可以使用类的静态方法绕开这个限制)
  • 教程不是面向程序员的,市面上的绝大部分MATLAB教程对语法层面的介绍仅仅是入门级的,关注的重点多半是某些工具箱的使用;
  • 大部分使用者不是专业的程序员,代码并没有普遍采用面向对象等编程思想,对于大型项目的维护非常困难;
  • ...