指针

官方信息技术老师·10 页·深入(追求细节与边界)·0 次浏览·3 天前
指针基础内存模型指针算术函数指针

指针

从裸指针到函数指针,把所有边界、陷阱与反直觉细节一次画清

按 空格/→ 演示下一步

1 / 10 页

全部页面点击任意一页,跳回舞台从这页播放

指针基础内存模型指针算术函数指针

指针

从裸指针到函数指针,把所有边界、陷阱与反直觉细节一次画清

1第 1 页 · 指针

认识指针

我们已经知道变量是数据的容器,但每个数据都住在一段内存里,内存有门牌号。指针,就是把门牌号记下来的小本子。

存的是地址
指针变量装的是某块内存的编号,不是数据本身——拿到编号才能找到数据。
&取址、*解引用
&x 取出 x 的地址赋给指针;用 *p 通过地址读出或写入那块内存。
类型决定步长
指针的基类型只决定加减 n 时跨多少字节,与能访问多大数据无关。
宽度由架构决定
不管指向 int 还是 struct,指针本身的字节数仅由 32/64 位决定。
三类典型应用
堆内存申请、函数传引用、链表/树等动态结构——这些都靠指针落地。
酒店房卡上的房号对应 →指针

房卡记房号、房号对应房间;指针记地址、地址对应数据。房号本身不是房间。

(&x)x*(\&x) \equiv x
2第 2 页 · 认识指针

指针运算

你已经知道指针存的是地址——一个数字。但指针不是普通整数:你不能把两个指针相乘,也不能拿指针除以 2。C/C 标准为指针定义了一套专属的算术规则,正是这套规则让指针遍历数组成为可能。

整数加减 n
指针 ± n 实际移动 n × sizeof(所指类型) 个字节,类型决定「一步多远」
同类型指针相减
结果是两指针间的元素个数(ptrdiff_t),不是字节差;是少数合法的双指针运算
关系比较
==、<、> 等可比较指针,结果反映地址顺序;跨对象比较行为未定义
禁止运算
两指针相加、指针乘除、指针与浮点数相加——编译器直接拒绝,不是运行时
自增 / 自减
++/-- 让指针前进或后退一个元素,常用于遍历数组;但要先保证不越界
书本翻页对应 →指针 +1 / -1

翻过一页就是一整页内容,不会逐字跳;指针 ±1 就是跳过一个完整元素

p+n(char*)p+nsizeof(p)p + n \equiv (\text{char*})p + n \cdot \text{sizeof}(*p)
3第 3 页 · 指针运算

指针与数组

指针能 ++、能相减,这套算术只有在连续内存上才有意义——而数组正是一段连续内存。这一页揭指针和数组之间那条剪不断的等价关系。

数组名退化为指针
除 sizeof 和 & 两种语境外,数组名自动转为指向首元素的指针常量
下标即指针运算
a[i] 完全等价于 *(a+i),下标操作只是指针算术的语法糖
指针 ≠ 数组
sizeof(数组) 是总字节数,sizeof(指针) 恒为 8;指针上还保留着元素类型
形参中的数组
void f(int arr[]) 实际就是 void f(int *arr),长度信息在传参时丢失
墙上的房号 vs 便签上的房号对应 →数组名 vs 指针变量

墙上房号固定不可擦(数组名值不变),便签可改写(指针可重新赋值),但都能定位同一排房间中的某一间

4第 4 页 · 指针与数组

指针与字符串

前页看到数组名就是首元素指针。字符串本质就是字符数组,但'字面量'与'字符数组'这两条路在指针层面差别很大——一个是借,一个是买。

字面量存只读区
"hello" 这类字面量编译后入只读数据段,char* 指向它能读不能写,写入会段错误
借与买的所有权
char* p = "hi" 是借的标签;char s[] = "hi" 是栈上拷一份,能改
指针步进遍历
++ 沿地址前进,遇 '\0' 即停——strlen、字符串打印、拷贝都基于此套路
const char* 传参
只读场景用 const char* 入函数签名,把'我不改'写进类型,让编译器替你把关
图书馆借的书 vs 自己买的本子对应 →字符串字面量 vs 字符数组

借的书只能看不能涂;买的本子随便改——对应:字面量只读、数组可写

5第 5 页 · 指针与字符串

指针数组

上页我们知道字符串本质是 char *。要存多条字符串,每条都是一个指针——怎么组织?把它们放进一个数组,这个数组就叫指针数组。

本质是数组
数组的每个元素都是同类型指针,如 int *p[5]
语法识别
[] 优先级高于 *,名字先和 [] 结合,再看元素类型
内存特性
数组元素(指针)连续排列,但各指针指向的位置可分散
典型应用
字符串数组、main 的 argv、函数指针跳转表
图书馆的索引卡片对应 →指针数组

卡片排成一列(数组连续),每张写一个书架位置(指针),书本身可散落各处

6第 6 页 · 指针数组

二级C考点解析之指针基本

前页已看到,指针能带着地址参与运算和比较。但运算的前提,是先分清对象、地址与指针;否则结果即使算对,也可能解引用到错误对象。

基本构成
`&x` 取得地址;`T *p` 中的 p 保存地址,T 决定解引用后的类型
两层含义
p 是地址值,`*p` 是该地址所标识的对象;`&*p` 等价于 p
解引用边界
指针须先有有效目标;NULL、悬空指针和 void* 均不可直接解引用
地址传参
把 `&x` 传给 `T*` 参数,函数内修改 `*p` 会影响调用者中的 x
门牌导航对应 →指针

导航保存门牌号像指针保存地址;按门牌找房像 `*p` 找目标,房号加实体信息才对应 T

&*p \equiv p \quad (p \ne \mathrm{NULL},\ p\text{ 指向有效对象})
7第 7 页 · 二级C考点解析之指针基本

二级C考点解析之指针与字

前面讲了指针和数组,但考试最爱挖坑的地方在「指针和字符串」——这块的陷阱最隐蔽,改一个字符就可能让程序崩溃。

字符串常量区
用双引号包的字面量,存放在只读数据区
指针与数组初始化
char s[]="abc" 元素可改;char *p="abc" 改 *p 崩溃
两个长度函数
sizeof 含终止符 '\0';strlen 不含,遇 '\0' 停
库函数返回值
strcpy、strcat 返回目标串首地址,支持链式调用
公司红头合同对应 →字符串常量

合同只能传阅不能涂改;字符串常量在只读区,改了就崩

8第 8 页 · 二级C考点解析之指针与字

本节要点

  • 指针本质即地址,`*`取值、`&`取地址是两层操作
  • 指针+1 跨过的是一个类型宽度,而非一个字节
  • 数组名退化为常量指针,不能自增也不能赋值
  • 字符串字面量在只读区,char*形式不可改写
  • 二级指针保存的是指针变量的地址,写作 T**
延伸主题:const 修饰指针的位置辨析动态内存malloc与free配对使用函数传参:值传递与指针传递
9第 9 页 · 本节要点

课后思考

先独立想一会儿再看参考答案,检验自己真懂了几分。

1指针变量里存的到底是什么?为什么不能直接把指针赋值给普通整数?

参考答案指针存的是另一个变量的内存地址。64 位系统上地址是 64 位二进制,普通 int 通常只有 32 位会截断;且不同类型指针步长不同,不能直接互通。

2如果两个指针相减,结果代表什么?这个运算为什么不能在非数组指针上做?

参考答案结果表示两指针之间相隔的元素个数,不是字节差。前置条件是两指针必须指向同一数组,否则结果未定义,所以脱离数组的指针减法没有意义。

3字符串字面量和字符数组在内存里的存放位置有何不同?修改它们的值时会有什么差别?

参考答案字面量放在只读数据区,多个相同内容可能共享同一地址,内容不可写;字符数组放在栈或全局区,独占一块可写内存。两者指针都能用,但语义完全不同。

10第 10 页 · 课后思考