
上一课标准答案*p 10最后x 20x表示“取得变量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 xvoid 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 xvoid 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* xvoid test3(int* x) { *x 100; }这是传地址调用时要注意int a 5; test3(a);不是test3(a);因为函数要求的是int*也就是一个int变量的地址。5. 指针版执行过程外面int a 5;假设a地址是1000调用test3(a);把a 1000传进去。函数里的指针xx ┌──────┐ │ 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