MATLAB Struct 结构体:零基础入门详解
MATLAB 作为强大的科学计算和数据分析工具,提供了多种数据类型来组织和管理信息。除了我们熟悉的数值矩阵、字符数组和单元格数组(Cell Array)之外,结构体(Struct) 是一种极其重要且灵活的数据类型,特别适用于组织和管理异构(heterogeneous)数据——即类型不同的相关数据。对于初学者来说,理解和掌握结构体是提升 MATLAB 编程能力的关键一步。本文将从零开始,详细介绍 MATLAB 结构体的概念、创建、访问、修改以及在实际应用中的技巧和优势。
一、 什么是 MATLAB 结构体 (Struct)?
想象一下,你需要记录一个学生的信息,可能包括:姓名(字符串)、学号(可能是数值或字符串)、年龄(数值)、各科成绩(一个数值向量或矩阵)、以及联系方式(可能包含电话和邮箱,又是字符串)。这些信息类型各不相同,如果用传统的数值矩阵,显然无法直接存储所有这些信息。
使用单元格数组(Cell Array)可以存储不同类型的数据,例如 student_cell = {'张三', 12345, 20, [85, 92, 78], {'138********', '[email protected]'}}
。但是,你需要记住每个单元格索引(1, 2, 3…)对应的是哪种信息,当信息项很多时,这会变得混乱且容易出错。
这时,结构体 (Struct) 就派上了用场。结构体就像一个带标签的容器,它允许你将多个不同类型的数据项组合在一个单一的变量名下。每个数据项都有一个唯一的字段名 (Field Name) 作为标签,你可以通过这个字段名来访问对应的数据值。
核心概念:
- 结构体变量 (Struct Variable): 存储整个结构体数据的变量名。
- 字段 (Field): 结构体内部用于存储单个数据项的“子变量”,每个字段都有一个唯一的名称(字段名)。
- 字段名 (Field Name): 用于标识和访问字段的字符串标签。
- 字段值 (Field Value): 存储在字段中的具体数据,可以是任何 MATLAB 数据类型(数值、字符串、矩阵、单元格数组,甚至可以是另一个结构体)。
用学生信息的例子来说:
* student
可以是结构体变量名。
* name
, id
, age
, grades
, contact
可以是字段名。
* '张三'
, 12345
, 20
, [85, 92, 78]
, struct('phone', '138********', 'email', '[email protected]')
(这里联系方式本身也用了结构体) 可以是对应的字段值。
二、 创建结构体 (Creating Structs)
在 MATLAB 中,主要有两种创建结构体的方式:使用点号 (.) 赋值和使用 struct()
函数。
1. 使用点号 (.) 赋值 (Dot Notation)
这是最直观、最常用的方法,特别适合手动创建或逐步构建结构体。语法非常简单:
matlab
结构体变量名.字段名 = 字段值;
如果结构体变量尚不存在,第一次使用点号赋值时 MATLAB 会自动创建它。
示例:创建一个表示单个学生的结构体
“`matlab
% 清除可能存在的同名变量
clear student;
% 开始创建结构体并赋值
student.name = ‘李四’; % 字段 ‘name’,值为字符串
student.id = ‘S67890’; % 字段 ‘id’,值为字符串
student.age = 21; % 字段 ‘age’,值为数值
student.courses = {‘Math’, ‘Physics’, ‘Computer Science’}; % 字段 ‘courses’,值为单元格数组
student.grades = [90, 88, 95]; % 字段 ‘grades’,值为数值向量
student.isActive = true; % 字段 ‘isActive’,值为逻辑值
% 显示创建的结构体
disp(‘创建的 student 结构体:’);
disp(student);
% 查看结构体的详细信息(包括字段和类型)
disp(‘使用 whos 查看 student 结构体信息:’);
whos student;
“`
输出结果:
“`
创建的 student 结构体:
name: ‘李四’
id: ‘S67890’
age: 21
courses: {‘Math’ ‘Physics’ ‘Computer Science’}
grades: [90 88 95]
isActive: 1 % MATLAB 中 true 显示为 1
使用 whos 查看 student 结构体信息:
Name Size Bytes Class Attributes
student 1×1 1158 struct
“`
disp(student)
会清晰地列出所有字段名及其对应的值。whos student
显示student
是一个1x1
的struct
结构体,并给出了它占用的内存大小。注意,即使只有一个学生信息,它也被认为是一个1x1
的结构体数组(稍后会详细讲结构体数组)。
关键点:
* 字段名必须是有效的 MATLAB 变量名(以字母开头,可包含字母、数字、下划线)。
* 字段名区分大小写 (student.Name
和 student.name
是不同的字段)。
* 你可以随时向已存在的结构体添加新的字段,只需使用点号赋一个新值即可。例如:student.major = 'Engineering';
2. 使用 struct()
函数
struct()
函数允许你一次性创建结构体,或者在字段名本身是变量的情况下动态创建结构体。
基本语法:
matlab
myStruct = struct('字段名1', 值1, '字段名2', 值2, ..., '字段名N', 值N);
示例:使用 struct()
函数创建学生结构体
“`matlab
% 使用 struct() 函数创建
student2 = struct(…
‘name’, ‘王五’, …
‘id’, ‘S11223’, …
‘age’, 22, …
‘courses’, {{‘Chemistry’, ‘Biology’}}, … % 注意: 当值为单元格数组时,需要再嵌套一层{}
‘grades’, [85, 91], …
‘isActive’, true …
);
disp(‘使用 struct() 函数创建的 student2 结构体:’);
disp(student2);
“`
输出结果:
使用 struct() 函数创建的 student2 结构体:
name: '王五'
id: 'S11223'
age: 22
courses: {{'Chemistry' 'Biology'}} % 注意这里的双层花括号
grades: [85 91]
isActive: 1
注意: 当使用 struct()
函数且某个字段的值本身就是一个单元格数组时,你需要将该单元格数组再用一层花括号 {}
包裹起来,如示例中的 'courses', {{'Chemistry', 'Biology'}}
。这是因为 struct()
函数期望成对的输入,如果直接给 {'Chemistry', 'Biology'}
,它会尝试将 'Chemistry'
和 'Biology'
分别赋给不同的结构体元素(如果创建结构体数组的话),而不是作为单个字段的值。
struct()
函数的优势:
* 程序化创建: 当字段名或值存储在变量中时非常有用。
matlab
fieldName1 = 'city';
fieldValue1 = 'Beijing';
fieldName2 = 'zipcode';
fieldValue2 = '100084';
address = struct(fieldName1, fieldValue1, fieldName2, fieldValue2);
disp(address);
% 输出:
% city: 'Beijing'
% zipcode: '100084'
* 创建空的结构体: emptyStruct = struct();
可以创建一个没有任何字段的空结构体。
* 创建结构体数组: struct()
函数是创建结构体数组(后面会讲)的一种高效方式。
三、 访问结构体中的数据
访问结构体字段值的方法非常简单,仍然是使用点号 (.)。
语法:
matlab
value = 结构体变量名.字段名;
示例:访问 student
结构体的数据
“`matlab
% 假设 student 结构体已按前面点号赋值方式创建
studentName = student.name;
studentAge = student.age;
studentFirstGrade = student.grades(1); % 访问 ‘grades’ 字段(向量)的第一个元素
studentCourses = student.courses; % 获取整个单元格数组
studentSecondCourse = student.courses{2}; % 访问 ‘courses’ 字段(单元格数组)的第二个元素
fprintf(‘学生姓名: %s\n’, studentName);
fprintf(‘学生年龄: %d\n’, studentAge);
fprintf(‘第一门课成绩: %d\n’, studentFirstGrade);
fprintf(‘第二门课程名称: %s\n’, studentSecondCourse);
“`
输出:
学生姓名: 李四
学生年龄: 21
第一门课成绩: 90
第二门课程名称: Physics
关键点:
* 如果字段值是一个数组、矩阵或单元格数组,你可以使用标准的索引方式(圆括号 ()
或花括号 {}
)来访问其内部的元素,如 student.grades(1)
和 student.courses{2}
。
四、 修改结构体
修改结构体包括更改现有字段的值、添加新字段以及删除字段。
1. 更改字段值
直接使用点号对已存在的字段重新赋值即可。
“`matlab
disp(‘修改前的 student 年龄:’);
disp(student.age);
student.age = 22; % 将年龄修改为 22
disp(‘修改后的 student 年龄:’);
disp(student.age);
“`
2. 添加新字段
同样使用点号,对一个不存在的字段名赋值,MATLAB 会自动添加这个新字段。
“`matlab
disp(‘添加 major 字段前的 student 结构体:’);
disp(student);
student.major = ‘Computer Engineering’; % 添加 ‘major’ 字段
disp(‘添加 major 字段后的 student 结构体:’);
disp(student);
“`
3. 删除字段
使用 rmfield()
函数可以删除结构体的一个或多个字段。
语法:
matlab
newStruct = rmfield(originalStruct, '字段名');
% 或删除多个字段
newStruct = rmfield(originalStruct, {'字段名1', '字段名2', ...});
注意: rmfield
函数不会修改原始结构体,而是返回一个新的、删除了指定字段的结构体。你需要将返回值赋给一个新的变量,或者覆盖原始变量。
示例:删除 student
结构体的 isActive
字段
“`matlab
disp(‘删除 isActive 字段前的 student 结构体字段:’);
disp(fieldnames(student)); % fieldnames() 函数返回所有字段名的列表
% 删除 isActive 字段,并将结果存回 student 变量
student = rmfield(student, ‘isActive’);
disp(‘删除 isActive 字段后的 student 结构体字段:’);
disp(fieldnames(student));
disp(‘删除后的 student 结构体:’);
disp(student);
“`
输出:
“`
删除 isActive 字段前的 student 结构体字段:
{‘name’ }
{‘id’ }
{‘age’ }
{‘courses’}
{‘grades’ }
{‘isActive’} % ‘isActive’ 字段存在
{‘major’ } % ‘major’ 字段已在前一步添加
删除 isActive 字段后的 student 结构体字段:
{‘name’ }
{‘id’ }
{‘age’ }
{‘courses’}
{‘grades’ }
{‘major’ } % ‘isActive’ 字段已被删除
删除后的 student 结构体:
name: ‘李四’
id: ‘S67890’
age: 22
courses: {‘Math’ ‘Physics’ ‘Computer Science’}
grades: [90 88 95]
major: ‘Computer Engineering’
“`
五、 结构体数组 (Struct Arrays)
到目前为止,我们创建的 student
结构体都只包含一个学生的信息(1x1
的结构体)。但通常情况下,我们需要处理多个具有相同数据结构的对象,例如一个班级的所有学生。这时就需要结构体数组 (Struct Array)。
结构体数组是一个数组,它的每个元素都是一个具有相同字段名集合的结构体。
关键特性:
* 数组中的所有结构体元素必须拥有完全相同的字段名。
* 不同元素的对应字段可以存储不同类型或大小的数据(只要该字段允许)。
1. 创建结构体数组
方法一:逐个元素赋值(使用索引)
类似于创建数值数组,你可以通过指定索引来创建或扩展结构体数组。
“`matlab
clear students; % 清除可能存在的 students 变量
% 创建第一个学生的信息
students(1).name = ‘张三’;
students(1).id = ‘S1001’;
students(1).grades = [85, 92];
% 添加第二个学生的信息 (MATLAB 会自动扩展数组大小)
students(2).name = ‘李四’;
students(2).id = ‘S1002’;
students(2).grades = [90, 88, 95]; % 注意:grades 字段的长度可以不同
% MATLAB 会自动为新元素(这里是 students(2))补充与第一个元素相同的字段
% 如果某个字段在添加新元素时没有显式赋值,MATLAB 可能会赋默认值(如空数组 [])
% 但最佳实践是确保为每个元素的每个字段都赋值,或者理解其默认行为
disp(‘创建的 students 结构体数组 (2个元素):’);
disp(students);
disp(‘查看 students 结构体数组的大小和类型:’);
whos students;
“`
输出:
“`
创建的 students 结构体数组 (2个元素):
1×2 struct array with fields:
name
id
grades
查看 students 结构体数组的大小和类型:
Name Size Bytes Class Attributes
students 1×2 738 struct
“`
disp(students)
现在显示这是一个1x2
的结构体数组,并列出了共有的字段名。whos students
确认了students
是一个1x2
的struct
。
方法二:使用 struct()
函数配合单元格数组
当你有所有元素的数据时,使用 struct()
函数结合单元格数组可以更高效地创建结构体数组。值的单元格数组的维度决定了最终结构体数组的维度。
“`matlab
% 字段名
field1 = ‘city’;
field2 = ‘population’;
% 对应的值,每个单元格数组包含所有元素在该字段的值
cities_names = {‘Beijing’, ‘Shanghai’, ‘Tokyo’}; % 1×3 cell array
cities_pops = {21.54e6, 24.28e6, 37.43e6}; % 1×3 cell array
% 使用 struct() 创建 1×3 结构体数组
cities = struct(field1, cities_names, field2, cities_pops);
disp(‘使用 struct() 创建的 cities 结构体数组:’);
disp(cities);
disp(‘查看 cities 结构体数组的大小和类型:’);
whos cities;
“`
输出:
“`
使用 struct() 创建的 cities 结构体数组:
1×3 struct array with fields:
city
population
查看 cities 结构体数组的大小和类型:
Name Size Bytes Class Attributes
cities 1×3 942 struct
“`
2. 访问结构体数组的数据
访问结构体数组需要结合数组索引和点号。
-
访问特定元素的特定字段:
结构体数组名(索引).字段名
“`matlab
% 访问 students 结构体数组 (假设已按方法一创建)
secondStudentName = students(2).name;
firstStudentGrades = students(1).grades;fprintf(‘第二个学生的姓名: %s\n’, secondStudentName);
fprintf(‘第一个学生的成绩: ‘);
disp(firstStudentGrades);
“` -
访问特定元素的所有字段 (返回一个结构体):
结构体数组名(索引)
matlab
firstStudentInfo = students(1); % firstStudentInfo 是一个 1x1 的 struct
disp('第一个学生的所有信息:');
disp(firstStudentInfo); -
访问所有元素的同一个字段 (返回一个逗号分隔列表 Comma-Separated List):
结构体数组名.字段名
这是一个非常重要但也容易混淆的操作。当你使用
结构体数组名.字段名
时,MATLAB 会返回一个逗号分隔列表,包含数组中每个元素的该字段值。“`matlab
% 获取所有学生的姓名
allNamesList = students.name;
% allNamesList 不是一个变量,而是一个临时的逗号分隔列表: ‘张三’, ‘李四’% 要捕获这个列表,通常需要将其放入 [] 或 {} 中:
allNamesCell = {students.name}; % 将所有姓名收集到一个单元格数组中
disp(‘所有学生的姓名 (单元格数组):’);
disp(allNamesCell);% 如果字段值是数值标量或可以水平串联的数组,可以放入 [] 中:
allStudentIDs_maybe = [students.id]; % 尝试将 ID 放入数值/字符数组
% 注意:如果 ID 是 ‘S1001’, ‘S1002’ 这种字符串,
% 结果会是 ‘S1001S1002’ (水平串联)
% disp(allStudentIDs_maybe); % 可能不是想要的结果% 如果字段值是数值标量,放入 [] 得到数值向量
% 假设添加一个 age 字段
students(1).age = 20;
students(2).age = 21;
allAges = [students.age]; % allAges 会是 [20, 21]
disp(‘所有学生的年龄 (数值向量):’);
disp(allAges);% 如果字段值是向量/矩阵,且你想对每个元素的向量进行操作
% 比如计算每个学生的平均分
avgGrade1 = mean(students(1).grades);
avgGrade2 = mean(students(2).grades);
fprintf(‘第一个学生的平均分: %.2f\n’, avgGrade1);
fprintf(‘第二个学生的平均分: %.2f\n’, avgGrade2);% 如果想一次性获取所有学生的成绩(得到逗号分隔列表)然后处理
allGradesList = students.grades; % 逗号分隔列表:[85, 92], [90, 88, 95]
% 需要使用 cellfun 或循环来处理这种包含不同大小数组的列表
allAvgGrades = cellfun(@mean, {students.grades}); % 使用 cellfun 计算每个单元格(成绩向量)的均值
disp(‘所有学生的平均分 (使用 cellfun):’);
disp(allAvgGrades);
“`
逗号分隔列表小结:
* structArray.fieldName
返回一个列表,不是直接可用的变量。
* 用 {}
包裹 structArray.fieldName
可得到包含每个元素字段值的单元格数组。
* 用 []
包裹 structArray.fieldName
可将所有元素的字段值水平串联起来,适用于数值标量、字符行向量等情况。对于更复杂的字段值(如不同大小的向量),结果可能不是你想要的,此时用 {}
更安全。
六、 嵌套结构体 (Nested Structs)
结构体的一个强大之处在于其字段的值可以是另一个结构体。这允许你创建具有层级关系的数据结构。
示例:为 student
结构体添加包含地址信息的嵌套结构体
“`matlab
% 假设 student 是单个学生的结构体
clear student;
student.name = ‘赵六’;
student.id = ‘S3003’;
% 创建一个地址结构体
addressInfo.street = ‘123 Main St’;
addressInfo.city = ‘Anytown’;
addressInfo.zipcode = ‘98765’;
% 将地址结构体 赋值给 student 结构体的一个新字段 ‘contactAddress’
student.contactAddress = addressInfo;
disp(‘包含嵌套地址结构体的 student 信息:’);
disp(student);
% 访问嵌套结构体中的字段
studentCity = student.contactAddress.city;
studentStreet = student.contactAddress.street;
fprintf(‘学生所在城市: %s\n’, studentCity);
fprintf(‘学生街道地址: %s\n’, studentStreet);
“`
输出:
“`
包含嵌套地址结构体的 student 信息:
name: ‘赵六’
id: ‘S3003’
contactAddress: [1×1 struct] % 显示这是一个嵌套结构体
学生所在城市: Anytown
学生街道地址: 123 Main St
“`
- 要访问嵌套结构体内部的字段,只需连续使用点号即可,如
student.contactAddress.city
。 - 嵌套结构体可以有多层深度。
七、 常用的结构体相关函数
MATLAB 提供了一些有用的函数来操作和查询结构体:
-
fieldnames(myStruct)
: 返回一个包含结构体myStruct
所有字段名的单元格数组。这在需要遍历结构体所有字段时非常有用。
“`matlab
fields = fieldnames(student); % fields = {‘name’; ‘id’; ‘contactAddress’} (列向量形式)
disp(‘Student 结构体的字段名:’);
disp(fields);% 遍历并显示所有字段及其值
for i = 1:length(fields)
fieldName = fields{i}; % 从单元格数组中取出字段名
fieldValue = student.(fieldName); % 使用动态字段名访问
fprintf(‘字段 “%s”: ‘, fieldName);
disp(fieldValue);
end
``
student.(fieldName)` 这种语法称为动态字段名 (Dynamic Field Names) 或计算字段名 (Computed Field Names),允许你使用存储在变量中的字符串作为字段名来访问结构体。
**注意:** -
isfield(myStruct, 'fieldName')
: 检查结构体myStruct
是否包含名为'fieldName'
的字段。返回逻辑值true
或false
。
matlab
hasAgeField = isfield(student, 'age'); % 检查 student 是否有 'age' 字段
hasNameField = isfield(student, 'name'); % 检查 student 是否有 'name' 字段
fprintf('Student 是否有 age 字段? %d\n', hasAgeField); % 输出 0 (false)
fprintf('Student 是否有 name 字段? %d\n', hasNameField); % 输出 1 (true) -
struct2cell(myStruct)
: 将一个非标量(即 1×1)结构体转换为一个单元格数组。转换后的单元格数组的维度与字段数量相关,并且丢失了字段名信息。通常,它会将每个字段的值放入单元格数组的一个单元格中。
matlab
% 假设 student 结构体有 name, id, contactAddress 字段
studentCell = struct2cell(student);
disp('student 结构体转换为单元格数组:');
disp(studentCell);
% 输出可能类似(顺序取决于内部存储):
% {'赵六'}
% {'S3003'}
% [1x1 struct]
注意: 这个函数对于结构体数组的行为可能不直观,且丢失字段名,使用场景相对有限,通常fieldnames
结合循环或动态字段名更常用。 -
cell2struct(myCell, fields, dim)
: 将单元格数组myCell
转换回结构体。需要提供一个包含字段名的单元格数组fields
,并指定沿着哪个维度dim
来划分数据以创建结构体(或结构体数组)。这个函数相对复杂,通常在特定数据导入/导出场景下使用。 -
orderfields(S1)
或orderfields(S1, S2)
或orderfields(S1, C)
: 重新排序结构体S1
的字段。可以按字母顺序排序,或者按照另一个结构体S2
或字段名单元格数组C
的顺序来排序。返回一个字段已排序的新结构体。
“`matlab
% 假设 student 字段顺序是 name, id, contactAddress
student_alpha_ordered = orderfields(student); % 按字母顺序排序字段
disp(‘按字母顺序排序字段后的 student 结构体:’);
disp(student_alpha_ordered); % 字段顺序变为 contactAddress, id, namefieldOrder = {‘id’, ‘name’, ‘contactAddress’};
student_custom_ordered = orderfields(student, fieldOrder); % 按指定顺序排序
disp(‘按指定顺序排序字段后的 student 结构体:’);
disp(student_custom_ordered); % 字段顺序变为 id, name, contactAddress
“`
八、 结构体的优势与适用场景
优势:
- 数据组织性强: 将相关但类型不同的数据项聚合在一起,代码更清晰、更易于理解和维护。
- 可读性高: 通过有意义的字段名访问数据 (
student.name
) 比通过索引 (student_cell{1}
) 更直观。 - 灵活性: 字段可以存储任何 MATLAB 数据类型,包括其他结构体或单元格数组,可以构建复杂的数据结构。
- 函数参数和返回值: 非常适合将多个配置参数打包传递给函数,或从函数返回多个不同类型的结果。
适用场景:
- 表示真实世界的对象或记录: 如学生、病人、产品、实验结果等,它们通常有多个不同类型的属性。
- 函数配置参数: 将函数的多个设置项(如算法名称、迭代次数、容差等)放在一个结构体中传递,使函数调用更简洁。
matlab
options.algorithm = 'gradient descent';
options.maxIterations = 1000;
options.tolerance = 1e-6;
result = optimizeFunction(data, options); - 从函数返回多个命名结果: 函数可以返回一个包含多个字段的结构体,每个字段代表一个输出结果。
matlab
function output = analyzeData(inputData)
% ... 分析过程 ...
output.meanValue = mean(inputData);
output.stdDev = std(inputData);
output.processedData = inputData.^2; % 示例处理
end
analysisResult = analyzeData([1, 2, 3, 4, 5]);
disp(analysisResult.meanValue); - 处理来自文件或数据库的结构化数据: 读取如 CSV、JSON、XML 等格式的数据时,将其映射到结构体或结构体数组通常很自然。
一些注意事项:
- 性能: 对于大规模纯数值计算,直接使用数值矩阵通常比在结构体数组中逐个访问数值字段更快。结构体的优势在于组织性而非原始计算速度。
- 结构体数组的字段一致性: 确保结构体数组的所有元素都具有相同的字段名,否则在访问或操作时可能出错。
九、 总结
MATLAB 的结构体 (Struct) 是一种强大的数据类型,它允许你通过命名字段来组织和管理异构数据。从创建单个结构体到使用结构体数组处理批量数据,再到构建嵌套结构体表示层级关系,结构体提供了极大的灵活性。掌握点号 (.
) 赋值和访问、struct()
函数、结构体数组的操作(特别是逗号分隔列表的处理)、以及 fieldnames
, rmfield
, isfield
等辅助函数,将使你能够更有效地在 MATLAB 中处理复杂的数据集和程序逻辑。
对于零基础的学习者来说,理解结构体的核心概念——带标签的容器——是第一步。然后,通过实践上述示例,尝试创建、访问、修改你自己的结构体和结构体数组,你将逐渐体会到它在代码组织和可读性方面带来的巨大好处。结构体是 MATLAB 工具箱中不可或缺的一部分,熟练运用它将显著提升你的 MATLAB 编程水平。