这一章在干嘛?
MATLAB = Matrix Laboratory,本章讲的就是它的灵魂:怎么创建向量(Vector)和矩阵(Matrix)、怎么访问和修改元素、怎么翻转变形,以及矩阵乘法与逐元素运算的区别。这一章学扎实,你就能理解为什么 MATLAB 写矩阵运算比 C 语言简洁十倍——后面所有章节都建立在它之上。
2.1 向量与矩阵:创建与索引
2.1.0 一切皆矩阵
MATLAB 里所有数据都按矩阵组织。一个 的矩阵就是 行 列的数值表;它还包涵几个特例:
- 标量(scalar):单个数,
- 列向量(column vector):,竖着排
- 行向量(row vector):,横着排
图 2.1:从左到右依次是标量(1×1)、列向量(3×1)、行向量(1×4)和矩阵(2×3)——标量和向量本质上都是矩阵的特例
所以严格说,MATLAB 里没有「单独的向量类型」,向量和标量只是特殊的矩阵。C 语言里的一维数组对应这里的向量,二维数组对应矩阵。矩阵里存的每个值叫元素(element)。
2.1.1 创建行向量
最直接的方法:把值放进方括号,用空格或逗号分隔:
>> v = [1 2 3 4] % 空格分隔
v =
1 2 3 4
>> v = [1,2,3,4] % 逗号分隔,结果完全一样但最常用的武器是冒号运算符(colon operator)——生成等差数列:
>> vec = 2:6 % 从 2 到 6,步长默认 1
vec =
2 3 4 5 6
>> nv = 1:2:9 % 起始:步长:结束
nv =
1 3 5 7 9注意两个细节:步长超了上限就停(1:2:6 得 [1 3 5]);步长可以是负数,生成递减序列:9:-2:1 得 [9 7 5 3 1]。冒号运算符造出的向量用得极多,尤其是后面画函数图时:x = 0:0.1:2*pi; y = sin(x); 两行就画出一条正弦曲线——这在 C 里是一个完整的循环加一条绘图库调用。
练一练:只用冒号运算符,写出创建下列向量的表达式。
| 目标向量 | 答案 |
|---|---|
2 3 4 5 6 7 | 2:7 |
1.1 1.3 1.5 1.7 | 1.1:0.2:1.7 |
8 6 4 2 | 8:-2:2 |
-5 -4 -3 -2 -1 | -5:-1 |
步长不限于整数,0:0.2:1 这样的「小数步长」完全合法,画图时天天用。
指定元素个数而不是步长时,用 linspace:
>> ls = linspace(3, 15, 5) % 3 到 15 之间均匀取 5 个点(含两端)
ls =
3 6 9 12 15linspace(x,y,n) 的 n 不写时默认 100 个点——画函数曲线时特别顺手。对数刻度的版本是 logspace(x,y,n),在 到 之间取 n 个点。
已有的向量还能拼接(concatenating)出新向量:
>> newvec = [nv ls]
newvec =
1 3 5 7 9 3 6 9 12 152.1.2 引用与修改元素
向量的元素从 1 开始编号(不是 C 的 0!),编号叫**索引(index)**或下标(subscript)。用「变量名(索引)」访问:
>> newvec(5) % 第 5 个元素
ans =
9
>> b = newvec(4:6) % 第 4~6 个元素,取出仍是向量
b =
7 9 3
>> newvec([1 10 5]) % 索引不必连续!按 [1 10 5] 的顺序取
ans =
1 15 9[1 10 5] 这样的索引数组叫索引向量(index vector),可以任意乱序、重复取元素——这是 C 里要写循环才能干的事。
练一练:已知 vec = 3:3:12(即 [3 6 9 12]),下列表达式各得什么?
vec(2)→ 6vec([3 1])→[9 3](顺序按索引向量走)vec(2:3)→[6 9]vec(5)→ 报错:索引超出数组范围
从 C 语言数组过来的三个不同
- 下标从 1 开始,不是 0。
v(1)是第一个元素;v(0)直接报错。- 不检查会变报错:越界访问(如 5 个元素的向量取
v(6))在 C 里是未定义行为(可能悄悄读出垃圾值),在 MATLAB 里会明确报错「Index exceeds…」——烦人但救命。- 负数下标没有「倒数」语义:Python 的
v[-1]是最后一个元素,MATLAB 里负数下标直接报错,倒数要用end(见 2.1.5 节)。
修改元素直接赋值;甚至可以给尚不存在的索引赋值来扩展向量:
>> rv = [3 55 11];
>> rv(4) = 2
rv =
3 55 11 2
>> rv(6) = 13 % 中间跳空的元素自动补 0
rv =
3 55 11 2 0 13为什么说「扩展向量」是坏习惯?
MATLAB 数组在内存中连续存放,每次加长都可能触发「重新分配内存 + 整体搬家」。在循环里反复扩展大向量,程序会明显变慢。好习惯是一次性分配好尺寸(如
zeros(1,n)),或用后面学的向量化写法。
2.1.3 创建列向量与矩阵
列向量用分号分隔元素:
>> c = [1; 2; 3; 4]
c =
1
2
3
4冒号运算符造出来的是行向量,但任何行向量都能用转置运算符——单引号 '——变成列向量(行列互换):
>> r = 1:3;
>> c = r'
c =
1
2
3矩阵就是「行内用空格/逗号分隔、行间用分号分隔」:
>> mat = [4 3 1; 2 5 6]
mat =
4 3 1
2 5 6行里也能嵌套冒号表达式;每行的元素个数必须相同,否则报错:
>> mat = [2:4; 3:5] % 两行各 3 个元素
mat =
2 3 4
3 4 5
>> mat = [3 5 7; 1 2]
Error using vertcat
Dimensions of matrices being concatenated are not consistent.随机矩阵和特殊矩阵用函数生成:
>> rand(2) % 2×2 随机实数矩阵
>> rand(1,3) % 1×3:先行数后列数
>> randi([5,10], 2, 3) % 2×3 的随机整数矩阵,元素在 5~10
>> zeros(3) % 3×3 全 0(方阵:一个参数行=列)
>> ones(2,4) % 2×4 全 1预分配内存的惯用法
zeros/ones除了造特殊矩阵,最重要的用途是预分配(preallocating):先造好目标尺寸的矩阵再往里填数,比一点点扩展快得多。MATLAB 只有 zeros 和 ones,没有 twos——想要全 10 的矩阵?ones(2,3)*10即可,这是下一节「标量运算」的伏笔。
2.1.4 矩阵的索引
矩阵元素用「行下标, 列下标」访问(永远行在前),这叫下标索引(subscripted indexing):
>> mat = [2:4; 3:5];
>> mat(2,3) % 第 2 行第 3 列
ans =
5
>> mat(1:2,2:3) % 子块:第 1~2 行、第 2~3 列
ans =
3 4
4 5
>> mat(1,:) % 第 1 行整行(: 代表"全部")
ans =
2 3 4
>> mat(:,2) % 第 2 列整列
ans =
3
4单独一个冒号「取整行 / 整列」是日常最高频操作,务必形成肌肉记忆。
如果只给一个索引,MATLAB 会按列优先顺序(column major order)把矩阵拉直——先数完第 1 列再数第 2 列,这叫线性索引(linear indexing):
>> intmat = [100 77; 28 14]
intmat =
100 77
28 14
>> intmat(1) % 第 1 列第 1 个
ans =
100
>> intmat(2) % 第 1 列第 2 个
ans =
28
>> intmat(3) % 第 1 列数完,才轮到第 2 列第 1 个
ans =
77为什么是列优先?因为 MATLAB 内部就是按列连续存储矩阵的(这继承自老牌数值计算语言 Fortran,而 C 是按行存)。知道这一点,你就明白为什么 sum、max 这些函数也默认按列处理——语言的设计一以贯之。
能用,但可读性差、易出错,实际写代码请坚持用行列下标。
修改与扩展:
>> mat(1,2) = 11 % 改单个元素
>> mat(2,:) = 5:7 % 整行替换(长度必须匹配)
>> m(2:3,3:5) = 3 % 标量可以"广播"到任意子块
>> mat(:,4) = [9 2]' % 追加一整列 → 矩阵变宽
>> mat(4,:) = 2:2:8 % 追加一整行,中间跳空的行补 0注意:矩阵不能只加单个元素(行会不齐),只能整行整列地加。
2.1.5 尺寸:length、size、numel 与 end
| 函数 | 返回 |
|---|---|
length(v) | 向量元素个数;矩阵则取行数与列数中较大者 |
size(mat) | [行数 列数];可用两个变量分别接住 |
numel(mat) | 元素总数(行 × 列) |
>> vec = -2:1;
>> length(vec)
ans =
4
>> mat = [1:3; 5:7]'; % 转置成 3×2
>> [r, c] = size(mat) % 一次返回两个值,用"变量向量"接住!
r =
3
c =
2
>> numel(mat)
ans =
6[r, c] = size(mat) 展示了 MATLAB 两个独门绝技:函数可以一次返回多个值,赋值号左边可以放变量列表。C 语言里要靠指针参数才能做到的事,这里一行搞定。
想造一个「和 mat 同尺寸的全零矩阵」?把函数嵌起来:zeros(size(mat))——size 的返回值直接喂给 zeros,不用中间变量。这种**嵌套调用(nesting calls)**是 MATLAB 的常见风格:函数返回的值可以直接当另一个函数的参数。
关键字 end 指代「最后一个」索引,免去查询尺寸:
>> mat = [1 4; 2 5; 3 6];
>> mat(end,1) % 最后一行第 1 列 → 3
>> mat(2,end) % 第 2 行最后一列 → 5
>> vec = 9:-2:1;
>> vec(end) % 向量的最后一个元素 → 1end 还能参与算术:vec(end-1) 是倒数第二个元素。注意 end 只能当下标用,出现在别处会报错。
练一练:对 mat = [1 2 3; 4 5 6](2×3),以下各取到什么?
mat(2,3)→ 6mat(1,:)→[1 2 3](整个第 1 行)mat(:,end)→[3; 6](整个最后一列)mat(3)→ 2(线性索引按列数:第 1 列是 1、4,第 2 列开头是 2)numel(mat)→ 6
常见坑:别把尺寸写死
初学者爱写
vec(10)、循环for i = 1:10。一旦数据变成 20 个元素就全崩。通用写法:向量长度用length(vec)或numel(vec),矩阵用[r,c] = size(mat),末尾元素用end。让代码对任意尺寸都成立,是编程风格指南反复强调的原则。
2.1.6 改变形状:reshape / flip / rot90 / repmat
这组函数只改变「元素的摆放方式」,不改变元素本身:
>> mat = randi(100, 3, 4) % 3×4,共 12 个元素
>> reshape(mat, 2, 6) % 变成 2×6,按列优先重排
>> fliplr(mat) % 左右翻转(列序颠倒)
>> flipud(mat) % 上下翻转(行序颠倒)
>> rot90(mat) % 逆时针旋转 90°
>> rot90(mat, 2) % 旋转 180°(第二个参数是 90° 的倍数)
>> rot90(mat, -1) % 负数 = 顺时针reshape 按列优先顺序重排:原第 1 列的元素先填进新矩阵,再填第 2 列……
>> mat = randi(100, 3, 4)
mat =
14 61 2 94
21 28 75 47
20 20 45 42
>> reshape(mat, 2, 6) % 12 个元素重排成 2×6
ans =
14 20 28 2 45 47
21 61 20 75 94 42对照着看:新矩阵第 1 列的 14、21 正是原矩阵第 1 列的前两个,20 被顺延——列优先顺序「流」进新形状。只要元素总数一致,3×4 可以重排成 2×6、6×2、4×3、1×12、12×1 任意形状。另外注意 reshape(mat,2,6) 不改变 mat 本身,只返回新矩阵;想保存结果要写 mat = reshape(mat,2,6)。
另外还有两个「复制粘贴」函数,注意区分:
repmat(mat, m, n):把整个 mat 当积木,平铺成 块;repelem(mat, m, n):把每个元素各自复制 遍。
>> intmat = [50 34; 96 59];
>> repmat(intmat,2,2) % 4×4:[intmat intmat; intmat intmat]
>> repelem(intmat,2,2) % 4×4:每个数都变成 2×2 的小方块2.1.7 空向量:删除元素的钥匙
[] 是空向量(empty vector),长度为 0。它有个妙用——删除元素:
>> vec = 4:8;
>> vec(3) = [] % 删掉第 3 个元素
vec =
4 5 7 8
>> vec = 3:10;
>> vec(2:4) = [] % 一次删一段
vec =
3 7 8 9 10矩阵则只能整行整列地删(mat(:,2) = [] 删第 2 列),单个元素删了行就不齐,会报错。
空向量也能从零攒出一个向量:evec = []; evec = [evec 4]; evec = [evec 11];……能跑,但效率低,能免则免。
三维矩阵是怎么回事?
MATLAB 矩阵不限于二维。把每个二维矩阵想成一「页」,三维矩阵就是一叠页,索引多一维:
mat(:,:,1) = 第一页; mat(:,:,2) = 第二页;,size会返回三个数。zeros(2,4,3)直接造 的三维矩阵。处理彩色图像(第 13 章)时 RGB 三个通道就是这么存的。
看一个真实的构建过程:先用 reshape 造两页 3×5 的矩阵,再把它们摞成一叠:
>> layerone = reshape(1:15,3,5)
layerone =
1 4 7 10 13
2 5 8 11 14
3 6 9 12 15
>> layertwo = fliplr(flipud(layerone)) % 旋转 180° 作为第二页
>> mat(:,:,1) = layerone;
>> mat(:,:,2) = layertwo;
>> size(mat)
ans =
3 5 2三个下标依次是行、列、页。日常计算 99% 的时间在二维世界,三维矩阵知道「存在、怎么建、size 返回三个数」即可,等学到图像处理再深入。
2.2 把向量矩阵喂给函数
MATLAB 的函数天然「吃」整个数组:传进去一个向量,它对每个元素逐一处理,返回同尺寸的结果——这就是别的语言里要写循环才实现的「遍历」:
>> vec = -2:1;
>> abs(vec) % 对每个元素取绝对值
ans =
2 1 0 1
>> sign([0 4 -3; -1 0 2]) % 矩阵同理
ans =
0 1 -1
-1 0 1另一类函数「压缩」整个向量成一个数:min、max、sum、prod:
>> vec1 = 1:5;
>> sum(vec1) % 1+2+3+4+5
ans =
15
>> prod([3 5 8 2]) % 3×5×8×2
ans =
240
>> min([3 5 8 2]) % 最小值 → 3
>> max([3 5 8 2]) % 最大值 → 8「累计版」cumsum / cumprod 则保留每一步的中间结果,结果和输入同长度——「running sum(滚动和)」:
>> cumsum(1:5) % 1, 1+2, 1+2+3, ...
ans =
1 3 6 10 15
>> cumprod(1:5) % 1, 1×2, 1×2×3, ...
ans =
1 2 6 24 120cumsum 的最后一个元素永远等于 sum 的结果——这个性质可以用来互相验证。R2014b 之后这些累计函数还支持 'reverse' 选项(从尾往前累计)以及 cummin / cummax 变体。
diff 函数算相邻元素的差,结果比原向量短 1(对矩阵同样按列):
>> diff([4 7 15 32])
ans =
3 8 17
>> diff([4 7 2 32])
ans =
3 -5 30diff 结果里出现负数,说明信号在这里「往下走」——差分是数值求导的基础,判断单调性、找峰值都靠它。
对矩阵,这些压缩型函数默认按列操作——sum(mat) 返回一个行向量,每项是 mat 对应列的和:
>> mat = [3 16 1 14 8; 9 20 17 16 14; 19 14 19 15 4];
>> sum(mat)
ans =
31 50 37 45 26 % 每列的和
>> max(mat)
ans =
19 20 19 16 14 % 每列的最大值
>> sum(mat') % 转置一下 = 按行求和
ans =
42 76 71
>> max(max(mat)) % 嵌套调用 = 全矩阵最大值
ans =
20cumsum 对矩阵也按列累计,结果与输入同尺寸:结果矩阵的第 2 行 = 原第 1 行 + 第 2 行,第 3 行再叠加。
记住口诀:「函数按列吃矩阵,转置即按行,嵌套得整体」。新版 MATLAB 也可以写 max(mat, [], 2) 显式指定按行,但原书年代以转置法为主,两种都值得认识。
2.3 标量运算与逐元素数组运算
标量运算
一个标量与向量 / 矩阵做加减乘除,自动作用到每一个元素上(标量乘法 / 除法):
>> v = [3 7 2 1];
>> v * 3
ans =
9 21 6 3
>> v / 2
ans =
1.5000 3.5000 1.0000 0.5000
>> ones(1,5) * 10 % 造全 10 向量的惯用法
ans =
10 10 10 10 10
>> zeros(2) + 10 % 2×2 全 10 矩阵
ans =
10 10
10 10这解决了 2.1.3 节的伏笔:MATLAB 没有造「全 10 矩阵」的函数,因为根本不需要——ones / zeros 加一个标量乘法或加法就能得到任何常数值的矩阵。顺带一提,给矩阵每个元素都加同一个数(如 mat + 1)在数学上并不严谨(矩阵只能加同尺寸矩阵),但 MATLAB 约定标量「广播」到所有元素,好用记牢。
练一练(原书 Practice 2.3 变体):对行向量 v = 1:8 与 2×4 矩阵 mat = [1:4; 5:8]:
- 每个元素减 3:
v - 3,得[-2 -1 0 1 2 3 4 5] - 每个元素除以 3:
mat / 3,得一个 2×4 的小数矩阵 - 每个元素平方:
mat .^ 2——注意mat ^ 2会报错,因为 2×4 不是方阵 - 想把 mat 的每个元素翻倍后存回 mat:
mat = mat * 2(标量乘法,*不用加点)
数组运算:那个要命的小点
两个同尺寸数组逐元素做加减乘除,叫数组运算(array operations)。加减速战速决(+、- 直接用);但一切基于乘除乘方的运算符必须加一个点,变成数组运算符 .* ./ .^ .\:
>> v1 = [2 3 4 5]; v2 = [33 11 5 1];
>> v1 + v2
ans =
35 14 9 6
>> v1 .* v2 % 逐元素相乘
ans =
66 33 20 5
>> v = [3 7 2 1];
>> v.^2 % 每个元素自己平方
ans =
9 49 4 1
>> v^2 % 不加点试试?
Error using ^
Inputs must be a scalar and a square matrix.
To compute elementwise POWER, use POWER (.^) instead.不加点的 * 是矩阵乘法(2.5 节),要求内维匹配,跟「逐元素」完全是两码事。
练一练:设 v = [3 7 2 1],写出完成下列任务的语句。
- 每个元素减 3 →
v - 3(标量运算,不用点) - 每个元素除以 2 →
v / 2 - 每个元素自己平方 →
v.^2(必须有点!) - 两个同长向量
v1、v2对应元素相乘 →v1 .* v2(必须有点!)
实战:算工资。一个向量里按顺序存着员工的工时和时薪(员工 1 的工时、员工 1 的时薪、员工 2 的工时……),如何用几行代码算出每人的总工资?
>> v = [33 10.5 40 18 20 7.5]; % 工时、时薪交替存放
>> hours = v(1:2:end) % 奇数位置:工时
hours =
33 40 20
>> rates = v(2:2:end) % 偶数位置:时薪
rates =
10.5000 18.0000 7.5000
>> pay = hours .* rates % 逐元素相乘 = 每人总工资
pay =
346.5000 720.0000 150.0000v(1:2:end) 这种「冒号 + end」的花式下标,加上 .*,三行就完成了 C 里一个带循环的完整小程序——这正是 MATLAB 的味道。
再来看一个「压缩型函数」的经典应用——调和级数求和:
C 的思路是循环累加;MATLAB 的思路是「先造向量,再一步求和」:
>> n = 100;
>> sum(1 ./ (1:n)) % 对向量 1~n 逐元素取倒数再求和
ans =
5.18741 ./ (1:n) 里的点不可少——这是「逐元素除法」。把数学式子直接翻译成向量运算,是 MATLAB 编程的核心手感。
练一练:只写一行,求 。(答案:sum([3 5 7 9] ./ [1 2 3 4]))
常见坑:忘写点 = 报错或错值
v^2、a.*b少个点,是 MATLAB 新手最高频的错误。好消息是新版报错信息会直接提示你「要不要用 .^」;坏消息是有时不报错(恰好满足矩阵乘法维度时),你会得到一个语义完全不同的结果且毫无警觉。写「逐元素」运算时,先问自己:这个符号前面有点吗?
2.4 逻辑向量:MATLAB 的筛选魔法
2.4.1 关系表达式作用于数组
第 1 章的关系运算符可以直接用在向量上,结果是同长度的 0/1 逻辑向量:
>> vec = [5 9 3 4 6 11];
>> isg = vec > 5 % 每个元素分别与 5 比较
isg =
1×6 logical 数组
0 1 0 0 1 1
>> sum(isg) % 逻辑向量求和 = 数"真"的个数
ans =
3逻辑向量的杀手级用法是逻辑索引(logical indexing)——把它塞进另一向量的下标里,只取出「对应位置为真」的元素:
>> vec(isg) % 只要 >5 的那些元素
ans =
9 6 11一行代码完成 C 语言里「循环 + if 判断 + 逐个拷贝」三步的工作。这就是 MATLAB 引以为傲的**向量化(vectorized)**思想雏形(第 5 章正式展开)。
逻辑索引的组合套路非常多,多到值得单独列一张「常用查询」表。设 vec 为数据向量:
| 想要什么 | 一行代码 |
|---|---|
| 大于 5 的元素有几个 | sum(vec > 5) |
| 取出所有偶数 | vec(mod(vec, 2) == 0) |
| 把负数全部换成 0 | vec(vec < 0) = 0 |
| 大于 5 的元素的平均值 | mean(vec(vec > 5)) |
| 有没有任何元素是负数 | any(vec < 0) |
| 是否所有元素都为正 | all(vec > 0) |
体会到美妙之处了吗:sum 数逻辑向量的真个数、「条件表达式直接当下标用」、统计函数(mean)接在逻辑索引后面——这些积木块自由组合,几乎不用写循环。
常见坑:double 的 0/1 不能当逻辑索引用
>> v = [0 1 0 0 1 1]; % 这是 double,不是 logical! >> vec(v) Subscript indices must either be real positive integers or logicals.看起来一样的 0/1,类型不同待遇天差地别。用
logical(v)转换后才能索引。反过来,造全真 / 全假的矩阵用true(1,5)、false(2),比logical(ones(...))地道。
2.4.2 逻辑函数三剑客与找位置
any(v):只要有一个元素为真就返回真;all(v):全部为真才返回真;find(条件):返回满足条件的元素的下标(不是值!):
>> vec = [5 3 6 7 2];
>> find(vec > 5) % 哪些元素 >5?→ 返回下标 3 和 4
ans =
3 4比较两个数组是否完全相等,不要用 all(a == b) 硬凑,内置 isequal 一步到位,且尺寸不同时返回 false 而不是报错:
>> vec1 = [1 3 -4 2 99]; vec2 = [1 2 -4 3 99];
>> vec1 == vec2 % 逐元素比较
ans =
1 0 1 0 1
>> isequal(vec1, vec2)
ans =
logical
0isequal 对字符串同样适用(字符串本质上就是字符向量):isequal('hello', 'hello') 返回 1。而 == 比较两个不同长度的数组会直接报错,isequal 则安然返回 0——批量校验数据时更稳。
find 的另一半用途是「找位置再动手」。比如删掉所有负数的另一种写法:先用 find(vec < 0) 拿到负数下标,再 vec(neg) = [] 删除。不过更地道的做法是直接用逻辑索引 vec(vec < 0) = [],省掉中间变量——两条路都认识,读别人的代码不慌。
实战:删掉向量里的负数——两种等价写法,第二种更地道:
>> vec = [11 -5 33 2 8 -4 25];
>> neg = find(vec < 0);
>> vec(neg) = [] % 法一:先找下标再删
>> vec(vec < 0) = [] % 法二:逻辑索引直接删
vec =
11 33 2 8 25实战:找信号过零点——信号分析常见需求「信号从正变负或从负变正的位置」,用 sign + diff + find 三连:
>> vec = [0.2 -0.1 -0.2 -0.1 0.1 0.3 -0.2];
>> dsv = diff(sign(vec)) % 符号序列的相邻差
dsv =
-2 0 0 2 0 -2
>> find(dsv ~= 0) % 差不为 0 处 = 过零点位置
ans =
1 4 6即过零发生在第 1-2、4-5、6-7 个元素之间。
这个例子值得咀嚼:它把三个「小」函数串成一条流水线——sign 把波形压成 ±1,diff 找符号跳变,find 定位跳变位置。学会用简单函数组合代替复杂循环,是从「会用 MATLAB」到「精通 MATLAB」的分水岭。
2.4.3 数组版的与或:& 和 |
第 1 章的 &&、|| 只能用于标量;对数组逐元素做逻辑运算要用单字符版 &(与)、|(或):
>> v1 = logical([1 0 1 1]); v2 = logical([0 0 1 0]);
>> v1 & v2
ans =
1×4 logical 数组
0 0 1 0
>> v1 | v2
ans =
1×4 logical 数组
1 0 1 1
>> v1 && v2 % 报错!&& 不吃数组
Operands to the || and && operators must be convertible
to logical scalar values.优先级方面记住一条:& 高于 |,二者都高于标量的 &&、||,全部低于关系运算符——拿不准就加括号。
2.5 矩阵乘法
2.5.1 规则:内维相等,外维定型
矩阵乘法不是逐元素相乘——它是线性代数里那个有严格定义的运算。相乘的前提:A 的列数必须等于 B 的行数(「内维」相等);结果的尺寸取「外维」:
结果元素的定义: 等于 A 的第 i 行与 B 的第 j 列对应元素乘积之和:
记不住公式没关系,记画面:「拿 A 的一行,横扫 B 的一列,对应相乘再求和」,结果填进 C 的第 i 行第 j 列。看一个完整例子(A 是 2×3,B 是 3×4,内维都是 3,可乘;C 是 2×4):
其中 ,。MATLAB 里就是一个 * 号:
>> A = [3 8 0; 1 2 5];
>> B = [1 2 3 1; 4 5 1 2; 0 2 3 0];
>> C = A*B
C =
35 46 17 19
9 22 20 5注意 B*A 不合法(B 的列数 4 ≠ A 的行数 2)——矩阵乘法不满足交换律,顺序有讲究。它和逐元素的 .* 完全是两种运算:即使两个矩阵同尺寸、两种乘法都能算,结果也截然不同。练一练:对 、,手算 A.*B、A*B、B*A 三个结果,再上机验证——你会看到三个互不相同的答案。
什么时候会真的用到?
解线性方程组 的解就是
A\b(左除,内部集成高斯消元);图形学的坐标变换、神经网络的前向传播,本质都是矩阵乘法。C 语言里三层 for 循环的活儿,这里是乘法运算符——而且「内维相等」的维度检查相当于白送的正确性验证。
2.5.2 向量乘法、点积与叉积
向量是特殊矩阵,上面的规则原样适用,但有两个「方向相反」的乘法值得专门看。设行向量 (1×4)、列向量 (4×1):
- (内维 4=4)→ 结果是 1×1 标量,这就是点积(dot product,又称内积):
- → 结果是 4×4 矩阵。同样的两个向量,换个顺序,结果从标量变矩阵!
>> c = [5 3 7 1]';
>> r = [6 2 3 4];
>> r*c
ans =
61
>> c*r % 变成 4×4 矩阵
ans =
30 10 15 20
18 6 9 12
42 14 21 28
6 2 3 4点积太常用(算向量夹角、信号相关、加权求和都靠它),MATLAB 给了专门的 dot 函数——本质上就是 row * col';三维向量的叉积(cross product,又称外积)(力学里求力矩、求平面法向量)也有 cross:
>> vec1 = [4 2 5 1]; vec2 = [3 6 1 2];
>> vec1 * vec2' % 转置成列向量再做矩阵乘法 = 点积
ans =
31
>> dot(vec1, vec2) % 等价写法
ans =
31
>> cross([4 2 5], [3 6 1]) % 叉积只对三维向量有定义
ans =
-28 11 18
.*、dot、c*r一分钟辨析
v1 .* v2:逐元素相乘,结果是同长度的向量;v1 * v2'或dot(v1,v2):对应相乘再求和,结果是一个标量;col * row:结果是 n×n 矩阵。 三者的操作数长度都相同,结果形态却完全不同,考试和工程里都爱在这里挖坑。
本章函数速查表
| 类别 | 函数 |
|---|---|
| 创建 | : 冒号、linspace、logspace、zeros、ones、rand、randi、true、false |
| 尺寸 | length、size、numel、end |
| 变形 | ' 转置、reshape、fliplr、flipud、flip、rot90、repmat、repelem |
| 统计(按列) | min、max、sum、prod、cumsum、cumprod、diff |
| 逻辑 | any、all、find、isequal |
| 向量专用 | dot(点积)、cross(叉积) |
本章通关标准:
- 能用方括号、冒号运算符、linspace 三种方式创建向量,并能解释
1:2:6、9:-2:1、linspace(3,15,5)各得到什么。- 给任意矩阵,能口算
mat(2,3)、mat(1,:)、mat(:,end)、线性索引mat(3)各取到哪个元素,并坚持用下标索引而非线性索引。- 能说清
.*与*的区别、&与&&的区别,并用一行逻辑索引写出「删掉向量中所有负数」。
1.
length和size该用哪个?对 3×5 矩阵各返回什么?矩阵用
size:[r,c] = size(mat)得 r=3、c=5。length返回较大的维度(此处 5),语义模糊容易埋雷,一般只对向量用length(或统一用numel拿元素总数)。
2.
repmat(intmat,2,2)和repelem(intmat,2,2)有什么区别?
repmat把整个矩阵当一块积木平铺 2×2 份;repelem把每个元素各自复制成 2×2 的小块。前者是「复印整页」,后者是「把每个字放大成块」。
3. 为什么
vec([0 1 0 0 1 1])报错而vec(logical([0 1 0 0 1 1]))可以?方括号里写出来的 0 和 1 默认是 double 类型,MATLAB 只允许逻辑类型的 0/1 做掩码索引。
logical()转换后才是 logical 数组,可用于逻辑索引。
4.
sum(mat)对矩阵求的是什么?如何求每行的和与全矩阵总和?
sum(mat)默认按列求和,得到 1×c 行向量。求每行的和:sum(mat, 2)或sum(mat')(转置后按列);求总和:sum(sum(mat))或sum(mat(:))。
5. A 是 2×3,B 是 3×4,
A*B和A.*B哪个合法?
A*B合法:矩阵乘法要求内维相等(都是 3),结果 2×4。A.*B非法:逐元素乘法要求两数组同尺寸,2×3 与 3×4 不满足。(反之若两个矩阵同尺寸,两种乘法都合法但结果完全不同。)