上篇聊了递归,有人后台问我:"struct这么简单也值得写一篇文章?"
值。
而且我可以很负责任地说,struct在面试里考得比你想象的频繁得多。特别是机器人方向,你写传感器数据结构、定义机器人状态、封装控制指令……哪一样离得开struct?
讲个真事。
前阵子面试一个应届生,我让他定义一个激光雷达点的数据结构。他写了:
struct Point { float x; float y; float z; int intensity; };我问他:"这个结构体占多少字节?"
他想都没想:"16字节。三个float各4字节,一个int 4字节,加起来16。"
我说:"你确定?用sizeof跑一下看看。"
他跑了一下——16。嗯,这次蒙对了。
但我追问了一句:"如果把int放到第一个字段,顺序变一下,还是16字节吗?"
他犹豫了。
这就是struct在面试里最爱考的点:内存对齐。很多人用了好几年struct,从来没搞明白为什么结构体的大小不等于各字段大小之和。
struct是什么?不只是"把几个变量打包在一起"
课本上对struct的定义通常是:"一种可以包含多个不同类型数据的数据结构。"
没错,但太笼统了。
struct的本质是什么?在内存中开辟一块连续的空间,把相关的变量按顺序放进去。
注意关键词:连续、按顺序。
这意味着struct里的字段不是随意摆放的,它们的排列方式直接影响内存占用和访问效率。
在机器人开发里,struct用得特别多。比如你要定义一个传感器数据结构:
struct IMUData { double timestamp; // 时间戳 float acc_x, acc_y, acc_z; // 加速度 float gyro_x, gyro_y, gyro_z; // 角速度 bool is_valid; // 数据是否有效 };这个结构体把IMU传感器的所有数据打包在一起,传递的时候只需要传一个IMUData,不用传七八个散落的变量。
但问题来了:这个结构体到底占多少字节?
内存对齐:面试中最容易算错的题
这就是刚才那个候选人踩坑的地方。
编译器在放置结构体字段时,不是简单地一个挨一个放。它会让每个字段的起始地址对齐到某个整数倍——这个倍数通常是字段本身的大小。
比如double要放在8的整数倍地址上,float要放在4的整数倍地址上。如果上一个字段结束后,下一个字段的对齐位置还没到,编译器就会在中间填充padding。
拿刚才的IMUData来算:
- timestamp:double,8字节,从偏移0开始
- acc_x, acc_y, acc_z:三个float,各4字节,从偏移8开始,共12字节
- gyro_x, gyro_y, gyro_z:三个float,各4字节,从偏移20开始,共12字节
- is_valid:bool,1字节,从偏移32开始
到这儿总共33字节。但整个结构体的大小要对齐到最大字段(double,8字节)的整数倍,所以会填充到40字节。
实际是多少?你可以用sizeof(IMUData)验证一下。
面试的时候,面试官特别喜欢让你手算结构体大小。如果你不知道内存对齐的规则,算出来的数一定不对。
有个小技巧:把占用空间大的字段放在前面,小的放在后面,可以减少padding,节省内存。
// 优化前:40字节 struct IMUData_v1 { bool is_valid; // 1字节 + 7字节padding double timestamp; // 8字节 float acc_x, acc_y, acc_z; float gyro_x, gyro_y, gyro_z; }; // 优化后:36字节 struct IMUData_v2 { double timestamp; // 8字节 float acc_x, acc_y, acc_z; // 12字节 float gyro_x, gyro_y, gyro_z; // 12字节 bool is_valid; // 1字节 + 3字节padding };在机器人开发里,如果你的传感器数据量很大(比如一帧点云几十万个点),每个点结构体省几个字节,总体内存差距就很可观了。
还有一种更激进的做法是用#pragma pack(1)强制取消对齐,让结构体按字面大小排列。不过这种写法在嵌入式开发里才常见,普通机器人开发不建议随便用,因为某些CPU架构访问未对齐数据会直接报错。
struct和class到底什么区别?
面试高频题:"struct和class有什么区别?"
标准答案是:默认访问权限不同。struct默认是public,class默认是private。
就这一个区别。在功能上,它们几乎完全一样——都能有成员函数、都能继承、都能有构造函数。
但实际开发中,大家有个约定俗成的习惯:struct用来表示纯数据,class用来表示有行为的对象。
比如你定义一个传感器数据,用struct:
struct LidarScan { std::vector<float> ranges; std::vector<float> angles; double timestamp; };但如果你定义一个传感器驱动,有初始化、读取、校准等行为,用class:
class LidarDriver { public: bool initialize(); LidarScan readScan(); void calibrate(); private: int fd; std::string port; };这不是语法要求,是代码风格。但在面试里,如果你能说出这个约定,面试官会觉得你有工程素养。
我见过有人把所有东西都写成class,连个纯数据包也搞一堆getter/setter。说实话,没必要。struct就是为纯数据准备的,简单直接。
struct在机器人开发里的实战经验
在机器人开发里,struct有几个特别常见的用法。
第一,定义消息类型。ROS2里的msg文件,编译出来就是struct。你也可以手动定义:
struct Twist { Vector3 linear; Vector3 angular; };第二,定义配置参数。机器人的各种参数,打包成一个struct传给初始化函数:
struct RobotConfig { double max_speed; double wheel_radius; double wheel_base; int control_freq; };这样比传一堆散落的参数清晰多了。
第三,定义状态。机器人的运行状态、错误码,用struct比用一堆全局变量干净:
struct RobotState { enum Mode { IDLE, RUNNING, ERROR }; Mode mode; int error_code; std::string error_msg; };面试的时候,如果你能举出这些实际场景,比只说"struct可以包含不同类型的数据"有说服力得多。
给正在准备面试的你一点建议
struct看着简单,但面试考得细。
我建议你做三件事:
第一,搞清楚内存对齐的规则,能手算常见结构体的大小。这个几乎每场面试都会问。
第二,知道struct和class的区别,不只是语法层面,还有使用习惯上的约定。
第三,去看看ROS2的message定义,看看人家是怎么用struct组织传感器数据的。理解"为什么这么设计"比"知道怎么用"更重要。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。
「机器人软件开发面试·从入门到精通」连载系列上一篇:第8篇 递归函数——运动学求解中的常见思路 下一篇预告:第10篇 联合体与枚举——状态机设计中的常用工具
有任何问题欢迎评论区留言,我会尽量回复。