类与对象:面试官让我用机器人传感器解释封装,我讲了五分钟
上篇聊了static的四种用法,今天进入面向对象的世界。
类与对象,是C++最核心的特性,没有之一。面试的时候,几乎每一轮都会涉及到。
讲个真实的面试场景。
去年面试一个应届生,简历上写着"熟悉面向对象编程"。我问:"什么是类?什么是对象?"
他说:"类是模板,对象是实例。"
我说:"能具体点吗?用你做过的项目举个例子。"
他想了半天,说:"比如学生管理系统里,Student是类,张三李四是对象。"
教科书式的回答,但太教科书了。
我追问:"你在机器人项目里,是怎么设计类的?"
他愣了一下,说:"我没怎么设计过,一般都用现成的。"
讲真,这个回答让我有点失望。面向对象不是背概念,是要能在项目里用起来的。
今天我就用机器人传感器的例子,把类与对象讲透。
先搞清楚:类和对象到底是什么关系
类是什么?类是对一类事物的抽象描述。它定义了这类事物有什么属性(数据)和什么行为(方法)。
对象是什么?对象是类的具体实例。每个对象有自己的数据,但共享同一套方法。
用机器人的传感器来理解最直观。
假设你有一个激光雷达传感器。它有什么属性?测量距离、角度范围、分辨率。它有什么行为?开始扫描、停止扫描、获取数据。
把这些抽象出来,就是一个类:
class LidarSensor {
// 属性(成员变量)
double max_range_; // 最大测量距离
double angle_range_; // 角度范围
double resolution_; // 分辨率
// 行为(成员函数)
public:
void startScan() { /* 开始扫描 */ }
void stopScan() { /* 停止扫描 */ }
vector<double> getData() { /* 返回扫描数据 */ }
};
然后你创建具体的对象:
LidarSensor front_lidar; // 前面的激光雷达
LidarSensor rear_lidar; // 后面的激光雷达
front_lidar.max_range_ = 30.0; // 前面的雷达测距30米
rear_lidar.max_range_ = 15.0; // 后面的雷达测距15米
front_lidar和rear_lidar都是LidarSensor类的对象。它们的类型相同(都是LidarSensor),但各自的属性值不同。
这就是类和对象的关系:类是图纸,对象是按图纸造出来的实物。
封装:把细节藏起来
面向对象的三大特性:封装、继承、多态。今天先讲封装。
封装是什么?简单说就是"把数据藏起来,只暴露必要的接口"。
为什么要封装?因为如果不封装,任何人都能随便修改内部数据,程序很容易出错。
还是用传感器举例。假设激光雷达有个内部状态——当前温度。温度过高时,传感器会自动降频保护。如果你不封装,别人可能直接改温度值:
// 不封装的写法
struct LidarSensor {
double max_range_;
double temperature_; // 任何人都能改
bool is_scanning_;
};
LidarSensor lidar;
lidar.temperature_ = -100; // 乱改!传感器要出问题了
这种写法,任何人都能直接访问temperature_,想改成多少就改成多少。出了问题,你都不知道是谁改的。
封装之后就不一样了:
class LidarSensor {
double max_range_;
double temperature_; // private,外部不能直接访问
bool is_scanning_;
public:
// 通过接口访问
double getTemperature() const {
return temperature_;
}
void setMaxRange(double range) {
if (range > 0 && range <= 100) { // 加合法性检查
max_range_ = range;
}
}
void updateTemperature(double temp) {
temperature_ = temp;
if (temperature_ > 80.0) {
// 温度过高,自动降频
reduceScanFrequency();
}
}
private:
void reduceScanFrequency() {
// 降频逻辑
}
};
现在,外部只能通过getTemperature()读温度,通过setMaxRange()设置距离范围(而且有合法性检查)。内部逻辑比如降频保护,是private的,外部看不到也改不了。
这就是封装的好处:
第一,保护数据。内部数据不会被外部随意修改。
第二,隐藏实现细节。使用者不需要知道降频是怎么实现的,只需要知道温度过高时会自动处理。
第三,便于修改。如果以后要改降频逻辑,只需要改类内部,不影响外部代码。
访问控制:public、private、protected
类的访问控制有三个级别:
public:外部可以访问。接口函数一般放这里。
private:只有类内部能访问。成员变量一般放这里。
protected:类内部和子类能访问。这个等讲继承的时候再说。
class Sensor {
private:
double value_; // 外部不能直接访问
void calibrate() {} // 外部不能调用
public:
double getValue() const { return value_; }
void setValue(double v) {
if (v >= 0) value_ = v; // 合法性检查
}
};
面试的时候,面试官可能会问:"为什么不把成员变量设为public?"
答案是:如果成员变量是public的,任何人都能修改它,你没法保证数据的合法性。比如上面的setValue,如果设成public,别人可能传入负数,而传感器的值不应该是负数。
通过private加setter,你可以在赋值的时候加合法性检查,保证数据始终是合法的。
struct和class的区别
C++里,struct和class几乎一样。唯一的区别是默认的访问控制不同:
struct默认public,class默认private。
struct Point {
double x, y, z; // 默认public
};
class Point {
double x, y, z; // 默认private
};
实际开发中,struct一般用于纯数据结构(POD),所有成员都是public的,没有复杂的逻辑。class用于有行为逻辑的类,需要封装的场景。
在机器人开发里,点云数据、坐标点这种纯数据,一般用struct:
struct Point3D {
double x, y, z;
};
struct Pose {
double x, y, z;
double roll, pitch, yaw;
};
而传感器、控制器这种有行为的对象,用class。
面试中的类设计题
面试的时候,面试官经常让你现场设计一个类。比如:"请你设计一个机器人传感器的类。"
这时候你要考虑几件事:
第一,有哪些属性?成员变量用什么类型?
第二,有哪些行为?需要哪些接口函数?
第三,哪些该暴露,哪些该隐藏?访问控制怎么设计?
第四,需不需要构造函数、析构函数?
如果你能把这些都考虑到,再结合机器人场景讲清楚为什么这么设计,面试官会觉得你不只是会写代码,而是有设计思维。
面试中还经常被追问的一个问题是:"你怎么决定一个类需要哪些成员函数?"我的经验是从使用场景倒推。先想清楚这个类的用户会怎么用他,需要哪些操作,然后把这些操作设计成public接口。内部实现细节全部藏到private里。比如在机器人项目里设计一个电机控制类,用户需要的是设置转速、读取当前状态、急停这些操作,那你就提供setSpeed、getStatus、emergencyStop这几个接口就够了,内部的PID计算、PWM输出这些细节完全不需要暴露。这种"最小接口"的设计思路,面试官听了会觉得你有实际项目经验。
给正在准备面试的你一点建议
类与对象是C++的基础中的基础。面试几乎必考。
我建议你这样练:找一个你熟悉的机器人组件,比如传感器、电机、控制器,试着用class来建模。想清楚它有什么属性、什么行为、哪些该封装、哪些该暴露。
然后把代码写出来,编译通过,跑一跑。
做完这些,你对类的理解就不只是"类是模板,对象是实例"了,而是真正能用到项目里的设计能力。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。
「机器人软件开发面试·从入门到精通」连载系列 上一篇:第17篇 static关键字的四种用法——面试考你理解深度 下一篇预告:第19篇 构造函数与析构函数——对象生命周期的管理
有任何问题欢迎评论区留言,我会尽量回复。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)