C++ 第 14 课:引用 &

上一课标准答案:

  1. 最后输出:

    5

因为 change(x) 只是把 x 的值复制给参数 a,函数里改的是副本。

  1. 依次输出:

    13
    3

原因是函数内部:

复制代码
number += 10;

把副本 number 从 3 改成 13,所以先输出 13。

但外面的:

复制代码
x

没有改变,仍然是 3。

  1. 因为:

    void change(int a)

采用的是值传递 。a 是 x 的副本,不是 x 本身。

  1. 一句话:

值传递就是把外部变量的值复制一份给函数参数,函数修改副本不会影响原变量。


第 14 课:引用 &

这一课非常重要。

前面我们遇到的问题是:

复制代码
void change(int a)
{
    a = 100;
}

int main()
{
    int x = 5;

    change(x);

    std::cout << x << std::endl;
}

最终:

复制代码
x = 5

因为:

复制代码
x → 复制一份 → a

但现在我就是想让函数:

直接修改原来的 x

怎么办?

C++ 可以使用:

复制代码
&

这就是:

引用 reference


1. 先看效果

把:

复制代码
void change(int a)

改成:

复制代码
void change(int& a)

完整代码:

复制代码
#include <iostream>

void change(int& a)
{
    a = 100;
}

int main()
{
    int x = 5;

    change(x);

    std::cout << x << std::endl;

    return 0;
}

这次输出:

复制代码
100

为什么只增加了一个:

复制代码
&

结果就完全不一样?


2. 值传递:给你一张复印件

以前:

复制代码
void change(int a)

可以想象成:

复制代码
main:

x
┌─────┐
│  5  │
└─────┘
   │
   │ 复制
   ▼
change:

a
┌─────┐
│  5  │
└─────┘

所以:

复制代码
a = 100;

只改变下面那个盒子:

复制代码
x = 5

a = 100

3. 引用:不是复印,而是给原变量起"别名"

现在:

复制代码
void change(int& a)

可以把 a 理解成:

x 的另一个名字。

调用:

复制代码
change(x);

之后可以粗略想象成:

复制代码
        ┌─────────┐
x ─────→│    5    │
        └─────────┘
             ↑
             │
             a

这里:

复制代码
x

和:

复制代码
a

不是两个盒子。

而是:

两个名字,指向同一个变量。

所以:

复制代码
a = 100;

其实就是在改:

复制代码
x = 100;

最终外面的 x 也变了。


4. 最重要的对比

值传递

复制代码
void change(int a)

含义:

复制代码
给函数一个副本

修改:

复制代码
a

不会影响原变量。


引用传递

复制代码
void change(int& a)

含义:

复制代码
让参数 a 直接代表原来的变量

修改:

复制代码
a

原变量也会变化。


5. & 到底放在哪里?

看:

复制代码
int& a

现在先把它整体理解成:

a 是一个 int 类型的引用。

不是:

复制代码
int a

而是:

复制代码
int& a

多了:

复制代码
&

这告诉 C++:

不要创建新的整数副本,a 要引用外面的那个整数。


6. 一个简单例子

复制代码
void addOne(int& number)
{
    number++;
}

然后:

复制代码
int main()
{
    int count = 10;

    addOne(count);

    std::cout << count << std::endl;
}

执行:

复制代码
count = 10

调用:

复制代码
addOne(count);

此时:

复制代码
number

就是 count 的引用。

然后:

复制代码
number++;

实际上修改:

复制代码
count:10 → 11

所以最后输出:

复制代码
11

7. 机器人例子:真正修改速度

上一课我们写:

复制代码
void stopMotor(double speed)
{
    speed = 0.0;
}

这个不能改变外面的速度。

现在改成:

复制代码
void stopMotor(double& speed)
{
    speed = 0.0;
}

然后:

复制代码
int main()
{
    double motor_speed = 100.0;

    stopMotor(motor_speed);

    std::cout << motor_speed << std::endl;
}

最后:

复制代码
motor_speed = 0

这次是真的改到了原变量。


8. 用"遥控器"来理解引用

值传递像:

拍了一张机械臂当前状态的照片给函数。

函数在照片上画来画去,不影响真实机械臂。

引用则像:

把真实机械臂的控制权交给函数。

函数一修改:

复制代码
speed = 0;

真实外部变量也改变。

这个区别你以后一定要记住。


9. 引用本身也可以在普通代码里创建

不只是函数参数可以用引用。

例如:

复制代码
int x = 5;

int& y = x;

这里:

复制代码
y

是:

复制代码
x

的引用。

可以理解成:

复制代码
x ─────┐
       ▼
   ┌──────┐
   │  5   │
   └──────┘
       ▲
y ─────┘

所以:

复制代码
y = 20;

以后:

复制代码
x = 20
y = 20

因为它们实际上对应同一个东西。


10. 看完整程序

复制代码
#include <iostream>

int main()
{
    int x = 5;

    int& y = x;

    y = 20;

    std::cout << x << std::endl;
    std::cout << y << std::endl;

    return 0;
}

输出:

复制代码
20
20

11. 引用不是"复制值"

这一点特别关键。

复制代码
int x = 5;
int y = x;

和:

复制代码
int x = 5;
int& y = x;

完全不同。

第一种:

复制代码
int y = x;

得到两个变量:

复制代码
x = 5

y = 5

之后:

复制代码
y = 100;

结果:

复制代码
x = 5
y = 100

互不影响。


第二种:

复制代码
int& y = x;

y 是 x 的引用。

执行:

复制代码
y = 100;

结果:

复制代码
x = 100
y = 100

12. 引用创建时通常必须绑定变量

例如:

复制代码
int x = 5;
int& y = x;

这里创建 y 的同时就告诉它:

你引用 x。

不能像普通变量那样随便理解成:

复制代码
int& y;

然后以后再说引用谁。

对于普通引用,通常创建时就必须绑定目标。

现在先记住这一点即可。


13. 引用一旦绑定,通常不能"换人"

看:

复制代码
int x = 5;
int z = 10;

int& ref = x;

这里:

复制代码
ref

绑定的是:

复制代码
x

如果后面写:

复制代码
ref = z;

初学者可能以为:

ref 改成引用 z。

不是。

实际上是:

把 z 的值赋给 ref 所引用的 x。

因为:

复制代码
z = 10

所以:

复制代码
x 变成 10

引用本身仍然是 x 的别名。

这个以后再深入。


14. 为什么 C++ 需要引用?

最直接的原因有两个。

第一:需要修改外部变量

例如:

复制代码
void reset(double& angle)
{
    angle = 0.0;
}

第二:以后处理很大的对象时,避免复制

比如以后你有:

复制代码
std::vector<double>

里面可能有大量数据。

如果每次传函数都完整复制一份,会浪费时间和内存。

所以以后经常会看到:

复制代码
void process(std::vector<double>& data)

甚至:

复制代码
void process(const std::vector<double>& data)

这里又出现了:

复制代码
const

以后我们会专门学。

现在只需要知道:

引用不仅能修改外部变量,还能避免不必要的数据复制。


15. 真实机械臂代码会经常见到

以后可能看到:

复制代码
void updateJointAngle(double& angle)

或者:

复制代码
void calculateIK(double x,
                 double y,
                 double z,
                 double& joint1,
                 double& joint2,
                 double& joint3)

这里前三个:

复制代码
x
y
z

可能只是输入。

而:

复制代码
joint1
joint2
joint3

使用引用,是因为函数需要把计算结果直接写出去。

例如:

复制代码
joint1 = 30.0;
joint2 = 45.0;
joint3 = -20.0;

那么外部对应的关节变量也会改变。


16. 一个简单的 IK 风格例子

先不管真实运动学,只模拟:

复制代码
void calculateAngles(double& joint1, double& joint2)
{
    joint1 = 30.0;
    joint2 = 45.0;
}

然后:

复制代码
int main()
{
    double j1 = 0.0;
    double j2 = 0.0;

    calculateAngles(j1, j2);

    std::cout << j1 << std::endl;
    std::cout << j2 << std::endl;
}

输出:

复制代码
30
45

因为:

复制代码
joint1 引用 j1
joint2 引用 j2

函数直接修改了:

复制代码
j1
j2

17. 参数是值还是引用,只差一个 &

非常值得对比。

值传递

复制代码
void change(int a)

复制

引用传递

复制代码
void change(int& a)

直接操作原变量

所以你以后看到函数参数:

复制代码
double angle

和:

复制代码
double& angle

一定要意识到:

行为可能完全不同。


18. 一个非常重要的安全意识

引用很方便,但也意味着函数可以修改你的原变量。

例如:

复制代码
void test(int& x)
{
    x = -999;
}

外面:

复制代码
int motor_speed = 100;

test(motor_speed);

结果:

复制代码
motor_speed = -999

所以以后看到:

复制代码
&

要多一个意识:

这个函数有没有可能修改我传进去的变量?

这对阅读真实工程代码非常重要。


19. 今天先不要把 & 和"地址"混在一起

你可能以后会听说:

复制代码
& 可以取地址

确实如此。

但是同一个符号:

复制代码
&

在不同位置可能有不同含义。

今天我们只学习:

复制代码
int& a

这里的 & 表示:

引用

"取地址"我们学习指针时再讲,否则现在很容易混乱。


20. 本课核心图

值传递:

复制代码
x
┌───┐
│ 5 │
└───┘
  │
  │复制
  ▼
a
┌───┐
│ 5 │
└───┘

引用:

复制代码
x ───┐
     ▼
  ┌─────┐
  │  5  │
  └─────┘
     ▲
a ───┘

这个图如果真正理解了,引用就掌握一半了。


本课小练习

第 1 题

复制代码
void change(int& a)
{
    a = 20;
}

int main()
{
    int x = 5;

    change(x);

    std::cout << x << std::endl;
}

最终输出什么?

第 2 题

复制代码
int x = 10;
int& y = x;

y = 30;

最后:

复制代码
x = ?
y = ?

第 3 题

下面两种写法有什么本质区别?

复制代码
void test(int a)

和:

复制代码
void test(int& a)

第 4 题

复制代码
void setZero(double& angle)
{
    angle = 0.0;
}

如果:

复制代码
double joint1 = 45.0;
setZero(joint1);

最后 joint1 是多少?

相关推荐
白山编程大哥1 小时前
适合初学者练习的 C/C++ 开源项目推荐
c语言·c++·开源
Joe_Wang51 小时前
【从0到1学习JVM · 21】既不浪费空间又没有碎片,标记整理算法到底慢在哪
jvm·学习·算法·垃圾回收·标记整理算法
让学习成为一种生活方式1 小时前
PlantCellChat--单细胞与空间转录组数据预测植物细胞间通讯的R包--The Plant Journal
算法
梅雅达编程笔记1 小时前
04-Python CSV数据保存与翻页抓取
开发语言·爬虫·python·pandas·数据采集·csv
C++ 老炮儿的技术栈2 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
估值探索者2 小时前
【Python量化策略实战 #04】Donchian 通道假突破太多?用 ATR 阈值过滤跑通真实突破信号
开发语言·python·接口·api接口·数据api接口·股票数据api接口
个 人 练 习 生2 小时前
C++:模板初阶详解
开发语言·c++·经验分享·学习
梦帮科技2 小时前
多任务权重流形融合:SLERP 球形线性插值、DARE 稀疏剪枝与多专家模型融合落地
人工智能·深度学习·算法·机器学习·tensorflow·聚类·剪枝
Tisfy2 小时前
LeetCode 0020.有效的括号:栈匹配
算法·leetcode·字符串·题解·括号匹配