:第九课《指针、地址与引用——变量到底住在内存的什么地方?》)
第九课 指针、地址与引用——变量到底住在内存的什么地方这一课要解决 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变量本身10a变量的地址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 ──→ ba和b里面的数字完全没有改变。所以一定要记p b是“换指向”不是“换数据”。十五、两个指针可以指向同一个变量例如int a 10; int *p a; int *q a;现在┌───────┐ p ────→│ │ │ a10 │ 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]可以理解为*(ai)这一点对初赛中的程序阅读很重要。十九、指针移动假设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 *pint 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 5f(a)p ───→ a 5(*p)p ───→ a 6所以输出6三十一、本课与前面知识的真正连接现在可以把前面9课串起来变量 ↓ 表达式 ↓ 条件、循环 ↓ 函数 ↓ 数组 ↓ 字符串 ↓ 栈、队列 ↓ 指针、引用 ↓ 内存同学们开始逐渐建立一个完整的认识程序 ↓ 变量 ↓ 变量存放在内存 ↓ 内存有地址 ↓ 指针可以保存地址 ↓ 通过地址找到变量 ↓ 函数可以通过引用/指针修改变量三十二、本课最重要的“符号翻译表”同学们要背下来。写法人话aa这个变量的值aa的地址int *pp是指向int的指针p ap指向app里面保存的地址*p找到p指向的变量*p 20修改p指向的变量nullptr不指向有效对象int r ar是a的另一个名字三十三、CSP-J初赛指针题“五步法”同学们看到指针程序不要慌。严格按照第一步找变量a、b、x……第二步找指针p、q……第三步画箭头例如p → a q → b第四步看到*p马上翻译找到p指向的变量。第五步程序每执行一步就更新图例如p → a10 *p20 p → a20三十四、综合例题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 pa nullptr要求能读懂程序。★★ 第二层重点掌握指针修改变量 多个指针指向同一个变量 指针与数组 引用 引用参数 指针参数要求能够完成程序模拟。★★★ 第三层提高了解指针运算 *(ai) 动态内存 new/delete 指针与结构体结构体指针以及new/delete、malloc/free的区别例如结构体指针访问成员时可以使用-动态分配的结构体对象可以用new最后用delete释放。这些内容可以参考后面专项讲义。三十六、课后练习第一组看懂符号判断int a10; int *pa;分别问a是什么a是什么p是什么*p是什么第二组画箭头int a10; int b20; int *pa; pb; *p100;要求不运行程序只画图并说出a、b最后是多少。第三组函数参数比较void f(int x)void f(int x)void f(int *x)请回答哪些情况下能修改调用者的变量第四组CSP-J程序阅读专门训练“指针箭头图 状态表”不要在脑子里算要画出来。三十七、本课给同学的一句话变量就像房间值是房间里的东西地址是房间号指针就是记住这个房间号的纸条而*p就是拿着纸条找到那个房间。还有要注意看到指针题千万别怕先画箭头再做程序模拟。