上篇聊了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篇 构造函数与析构函数——对象生命周期的管理

有任何问题欢迎评论区留言,我会尽量回复。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐