C++ 第 22 课:const —— 不允许随便修改的数据

上一课标准答案:

  1. m.speed = 100.0
  2. Joint 是我们自己通过 struct 定义出来的新类型
复制代码
Joint joints[6];

表示创建一个长度为 6 的 Joint 数组,也就是可以保存 6 个关节结构体。

复制代码
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.0

C++ 不会阻止你。

但问题是:

圆周率本来就不应该随便改。

这时候可以使用:

复制代码
const

2. 第一个 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();

到底是什么。

相关推荐
web打印社区1 小时前
JS 静默打印怎么做:纯前端为什么不行,以及最小可跑通写法
开发语言·前端·javascript·websocket·网络协议·http·pdf
大圣编蚕1 小时前
C语言怎么写小游戏?从零开始做一个贪吃蛇
c语言·开发语言
蒸蒸yyyyzwd1 小时前
力扣二分总结-lowerbound
c++
Chen—LSN1 小时前
数据结构——拿捏复杂度
java·c语言·开发语言·数据结构·经验分享·笔记·算法
Chen_harmony1 小时前
六、排序详解
c语言·数据结构·算法·排序算法
ShineWinsu1 小时前
对于Redis:List类型的解析
数据库·c++·redis·链表·缓存·面试·list
阿钱真强道1 小时前
25 嵌入式操作系统 | 设备面板:用 Qt 控件把“设备“画出来(本节不接串口)
开发语言·qt·flask·架构设计·协议契约
-dzk-1 小时前
【贪心算法】LC 55.跳跃游戏
算法·游戏·贪心算法
ofoxcoding1 小时前
Jev 模型评测深度解析:核心得分、能力局限与最佳适用任务场景
人工智能·算法·机器学习·ai