上一课标准答案:
-
最后输出:
5
因为 change(x) 只是把 x 的值复制给参数 a,函数里改的是副本。
-
依次输出:
13
3
原因是函数内部:
number += 10;
把副本 number 从 3 改成 13,所以先输出 13。
但外面的:
x
没有改变,仍然是 3。
-
因为:
void change(int a)
采用的是值传递 。a 是 x 的副本,不是 x 本身。
- 一句话:
值传递就是把外部变量的值复制一份给函数参数,函数修改副本不会影响原变量。
第 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 是多少?