上一课标准答案:
-
不能再写:
JOINT_COUNT = 7;
因为:
const int JOINT_COUNT = 6;
表示 JOINT_COUNT 是常量,创建后不允许修改。
-
不能。
void printJoint(const Joint& joint)
里的 joint 是只读引用,所以:
joint.angle = 100.0;
会报错。
const Joint& joint
可以理解成:
joint引用外面的Joint对象,不复制它,但也不允许通过这个引用修改它。
也就是:
只读引用。
void printJoint(const Joint& joint)
更适合:
只读取、查看、打印关节信息,不修改原对象。
而:
void resetJoint(Joint& joint)
更适合:
需要直接修改原关节对象。
比如:
joint.angle = 0.0;
第 23 课:class ------ 开始真正进入面向对象
这一课很重要。
因为真实机器人 C++ 项目里,你以后会大量看到:
class Robot
class Motor
class Controller
class Camera
class SerialPort
甚至 ROS2 代码里也经常会看到:
class MyNode
先不要把"面向对象"想得特别玄学。
我们先从你已经会的 struct 出发。
1. 先复习 struct
我们之前写过:
struct Joint
{
double angle;
double speed;
bool enabled;
};
然后:
Joint joint1;
joint1.angle = 30.0;
joint1.speed = 5.0;
joint1.enabled = true;
Joint 主要是在做:
把相关数据打包在一起。
比如:
Joint
├── angle
├── speed
└── enabled
2. 但真实"关节"不只有数据
一个真实关节除了有:
angle
speed
enabled
它还应该能做事情。
例如:
设置角度
停止
使能
打印状态
也就是说,一个"关节"应该同时拥有:
数据 + 功能
于是我们开始进入:
class
3. class 最直觉的理解
你可以先把 class 理解成:
设计一种对象,规定它有哪些数据,以及它会做什么。
例如:
class Joint
{
public:
double angle;
void printAngle()
{
std::cout << angle << std::endl;
}
};
这里一个 Joint 里面不仅有:
angle
还有:
printAngle()
也就是一个函数。
4. class 里面可以放函数
这就是和我们前面单纯 struct 最大的学习重点。
以前:
struct Joint
{
double angle;
};
主要关注数据。
现在:
class Joint
{
public:
double angle;
void printAngle()
{
std::cout << angle << std::endl;
}
};
一个 Joint 同时拥有:
数据:
angle
功能:
printAngle()
5. 怎么创建对象?
定义:
class Joint
{
public:
double angle;
void printAngle()
{
std::cout << angle << std::endl;
}
};
然后:
Joint joint1;
这和之前:
Joint joint1;
看起来一样。
这里的:
joint1
叫:
对象 object
你可以先理解成:
按照
Joint这个设计图纸制造出来的一个具体东西。
6. class 是图纸,对象是实物
可以这样理解:
class Joint
↓
设计图纸
而:
Joint joint1;
Joint joint2;
相当于按照图纸制造:
joint1
joint2
两个实际对象。
这和机械设计非常像。
你画了一个:
关节模组设计图
然后可以生产:
第1个关节
第2个关节
第3个关节
每个结构相同,但数据可以不同。
7. 对象自己的数据
例如:
Joint joint1;
Joint joint2;
joint1.angle = 30.0;
joint2.angle = 60.0;
那么:
joint1
angle = 30
joint2
angle = 60
它们互不影响。
8. 对象自己的函数
如果类里有:
void printAngle()
{
std::cout << angle << std::endl;
}
那么:
joint1.printAngle();
输出:
30
而:
joint2.printAngle();
输出:
60
9. 为什么是 joint1.printAngle()
之前你已经见过点号:
joint1.angle
意思:
joint1里面的angle
现在:
joint1.printAngle()
意思:
调用
joint1里面的printAngle()函数。
所以:
.
依然可以理解成:
"这个对象里面的......"
10. 完整程序
#include <iostream>
class Joint
{
public:
double angle;
void printAngle()
{
std::cout << angle << std::endl;
}
};
int main()
{
Joint joint1;
joint1.angle = 30.0;
joint1.printAngle();
return 0;
}
输出:
30
11. 新东西:public
现在出现:
public:
这是面向对象里非常重要的东西。
你现在先简单理解:
public下面的内容,外部可以访问。
例如:
class Joint
{
public:
double angle;
};
所以外面可以:
joint1.angle = 30.0;
12. 如果没有 public 呢?
这里出现一个重要区别。
在:
class
里面,成员默认通常是:
private
也就是:
外部不能直接访问。
所以:
class Joint
{
double angle;
};
然后:
Joint joint1;
joint1.angle = 30.0;
通常会报错。
因为 angle 是私有的。
13. private 是什么?
例如:
class Joint
{
private:
double angle;
};
意思:
angle是这个对象内部的数据,外部不允许直接随便改。
这其实很符合真实工程思维。
比如真实机械臂关节角度,不应该允许任何代码随便:
joint.angle = 999999;
否则很危险。
14. 那外部怎么改?
通过函数。
例如:
class Joint
{
public:
void setAngle(double new_angle)
{
angle = new_angle;
}
private:
double angle;
};
外面:
Joint joint1;
joint1.setAngle(30.0);
这样不是直接修改:
angle
而是通过:
setAngle()
去修改。
15. 为什么这样更好?
因为函数里可以先检查。
例如:
void setAngle(double new_angle)
{
if (new_angle >= -90.0 && new_angle <= 90.0)
{
angle = new_angle;
}
}
这就非常像真实机械臂限位。
如果外部传:
joint1.setAngle(45.0);
合法:
45 在 -90 ~ 90
所以修改。
但如果:
joint1.setAngle(500.0);
不合法。
函数可以拒绝。
16. 这叫"封装"
你以后会经常听到:
Encapsulation,封装
先不用背复杂定义。
你可以理解成:
不允许别人随便碰内部数据,而是规定必须通过合适的接口操作。
生活里很像汽车。
你开车时不会直接:
用手修改发动机喷油器内部状态。
你通过:
油门
刹车
方向盘
这些接口控制汽车。
class 也是类似思想。
17. 机器人类的雏形
我们可以写:
class Robot
{
public:
void start()
{
running = true;
}
void stop()
{
running = false;
}
private:
bool running = false;
};
那么外面:
Robot robot;
robot.start();
robot.stop();
你现在已经可以读懂:
创建一个 Robot 对象 robot
调用 robot 的 start()
调用 robot 的 stop()
18. running 为什么放 private
因为我们希望:
机器人运行状态
由类自己管理。
而不是外面随便:
robot.running = true;
所以:
private:
bool running;
让外部不能直接乱改。
这会让程序结构更可靠。
19. 一个完整机械臂关节类
先做一个简单版本:
#include <iostream>
class Joint
{
public:
void setAngle(double new_angle)
{
if (new_angle >= -90.0 && new_angle <= 90.0)
{
angle = new_angle;
}
}
void printAngle()
{
std::cout << angle << std::endl;
}
private:
double angle = 0.0;
};
然后:
int main()
{
Joint joint1;
joint1.setAngle(45.0);
joint1.printAngle();
return 0;
}
输出:
45
20. 一层一层拆
Joint joint1;
创建一个 Joint 对象。
joint1.setAngle(45.0);
调用 joint1 的:
setAngle()
把:
45
传进去。
函数里面:
if (new_angle >= -90.0 && new_angle <= 90.0)
检查是否合法。
合法后:
angle = new_angle;
修改类内部私有变量。
最后:
joint1.printAngle();
输出内部角度。
21. 类里的函数叫什么?
像:
void setAngle()
放在类里面时,通常叫:
成员函数 member function
而:
double angle;
这种放在类里的变量叫:
成员变量 member variable
所以:
class Joint
成员变量:
angle
成员函数:
setAngle()
printAngle()
22. 这对真实机器人代码非常关键
以后你可能看到:
class RobotArm
{
public:
void connect();
void enable();
void moveJoint(int joint, double angle);
void stop();
private:
bool connected;
bool enabled;
double joint_angles[6];
};
你现在已经能大致读懂。
这个类叫:
RobotArm
外部可以调用:
connect
enable
moveJoint
stop
内部自己保存:
connected
enabled
joint_angles
23. 那么外面可能写
RobotArm robot;
robot.connect();
robot.enable();
robot.moveJoint(1, 30.0);
robot.stop();
以前你第一次看到这种代码可能会觉得:
robot.xxx()是什么?
现在你应该知道:
robot
↓
RobotArm 类型的对象
.
↓
访问对象里面的成员
connect()
↓
成员函数
24. struct 和 class 到底什么区别?
在 C++ 里,它们其实非常接近。
两者都可以有:
变量
函数
public
private
一个最明显的默认区别是:
struct 默认 public
class 默认 private
例如:
struct A
{
int x;
};
x 默认可以从外面访问。
但:
class A
{
int x;
};
x 默认是 private。
25. 初学阶段怎么区分使用?
你可以先这样理解:
struct
更常用于:
主要打包一组数据。
例如:
struct Point
{
double x;
double y;
double z;
};
class
更常用于:
表达一个有状态、有行为的对象。
例如:
class Robot
class Motor
class Camera
但这不是绝对规则。
26. 机器人例子
一个位姿数据:
struct Pose
{
double x;
double y;
double z;
};
很自然。
因为它主要是一组数据。
而机械臂控制器:
class RobotController
{
public:
void update();
void stop();
private:
double error;
};
用 class 就很自然。
因为它不仅保存数据,还负责执行控制功能。
27. 本课先别急着学构造函数
真正的 class 后面还有:
构造函数
析构函数
this
getter/setter
继承
多态
虚函数
但我们不能一次全塞进来。
这一课只要真正理解:
class = 数据 + 操作这些数据的函数,并可以通过 public/private 控制谁能访问。
就够了。
本课小练习
第 1 题
class Motor
{
public:
double speed;
};
外面:
Motor m;
m.speed = 100.0;
能不能这样写?
第 2 题
class Motor
{
private:
double speed;
};
外面能不能直接:
m.speed = 100.0;
为什么?
第 3 题
下面:
robot.stop();
你现在怎么解释?
第 4 题
下面这个类:
class Joint
{
public:
void setAngle(double angle);
private:
double current_angle;
};
哪些东西是成员函数?哪些是成员变量?
下一课我们继续 class,讲一个非常核心的新概念:
第 24 课:构造函数 Constructor
你会明白为什么以后经常看到:
Robot robot("COM3", 115200);
或者:
Motor motor(1);
以及为什么创建对象时,它可以自动完成初始化。