上一课标准答案:

  1. 正确答案是 B
Motor();

因为构造函数:

  • 名字必须和类名一样
  • 前面没有 void、int、double 等返回类型

所以:

void Motor();

不是正确的构造函数写法。

Motor motor(3);

会把:

motor_id = 3

传入构造函数:

Motor(int motor_id)
    : id(motor_id)
{
}

所以最终:

id = 3
  1. 构造函数适合初始化,因为它会在:

对象创建时自动执行

这样可以保证一个对象“刚出生”时,就拥有合理的数据,而不是创建后还要依赖程序员记得逐项设置。

Robot robot("COM3", 115200);

可以解释成:

创建一个 Robot 类型的对象 robot,并在创建时把 "COM3" 和 115200 传给 Robot 的构造函数,用来初始化这个机器人对象。

比如可能分别表示:

"COM3"  → 串口
115200  → 波特率

第 25 课:getter / setter 与 this

这一课解决一个非常实际的问题:

如果类里面的数据设成 private,外面不能直接访问,那我要怎么读取和修改?

例如:

class Joint
{
private:
    double angle;
};

外面不能:

Joint joint1;

joint1.angle = 30.0;   // 不允许

因为:

angle

是 private。

所以我们通常给它提供专门的“入口”。


1. setter:专门负责修改

例如:

class Joint
{
public:
    void setAngle(double new_angle)
    {
        angle = new_angle;
    }

private:
    double angle;
};

这里:

setAngle()

就是一个典型的:

setter

可以理解成:

负责设置数据的函数。

于是外面可以:

Joint joint1;

joint1.setAngle(30.0);

而不是直接:

joint1.angle = 30.0;

2. getter:专门负责读取

如果想知道当前角度,可以写:

class Joint
{
public:
    double getAngle()
    {
        return angle;
    }

private:
    double angle;
};

然后:

double current = joint1.getAngle();

意思:

调用 joint1 的 getAngle(),把当前角度返回出来。

所以:

setter
↓
写数据

getter
↓
读数据

3. 为什么不直接把成员变量 public?

你可能会想:

那我直接:

public:
    double angle;

不就完了?

确实可以。

但这样外部可以随便:

joint.angle = 999999;

甚至:

joint.angle = -123456;

没有任何保护。

但用 setter:

void setAngle(double new_angle)
{
    if (new_angle >= -90.0 &&
        new_angle <= 90.0)
    {
        angle = new_angle;
    }
}

就可以做限位检查。

这就是封装的价值。


4. 一个机械臂例子

class Joint
{
public:
    void setAngle(double new_angle)
    {
        if (new_angle >= -90.0 &&
            new_angle <= 90.0)
        {
            angle = new_angle;
        }
    }

    double getAngle()
    {
        return angle;
    }

private:
    double angle = 0.0;
};

使用:

Joint joint1;

joint1.setAngle(45.0);

double angle = joint1.getAngle();

最后:

angle = 45.0

5. 现在来看 this

你以后经常会看到:

void setAngle(double angle)
{
    this->angle = angle;
}

第一次看可能很怪:

this->

到底是什么?

我们慢慢拆。


6. 先制造一个名字冲突

假设类里面有成员变量:

double angle;

然后函数参数也叫:

double angle

于是:

void setAngle(double angle)
{
    angle = angle;
}

现在问题来了:

左边 angle 和右边 angle 到底是谁?

这句话实际上非常糟糕。

因为函数参数:

angle

会遮蔽类里的成员变量 angle。


7. 这时 this 就出现了

可以写:

void setAngle(double angle)
{
    this->angle = angle;
}

其中右边:

angle

表示:

函数参数 angle

左边:

this->angle

表示:

当前这个对象里面的成员变量 angle

所以整句话:

this->angle = angle;

就是:

把传进来的 angle,存到当前对象自己的 angle 成员里。


8. this 到底是什么?

你已经学过指针。

所以现在可以稍微正式一点:

this 是一个指针,指向当前正在调用成员函数的那个对象。

例如:

Joint joint1;

joint1.setAngle(30.0);

进入 setAngle() 时:

this

大致可以理解成:

指向 joint1

所以:

this->angle

就是:

joint1 里面的 angle


9. 再创建第二个对象

Joint joint1;
Joint joint2;

joint1.setAngle(30.0);
joint2.setAngle(60.0);

第一次调用:

joint1.setAngle(30.0);

此时:

this → joint1

所以:

this->angle = 30.0;

修改的是 joint1.angle。

第二次:

joint2.setAngle(60.0);

此时:

this → joint2

所以修改的是 joint2 自己的数据。


10. -> 是什么?

你已经学过指针:

Joint* p;

如果 p 指向一个对象,访问成员时通常可以写:

p->angle

所以:

this->angle

之所以用:

->

是因为:

this 本身是指针。

你可以先记:

对象访问成员:

object.member


指针访问对象成员:

pointer->member

例如:

Joint joint;
joint.setAngle(30);

而如果:

Joint* p = &joint;

就可以:

p->setAngle(30);

这个我们后面还会专门复习。


11. 那为什么以前可以直接写 angle?

例如:

void setAngle(double new_angle)
{
    angle = new_angle;
}

这里没有名字冲突,所以 C++ 知道:

angle

指的是成员变量。

因此实际上可以粗略理解成:

this->angle = new_angle;

所以这两个在这里效果相同:

angle = new_angle;

和:

this->angle = new_angle;

12. 为什么很多程序员喜欢参数和成员同名?

例如:

class Motor
{
public:
    void setSpeed(double speed)
    {
        this->speed = speed;
    }

private:
    double speed;
};

这样名字非常自然:

成员变量:speed

参数:speed

然后用:

this->speed

明确表示:

当前对象自己的 speed。


13. 构造函数里也会大量看到 this

例如:

class Motor
{
public:
    Motor(int id)
    {
        this->id = id;
    }

private:
    int id;
};

这里:

this->id

是成员变量。

右边:

id

是构造函数参数。

所以:

this->id = id;

就是:

把传进来的 id 存进对象自己的 id。

不过现代 C++ 更常用初始化列表:

Motor(int id)
    : id(id)
{
}

你已经学过这种写法了。


14. 现在看 getter 的另一个常见写法

你以后经常看到:

double getAngle() const
{
    return angle;
}

注意:

const

这次跑到:

()

后面去了。

这是什么意思?


15. 成员函数后面的 const

double getAngle() const

表示:

这个成员函数承诺不会修改当前对象的数据。

所以:

double getAngle() const
{
    return angle;
}

只是读取:

angle

很合理。


16. 如果它偷偷修改呢?

例如:

double getAngle() const
{
    angle = 100.0;
    return angle;
}

通常会报错。

因为你已经承诺:

getAngle() const

不会修改对象。


17. 为什么 getter 特别喜欢加 const

因为 getter 的职责通常是:

查看数据,而不是修改数据。

所以:

double getAngle() const

比:

double getAngle()

更明确。

它告诉别人:

你可以放心调用,我只是读,不会乱改对象状态。


18. 注意两个 const 的位置

你已经学过:

void printJoint(const Joint& joint)

这里 const 修饰的是:

参数 joint

表示不能通过这个引用修改对象。

现在又学:

double getAngle() const

这里的 const 是成员函数限定:

这个函数不会修改当前对象。

虽然都叫 const,但位置不同。


19. 一个比较标准的 Joint 类

现在把之前知识串起来:

#include <iostream>

class Joint
{
public:
    Joint(double angle)
        : angle(angle)
    {
    }

    void setAngle(double angle)
    {
        if (angle >= -90.0 &&
            angle <= 90.0)
        {
            this->angle = angle;
        }
    }

    double getAngle() const
    {
        return angle;
    }

private:
    double angle;
};

20. 使用它

int main()
{
    Joint joint1(0.0);

    joint1.setAngle(45.0);

    std::cout
        << joint1.getAngle()
        << std::endl;

    return 0;
}

输出:

45

21. 逐步理解

创建:

Joint joint1(0.0);

构造函数:

Joint(double angle)
    : angle(angle)
{
}

初始化:

joint1.angle = 0

然后:

joint1.setAngle(45.0);

进入:

void setAngle(double angle)

参数:

angle = 45

判断合法。

然后:

this->angle = angle;

就是:

joint1 自己的 angle = 45

最后:

joint1.getAngle();

返回:

45

22. 这已经非常接近真实机器人类了

以后一个电机类可能写成:

class Motor
{
public:
    Motor(int id)
        : id(id),
          speed(0.0),
          enabled(false)
    {
    }

    void setSpeed(double speed)
    {
        this->speed = speed;
    }

    double getSpeed() const
    {
        return speed;
    }

    void enable()
    {
        enabled = true;
    }

    void disable()
    {
        enabled = false;
    }

private:
    int id;
    double speed;
    bool enabled;
};

你现在已经能读懂很多了。


23. 为什么不用全部 getter / setter?

不是每个成员都必须机械地写:

getXXX
setXXX

真实工程里应该看设计需求。

比如:

setSpeed()

可能需要限幅;

而:

enable()

比:

setEnabled(true)

语义可能更清晰。

所以 getter / setter 是一种常见手段,不是必须把每个成员都套一遍。


24. 本课核心

setter

void setAngle(double angle)
{
    this->angle = angle;
}

负责修改对象内部状态。

getter

double getAngle() const
{
    return angle;
}

负责读取对象内部状态。

this

this

是:

指向当前对象的指针。

this->angle

表示:

当前这个对象里的 angle 成员。


本课小练习

第 1 题

class Motor
{
public:
    void setSpeed(double speed)
    {
        this->speed = speed;
    }

private:
    double speed;
};

这里左边:

this->speed

和右边:

speed

分别表示什么?

第 2 题

double getSpeed() const
{
    return speed;
}

函数后面的:

const

是什么意思?

第 3 题

如果:

Motor m;
Motor* p = &m;

下面哪种调用是指针访问成员?

A. m.setSpeed(10)

B. p->setSpeed(10)

第 4 题

一句话解释:

robot.getState()

是什么意思。

下一课我们继续面向对象,但开始学习一个非常实用的新概念:

第 26 课:对象、指针与 ->

我们会彻底比较:

robot.stop();
Robot* p = &robot;
p->stop();

以及为什么 ROS2、OpenCV、硬件驱动和很多真实机器人源码里会出现大量:

ptr->function()

这会把你前面学过的类 + 对象 + 指针真正连接起来。

Logo

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

更多推荐