C++ 第 15 课:指针 * 和地址 &

上一课标准答案:

  1. 最终输出:

    20

因为:

复制代码
void change(int& a)

中的 a 是 x 的引用,修改 a 就是在修改原来的 x。

复制代码
x = 30
y = 30

因为:

复制代码
int& y = x;

表示 y 是 x 的别名,它们对应同一个变量。

  1. 本质区别:

    void test(int a)

是值传递:

把原变量的值复制一份给 a,修改 a 不影响原变量。

而:

复制代码
void test(int& a)

是引用传递:

a 直接引用原变量,修改 a 会修改原变量。

  1. 最后:

    joint1 = 0.0

因为:

复制代码
setZero(joint1);

把 joint1 通过引用传进函数,函数里的:

复制代码
angle = 0.0;

直接修改了 joint1。


第 15 课:指针 * 与地址 &

这节是 C++ 里第一个真正容易绕晕人的知识点。

但我们不背定义。

我们先回答一个最基础的问题:

变量到底放在哪里?


1. int x = 5; 到底发生了什么?

你现在已经知道:

复制代码
int x = 5;

可以粗略理解成:

复制代码
x
┌─────┐
│  5  │
└─────┘

但这个"盒子"实际上不是凭空存在的。

它要放在电脑的:

内存 RAM

里面。

你可以把内存想象成一排非常非常多的小房间。

复制代码
内存

地址1000     地址1004     地址1008     地址1012
┌──────┐    ┌──────┐    ┌──────┐    ┌──────┐
│      │    │      │    │      │    │      │
└──────┘    └──────┘    └──────┘    └──────┘

每一块位置都有自己的:

地址

就像现实中的房子有门牌号。


2. 变量名和地址不是一回事

假设:

复制代码
int x = 5;

电脑可能把 x 放在某个内存位置。

比如为了理解,我们假设:

复制代码
变量名:x

内存地址:1000

内容:5

那么可以画:

复制代码
        x
        ↓
地址 1000
┌──────────┐
│    5     │
└──────────┘

这里:

复制代码
x

是你给变量起的名字。

而:

复制代码
1000

是它真正位于内存中的地址。

现实程序里的地址不会这么简单,可能长得像:

复制代码
0x7ffd1234abcd

现在先不用管。


3. 地址就像门牌号

这是理解指针最好的类比之一。

假设:

复制代码
杭州某小区 3栋502室

这是一个地址。

房间里面住着一个人。

对应 C++:

复制代码
地址
↓
内存中的位置

值
↓
这个位置里面存放的数据

所以:

复制代码
int x = 5;

可以想成:

复制代码
门牌号:1000
房间内容:5

4. 怎么知道 x 的地址?

C++ 可以写:

复制代码
&x

注意:

这里的:

复制代码
&

和上一课的:

复制代码
int& y = x;

虽然符号一样,但位置不同,意义也不同。

这里:

复制代码
&x

表示:

取得变量 x 的地址。


5. 看一个程序

复制代码
#include <iostream>

int main()
{
    int x = 5;

    std::cout << x << std::endl;
    std::cout << &x << std::endl;

    return 0;
}

第一句:

复制代码
std::cout << x << std::endl;

输出的是:

复制代码
5

第二句:

复制代码
std::cout << &x << std::endl;

输出的是:

x 在内存中的地址。

你可能看到类似:

复制代码
0x7ffdf2817a4c

每次运行甚至可能不一样。

现在不需要解释为什么。


6. 两种 & 千万先分开

目前你遇到两种:

第一种

复制代码
int& y = x;

这里:

复制代码
&

表示:

y 是一个引用。


第二种

复制代码
&x

这里:

复制代码
&

表示:

取得 x 的地址。

你可以先根据位置区分:

复制代码
int& y
    ↑
声明引用

&x
↑
获取地址

以后会逐渐习惯。


7. 那"指针"是什么?

终于到了核心。

假设:

复制代码
int x = 5;

而:

复制代码
&x

能获得 x 的地址。

那么我们能不能创建一个变量,专门保存这个地址?

可以。

这就是:

指针 pointer

例如:

复制代码
int* p = &x;

第一次看到很怪。

我们一点点拆。


8. int* p = &x;

先看右边:

复制代码
&x

意思:

x 的地址。

假设:

复制代码
&x = 1000

然后:

复制代码
int* p

表示:

创建一个指针变量,名字叫 p。

它用来保存:

一个 int 类型变量的地址。

所以:

复制代码
int* p = &x;

可以翻译成:

创建一个指向 int 的指针 p,让它保存 x 的地址。


9. 画出来就非常直观了

复制代码
int x = 5;
int* p = &x;

可以想成:

复制代码
x
地址1000
┌─────┐
│  5  │
└─────┘
   ▲
   │
   │ p里面存着1000
   │
p
┌──────┐
│ 1000 │
└──────┘

也就是说:

复制代码
p

不是存 5。

而是存:

x 在哪里。

这就是指针。


10. 一句话理解指针

指针就是一个专门保存内存地址的变量。

普通变量:

复制代码
int x = 5;

里面放:

复制代码
5

指针变量:

复制代码
int* p = &x;

里面放:

复制代码
x 的地址

11. * 又是什么?

现在出现第二个核心符号:

复制代码
*p

如果:

复制代码
p

里面存的是 x 的地址,那么:

复制代码
*p

意思就是:

根据 p 保存的地址,找到那个位置里面的数据。

这个操作叫:

解引用 dereference

暂时不用背英文。

你可以把:

复制代码
*p

理解成:

"顺着 p 找过去,看看里面是什么。"


12. 举例

复制代码
int x = 5;

int* p = &x;

现在:

复制代码
p 存着 x 的地址

所以:

复制代码
*p

就是:

复制代码
5

例如:

复制代码
std::cout << *p << std::endl;

输出:

复制代码
5

13. 现在我们有三个不同的东西

假设:

复制代码
int x = 5;
int* p = &x;

那么:

复制代码
x

表示:

x 里面的值

也就是:

复制代码
5

复制代码
&x

表示:

x 的地址

例如假设:

复制代码
1000

复制代码
p

表示:

p 里面保存的内容

而 p 保存的就是:

复制代码
1000

复制代码
*p

表示:

去 p 指向的位置取值

于是得到:

复制代码
5

所以:

复制代码
x   → 5

&x  → 1000

p   → 1000

*p  → 5

这是本课最核心的关系。


14. 为什么 *p 能修改 x?

看:

复制代码
int x = 5;
int* p = &x;

*p = 100;

这里:

复制代码
*p

指的是:

p 指向的那个变量。

而 p 指向:

复制代码
x

所以:

复制代码
*p = 100;

实际上就是:

复制代码
x = 100;

最终:

复制代码
x = 100

15. 图示理解

开始:

复制代码
p
┌──────┐
│ 1000 │
└──────┘
    │
    ▼

地址1000
x
┌──────┐
│  5   │
└──────┘

执行:

复制代码
*p = 100;

意思:

顺着 p 找到地址 1000,把那个位置的内容改成 100。

于是:

复制代码
p
┌──────┐
│ 1000 │
└──────┘
    │
    ▼

x
┌──────┐
│ 100  │
└──────┘

16. 完整程序

复制代码
#include <iostream>

int main()
{
    int x = 5;

    int* p = &x;

    std::cout << x << std::endl;
    std::cout << *p << std::endl;

    *p = 100;

    std::cout << x << std::endl;

    return 0;
}

输出:

复制代码
5
5
100

为什么?

第一次:

复制代码
std::cout << x;

输出:

复制代码
5

第二次:

复制代码
std::cout << *p;

因为 p 指向 x,所以也是:

复制代码
5

然后:

复制代码
*p = 100;

直接修改 x。

最后输出:

复制代码
100

17. 指针和引用是不是很像?

非常像。

引用:

复制代码
int x = 5;

int& ref = x;

ref = 100;

结果:

复制代码
x = 100

指针:

复制代码
int x = 5;

int* p = &x;

*p = 100;

结果同样:

复制代码
x = 100

所以你可能问:

那为什么还要两种?

非常好的问题。

现在先不深入,先建立最简单区别。


18. 引用更像"别名"

复制代码
int& ref = x;

可以理解成:

复制代码
x 和 ref 是同一个东西的两个名字

以后正常使用:

复制代码
ref = 10;

不需要特殊符号。


19. 指针更像"地址纸条"

复制代码
int* p = &x;

p 自己是一个变量。

里面保存:

x 的地址。

如果想操作 x:

复制代码
*p

需要先"顺着地址找过去"。

所以:

复制代码
引用
≈ 别名

指针
≈ 保存地址的变量

20. 用现实地址类比

假设:

复制代码
x = 一台机械臂

引用:

复制代码
ref

相当于:

给这台机械臂起了另一个名字。


指针:

复制代码
p

相当于你手里有一张纸:

复制代码
"机械臂在 3号实验室"

这个纸条不是机械臂本身。

它只是告诉你:

去哪里找到机械臂。

然后:

复制代码
*p

就相当于:

按纸条上的地址找到机械臂本体。


21. int* p 里的 * 和 *p 又有区别

这也是初学者容易混的地方。

声明时:

复制代码
int* p;

这里的:

复制代码
*

表示:

p 是一个指针变量。


使用时:

复制代码
*p

这里的:

复制代码
*

表示:

访问 p 指向的那个变量。

虽然都是 *,但是所在位置不同。

先根据上下文区分。


22. 现在把四个东西放在一起

复制代码
int x = 5;
int* p = &x;

牢记:

复制代码
x
↓
变量的值
5


&x
↓
变量的地址


p
↓
p 保存的地址
也就是 &x


*p
↓
p 指向地址里的值
也就是 x
也就是 5

所以可以记成:

复制代码
p = &x

*p = x

这是非常重要的一组关系。


23. 指针怎么用于函数?

上一课我们用引用:

复制代码
void change(int& a)
{
    a = 100;
}

指针也能做到。

例如:

复制代码
void change(int* p)
{
    *p = 100;
}

调用时:

复制代码
int x = 5;

change(&x);

注意这里不再传:

复制代码
x

而是传:

复制代码
&x

也就是:

x 的地址。


24. 一步一步看

函数:

复制代码
void change(int* p)

表示:

change 需要接收一个 int 的地址。

外面:

复制代码
int x = 5;

然后:

复制代码
change(&x);

把:

复制代码
x 的地址

传进去。

于是函数里的:

复制代码
p

保存:

复制代码
x 的地址

再执行:

复制代码
*p = 100;

就是:

根据地址找到 x,把它改成 100。

所以最终:

复制代码
x = 100

25. 引用参数和指针参数对比

引用

复制代码
void change(int& a)
{
    a = 100;
}

change(x);

指针

复制代码
void change(int* p)
{
    *p = 100;
}

change(&x);

两者都能修改外部的 x。

但是写法明显不同。

现在先记住结构,不需要判断"哪个更好"。

后面随着工程代码学习,我们会讲使用场景。


26. 机器人里为什么会遇到指针?

非常多。

比如以后你处理:

复制代码
传感器数据
图像
数组
动态内存
ROS2消息
设备驱动
串口缓存区
CAN数据

经常会出现地址。

例如你以后可能见:

复制代码
uint8_t* data;

它大概意思就是:

data 是一个指针,指向一块 uint8_t 数据。

或者 OpenCV、底层驱动程序里经常会涉及:

一块连续内存到底在哪里。

所以指针是 C/C++ 底层编程能力的核心。


27. 但为什么指针容易出问题?

因为指针保存的是地址。

如果地址错误:

复制代码
找错房间

你就可能访问不属于自己的内存。

这会导致:

复制代码
程序崩溃
数据被破坏
Segmentation fault

所以以后我们会专门讲:

复制代码
nullptr

以及:

空指针、野指针、悬空指针

但今天不要一下塞太多。


28. 今天只掌握这一张图

复制代码
int x = 5;
int* p = &x;

对应:

复制代码
           p
       ┌────────┐
       │ 地址   │
       └───┬────┘
           │
           ▼
           x
       ┌────────┐
       │   5    │
       └────────┘

并且:

复制代码
x   → 5

&x  → x的地址

p   → x的地址

*p  → 5

如果把这个真正理解,指针就迈过第一道坎了。


本课小练习

第 1 题

复制代码
int x = 10;

int* p = &x;

那么:

复制代码
*p = ?

第 2 题

复制代码
int x = 5;
int* p = &x;

*p = 20;

最后:

复制代码
x = ?

第 3 题

用自己的话分别解释:

复制代码
&x

和:

复制代码
*p

第 4 题

下面代码:

复制代码
void change(int* p)
{
    *p = 50;
}

int main()
{
    int x = 3;

    change(&x);
}

最后:

复制代码
x = ?
相关推荐
奶茶树1 小时前
【C++】14. C++11新特性【下】
开发语言·c++
web打印社区1 小时前
汽修 / 4S 店:维修工单、结算单网页怎么静默出纸
开发语言·前端·javascript·chrome·ecmascript
Sirens.1 小时前
Java多线程实例:单例模式、阻塞队列、线程池与定时器
java·开发语言·单例模式
牢姐与蒯1 小时前
c++加餐之位图
c++·算法·哈希
沫璃染墨1 小时前
《从零入门Linux系统篇(五十三):线程篇·六——线程互斥详解:从并发问题到mutex互斥锁》
linux·运维·服务器·开发语言·后端·架构·系统架构
老王爱玩车1 小时前
深入理解指针6
c语言·开发语言·学习
苏辰澈1 小时前
C++自己的族谱规则:继承详解上
c语言·c++·visualstudio
Eloudy1 小时前
全文 - version.A - AMBA CHI Chip-to-Chip(C2C)
java·开发语言·数据库·gpu·chiplet
秋名RG1 小时前
Java历代LTS版本新特性详解(代码增强版)
java·开发语言·python