学MATLAB绕不开矩阵,就像学中文绕不开汉字。前几天整理自己的学习笔记,发现大部分基础内容其实都和矩阵有关。这篇笔记主要是记录我入门MATLAB时最常碰到的矩阵操作:怎么创建、怎么索引、怎么运算,以及一堆当时踩过的坑。如果你是刚开始接触MATLAB,或者想系统回顾矩阵这部分的基础知识,这份笔记应该能帮上忙。
1. 为什么MATLAB里一切皆矩阵
1.1 矩阵不是普通的“二维表”
MATLAB的全称是Matrix Laboratory,翻译过来就是“矩阵实验室”,所以矩阵这个概念几乎刻在它骨子里。刚开始学习时,我总习惯把矩阵理解成Excel表格,后来才发现这个类比很危险。矩阵在MATLAB里不仅仅是一个表格,而是一个有严格维度定义的数据对象。更关键的是,MATLAB里的标量、向量、矩阵、多维数组全都统一在“数组”这个框架下:一个数可以看成1×1矩阵,一个行向量是1×n矩阵,一个列向量是n×1矩阵,二维矩阵自然就是m×n,再往上还可以有页、可以有更高维度。
这个设计带来的直接好处是:你不需要像C语言那样区分数组、指针、结构体,几乎所有数据都可以用矩阵来表达。比如你读一张图片,它其实是一个三维矩阵,长宽是图片尺寸,第三维是RGB通道;你录一段声音,它往往是一个很长的列向量;一个表格数据导入后,也常常变成一个二维矩阵。理解这一点后,你再看MATLAB的很多操作就顺了,因为大部分内置函数都在和矩阵打交道。
1.2 矩阵的维度、行数、列数和元素总数
在入门阶段,有三个函数很容易混淆:size、length和numel。size(A)返回的是A每个维度的大小,比如一个3行4列的矩阵,size(A)返回[3 4];numel(A)返回矩阵里总的元素个数,也就是行数乘列数,3×4的矩阵返回12;length(A)则有点坑,它返回的是最大维度的值,对于3×4矩阵length返回4,如果这个矩阵恰好是100×2,length居然返回100。
我当时就犯过一个大意:要用一个矩阵的行数,结果用length(A)去拿,遇到列数比行数多的情况,行数拿错,后续整个循环全乱套。后来我给自己定了规矩——要行数就用size(A,1),要列数就用size(A,2),要总数就用numel(A),尽量不要用length,只有在明确知道矩阵是向量时才用它。这个习惯看起来小,但能少踩很多坑。
把矩阵想象成一栋房子:行是楼层,列是房间号。只知道“最长的那一排”并不能让你知道房子到底有几层。size才是那个告诉你完整结构的尺子。
2. 矩阵创建:手动输入与快捷函数
2.1 方括号、空格、逗号和分号的使用规则
创建矩阵最直接的方式就是使用方括号。行元素之间用空格或逗号分隔,行与行之间用分号分隔。例如:
A = [1 2 3; 4 5 6; 7 8 9]这个命令生成一个3×3矩阵。如果你想创建行向量,可以写作v = [1 2 3],创建列向量可以写作v = [1; 2; 3],或者写成v = [1 2 3].'。我特别喜欢用分号创建,因为一眼能看出矩阵形状。
这里有一个细节:如果你在命令行运行命令时结尾没有加分号,MATLAB会立刻把结果打印在屏幕上。这在学习时很有用,但如果你用脚本跑一个很大的矩阵,忘记加分号会把整个命令窗口刷爆,运行速度也会变慢。我的习惯是,所有创建矩阵的语句后面都加分号,需要查看时单独运行变量名。
2.2 冒号表达式与linspace:生成等差向量
MATLAB里最灵活的向量生成工具是冒号表达式,基本格式是start:step:end。比如1:2:9生成[1 3 5 7 9];10:-2:2生成[10 8 6 4 2];如果中间不写步长,默认步长为1,比如1:5生成[1 2 3 4 5]。这里有个容易忽略的地方:如果start大于end,而步长又是正数,得到的是空矩阵。我见过不少初学者写5:2:1,结果得到一个空数组,然后在后面索引时直接报错。
如果你需要生成一个固定数量的向量,而不是靠步长计算,可以用linspace。linspace(0,1,5)生成从0到1之间均匀分布的5个点:0 0.25 0.5 0.75 1。这个方法特别适合画图时构造横坐标,比冒号表达式更不容易出错。还有一个类似的logspace,用于生成对数等分的向量,比如做频响分析时常用。我建议初学者优先掌握linspace,因为它不依赖步长的精度计算,数量由你直接指定,思路更直观。
2.3 zeros、ones、eye与随机矩阵:预分配和初始化
创建特殊矩阵的函数非常多,但最核心的四个是zeros、ones、eye和rand。
zeros(m,n)生成m×n全零矩阵;ones(m,n)生成全一矩阵;eye(n)生成n×n单位矩阵,即对角线为1、其余为0;rand(m,n)生成0到1之间均匀分布的随机数矩阵;randn(m,n)生成服从标准正态分布的随机数矩阵。
为什么要单独提zeros?因为它在编程中经常用来“预分配”。如果你要写一个循环把结果逐个存进矩阵,最忌讳的做法是每次循环让矩阵自动增长,比如A(end+1) = x,这样每增加一个元素,MATLAB都要重新分配内存,循环次数一多,运行时间会成倍增加。正确做法是先把矩阵尺寸定好,用zeros初始化,然后循环里直接赋值。比如要存100个计算结果,就预先写result = zeros(100,1),这是MATLAB性能优化的基本功。
2.4 矩阵拼接和重复:cat、repmat与repelem
创建矩阵之后,经常需要把两个矩阵拼在一起。横向拼接可以用[A B],纵向拼接用[A; B],但更规范的写法是用cat函数:
C = cat(1, A, B); % 按行方向拼接 D = cat(2, A, B); % 按列方向拼接cat(1,...)相当于vertcat,cat(2,...)相当于horzcat。对于三维数据,还可以用cat(3,A,B)在第三维上拼接,这在处理多通道图像时很常见。
有时候需要把一个小矩阵重复成大矩阵,这时候用repmat和repelem。repmat(A, 2, 3)把A在行方向重复2次、列方向重复3次,相当于整体复制。repelem(A, 2, 3)则是将每个元素各重复2次和3次。举个例子:
A = [1 2; 3 4]; repmat(A, 1, 2) % 结果:[1 2 1 2; 3 4 3 4] repelem(A, 1, 2) % 结果:[1 1 2 2; 3 3 4 4]两者用途完全不同。需要把一组数据平铺到更大区域时我会用repelem;需要把整个模式复制多份时用repmat。
3. 矩阵索引:访问元素与子区域
3.1 下标索引与线性索引:两个角度访问同一个数
访问矩阵元素有两种方式:下标索引和线性索引。下标索引就是指定行和列,比如A(2,3)表示第2行第3列的元素。线性索引则只用一个数字表示,按“列优先”的顺序数过去。所谓列优先,就是先数完第一列,再数第二列,依次往后。例如:
A = [10 20 30; 40 50 60]; A(3) % 结果是20,因为存储顺序是10, 40, 20, 50, 30, 60很多初学者在这里卡住,觉得A(3)应该是第三个数,也就是“第一行第三列”的30,但实际是20。原因就是MATLAB在内存中把矩阵按列来排列。如果想从行列下标得到线性索引,可以用sub2ind;反向用ind2sub:
linearIndex = sub2ind(size(A), 2, 3); % 求第2行第3列的线性索引 [row, col] = ind2sub(size(A), 5); % 线性索引5对应的行列有人说,用下标不就行了吗,为什么还要理解线性索引?因为很多内置函数返回的位置是线性索引,比如后面要说的find,如果不理解列优先,拿到索引值后根本对应不上元素。
3.2 冒号用法与end关键字:提取行、列和子块
提取矩阵的某一行、某一列,几乎离不开冒号。A(:,1)提取第一列,A(1,:)提取第一行,A(1:2,2:3)提取左上角2×2子块。这里的冒号表示“所有”。end是一个Magic Word,它表示“最后一个索引位置”。比如A(end,:)取最后一行,A(end-1, end)取倒数第二行最后一列。end在循环和动态编程里特别有用。
我记得自己最早写代码时,要往矩阵末尾追加一行,总是先数矩阵有多少行,再赋值。后来发现直接写A(end+1,:) = newRow就行,这个操作会在原矩阵下方新增一行。这种写法不仅简洁,还避免了手动维护行数出错。
3.3 逻辑索引:用条件直接筛选元素
逻辑索引是我觉得MATLAB最优雅的地方之一。你可以用一个真/假矩阵作为索引,只有对应位置为true的元素才会被选中。例如:
A = [1 5 3; 2 8 4]; logicalIndex = A > 4; result = A(logicalIndex);result会返回一个列向量,里面是所有满足大于4的元素。这等价于两步合并成一步:A(A>4)。这个写法在处理数据清洗时极好用,比如把矩阵里所有负数清零,直接写A(A<0)=0。
需要注意:逻辑索引的矩阵维度必须和原矩阵相同,或者维度可以被隐式扩展。逻辑索引的选择结果永远是列向量形式,因为MATLAB会把选出来的元素按线性顺序依次排列。如果你希望保留行列位置,可以先转成下标,也就是用find。
3.4 修改矩阵:赋值、删除与扩展
索引不但可以读,还可以写。A(2,2)=100修改单个元素;A(:,2)=[10;20;30]整列替换;A(2,:)=[]删除第2行,注意这里用空方括号[]作为赋值内容时,MATLAB会直接删掉相应行,同时矩阵行数减一。
删除操作在循环里要格外小心。如果你在for i=1:size(A,1)循环中删除某一行,循环次数不会动态变化,那么跳过某些行或者索引越界几乎是必然的。我的经验是:如果要做逐行筛选删除,最好先通过逻辑索引一次性选出要保留的行,或者倒着循环。比如从后往前删:for i = size(A,1):-1:1,这样索引不会乱。
4. 矩阵运算:数组运算与线性代数运算
4.1 最关键的区分:A*B与A.*B
矩阵运算中最容易混淆的就是乘号。A * B是线性代数中的矩阵乘法,要求A的列数等于B的行数,结果第(i,j)个元素是A第i行与B第j列的点积。A .* B是对应元素相乘,要求A和B维度完全一致,结果矩阵的每个元素是A(i,j)*B(i,j)。
我见过太多新手用*去做对应元素相乘,结果要么维度不匹配报错,要么得到完全错误的结果。记住一个口诀:带点的运算是在说“按位置算”,不带点的乘除法是线性代数里的矩阵运算。这个区分同样适用于除法和幂次:A./B是逐元素相除,A/B是右除;A.^2是每个元素求平方,A^2是矩阵自乘。
从物理意义上想,.*更像是对照片每个像素做亮度调整,*则更像坐标系变换,两者服务于完全不同的场景。
4.2 转置与共轭转置:一个撇关系的两个世界
在MATLAB里,单引号'是共轭转置,点号加单引号.'才是普通的非共轭转置。当矩阵是实数时,两者结果一样;一旦涉及复数,结果就完全不同了。例如:
Z = [1+2i, 3-4i]; Z' % 得到 [1-2i; 3+4i] Z.' % 得到 [1+2i; 3-4i]如果你在做傅里叶变换、滤波器设计这类复数运算,忘记加地方实会用错。建议在没有复数参与的时候,也统一使用.',这样代码更安全,读代码的人也不会误解你的意图。这是一个非常小但很专业的习惯。
4.3 左除和右除:解线性方程组的最佳姿势
很多人初学MATLAB时会找逆矩阵inv(A)来解线性方程Ax=b,书上有时也这么写。但实际做数值计算时,更好用的是左除运算符\:
x = A \ b;它的意思是求解线性方程组A*x = b。MATLAB内部会根据A的特点自动选择高斯消去、LU分解、Cholesky分解等方法,比直接用inv(A)*b更快、更稳定。右除/则对应x*A = b形如的方程,比如x = b / A。
你可能会问,为什么不推荐inv?因为求逆本身计算量更大,而且当矩阵接近奇异时,数值误差会被放大。用\相当于让MATLAB悄悄选一条更安全的路径。我平时几乎不写inv,除非是要在公式里显式写出逆矩阵。
4.4 常用函数速查:从行列式到特征值
矩阵基础必须掌握的函数大概有这么几类:
- 线性代数类:
det行列式、rank秩、trace迹、eig特征值和特征向量、svd奇异值分解、norm范数、cond条件数; - 统计类:
sum求和、mean均值、median中位数、std标准差、max最大值、min最小值、sort排序; - 数组操作类:
diff差分、cumsum累积和、flip翻转、unique去重。
举个例子:
A = [1 2 3; 4 5 6; 7 8 10]; sum(A) % 默认对每一列求和,结果[12 15 19] sum(A, 2) % 对每一行求和,结果[6; 15; 25] max(A, [], 1) % 对每一列取最大值 max(A, [], 2) % 对每一行取最大值注意max和min的写法很特别,想按维度求值时要传一个空数组[]作为第二个参数,否则max(A)会把整个矩阵当成一个向量来比较。这个小细节踩一次就记住了。
5. 矩阵形变与批量处理技巧
5.1 reshape的列优先陷阱与permute的正确用法
reshape可以把矩阵改成任意维度,只要元素总数不变。例如:
A = 1:12; B = reshape(A, 3, 4);结果B是3行4列,但顺序是列优先填充的:第一列是1、2、3,第二列是4、5、6,如此类推。如果你想按行优先的顺序重新排布,必须先对原数据做一些处理。一个常见需求是:你有一个按行采集的信号序列,想把它重排成多行多列,但reshape默认按列排,可能会打乱信号顺序。
这时可以用转置或permute来解决。比如把行向量1:12变成3行4列且按行顺序排列,可以先reshape再转置:
B = reshape(A, 4, 3)';permute是高维版的“转置”,适合处理三维矩阵的维度交换。比如permute(M, [2 1 3])会把前两维互换。对于初学者,我建议先用permute做实验,观察维度变化,理解后再用于更复杂的数据处理。
5.2 用find与逻辑矩阵做批量筛选
在数据处理中,经常需要知道哪些元素满足条件,以及它们的位置。find是解决这个问题的利器。
A = [10 20 30; 40 50 60]; [row, col] = find(A > 25); linearIndex = find(A > 25);不带赋值时,find(A > 25)返回满足条件的线性索引向量。如果你写成[row, col] = find(...),则返回行列下标。这个行为与逻辑索引互补:逻辑索引直接给你值,find给你位置,两者结合可以高效地对满足条件的位置做修改。
比如你想把矩阵中所有大于100的替换为100,可以直接用A(A>100)=100;但如果你想以某个特定顺序逐个修改这些位置,那就需要先拿到线性索引,再循环处理。
5.3 隐式扩展:自动广播维度的新特性
从R2016b开始,MATLAB支持隐式扩展,让不同维度的矩阵在操作时自动扩展。比如:
A = [1 2 3; 4 5 6]; b = [10; 20]; C = A + b;这里A是2×3,b是2×1。按照老版本规则,维度不一致会报错;新版本里b会沿着列方向自动复制成2×3,然后再相加。同理,A - [1 2 3]会把行向量自动复制到每一行。
这个功能很方便,但也带来隐患:如果代码里有维度不匹配的bug,不再报错,而是默默做广播,产生一个很大但很蠢的结果。我的建议是:利用隐式扩展时,务必先用size确认原来矩阵的形状,特别是做矩阵和向量混合运算时,最好心里明确哪个维度会被扩展。
5.4 利用all、any和逻辑运算简化判断
当你想检查矩阵中是否所有元素满足条件、是否存在某些元素满足条件,all和any是必用的。
A = [1 2 3; 4 5 6]; all(A > 0) % 返回 [1 1 1],因为每列所有元素都大于0 any(A > 5, 2) % 对每一行判断,返回 [0; 1]配合方向参数,可以处理行和列两个维度。比如你想筛选出所有行中至少含有一个大于5的行,可以用:
rows = any(A > 5, 2); selected = A(rows, :);这种写法比循环简洁得多,也是“向量化思维”的典型例子。矩阵基础学得好不好,一个很重要的标志就是你写代码时还会不会动不动就写for循环。
6. MATLAB矩阵编程常见问题与避坑
6.1 索引越界和维度不一致
我见过最多的报错就是“Index exceeds the number of array elements”和“Matrix dimensions must agree”。索引越界通常是因为你用了固定大小去访问动态矩阵,比如在循环里只判断了行数,没判断列数。解决这类问题的关键是每次操作前想清楚矩阵当前的实际大小,可以用size、numel确认。
维度不一致则多发生在点运算时,两个矩阵需要对位相乘,结果一个2×3,一个3×2,直接报错。遇到这种情况,我通常会立刻检查两个矩阵的size输出,重点看是不是转置忘了写'。这类错误在脚本运行中很常见,但定位并不难,难的是你是否养成了先查看维度的习惯。
6.2 循环中动态增长矩阵导致性能骤降
一个反例:
x = 1:10000; y = []; for i = 1:length(x) y(i) = x(i)^2; end这段代码本身没有错,但每次循环都要给y重新分配内存,运行会特别慢。正确的写法是:
y = zeros(size(x)); for i = 1:length(x) y(i) = x(i)^2; end更好的是直接向量化:
y = x.^2;用tic和toc测一下,你会发现向量化比循环快几个数量级。这不是鼓励初学者一开始就拒绝循环,而是提醒你:当数据量大了,矩阵化思维才是MATLAB的看家本领。
6.3 浮点数比较别用等号
由于浮点数的二进制表示有误差,A中某个数值即使看起来等于0.1,实际存储可能是0.10000000000000001。因此判断矩阵中哪些元素等于某个特定小数时,不建议用A == 0.1,而应该用:
tol = 1e-8; A(abs(A - 0.1) < tol) = 0;这个技巧在做数值计算时非常重要,尤其是在处理迭代法收敛判断、滤波器系数比较时。直接等号比较往往找不齐目标元素,或者误判数值,导致结果偏离预期。
6.4 变量名与内置函数冲突
矩阵基础知识虽然简单,但有一个特别隐蔽的坑:变量名会覆盖函数名。比如你定义一个变量叫zeros,之后调用zeros(3,3)就会出错。因为MATLAB解析变量名的优先级高于函数。我的建议是永远不用内置函数名作为变量名,比如不要用sum、max、mean、size。写代码时可以先运行which size检查某个名字是否被占用。
还有一个容易忽略的点:MATLAB对大小写敏感。A和a是两个不同的变量。在长脚本里,大小写混乱很容易导致“Undefined function or variable”错误。统一驼峰或小写命名,能减少这类问题。
6.5 分号、清屏与脚本调试的细节
命令窗口直接执行语句时,如果想看到结果,就不加分号;如果想隐藏结果,就加分号。在脚本编辑器中,建议默认加,避免中间过程输出刷屏。
调试矩阵相关代码时,我通常会这样操作:在关键行前面去掉分号运行一次,或者用disp打印size(A);也可以用whos查看所有变量的大小和类型,这个命令比who更详细。如果发现某个变量维度不对,可以用clear A单独清除它,而不是动不动就clear all,否则脚本里其他变量全没了,前功尽弃。
最后再分享一个小技巧:在命令窗口输入doc size或者help reshape,能直接打开官方文档,里面有很多示例,比网上搜到的二手答案靠谱得多。我当年学矩阵基础时就是靠“遇到不懂就doc”这个习惯,把repmat、reshape、logical indexing这些概念真正用熟的。矩阵基础这块内容看似琐碎,但它是后续所有MATLAB应用的地基,哪怕你后面去做图像处理、数值分析、深度学习,理解矩阵怎么存、怎么取、怎么算,永远绕不开。