news 2026/10/2 6:55:21

C++ 第 16 课:指针、引用、值传递三者彻底对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ 第 16 课:指针、引用、值传递三者彻底对比

上一课标准答案:

  1. *p = 10
  2. 最后x = 20
  3. &x表示“取得变量x的内存地址”;*p表示“沿着指针p保存的地址找到那个变量,并访问它的值”
  4. 最后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 300

8. 为什么引用和指针都能修改原变量?

因为它们都不是普通的“值副本”。

引用:

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 = 0

13. 那以后到底用引用还是指针?

现在不要把问题复杂化。

初学阶段先记:

值传递

适合:

函数只需要读取一个简单值,不需要修改外部变量。

例如:

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
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 6:55:15

SpringBoot+Vue车辆管理系统:从源码到部署的毕设全攻略

手头这套《SpringBootVue 车辆管理系统平台》是一个很典型的Java Web毕业设计源码包&#xff0c;我经常被读者问到“完整项目源码SQL脚本接口文档”这种搭配到底怎么用&#xff0c;其实里面包含了后端SpringBoot工程、前端Vue页面、数据库初始化脚本和接口说明文档四块内容&…

作者头像 李华
网站建设 2026/10/2 6:54:18

病毒寄生、自指崩溃与经验实体绑定:大语言模型“波话连篇”的逻辑根源、学术分赃机制与价值AI重构

论符号宿命、认知置换与学术特权分赃&#xff1a;大语言模型中的 “波普尔病毒” 现象、自指双重死局与物理因果绑定的重构摘要当前全球大语言模型&#xff08;LLMs&#xff09;的迭代狂热正处于技术神话与资本叙事的交汇巅峰。然而&#xff0c;基于下一词预测&#xff08;Next…

作者头像 李华
网站建设 2026/10/2 6:51:35

AI开发实战:OpenClaw小龙虾图形化界面安装,读取腾讯文档写周报

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华