欢迎光临
我们一直在努力

CSP-J 初赛(以满分为目标):第九课《指针、地址与引用——变量到底住在内存的什么地方?》


 第九课  指针、地址与引用

——变量到底住在内存的什么地方?

这一课要解决 CSP-J 初赛中一个非常核心的问题:

程序中的变量,到底是怎样放进计算机里的?

&a 是什么?

*p 是什么?

p = &a 到底做了什么?

为什么修改 *p,变量 a 也会跟着变化?

首先从计算机内存讲起:内存由许多存储单元组成,每个字节都有一个固定编号,这个编号称为地址,CPU 按地址访问内存中的数据。

所以这一课真正要建立的是:

“变量”和“变量的地址”是两回事。


一、先从一个故事开始:每个变量都有自己的“房间”

假设:

int a = 10;

我们可以把它想象成学校宿舍:

变量名:a
房间号:1000
房间里的东西:10

也就是:

内存
┌─────────────┐
1000 → │ 10 │
└─────────────┘
↑
│
a

这里有两个完全不同的东西:

① a

表示:

房间里面存放的东西。

也就是:

10

② &a

表示:

这个房间的门牌号。

例如:

1000

所以:

a 是数据,&a 是地址。

这是本课第一个必须掌握的内容。


二、什么是内存地址?

内存地址:

内存中的每个字节都有一个固定编号,这个编号叫作地址,CPU 按地址访问数据。

可以把内存想象成一条特别长的街道:

地址:

1000 1001 1002 1003 1004 1005
↓ ↓ ↓ ↓ ↓ ↓
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ │ │ │ │ │ │ │ │ │ │ │
└───┘ └───┘ └───┘ └───┘ └───┘ └───┘

每个位置都有自己的编号。

因此:

地址就是内存中一个位置的编号。


三、&:取地址运算符

例如:

int a = 10;

如果写:

cout << &a;

意思不是输出10。

而是:

告诉我变量 a 在内存中的地址。

例如可能输出:

0x61ff0c

这个具体数字每次运行都可能不同。

所以 CSP-J 程序阅读时:

一般不要纠结地址具体是多少。

我们真正关心的是:

谁和谁指向同一个内存位置?


四、一个非常重要的区别

看下面:

int a = 10;

cout << a;
cout << &a;

第一个:

a

表示:

a里面的数据。

第二个:

&a

表示:

a所在的位置。

可以记成:

a
↓
“里面是什么?”

&a
↓
“住在哪里?”


五、什么是指针?

现在假设:

int a = 10;

我们已经知道:

&a

可以得到 a 的地址。

那么:

如果我想把这个地址保存起来怎么办?

这就是指针。

例如:

int *p;

这里的 p 是一个:

指针变量。

它专门用来保存地址。


六、int *p 到底是什么意思?

不要把它死记成:

“星号就是指针。”

更准确的理解是:

int *p;

表示:

p 是一个指针,它保存的是一个 int 类型变量的地址。

例如:

int a = 10;
int *p = &a;

现在:

a
┌─────┐
│ 10 │
└─────┘
↑
│
│ &a
│
┌─────┐
p ───→ │地址 │
└─────┘

也就是说:

p里面保存的是a的地址。

所以:

p = &a;

可以理解成:

“让p记住a住在哪里。”


七、指针最重要的三个符号

本课要把三个概念分开:

int a = 10;
int *p = &a;

a

变量本身:

10

&a

变量的地址:

a住在哪里

p

保存这个地址:

p里面存着&a


八、那么 *p 又是什么?

这是指针最容易把初学者绕晕的地方。

现在:

int a = 10;
int *p = &a;

因为:

p → a

所以:

*p

意思是:

“顺着p保存的地址,找到那个变量。”

因此:

cout << *p;

输出:

10


九、用“快递地址”理解 *

可以这样理解。

假设:

p里面保存:
“幸福路100号”

那么:

p

是:

地址纸条。

而:

*p

是:

根据地址找到房子,然后看看房子里面有什么。

所以:

p
↓
地址

*p
↓
这个地址对应的内容

这就是指针最核心的思想。


十、*p 不仅能读,还能修改

这是重要的一步。

int a = 10;
int *p = &a;

*p = 20;

发生了什么?

第一步:

a = 10

第二步:

p → a

第三步:

*p = 20;

意思:

找到 p 指向的那个变量,把它改成20。

因此:

cout << a;

输出:

20


十一、程序阅读的重点

以后看到:

int a = 10;
int *p = &a;

*p = 100;

一定要马上想到:

p
↓
a

因此:

a = 100

而不是认为:

“修改了p。”

实际上:

修改的是p指向的那个变量。


十二、画箭头,是解决指针题,好的方法

例如:

int a = 5;
int b = 10;

int *p = &a;
int *q = &b;

不要在脑子里想。

直接画:

p ─────→ a = 5

q ─────→ b = 10

然后:

*p = 20;

得到:

p ─────→ a = 20

q ─────→ b = 10


十三、经典程序阅读题

看:

int a = 10;
int b = 20;

int *p = &a;

*p = 30;

p = &b;

*p = 40;

cout << a << " " << b;

我们一步一步模拟。


第一步

int a = 10;
int b = 20;

状态:

a = 10
b = 20


第二步

p = &a;

画:

p ─────→ a
10


第三步

*p = 30;

所以:

p ─────→ a
30


第四步

p = &b;

注意!

这一步不是修改 b。

而是:

让p改去指向b。

现在:

p ─────→ b
20


第五步

*p = 40;

因此:

p ─────→ b
40

最终:

a = 30
b = 40

输出:

30 40


十四、指针题容易出现的“换目标”

例如:

int a = 1;
int b = 2;

int *p = &a;

p = &b;

有的孩子会认为:

a和b发生了交换。

完全错误。

实际上只是:

原来:

p ──→ a

后来:

p ──→ b

a和b里面的数字完全没有改变。

所以一定要记:

p = &b 是“换指向”,不是“换数据”。


十五、两个指针可以指向同一个变量

例如:

int a = 10;

int *p = &a;
int *q = &a;

现在:

┌───────┐
p ────→│ │
│ a=10 │
q ────→│ │
└───────┘

所以:

*p = 20;

那么:

cout << *q;

也是:

20

因为:

p和q指向的是同一个变量。


十六、这就是“别名”思想

两个名字:

p
q

实际上都找到了:

同一个a

所以:

一个人有两个绰号

改的是同一个人。

这是以后理解:

  • 引用

  • 函数引用传参

  • 链表

  • 动态内存

  • 树

  • 图

的重要基础。


十七、指针和数组有什么关系?

例如:

int a[5] = {10,20,30,40,50};

数组中的元素:

a[0] a[1] a[2] a[3] a[4]

可以想象成一排连续的小房间:

┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘

而指针可以指向其中某个位置。

例如:

int *p = &a[2];

那么:

p
↓
a[2]
↓
30

所以:

cout << *p;

输出:

30


十八、指针与数组的一个重要关系

考试中经常会看到:

*(a + i)

它与:

a[i]

有密切关系。

在C++中:

a[i]

可以理解为:

*(a+i)

这一点对初赛中的程序阅读很重要。


十九、指针移动

假设:

int a[5] = {10,20,30,40,50};

int *p = a;

可以理解为:

p
↓
10

如果:

p++;

那么:

p
↓
20

再:

p++;

变成:

p
↓
30

所以:

指针可以沿着数组移动。


二十、为什么 p++ 不是简单地加1?

这是一个稍微提高一点的知识。

如果:

int *p;

那么:

p++

表示:

指向下一个 int。

并不是简单地让地址数字增加1。

因为一个 int通常占多个字节。

这一点在初赛中可能作为概念题出现,因此孩子应该记住:

指针加1,移动的是“一个元素”,而不是简单的一个字节。


二十一、空指针 nullptr

有一个非常重要的概念:

int *p = nullptr;

表示:

p目前没有指向任何有效变量。

可以画成:

p ─────→ 什么也没有

这和:

int *p = &a;

完全不同。

后者:

p ─────→ a


二十二、为什么不能随便 *p?

如果:

int *p = nullptr;

然后:

cout << *p;

就是错误的。

因为:

p没有告诉计算机“去哪里找”。

所以:

p = nullptr

的时候:

不要解引用 *p。


二十三、野指针是什么?

对于 CSP-J 初赛,只需要形成一个初步概念。

例如:

int *p;

这里的 p 没有初始化。

它里面可能是一个乱七八糟的地址。

这类指针可能成为:

野指针。

可以理解成:

“它像一个没有写地址的快递员,却被要求去送货。”

所以:

int *p = nullptr;

比:

int *p;

安全得多。


二十四、引用是什么?

讲完指针之后,要掌握引用:

int a = 10;
int &b = a;

这里的:

b

是 a 的:

引用。

可以理解成:

b不是另外创建了一个新的10,而是给 a 起了另一个名字。


二十五、指针和引用的直观区别

例如:

int a = 10;
int &b = a;

可以画:

┌───────┐
a ─────→│ 10 │
└───────┘
↑
│
b

也就是说:

a和b
是同一个变量的两个名字。

所以:

b = 20;

之后:

cout << a;

得到:

20


二十六、指针和引用的区别

这是初赛经常考选择题的地方。

对比指针引用
保存地址 是 本质上是别名
可以修改指向 可以 一旦绑定不能改绑
可以为空 可以,用nullptr 通常不能表示“空引用”
使用数据 *p 直接使用名字
常见形式 int *p int &r

例如:

int a = 10;
int b = 20;

int *p = &a;
p = &b;

可以。

但:

int &r = a;

之后:

r = b;

不是让 r 改去引用 b。

而是:

把 b 的值赋给 a。

也就是:

a = b

这一点容易出错。


二十七、为什么函数参数经常出现引用?

前面的第7课我们讲了函数。

例如:

void change(int x)
{
x = 100;
}

调用:

int a = 10;
change(a);

函数里的 x 是一个副本。

所以:

a还是10


但是:

void change(int &x)
{
x = 100;
}

调用:

int a = 10;
change(a);

此时:

x
和
a

是同一个变量。

所以:

a变成100

这就把:

“函数参数”

和

“引用”

连接起来了。


二十八、CSP-J程序阅读经典模型

以后看到:

void f(int &x)
{
x++;
}

int main()
{
int a = 10;
f(a);
cout << a;
}

不要认为:

f里面有一个新的x。

应该画:

a ───┐
↓
[10]
↑
│
x

执行:

x++;

之后:

[11]

所以:

a = 11


二十九、指针参数也一样

例如:

void f(int *p)
{
*p = 100;
}

int main()
{
int a = 10;
f(&a);
cout << a;
}

调用:

f(&a);

相当于把:

a的地址

交给了 p。

所以:

p ───→ a
10

执行:

*p = 100;

变成:

p ───→ a
100

最终:

a = 100


三十、这一题一定要会画

void f(int *p)
{
(*p)++;
}

int main()
{
int a = 5;
f(&a);
cout << a;
}

分析:

调用之前

a = 5

f(&a)

p ───→ a
5

(*p)++

p ───→ a
6

所以输出:

6


三十一、本课与前面知识的真正连接

现在可以把前面9课串起来:

变量
↓
表达式
↓
条件、循环
↓
函数
↓
数组
↓
字符串
↓
栈、队列
↓
指针、引用
↓
内存

同学们开始逐渐建立一个完整的认识:

程序
↓
变量
↓
变量存放在内存
↓
内存有地址
↓
指针可以保存地址
↓
通过地址找到变量
↓
函数可以通过引用/指针修改变量


三十二、本课最重要的“符号翻译表”

同学们要背下来。

写法人话
a a这个变量的值
&a a的地址
int *p p是指向int的指针
p = &a p指向a
p p里面保存的地址
*p 找到p指向的变量
*p = 20 修改p指向的变量
nullptr 不指向有效对象
int &r = a r是a的另一个名字

三十三、CSP-J初赛指针题“五步法”

同学们看到指针程序,不要慌。

严格按照:

第一步:找变量

a、b、x……

第二步:找指针

p、q……

第三步:画箭头

例如:

p → a
q → b

第四步:看到 *p

马上翻译:

找到p指向的变量。

第五步:程序每执行一步,就更新图

例如:

p → a=10

*p=20

p → a=20


三十四、综合例题

CSP-J 程序阅读题:

void f(int *p, int &x)
{
*p += 10;
x += 20;
}

int main()
{
int a = 5;
int b = 10;

f(&a, b);

cout << a << " " << b;
}

我们一步一步看。


第一步

a = 5
b = 10


第二步

调用:

f(&a,b);

因此:

p ───→ a
5

x ───→ b
10

注意:

p是指针。

x是引用。


第三步

*p += 10;

所以:

a = 15


第四步

x += 20;

因为:

x就是b的另一个名字

所以:

b = 30


最终:

a = 15
b = 30

输出:

15 30


三十五、本课需要掌握的知识层次

对于准备 CSP-J 初赛的小学生,要分成三个层次来看。

★ 第一层:必须掌握

变量
地址
&a
指针
*p
p=&a
nullptr

要求:

能读懂程序。


★★ 第二层:重点掌握

指针修改变量
多个指针指向同一个变量
指针与数组
引用
引用参数
指针参数

要求:

能够完成程序模拟。


★★★ 第三层:提高了解

指针运算
*(a+i)
动态内存
new/delete
指针与结构体

结构体指针,以及 new/delete、malloc/free 的区别;例如结构体指针访问成员时可以使用 ->,动态分配的结构体对象可以用 new,最后用 delete 释放。

这些内容可以参考后面专项讲义。


三十六、课后练习

第一组:看懂符号

判断:

int a=10;
int *p=&a;

分别问:

  • a是什么?

  • &a是什么?

  • p是什么?

  • *p是什么?


  • 第二组:画箭头

    int a=10;
    int b=20;

    int *p=&a;
    p=&b;
    *p=100;

    要求:

    不运行程序,只画图并说出a、b最后是多少。


    第三组:函数参数

    比较:

    void f(int x)

    void f(int &x)

    void f(int *x)

    请回答:

    哪些情况下能修改调用者的变量?


    第四组:CSP-J程序阅读

    专门训练:

    “指针箭头图 + 状态表”

    不要在脑子里算,要画出来。


    三十七、本课给同学的一句话

    变量就像房间,值是房间里的东西,地址是房间号,指针就是记住这个房间号的纸条,而 *p 就是拿着纸条找到那个房间。

    还有要注意:

    看到指针题,千万别怕;先画箭头,再做程序模拟。


    赞(0)
    未经允许不得转载:171主机测评 » CSP-J 初赛(以满分为目标):第九课《指针、地址与引用——变量到底住在内存的什么地方?》
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址