上一课标准答案:
m.speed = 100.0Joint是我们自己通过struct定义出来的新类型
Joint joints[6];表示创建一个长度为 6 的Joint数组,也就是可以保存 6 个关节结构体。
4.
joints[2].angle = 45.0;表示把数组中第 3 个关节的angle成员设置为45.0。
注意还是那个规则:
joints[0] → 第1个关节 joints[1] → 第2个关节 joints[2] → 第3个关节第 22 课:const—— 不允许随便修改的数据
这一课非常重要。
以后你看真实 C++ 工程代码,会大量看到:
const尤其是:
const double const int const std::string& const Joint&先不急着背。
我们从最简单的问题开始:
有些变量,我创建以后根本不希望别人修改,怎么办?
1. 普通变量可以修改
例如:
double pi = 3.14159; pi = 5.0;这是合法的。
因为pi是普通变量。
最开始:
pi = 3.14159后来:
pi = 5.0C++ 不会阻止你。
但问题是:
圆周率本来就不应该随便改。
这时候可以使用:
const2. 第一个const
写:
const double PI = 3.14159;可以理解成:
创建一个
double数据PI,但以后不允许修改它。
所以:
PI = 5.0;会报错。
因为你已经告诉 C++:
PI是只读的。
3.const怎么理解?
英文:
constant有:
常量、不变
的意思。
所以现阶段可以把:
const理解成:
这个东西不允许被修改。
例如:
const int joint_count = 6;表示:
joint_count是 6,并且程序后面不应该再改变它。
4. 为什么这很有用?
假设你的机械臂永远是 6 自由度。
可以写:
const int JOINT_COUNT = 6;以后:
for (int i = 0; i < JOINT_COUNT; i++) { }比直接到处写:
6更容易理解。
因为你一看:
JOINT_COUNT就知道:
这是关节数量。
5. 为什么很多常量名字写成大写?
例如:
const int JOINT_COUNT = 6;或者:
const double MAX_ANGLE = 90.0;全大写并不是 C++ 强制要求。
这是很多工程中的命名习惯:
一看到大写,就知道这个东西可能是常量。
比如:
const double PI = 3.14159; const double MAX_SPEED = 100.0; const int JOINT_COUNT = 6;不过以后你也会看到不同团队采用不同命名风格。
6. 机械臂限位例子
例如:
const double MAX_ANGLE = 90.0; const double MIN_ANGLE = -90.0; double angle = 45.0; if (angle >= MIN_ANGLE && angle <= MAX_ANGLE) { std::cout << "Valid" << std::endl; }这样比:
if (angle >= -90.0 && angle <= 90.0)更容易看懂。
因为:
MIN_ANGLE MAX_ANGLE直接说明了这些数字的意义。
7. 常量也有类型
例如:
const int a = 5;这是:
不允许修改的
int
而:
const double angle = 30.5;是:
不允许修改的
double
所以const并不是一种新的数据类型。
更准确地说,它是在告诉 C++:
对这个变量增加“不可修改”的限制。
8.const和函数参数有什么关系?
现在进入一个非常重要的地方。
假设我们有:
struct Joint { double angle; double speed; };然后写函数:
void printJoint(Joint joint) { std::cout << joint.angle << std::endl; }这里:
Joint joint是值传递。
也就是说:
复制一份 Joint 进去。
如果Joint很大,这样每次调用都复制,可能有点浪费。
所以我们之前学过引用:
void printJoint(Joint& joint)这就不会复制。
9. 但是引用有一个风险
看:
void printJoint(Joint& joint) { joint.angle = 999.0; }因为:
Joint& joint是原对象的引用。
所以函数可以直接修改外面的关节。
例如:
Joint j; j.angle = 30.0; printJoint(j);函数执行后:
j.angle = 999但这个函数明明叫:
printJoint我们本意只是:
打印数据。
根本不希望它修改。
怎么办?
10.const Joint&
这时候就可以写:
void printJoint(const Joint& joint) { std::cout << joint.angle << std::endl; }这句非常重要。
我们一点点拆。
11. 先看Joint& joint
你已经学过:
Joint& joint表示:
joint是外部 Joint 对象的引用。
没有复制。
12. 再加上const
const Joint& joint表示:
引用外面的 Joint,但不允许通过这个
joint去修改它。
你可以理解成:
只读引用。
这四个字非常重要。
13. 所以这句怎么读?
void printJoint(const Joint& joint)可以翻译成:
printJoint接收一个Joint的只读引用。
它有两个优点:
不复制对象 + 不允许函数乱改对象这就是为什么真实 C++ 工程里非常喜欢这种写法。
14. 对比三种方式
第一种:值传递
void printJoint(Joint joint)意思:
复制一份。
优点:
不会改到外面。
缺点:
需要复制。
第二种:普通引用
void printJoint(Joint& joint)意思:
不复制,直接引用原对象。
优点:
高效,而且可以修改。
缺点:
函数可能改坏外部数据。
第三种:const 引用
void printJoint(const Joint& joint)意思:
不复制,但只允许读取。
这通常特别适合:
函数只需要看数据,不需要修改数据。
15. 用文件类比
假设你有一份机器人参数文件。
值传递
像:
复印一份文件给别人。
他改复印件,不影响原件。
引用
像:
把原件直接交给别人。
他可以直接涂改原件。
const引用
像:
把原件放进透明保护套,只允许看,不允许修改。
这个类比非常好记。
16. 完整例子
#include <iostream> struct Joint { double angle; double speed; }; void printJoint(const Joint& joint) { std::cout << joint.angle << std::endl; std::cout << joint.speed << std::endl; } int main() { Joint j; j.angle = 30.0; j.speed = 5.0; printJoint(j); return 0; }这里调用:
printJoint(j);不会复制完整j。
函数只是通过引用查看它。
但是函数里面不能写:
joint.angle = 100;因为:
joint是const。
17. 如果硬要修改会怎样?
例如:
void printJoint(const Joint& joint) { joint.angle = 100.0; }编译器会报错。
因为你前面已经承诺:
const Joint& joint是只读的。
这其实是在帮你防止 Bug。
18. 普通基础类型还需要const&吗?
例如:
void printNumber(const int& number)理论上可以。
但对于:
int double bool char这类很小的数据,复制成本很低。
很多时候直接:
void printNumber(int number)就够了。
所以你以后经常会看到:
void setAngle(double angle)而不是:
void setAngle(const double& angle)19. 但string、vector、struct、class 很常见
例如:
void printName(const std::string& name)或者:
void processData(const std::vector<double>& data)或者:
void printJoint(const Joint& joint)因为这些对象可能比一个普通double大得多。
所以:
不复制 + 不修改
是非常常见的工程需求。
20. 你以后会大量见到这种函数
例如:
void printAngles(const std::vector<double>& angles)拆开:
std::vector<double> ↓ 一个 double vector & ↓ 引用,不复制 const ↓ 只读,不能修改整句:
函数接收一个只读的
double vector引用。
21. 一个机器人轨迹例子
假设:
std::vector<double> trajectory = { 10.0, 20.0, 30.0 };函数:
void printTrajectory(const std::vector<double>& trajectory) { for (int i = 0; i < trajectory.size(); i++) { std::cout << trajectory[i] << std::endl; } }它只负责看数据、打印数据。
所以很适合:
const std::vector<double>&因为我们根本不需要改它。
22. 那如果函数就是需要修改呢?
那就不要加const。
例如:
void setZero(Joint& joint) { joint.angle = 0.0; }因为我们的目的就是:
修改原来的
joint
所以这里必须允许修改。
不能写:
void setZero(const Joint& joint)否则:
joint.angle = 0.0;会被禁止。
23. 所以怎么选?
现在可以先这样判断。
如果函数:
需要修改外部对象
用:
Joint&例如:
void resetJoint(Joint& joint)如果函数:
只需要读取外部对象,不需要修改
优先考虑:
const Joint&例如:
void printJoint(const Joint& joint)如果参数只是一个简单数字:
int double bool通常直接值传递即可:
void setSpeed(double speed)24. 再看一个非常典型的机器人函数
bool isJointSafe(const Joint& joint) { return joint.angle >= -90.0 && joint.angle <= 90.0; }这句现在你已经可以完整拆出来:
bool ↓ 函数最后返回 true/false isJointSafe ↓ 函数名 const Joint& joint ↓ 接收一个 Joint,只读引用,不复制 return ... ↓ 判断关节角度是否合法这已经很像真实工程 C++ 代码了。
25.const还能和指针结合
以后你会见到:
const int* p以及:
int* const p甚至:
const int* const p但现在先不学。
因为const + 指针的位置不同,含义会变化,很容易一下绕晕。
我们后面会专门开一课讲。
26. 本课最核心的三个写法
普通变量:
double angle = 30.0;可以修改。
常量:
const double MAX_ANGLE = 90.0;不允许修改。
只读引用:
const Joint& joint不复制外部 Joint,而且不允许通过这个引用修改它。
本课小练习
第 1 题
const int JOINT_COUNT = 6;后面能不能写:
JOINT_COUNT = 7;为什么?
第 2 题
下面函数:
void printJoint(const Joint& joint)里面能不能写:
joint.angle = 100.0;第 3 题
一句话解释:
const Joint& joint是什么意思。
第 4 题
下面两个函数分别更适合哪种场景?
void printJoint(const Joint& joint)void resetJoint(Joint& joint)下一课我们进入:
第 23 课:class—— 从 struct 走向真正的面向对象
你会开始理解为什么真实机器人程序里会出现:
class Robot { public: void connect(); void moveJoint(); void stop(); private: double joint_angle; };也会第一次真正理解:
robot.connect(); robot.stop();到底是什么。