这一章在干嘛?

MATLAB = Matrix Laboratory,本章讲的就是它的灵魂:怎么创建向量(Vector)和矩阵(Matrix)、怎么访问和修改元素、怎么翻转变形,以及矩阵乘法与逐元素运算的区别。这一章学扎实,你就能理解为什么 MATLAB 写矩阵运算比 C 语言简洁十倍——后面所有章节都建立在它之上。

2.1 向量与矩阵:创建与索引

2.2 把向量矩阵喂给函数

2.3 标量运算与逐元素数组运算

2.4 逻辑向量:MATLAB 的筛选魔法

2.5 矩阵乘法

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 72:7
1.1 1.3 1.5 1.71.1:0.2:1.7
8 6 4 28:-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    15

linspace(x,y,n) 的 n 不写时默认 100 个点——画函数曲线时特别顺手。对数刻度的版本是 logspace(x,y,n),在 之间取 n 个点。

已有的向量还能拼接(concatenating)出新向量:

>> newvec = [nv ls]
newvec =
     1     3     5     7     9     3     6     9    12    15

2.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]),下列表达式各得什么?

  1. vec(2) → 6
  2. vec([3 1])[9 3](顺序按索引向量走)
  3. vec(2:3)[6 9]
  4. vec(5) → 报错:索引超出数组范围

从 C 语言数组过来的三个不同

  1. 下标从 1 开始,不是 0。v(1) 是第一个元素;v(0) 直接报错。
  2. 不检查会变报错:越界访问(如 5 个元素的向量取 v(6))在 C 里是未定义行为(可能悄悄读出垃圾值),在 MATLAB 里会明确报错「Index exceeds…」——烦人但救命。
  3. 负数下标没有「倒数」语义: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 是按行存)。知道这一点,你就明白为什么 summax 这些函数也默认按列处理——语言的设计一以贯之。

能用,但可读性差、易出错,实际写代码请坚持用行列下标。

修改与扩展:

>> 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)       % 向量的最后一个元素 → 1

end 还能参与算术:vec(end-1) 是倒数第二个元素。注意 end 只能当下标用,出现在别处会报错。

练一练:对 mat = [1 2 3; 4 5 6](2×3),以下各取到什么?

  • mat(2,3) → 6
  • mat(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

另一类函数「压缩」整个向量成一个数:minmaxsumprod

>> 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   120

cumsum 的最后一个元素永远等于 sum 的结果——这个性质可以用来互相验证。R2014b 之后这些累计函数还支持 'reverse' 选项(从尾往前累计)以及 cummin / cummax 变体。

diff 函数算相邻元素的差,结果比原向量短 1(对矩阵同样按列):

>> diff([4 7 15 32])
ans =
     3     8    17
>> diff([4 7 2 32])
ans =
     3    -5    30

diff 结果里出现负数,说明信号在这里「往下走」——差分是数值求导的基础,判断单调性、找峰值都靠它。

对矩阵,这些压缩型函数默认按列操作——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 =
    20

cumsum 对矩阵也按列累计,结果与输入同尺寸:结果矩阵的第 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]

  1. 每个元素减 3:v - 3,得 [-2 -1 0 1 2 3 4 5]
  2. 每个元素除以 3:mat / 3,得一个 2×4 的小数矩阵
  3. 每个元素平方:mat .^ 2——注意 mat ^ 2 会报错,因为 2×4 不是方阵
  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],写出完成下列任务的语句。

  1. 每个元素减 3 → v - 3(标量运算,不用点)
  2. 每个元素除以 2 → v / 2
  3. 每个元素自己平方 → v.^2(必须有点!)
  4. 两个同长向量 v1v2 对应元素相乘 → 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.0000

v(1:2:end) 这种「冒号 + end」的花式下标,加上 .*,三行就完成了 C 里一个带循环的完整小程序——这正是 MATLAB 的味道。

再来看一个「压缩型函数」的经典应用——调和级数求和

C 的思路是循环累加;MATLAB 的思路是「先造向量,再一步求和」:

>> n = 100;
>> sum(1 ./ (1:n))        % 对向量 1~n 逐元素取倒数再求和
ans =
    5.1874

1 ./ (1:n) 里的点不可少——这是「逐元素除法」。把数学式子直接翻译成向量运算,是 MATLAB 编程的核心手感。

练一练:只写一行,求 。(答案:sum([3 5 7 9] ./ [1 2 3 4])

常见坑:忘写点 = 报错或错值

v^2a.*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)
把负数全部换成 0vec(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
   0

isequal 对字符串同样适用(字符串本质上就是字符向量):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.*BA*BB*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

.*dotc*r 一分钟辨析

  • v1 .* v2:逐元素相乘,结果是同长度的向量
  • v1 * v2'dot(v1,v2):对应相乘再求和,结果是一个标量
  • col * row:结果是 n×n 矩阵。 三者的操作数长度都相同,结果形态却完全不同,考试和工程里都爱在这里挖坑。

本章函数速查表

类别函数
创建: 冒号、linspacelogspacezerosonesrandranditruefalse
尺寸lengthsizenumelend
变形' 转置、reshapefliplrflipudfliprot90repmatrepelem
统计(按列)minmaxsumprodcumsumcumproddiff
逻辑anyallfindisequal
向量专用dot(点积)、cross(叉积)

本章通关标准:

  1. 能用方括号、冒号运算符、linspace 三种方式创建向量,并能解释 1:2:69:-2:1linspace(3,15,5) 各得到什么。
  2. 给任意矩阵,能口算 mat(2,3)mat(1,:)mat(:,end)、线性索引 mat(3) 各取到哪个元素,并坚持用下标索引而非线性索引。
  3. 能说清 .** 的区别、&&& 的区别,并用一行逻辑索引写出「删掉向量中所有负数」。