上一课标准答案:
*p = 10- 最后
x = 20 &x表示“取得变量x的内存地址”;*p表示“沿着指针p保存的地址找到那个变量,并访问它的值”- 最后
x = 50
你上一课最核心的关系是:
int x = 5; int* p = &x;对应:
x → 5 &x → x 的地址 p → x 的地址 *p → 5第 16 课:值传递、引用、指针彻底对比
这一课我们不引入太多新东西。
目标只有一个:
彻底分清“副本、别名、地址”。
这三个如果分清了,后面数组、类、ROS2 参数、机械臂程序都会容易很多。
1. 三个函数放在一起
先看:
void test1(int x) { x = 100; }void test2(int& x) { x = 100; }void test3(int* x) { *x = 100; }看起来只差一点点:
int x int& x int* x但含义完全不同。
2. 第一种:int x
void test1(int x) { x = 100; }这是:
值传递
假设外面:
int a = 5; test1(a);调用时相当于:
外面的 a ┌─────┐ │ 5 │ └─────┘ │ │ 复制 ▼ 函数里的 x ┌─────┐ │ 5 │ └─────┘函数执行:
x = 100;只改副本:
a = 5 x = 100函数结束后:
a 仍然 = 5所以:
int x=给我一份副本
3. 第二种:int& x
void test2(int& x) { x = 100; }这是:
引用传递
调用:
int a = 5; test2(a);这次没有复制出一个新的普通整数。
可以粗略理解成:
┌─────┐ a ───────→│ 5 │ └─────┘ ↑ │ x ───────────┘x是a的另一个名字。
所以:
x = 100;其实就是:
a = 100;最终:
a = 100所以:
int& x=给原变量起一个别名
4. 第三种:int* x
void test3(int* x) { *x = 100; }这是:
传地址
调用时要注意:
int a = 5; test3(&a);不是:
test3(a);因为函数要求的是:
int*也就是:
一个
int变量的地址。
5. 指针版执行过程
外面:
int a = 5;假设a地址是:
1000调用:
test3(&a);把:
&a = 1000传进去。
函数里的指针x:
x ┌──────┐ │ 1000 │ └──────┘ │ ▼ 地址1000: a ┌─────┐ │ 5 │ └─────┘然后:
*x = 100;意思是:
顺着地址找到
a,把它改成 100。
最终:
a = 100所以:
int* x=给函数原变量的地址
6. 三者一句话对比
你可以先牢牢记这三个词:
int x 副本 int& x 别名 int* x 地址这是本课最重要的总结。
7. 完整实验
#include <iostream> void byValue(int x) { x = 100; } void byReference(int& x) { x = 200; } void byPointer(int* x) { *x = 300; } int main() { int a = 5; byValue(a); std::cout << a << std::endl; byReference(a); std::cout << a << std::endl; byPointer(&a); std::cout << a << std::endl; return 0; }我们一步一步推。
开始:
a = 5第一次
byValue(a);传副本。
函数内部变成:
x = 100外面没变:
a = 5所以输出:
5第二次
byReference(a);x是a的引用。
执行:
x = 200;于是:
a = 200输出:
200第三次
byPointer(&a);把a的地址传进去。
执行:
*x = 300;于是:
a = 300输出:
300最终:
5 200 3008. 为什么引用和指针都能修改原变量?
因为它们都不是普通的“值副本”。
引用:
int& x直接代表原变量。
指针:
int* x保存原变量的地址。
所以两者最终都能找到原来的变量。
但方式不一样:
引用 ↓ 直接把它当原变量用 指针 ↓ 先拿到地址 ↓ 再用 * 找到原变量9. 调用方式也不一样
这是一个非常实用的识别方法。
值传递
函数:
void test(int x)调用:
test(a);引用
函数:
void test(int& x)调用:
test(a);注意:
调用处看起来和普通值传递一样。
指针
函数:
void test(int* x)调用:
test(&a);这次调用者必须明确传:
&a也就是地址。
10. 所以引用有时候更“隐蔽”
比如:
change(a);只看这一句,你不知道a会不会被修改。
因为函数可能是:
void change(int a)也可能是:
void change(int& a)所以以后阅读工程代码,不能只看调用处。
要去看函数声明。
这在真实机械臂项目源码里非常重要。
11. 指针调用看起来更明显
如果看到:
change(&a);你马上就应该警觉:
这个函数拿到了
a的地址,很可能会修改a。
所以指针版本在调用处通常更明显。
12. 机械臂角度例子
假设:
double joint_angle = 45.0;值传递
void reset(double angle) { angle = 0.0; }调用:
reset(joint_angle);结果:
joint_angle = 45引用
void reset(double& angle) { angle = 0.0; }调用:
reset(joint_angle);结果:
joint_angle = 0指针
void reset(double* angle) { *angle = 0.0; }调用:
reset(&joint_angle);结果:
joint_angle = 013. 那以后到底用引用还是指针?
现在不要把问题复杂化。
初学阶段先记:
值传递
适合:
函数只需要读取一个简单值,不需要修改外部变量。
例如:
double square(double x)引用
适合:
函数需要直接操作原变量,或者以后为了避免大型对象复制。
例如:
void setZero(double& angle)指针
常见于:
底层接口、数组、动态内存、C 风格 API、硬件驱动、可能需要“没有对象”的情况等。
例如以后:
uint8_t* data这种在嵌入式、CAN、串口里会很常见。
现在不要死记使用规则,随着后面代码会越来越清楚。
14. 引用和指针还有一个很大的区别
指针本身是个变量,里面真的保存地址。
比如:
int x = 5; int* p = &x;p有自己的存储空间。
而引用:
int& ref = x;更像是:
x的别名。
所以从使用感觉来说:
ref = 10;直接操作。
但指针要:
*p = 10;才能操作目标。
15. 指针可以“什么都不指”
这是指针和引用的一个重要差异。
例如:
int* p = nullptr;这里:
nullptr意思可以先理解成:
这个指针现在没有指向任何有效变量。
就像:
p 手里有一张“空地址纸条”而普通引用通常创建时就必须绑定一个对象:
int x = 5; int& ref = x;这个区别后面会正式学习。
16. 为什么真实机器人底层代码大量用指针?
因为底层程序经常要操作:
一整块数据 连续内存 CAN 帧 串口接收缓冲区 图像数据 传感器数据 设备驱动例如一个 CAN 数据帧可能有:
8 个字节底层函数经常只需要知道:
这一串数据从内存哪里开始?
所以地址就特别重要。
以后可能看到:
uint8_t* data你现在至少应该能读出:
data是一个指针,保存某个uint8_t数据所在的地址。
17. 三种写法最终做成一张表
| 写法 | 直觉 | 能否直接改变外部变量 |
|---|---|---|
int x | 副本 | 否 |
int& x | 别名 | 是 |
int* x | 地址 | 可以,通过*x |
调用方式:
void A(int x); void B(int& x); void C(int* x);对应:
A(a); B(a); C(&a);这个表值得你多看几遍。
18. 再给你一个阅读真实代码的方法
以后看到:
void update(double value)第一反应:
value是副本。
看到:
void update(double& value)第一反应:
value是引用,这个函数可能修改外部变量。
看到:
void update(double* value)第一反应:
value是指针,里面保存地址,需要看*value如何被使用。
这个阅读习惯特别重要。
本课小练习
第 1 题
void test(int a) { a = 20; } int x = 5; test(x);最后:
x = ?第 2 题
void test(int& a) { a = 20; } int x = 5; test(x);最后:
x = ?第 3 题
void test(int* a) { *a = 20; } int x = 5; test(&x);最后:
x = ?第 4 题
一句话分别解释:
int a int& a int* a