在编程实践中,仅仅只靠浮点数数组还是不够的,我们还需要其他更灵活的数据结构(运行效率也会更慢),最常见的数据结构是元胞数组和结构体数组。

元胞数组

元胞数组是一种包含名为cell的索引数据容器的数据类型,其中的每个cell都可以包含任意类型的数据。 元胞数组可以包含文本列表、文本和数字的组合或者不同大小的矩阵等,通常用于打包一组矩阵或多组矩阵。 通过将索引括在圆括号()中可以引用cell,使用花括号{}进行索引来直接访问cell的内容,两者区别见下文。

元胞数组的创建方式如下,元胞数组通常使用二维的结构,m行n列,每一个元素是一个矩阵或其他对象,元胞数组也支持多维的定义和操作。

1
2
3
C1 = {}; % 空的元胞数组
C2 = {1,2,3;
'text',rand(5,10,2),{11; 22; 33}} % 2*3的元胞数组

还可以定义指定尺寸的,每一个元素均为空矩阵的元胞数组:

  • C = cell(n),返回由空矩阵构成的\(n\times n\)元胞数组。
  • C = cell(s1,...,sn) 返回由空矩阵构成的 \(s_1 \times \dots \times s_n\) 元胞数组。例如,cell(2,3) 返回一个 \(2\times 3\) 元胞数组。
  • C = cell(a) 返回由空矩阵构成的元胞数组,并由行向量a来定义尺寸,例如,cell([2 3])返回一个 \(2\times 3\) 元胞数组。(使用列向量是非法操作)

元胞数组和普通矩阵一样,可以在赋值的同时直接进行扩充,并且与矩阵不同,元胞数组支持不同的数据组成cell

1
2
3
4
5
C = {'2017-08-16',[56 67 78]}; % size 1*2

% append
C(2,:) = {'2017-08-17',[58 69 79]}; % size 2*2
C(3,:) = {'2017-08-18',[60 68 81]}; % size 3*2

此时(){}索引得到的结果是不同的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>> C(1,1)
ans =
1×1 cell array
{'2017-08-16'}
>> C{1,1}
ans =
'2017-08-16'

>> C(1,2)
ans =
1×1 cell array
{[56 67 78]}
>> C{1,2}
ans =
56 67 78

前者仍然是一个\(1\times 1\)的元胞数组,后者得到的则是实际的矩阵或字符串。

基于花括号{}索引获取分量或矩阵之后,就可以直接赋值和修改了,略。

结构体数组

虽然MATLAB官方将其称为结构体数组,但是实际上不管是从使用还是实现的角度,将其称为字典都更合适。在C++的语境中,结构体约等于自定义类型,而对于MATLAB,自定义类型和结构体数组是截然不同的。

结构体数组(struct)是 MATLAB 中一种非常灵活的具名数据类型,可以将不同类型的数据直接组合在一起,每个字段具有名称,可以包含不同类型和大小的数据,结构体数组使得组织和管理复杂数据更加方便。

结构体数组的使用非常灵活,可以使用点符号直接创建

1
2
3
4
5
% 此前person未定义
% 创建一个结构体数组并添加字段
person.name = 'John Doe';
person.age = 30;
person.height = 1.75;

注意:这里person变量尚未定义,这个语法糖导致我们可能无意之间写出错误的语句——创建了一个新的结构体数组, 而非对已有的结构体数组操作,这个错误还很难检查。

我们还可以使用struct函数创建,一次性添加多个字段以及对应的数据

1
person = struct('name', 'Jane Doe', 'age', 28, 'height', 1.65);

结构体数组并没有明确完整的创建过程,我们可以在任何时间自由地向结构体数组中添加字段,或对现有字段进行访问和修改。 每一个结构体数组都是完全独立的,对于这个结构体数组可以有(或者必须有)哪些字段没有任何要求

1
2
3
disp(person.name); % 输出:John Doe
disp(person.age); % 输出:30
disp(person.height); % 输出:1.75

尝试访问不存在的字段会导致错误。

我们可以使用内置函数isfield来检查结构体数组是否具有某个字段

1
2
3
% 检查字段
hasAge = isfield(person, 'age'); % 返回 true
hasHeight = isfield(person, 'height'); % 返回 false

我们可以使用内置函数rmfield来移除结构体数组的字段,需要用返回值重新赋值

1
2
3
4
5
% 删除字段
person = rmfield(person, 'height');

% 尝试访问已删除的字段将会导致错误
% disp(person.height); % 错误

使用内置函数filedsname可以基于结构体数组的所有字段名,创建为一个\(n \times 1\)的元胞数组,例如

1
2
3
4
5
6
>> fieldnames(person);
ans =
3×1 cell array
{'name' }
{'age' }
{'height'}

与之对应的是,使用内置函数struct2cell可以基于结构体数组所有字段的值,创建为一个\(n \times 1\)的元胞数组,例如

1
2
3
4
5
6
>> struct2cell(person)
ans =
3×1 cell array
{'Jane Doe'}
{[ 28]}
{[ 1.6500]}

使用内置函数cell2struct可以通过存储字段名和值的元胞数组,创建对应的结构体数组

1
2
3
4
5
6
7
cellArray = {'John Doe', 30, 1.75};
fieldNames = {'name', 'age', 'height'};

% 将单元数组转换为结构体数组
person = cell2struct(cellArray, fieldNames, 2);

disp(person);

需要注明的是,第三个参数是创建结构体数组所使用的维度,1代表使用的是列维度,这里使用的2代表行维度。

不同的结构体数组也可以进一步组成数组,结构体数组还可以相互嵌套,略。

从上面的介绍可以看出,结构体数组实际上就是一个键为字符串的字典,语法非常灵活,易与后文中的面向对象的语法产生冲突,因此决定尽量避免使用结构体数组,转而使用更安全的面向对象的语法!