零基础学习 MATLAB Struct 结构体 – wiki基地


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) 作为标签,你可以通过这个字段名来访问对应的数据值。

核心概念:

  1. 结构体变量 (Struct Variable): 存储整个结构体数据的变量名。
  2. 字段 (Field): 结构体内部用于存储单个数据项的“子变量”,每个字段都有一个唯一的名称(字段名)。
  3. 字段名 (Field Name): 用于标识和访问字段的字符串标签。
  4. 字段值 (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 是一个 1x1struct 结构体,并给出了它占用的内存大小。注意,即使只有一个学生信息,它也被认为是一个 1x1 的结构体数组(稍后会详细讲结构体数组)。

关键点:
* 字段名必须是有效的 MATLAB 变量名(以字母开头,可包含字母、数字、下划线)。
* 字段名区分大小写 (student.Namestudent.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 是一个 1x2struct

方法二:使用 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 提供了一些有用的函数来操作和查询结构体:

  1. 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),允许你使用存储在变量中的字符串作为字段名来访问结构体。

  2. isfield(myStruct, 'fieldName'): 检查结构体 myStruct 是否包含名为 'fieldName' 的字段。返回逻辑值 truefalse
    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)

  3. struct2cell(myStruct): 将一个非标量(即 1×1)结构体转换为一个单元格数组。转换后的单元格数组的维度与字段数量相关,并且丢失了字段名信息。通常,它会将每个字段的值放入单元格数组的一个单元格中。
    matlab
    % 假设 student 结构体有 name, id, contactAddress 字段
    studentCell = struct2cell(student);
    disp('student 结构体转换为单元格数组:');
    disp(studentCell);
    % 输出可能类似(顺序取决于内部存储):
    % {'赵六'}
    % {'S3003'}
    % [1x1 struct]

    注意: 这个函数对于结构体数组的行为可能不直观,且丢失字段名,使用场景相对有限,通常 fieldnames 结合循环或动态字段名更常用。

  4. cell2struct(myCell, fields, dim): 将单元格数组 myCell 转换回结构体。需要提供一个包含字段名的单元格数组 fields,并指定沿着哪个维度 dim 来划分数据以创建结构体(或结构体数组)。这个函数相对复杂,通常在特定数据导入/导出场景下使用。

  5. 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, name

    fieldOrder = {‘id’, ‘name’, ‘contactAddress’};
    student_custom_ordered = orderfields(student, fieldOrder); % 按指定顺序排序
    disp(‘按指定顺序排序字段后的 student 结构体:’);
    disp(student_custom_ordered); % 字段顺序变为 id, name, contactAddress
    “`

八、 结构体的优势与适用场景

优势:

  1. 数据组织性强: 将相关但类型不同的数据项聚合在一起,代码更清晰、更易于理解和维护。
  2. 可读性高: 通过有意义的字段名访问数据 (student.name) 比通过索引 (student_cell{1}) 更直观。
  3. 灵活性: 字段可以存储任何 MATLAB 数据类型,包括其他结构体或单元格数组,可以构建复杂的数据结构。
  4. 函数参数和返回值: 非常适合将多个配置参数打包传递给函数,或从函数返回多个不同类型的结果。

适用场景:

  1. 表示真实世界的对象或记录: 如学生、病人、产品、实验结果等,它们通常有多个不同类型的属性。
  2. 函数配置参数: 将函数的多个设置项(如算法名称、迭代次数、容差等)放在一个结构体中传递,使函数调用更简洁。
    matlab
    options.algorithm = 'gradient descent';
    options.maxIterations = 1000;
    options.tolerance = 1e-6;
    result = optimizeFunction(data, options);
  3. 从函数返回多个命名结果: 函数可以返回一个包含多个字段的结构体,每个字段代表一个输出结果。
    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);
  4. 处理来自文件或数据库的结构化数据: 读取如 CSV、JSON、XML 等格式的数据时,将其映射到结构体或结构体数组通常很自然。

一些注意事项:

  • 性能: 对于大规模纯数值计算,直接使用数值矩阵通常比在结构体数组中逐个访问数值字段更快。结构体的优势在于组织性而非原始计算速度。
  • 结构体数组的字段一致性: 确保结构体数组的所有元素都具有相同的字段名,否则在访问或操作时可能出错。

九、 总结

MATLAB 的结构体 (Struct) 是一种强大的数据类型,它允许你通过命名字段来组织和管理异构数据。从创建单个结构体到使用结构体数组处理批量数据,再到构建嵌套结构体表示层级关系,结构体提供了极大的灵活性。掌握点号 (.) 赋值和访问、struct() 函数、结构体数组的操作(特别是逗号分隔列表的处理)、以及 fieldnames, rmfield, isfield 等辅助函数,将使你能够更有效地在 MATLAB 中处理复杂的数据集和程序逻辑。

对于零基础的学习者来说,理解结构体的核心概念——带标签的容器——是第一步。然后,通过实践上述示例,尝试创建、访问、修改你自己的结构体和结构体数组,你将逐渐体会到它在代码组织和可读性方面带来的巨大好处。结构体是 MATLAB 工具箱中不可或缺的一部分,熟练运用它将显著提升你的 MATLAB 编程水平。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部