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 小时前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
Elastic 中国社区官方博客2 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1882 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn3 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su3 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
传奇开心果编程3 小时前
【Rust入门练中学】第5课:引用与借用
开发语言·rust·引用·编程语言理论·通用编程语言·借用
residual_fan4 小时前
船用柴油机动态机动工况故障诊断:控制感知仿射网络(CAAN)的技术实现
人工智能·算法·数据挖掘·数据分析
qetfw4 小时前
Bash 变量:export、位置参数与默认值展开验证
linux·开发语言·chrome·bash
电子云与长程纠缠4 小时前
UE5魂系游戏开发教程05
开发语言·学习·游戏·ui·ue5
syagain_zsx4 小时前
BRpc 二次封装:Channel 管理、服务管理与回调工厂
c++·学习·程序人生·rpc·谷歌