3、Matlab高级编程:匿名函数、函数句柄、结构体与元胞数组、文件I/O操作、调试与性能分析
各位好,欢迎来到第三章。这一章的内容,说白了就是让你从「会用Matlab」进阶到「用好Matlab」。我个人觉得,这是区分初级工程师和资深工程师的一道分水岭。你想想看,如果只会写脚本,那跟用计算器有什么区别?真正的嵌入式系统开发,需要处理复杂的数据结构、灵活的函数调用,还得跟文件系统打交道。嗯,咱们一个一个来。
3.1 匿名函数与函数句柄:让代码活起来
先说说函数句柄。这东西就像一个遥控器,你不需要把电视机搬过来,按一下遥控器就能换台。函数句柄就是函数的遥控器。
创建函数句柄很简单,用@符号就行:
% 创建一个正弦函数的句柄
sin_handle = @sin;
result = sin_handle(pi/2); % 返回1
匿名函数就更灵活了。它不需要单独写一个.m文件,现场定义现场用:
% 定义一个平方函数
square = @(x) x.^2;
result = square(5); % 返回25
% 多输入匿名函数
distance = @(x1, y1, x2, y2) sqrt((x2-x1)^2 + (y2-y1)^2);
d = distance(0, 0, 3, 4); % 返回5
函数句柄最大的用处是作为参数传递给其他函数。比如fzero、integral这些数值计算函数,都需要传入函数句柄:
% 求方程 x^2 - 2 = 0 的根
f = @(x) x^2 - 2;
root = fzero(f, 1); % 返回1.4142
3.2 结构体与元胞数组:数据组织的艺术
做嵌入式开发,数据从来不是孤立的。一个传感器数据包,可能包含温度、湿度、时间戳、设备ID。用普通数组根本没法组织这种异构数据。这时候就需要结构体和元胞数组了。
3.2.1 结构体:带名字的数据容器
结构体就像一个有标签的收纳盒,每个字段都有自己的名字:
% 创建一个传感器数据结构体
sensor_data = struct();
sensor_data.id = 'SENSOR_01';
sensor_data.temperature = 25.6;
sensor_data.humidity = 68.3;
sensor_data.timestamp = now;
% 或者直接创建
sensor_data = struct('id', 'SENSOR_01', ...
'temperature', 25.6, ...
'humidity', 68.3, ...
'timestamp', now);
结构体数组特别适合存储多个设备的数据:
% 创建结构体数组
sensors(1).id = 'S1';
sensors(1).temp = 25.0;
sensors(2).id = 'S2';
sensors(2).temp = 26.5;
% 批量访问
all_temps = [sensors.temp]; % 返回 [25.0, 26.5]
table或者数值数组。
3.2.2 元胞数组:万能容器
元胞数组比结构体更灵活,它可以装任何类型的数据:
% 创建一个元胞数组
data = {42, 'hello', [1 2 3], @(x) x^2};
% 访问元胞内容
value = data{1}; % 返回42
text = data{2}; % 返回'hello'
% 元胞数组的常见用法:存储变长字符串
names = {'sensor1', 'sensor2', 'sensor3'};
我个人习惯用元胞数组来处理不定长的数据记录。比如从串口读取的数据包,每个包长度不一样,用元胞数组最合适。
3.3 文件I/O操作:读写数据的基本功
嵌入式系统离不开文件操作。日志记录、配置读取、数据导出,样样都要跟文件打交道。
3.3.1 文本文件读写
fprintf和fscanf是最常用的文本文件操作函数:
% 写入文本文件
fid = fopen('log.txt', 'w');
fprintf(fid, '时间: %s, 温度: %.2f°C\n', datestr(now), 25.6);
fclose(fid);
% 读取文本文件
fid = fopen('log.txt', 'r');
line = fgetl(fid); % 读取一行
fclose(fid);
3.3.2 二进制文件读写
二进制文件读写速度快,适合存储大量数值数据:
% 写入二进制文件
data = 1:100;
fid = fopen('data.bin', 'wb');
fwrite(fid, data, 'double');
fclose(fid);
% 读取二进制文件
fid = fopen('data.bin', 'rb');
read_data = fread(fid, inf, 'double');
fclose(fid);
3.3.3 高级文件操作
Matlab还提供了csvread、csvwrite、xlswrite等专用函数。不过我个人更推荐用readtable和writetable,它们能自动处理数据类型:
% 读取CSV文件
T = readtable('sensor_data.csv');
% 写入CSV文件
writetable(T, 'output.csv');
3.4 调试与性能分析:让代码跑得更快
写代码容易,调代码难。这是每个工程师的共识。Matlab提供了强大的调试工具,用好它们能省下大量时间。
3.4.1 调试技巧
最基本的调试方法就是设置断点。在编辑器左侧点击行号,就会出现红点:
- 普通断点:程序运行到这一行会暂停
- 条件断点:右键点击断点,设置条件,比如
i == 100 - 错误断点:在命令窗口输入
dbstop if error,程序出错时自动暂停
调试时常用的命令:
dbstop if error % 出错时暂停
dbcont % 继续运行
dbstep % 单步执行
dbquit % 退出调试模式
3.4.2 性能分析
代码写完了,跑得慢怎么办?别瞎猜,用profile工具:
% 启动性能分析
profile on
% 运行你的代码
my_function();
% 查看分析结果
profile viewer
性能分析会告诉你:
- 每行代码执行了多少次
- 每行代码花了多少时间
- 哪个函数是性能瓶颈
常见的性能优化技巧:
- 预分配数组:别让数组在循环里不断增长
- 向量化操作:能用
.运算就别用循环 - 避免重复计算:把不变的计算提到循环外面
% 不好的写法
for i = 1:10000
result(i) = sin(i) * cos(i); % 每次都要计算sin和cos
end
% 好的写法
x = 1:10000;
result = sin(x) .* cos(x); % 向量化,一次搞定
3.5 本章知识体系
下面这张图总结了本章的核心内容,你可以把它当作一个快速参考:
好了,这一章的内容就到这里。记住,高级编程不是炫技,而是为了让代码更清晰、更高效。你想想看,一个结构体就能解决的问题,何必写一堆乱七八糟的变量?一个匿名函数就能搞定的回调,何必单独建一个文件?
下一章我们会进入更深入的嵌入式建模内容。不过在那之前,建议你把今天讲的这些基础打牢。调试工具用熟了,性能分析做透了,写出来的代码自然就漂亮了。