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
默认是虚数单位,但是它们非常可能在代码中被作为循环变量赋值使用,此时就不再是虚数单位了,果需要使用虚数,可以重新赋值,例如
1
i = complex(0, 1);
更可靠的做法是使用 1i,1j
这种安全的虚数字面量形式。
在命令行中:
- 执行
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不支持!作为否定。
运算符
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
B/A = (A'\B')'
MATLAB 为了降低使用难度,通过这两个运算符隐藏了各种常见的方程组数值求解算法,在实际计算时涉及一套复杂的判定流程,并根据不同情况调用合适的数值算法,对于病态的方程组在求解过程中会发出警告。
关系运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
== |
等于 | A == B |
~= |
不等于 | A ~= B |
> |
大于 | A > B |
< |
小于 | A < B |
>= |
大于等于 | A >= B |
<= |
小于等于 | A <= B |
注意:
- MATLAB 使用的不等号并不是常见的
!=,而是~=。 <在面向对象中用于表示继承关系。
逻辑运算符
| 运算符 | 说明 | 示例 | 等价函数 |
|---|---|---|---|
& |
按元素与 | A & B |
and(A, B) |
| |
按元素或 | A | B |
or(A, B) |
~ |
取反 | ~A |
not(A) |
&& |
短路与 | A && B |
|
|| |
短路或 | 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 没有以 &、|、~
等形式提供位运算符,所有按位运算均需要使用专门的 bit*
函数(例如bitand(A, B)代表按位与),而且针对int等数据类型进行位运算才有意义。
1 | A = uint8([0 1; 0 1]); |
常用片段/脚本
运行前清理
下面几个清理性质的命令在主脚本的开头很常见 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'):关闭所有正在打开的文件
调用 clear all、clear classes 和
clear functions
会降低代码执行的性能,而且通常没有必要。建议的用法如下:
- 要从当前工作区中清除一个或多个特定变量,可以逐个指定变量,使用
clear name1 ... nameN。 - 要清除当前工作区中的所有变量,可以使用
clear或clearvars。 - 要清除所有全局变量,可以使用
clear global或clearvars –global。 - 要清除特定类,可以使用
clear myClass。 - 要清除特定函数,可以使用
clear functionName。
工作目录
ATLAB始终是在它提供的模拟终端中运行脚本或命令,存在当前工作目录的概念,可以使用pwd查看。(在GUI中的目录切换也会立刻影响到模拟终端)
在脚本中使用如下命令可以将当前工作目录切换到脚本所在的目录
1
cd(fileparts(mfilename('fullpath')));
直接执行脚本时,脚本中所有的相对路径都是相对于当前工作目录的,而不是相对于脚本所在目录。(这是几乎所有脚本语言都采取的做法)
如果通过run命令来执行脚本,那么情况有点复杂,按照 官方文档
来解释:
- 绝大多数情况下,
run命令在执行时会先切换工作目录到脚本所在的文件夹,执行脚本,然后切换回原始工作目录。 - 脚本默认应该是在脚本所在的文件夹中执行,如果脚本(实质性地)切换了工作目录,那么在脚本执行完成之后,
run命令就不会自动切换回原始工作目录。
搜索路径配置
MATLAB经常会出现找不到函数或文件的情况,此时需要配置搜索路径,可以通过GUI或命令行操作。
基于 addpath 函数可以将指定路径添加到搜索路径中,例如
1
2addpath('./base');
addpath('./src','./utils');
如果传递的是相对路径,那么仍然是相对于当前工作目录的。
注意:
- 对MATLAB搜索路径的修改在MATLAB整个会话期间会持续生效;
- MATLAB会对搜索路径集合自动去重,因此重复执行没有副作用。
- 默认情况下会把新路径添加到搜索路径列表的开头,可以加上参数
-end将其加到结尾,搜索路径的顺序显然会影响搜索结果。
一种简单的做法是在合适的位置放置一个setup.m脚本,在其中添加所有需要的搜索路径(相对于setup.m所在路径),例如
1 | addpath('./base'); |
然后在主脚本中使用 run('/path/to/setup.m')
来添加所有路径,如果使用的是 setup.m
的相对路径,需要先切换工作目录到当前脚本所在路径 1
2cd(fileparts(mfilename('fullpath')));
run('relative/path/to/setup.m');
在项目中可能存在多级目录,使用 genpath
可以生成指定路径以及它的所有子文件夹组成的字符串 1
2
3>> genpath('myfolder')
ans =
'myfolder:myfolder/mysubfolder:'
然后可以将其全部添加到搜索路径中 1
addpath(genpath('myfolder'))
基于上述做法,可以实现一个更复杂的搜索路径管理方案:使用下面的
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 在处理路径时需要考虑文件夹分隔符和路径分隔符的表示,这是和平台相关的,可以使用
filesep和pathsep来获取。(在 Windows 中为\和;,在 Linux 中则为/和;)
杂项
命名风格
从语法的角度,MATLAB的变量、函数名以及自定义类型名都只允许使用大小写字母,数字和下划线_组成,不允许使用连字符-,只能使用字母开头。
下面是一些建议的命名风格:
- MATLAB 在函数和自定义类型名称中通常不使用下划线,更偏向于使用大小写来分隔单词。
- 函数名可以使用小驼峰,小写,或者小写下划线的形式,例如
myFunc,myfunc,my_func。官方文档中似乎混用了前两种方式,为了与内置函数区分,可以使用第三种方式。 - 自定义类型建议使用大驼峰命名法,例如
CarModel,DataProcessor。 - 自定义类型的方法函数命名与普通函数相同。
- 对于约定的常量,通常使用全大写加下划线的风格,例如
MAX_VALUE。 - 对于变量的命名比较自由,但是通常只在循环指标中使用单个小写字母作为变量,并且注意最好避免使用
i和j,可以使用ii和jj或者其它字母替代。
调试控制
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在运行时会整体加载所需要的代码文件,如果尝试在程序暂停修改代码文件,在暂停恢复后这些修改并不会生效,但是下次重新运行程序时会生效。
终端运行 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()命令必须传入字符数组,通常是脚本的文件名,可以是完整路径或相对路径,也可以是不带后缀的文件名。
老版本不支持-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对编码和换行符并没有一套完整的支持:
- 关于源文件编码:
- 可以使用
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 在涉及矩阵操作时就有很多反直觉的地方。
不应该使用的垃圾语法:
global全局变量- 直接通过名称执行脚本
- 通过
run直接执行其它脚本(除了调用脚本配置环境和初始化) - 对于无参数的函数调用,在调用语句中省略括号
- 在使用结构体时,不使用显式的
struct()函数来创建结构体 - ...
