MATLAB 学习笔记——1. 基础
之前对 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
虚数单位,两者相等。
特别注意:i
和 j
默认是虚数单位,但是作为循环变量赋值之后就不再是虚数单位了,此时如何需要使用虚数,需要重新赋值,例如
1
i = complex(0, 1);
在命令行中:
- 执行
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
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
3x = 1; % double
x = int64(1); % int64
因为针对int
类型的所有运算仍然会得到int
,并不会自动转换为double
,这很可能会导致程序bug
1
2
3a = int32(2);
b = a * 1.2 % int32 2
在通常的使用中直接用字面量2
或2.0
即可,常见的操作包括索引等也不需要专门使用int
类型,即使有取整的需求,使用floor()
也是足够的,并且返回值仍然是double
类型
1
2
3a = floor(1.2)
b = a * 1.2 % 1.2000
字符与字符串
MATLAB 支持字符和字符数组,全部使用单引号 ''
包裹。用下面的语句可以创建和修改字符数组:
1 | % 创建字符 |
注意,这里由字符组成的字符数组会默认显示为连续的形式,而不是类似于矩阵的形式
1
hello, MATLAB! is great!
在较新的版本中,MATLAB 额外提供了字符串类型,需要使用双引号
""
包裹。用下面的语句可以创建和修改字符串和字符串数组,与字符数组不同,字符串数组的使用则更像是字符串的“矩阵”
1 | % 创建字符串 |
字符、字符数组与字符串分别采用了单引号和双引号,它们也需要特殊处理引号:
- 在字符数组中,使用连续的单引号
''
表示一个单引号,对于双引号不需要额外处理; - 在字符串中,对于单引号不需要额外处理,使用连续的双引号
""
表示一个双引号。
布尔类型
布尔值可以直接使用 true
和 false
关键字创建,或通过逻辑表达式生成。
1 | % 创建布尔值 |
需要注意的是,向控制台输出布尔值时会自动转换为1
或0
。
多个布尔值可以组成数组,在使用逻辑判断作用于矩阵时也会得到布尔数组
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
3vec1 = [true, false, true];
vec2 = [true, true, false];
result = vec1 | vec2; % 结果是 [true, true, true]
容易混淆的运算是
&&
和||
,它们常用于条件表达式中,只支持标量间运算,并且具有短路逻辑,MATLAB不支持!
作为否定。
常用片段/脚本
运行前清理
下面几个清理性质的命令在主脚本的开头很常见 1
2
3
4
5
6clc;
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
脚本将其中的子文件夹添加到搜索路径中
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版本
- 系统内存大小
- 系统核心数(支持的并行数目)
下面的脚本可用于检测这些关键信息 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
2dbup
dbdown
我们要避免在程序暂停时修改代码文件,因为MATLAB在运行时会整体加载所需要的代码文件,如果尝试在程序暂停修改代码文件,在暂停恢复后这些修改并不会生效,但是下次重新运行程序时会生效。
服务器运行
在Linux服务器上使用MATLAB有一些不同点,最典型的不同是没有GUI界面,不支持显示图像等,可以使用如下选项
-nodisplay
:不启动GUI显示。-nosplash
:不显示启动画面。-nodesktop
:不启动桌面环境。-batch "..."
:以非交互方式运行指定的命令或脚本
例如 1
2export SCRIPT_PATH='/path/to/my_script.m'
matlab -nodisplay -nosplash -nodesktop -batch "run('$SCRIPT_PATH')"
在调用run()
需要使用字符数组作为参数,表示执行的脚本名称(可以含路径,含后缀),如果改成直接输入脚本名称,则不能带.m
后缀;
老版本不支持-batch
选项,需要使用-r
选项,并且建议使用try-catch
包裹执行脚本的语句,在脚本出错时保留一些错误信息,并自行提供返回值。
1
2export SCRIPT_PATH='/path/to/my_script.m'
matlab -nodisplay -nosplash -nodesktop -r "try, run('$SCRIPT_PATH'); catch ME, disp(getReport(ME)); exit(1); end; exit(0);"
版本兼容性
MATLAB在不同版本之间并不保证兼容,主要的差异包括:
- 低版本不允许在M文件内部定义局部函数,只允许在一个单独的函数文件中定义一个函数,不允许脚本文件中定义函数,在R2016b之后允许定义局部函数;
- 在低版本中不同尺寸的矩阵运算不会自动扩展,必须保持矩阵尺寸一致,在R2016b之后支持维数隐式扩展;
- 在低版本中只有字符数组,没有字符串类型,在R2016b之后提供了字符串类型。
- 实时交互式脚本(
.mlx
文件)大约在R2019b之后在各个平台上才被完整支持。
除此之外,MATLAB提供的海量内置函数的接口也可能随着版本而变化。
编码与换行符问题
MATLAB对编码和换行符并没有一套完整的支持:
- 关于源文件编码:新版本的MATLAB文件默认都采用UTF-8编码,但是老版本可能采用GBK编码,在保存时也可以指定GBK编码;
- 关于源文件回车:Windows下的MATLAB创建并编辑的新文件始终采用的是CRLF,但是对于LF文件进行编辑改动,则会保持与文件一致的换行符,并不会产生异常。
在命令行窗口和绘图窗口中,都很可能会出现不支持中文字符输入和显示的问题。
评价
MATLAB经常被程序员鄙视,普遍认为这不是一个设计良好的编程语言,我也这么认为, 因为和C++,Java,Python这些典型的编程语言相比,MATLAB有很多对编程不友好的地方:
- MATLAB本身不是开源的,我们不清楚它的具体底层实现,无法进行深度优化;
- IDE太垃圾,连黑暗模式都没有,智能提示与其他语言差距甚远;
- 语法设计并不是完全符合编程思想的,而是基于实用主义,和其他正经编程语言的成体系的语法规则截然不同;
- 内置函数太多,而且命名和接口等非常不规范,虽然MATLAB的官方文档是非常详细的,但是我们仍然很可能误用这些内置函数;
- MATLAB只允许一个函数文件对外暴露一个函数接口,这导致我们必须拆分出很多的小函数文件,维护非常不方便;(可以使用类的静态方法绕开这个限制)
- 教程不是面向程序员的,市面上的绝大部分MATLAB教程对语法层面的介绍仅仅是入门级的,关注的重点多半是某些工具箱的使用;
- 大部分使用者不是专业的程序员,代码并没有普遍采用面向对象等编程思想,对于大型项目的维护非常困难;
- ...