Skip to content

1.8 指针

计算机程序设计 · C语言 · 1.8 指针(零基础讲义)

指针 ⭐

🎯 一句话秒杀:指针就是地址——指针变量是存储地址的变量。* 是"取出指针指向的内容",& 是"取变量的地址"。


① 📊【历年真题考情】

本章(指针)在广东专升本计算机统考中的位置:

项目结论
出题频次⭐⭐⭐⭐⭐(五星必考)——2026 考纲全解标注"指针 ⭐⭐⭐,能独立手推和编写完整程序";连续 6 年真题必考
常考题型单选(2021 单选6、2026 单选5/7)、判断(2021 判断23)、填空(2024 填空1)、计算/读程(2021 大题41)、编程大题(2021 大题43)
预估分值10~25 分 / 200 分(约 5%~13%,与函数并列最高分值模块)
考纲归属2026 考纲「复合数据」考点7(指针变量、指针与数组/函数/字符串)

考场真实出现过的高频题:

  • 2021 单选6:指针变量 p 不能初始化为 → 1(D)
  • 2021 判断23:指针变量不可以为函数参数 → (可以)
  • 2021 大题43:用指针交换 a、b 并输出较大值
  • 2024 填空1:指针形参返回两数和 → *z = x + y
  • 2026 单选5:能交换调用者两个整型变量的声明 → B. void swap(int *a, int *b)
  • 2026 单选7:int *p=&x; (*p)++; 后 → x 变为 11(B)

🎯 学习目标:学完本章,你能 ① 判断指针能否如此初始化;② 手推 (*p)++*p++ 的区别;③ 判断哪个函数声明能交换变量;④ 写出 malloc/free 完整代码——这四类题就是本章真题的全部考法。


② 🗣️【零基础大白话引入】

为什么需要指针? 想象一排快递柜(内存),每个柜子有编号(地址)和里面的包裹(值)。普通变量是"我知道包裹在 3 号柜,直接去拿";指针是"我记住 3 号柜这个编号,以后通过编号去拿"。

比喻对应:

  • 变量 a = 柜子本身(值 10 在 0x1000 号柜)
  • &a = 柜子的编号(0x1000)
  • 指针 p = 一张写着"0x1000"的纸条(存的是编号)
  • *p = 拿着纸条去取包裹(取 0x1000 柜的值 10)

为什么能交换变量? 你直接报"把 3 号柜和 5 号柜的包裹互换"——但普通函数传参时,只把包裹复印一份给你,你换的是复印件!传指针就是直接把柜子编号给你,你才能动真包裹。

*& 是什么?

  • &a = 问"a 在几号柜"(取地址)
  • *p = 问"纸条 p 指向的柜里是什么"(取内容)
  • 这俩是互逆操作*&a == a

💡 本章主线:*指针是什么(地址)→ 怎么用(& 和 )→ 和数组什么关系 → 怎么让函数改外部变量(交换)→ 怎么动态分配内存。全是最实用、考试最高频的技能。


③ 📖【正式核心知识点讲解】

本章知识点 = 考纲要求 + 历年真题实际考过的内容。真题未考、考纲不作要求的超纲内容已剔除或标注"了解"。

3.1 什么是指针?

c
int a = 10;     // 普通变量:存值
int *p = &a;    // 指针变量:存地址
内存模型:
变量 a: [10 ]  ← 地址 0x1000
指针 p: [0x1000]  ← 指向 a(存储的是a的地址)

指针内存模型:变量 a 的格子和指针 p 的纸条指向

c
printf("%d", a);   // 10 — 直接访问
printf("%d", *p);  // 10 — 间接访问(通过地址找到a的值)

3.2 指针的定义与使用

c
int *p;       // p是指向int的指针
char *cp;     // cp是指向char的指针
double *dp;   // dp是指向double的指针

int a = 10;
p = &a;       // p指向a(p存a的地址)
*p = 20;      // 通过p修改a的值(a变成20)

🚨 红牌警告

定义时 * 是类型的一部分: int *p; 表示 pint* 类型 使用时 * 是间接访问运算符: *p 表示取p指向的值 没有初始化的指针不能用! 野指针会导致程序崩溃

3.3 指针的合法初始化(2021 单选6 必考)

指针变量只能初始化为:① 一个变量的地址(&a);② NULL;③ 字面量 0(等价 NULL)

初始化写法合法?说明
int *p = &a;地址,最常用
int *p = NULL;空指针
int *p = 0;0 等价于 NULL
int *p = 1;非法1 不是合法地址/NULL 语义

2021 单选6:指针变量 p 不能初始化为 → D. 1 解析:地址、NULL0 都合法;1 既不是有效地址也不是空指针语义。

3.4 指针的运算(*p++ 优先级 · 2026 单选7 必考)

运算含义示例
p + n指向后面第n个元素p + 1 指向下一个int(地址+4)
p - n指向前面第n个元素p - 1 指向上一个元素
p1 - p2两个指针间的元素个数
*p取内容
&a取地址

🔑 秒杀秘籍——指针加1加多少?

c
int *p;     // p+1 → 地址+4(一个int 4字节)
char *cp;   // cp+1 → 地址+1(一个char 1字节)
double *dp; // dp+1 → 地址+8(一个double 8字节)

指针+1 = 地址 + sizeof(指向的类型) × 1 所以指针加1是"跳到下一个元素",不是"地址加1"

3.4.1 (*p)++ vs *p++ vs *++p 优先级逐层推演(2026 单选7 必考)

三个长得像的表达式,结果完全不同——考的就是运算符优先级和结合性!

优先级规则

  • ++(后置)优先级高于 *(解引用)
  • *++(前置)同级,右结合

(*p)++:先解引用,再对"指向的值"自增

c
int x = 10;
int *p = &x;
(*p)++;

逐层推演:

  1. 括号优先:先算 *p → 取 p 指向的值,即 x 的值 10
  2. ++ 作用于这个值:把 x 的值 10 → 11
  3. 结果:x 变为 11,p 不变(还指向 x)

2026 单选7(*p)++; 后 → B. x 变为 11 记忆:(*p)++ 改的是"指向的值"

*p++:先自增指针,再解引用(等价的优先级是 *(p++)

c
int a[3] = {10, 20, 30};
int *p = a;   // p 指向 a[0]
printf("%d", *p++);

逐层推演:

  1. 后置 ++ 优先级高于 **p++ 等价于 *(p++)
  2. p++ 是"先用后加":先取 p 当前值(指向 a[0]),p 再自增(指向 a[1])
  3. * 作用在 p 的旧值上 → 取 a[0] = 10
  4. 副作用:p 现在指向 a[1]
  5. 输出:10,且 p 已指向 a[1]

记忆:*p++ 改的是"指针本身"(先取旧值,指针后移)

*++p:先自增指针,再解引用(等价的优先级是 *(++p)

c
int a[3] = {10, 20, 30};
int *p = a;   // p 指向 a[0]
printf("%d", *++p);

逐层推演:

  1. 前置 ++* 同级右结合:*++p 等价于 *(++p)
  2. ++p 先自增:p 先指向 a[1]
  3. * 作用在 p 的新值上 → 取 a[1] = 20
  4. 输出:20,p 现在指向 a[1]

三者对比表(必背):

表达式等价写法先做什么结果
(*p)++解引用后自增值先取 *p 再值+1改指向的值
*p++*(p++)先用 p 旧值再 p 后移取旧值,指针后移
*++p*(++p)先 p 前移再取新值取新值,指针前移

3.5 指针与数组(重点!)

数组名就是首元素地址

c
int a[5] = {10, 20, 30, 40, 50};
int *p = a;     // p指向a[0],等价于 int *p = &a[0]

通过指针访问数组元素

c
// 以下四种方式完全等价!
a[0]        // 下标法
*a          // 指针法
p[0]        // 指针当下标用
*(p+0)      // 指针偏移

// 一般情况:a[i] ≡ *(a+i) ≡ *(p+i) ≡ p[i]

遍历数组

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

// 方式1:下标
for (int i = 0; i < 5; i++)
    printf("%d ", a[i]);

// 方式2:指针
for (p = a; p < a + 5; p++)
    printf("%d ", *p);

🚨 红牌警告

数组名是常量指针——不能修改!

c
int a[5];
a++;      // ❌ 编译错误!a是常量

int *p = a;
p++;      // ✅ 指针变量可以修改

数组名作为函数参数

实际上传递的是指针:

c
// 这三个声明完全等价
void func(int arr[]);
void func(int arr[10]);  // 写的10没用
void func(int *arr);

// 函数内部:
void func(int arr[]) {
    int n = sizeof(arr) / sizeof(arr[0]);  // ❌ 错误!
    // sizeof(arr) 在这里是4(指针大小),不是数组大小!
}

2022 单选10:数组名作为函数参数传递的是 → B. 首地址(不是长度、元素值、个数) ✅ 2021 判断23:指针变量不可以为函数参数 → (指针完全可以作函数参数,且是交换变量的关键)

3.6 指针与函数(2026 单选5 必考)

3.6.1 用指针实现交换

c
void swap(int *a, int *b) {
    int temp = *a;     // 取出a指向的值
    *a = *b;           // 把b指向的值赋给a指向的位置
    *b = temp;         // temp赋给b指向的位置
}
// 调用:swap(&x, &y);  → x和y被交换 ✅

3.6.2 哪个函数声明能交换?四选项真题判断(2026 单选5)

真题:下列函数声明中,能够交换调用者两个整型变量的是(  )

选项声明能否交换原因
Avoid swap(int a, int b)值传递,形参是拷贝
Bvoid swap(int *a, int *b)传地址,函数内 *a *b 解引用改外部变量
Cint swap(int a)只有一个参数且返回 int,无法交换
Dvoid swap(const int a, const int b)const 禁止修改

2026 单选5:答案 B。关键:C 参数值传递改不了外部;必须传地址(指针),函数内通过解引用完成交换。 逐行推演 swap 交换过程:

  1. int temp = *a; — 取出 a 指向的值存 temp
  2. *a = *b; — 把 b 指向的值赋给 a 指向的位置
  3. *b = temp; — temp 赋给 b 指向的位置 三步完成交换(经典三变量交换的指针版)

3.6.3 指针形参返回多个值(2024 填空1)

c
void add(int x, int y, int *z) {
    *z = x + y;   // 通过指针"带出"结果
}
// 调用:int s; add(3, 5, &s); → s = 8

2024 填空1:补全 ____*z = x + y(z 是 int*,必须解引用 *z 才能改外部变量)

指针作为函数返回值

c
int *find_max(int *arr, int n) {
    int *max = arr;
    for (int i = 1; i < n; i++)
        if (arr[i] > *max) max = &arr[i];
    return max;
}
// 调用:int *p = find_max(a, 5);

🚨 红牌警告

不能返回局部变量的地址!

c
int *bad() {
    int a = 10;
    return &a;  // ❌ a是局部变量,函数结束就销毁
}

3.7 动态内存分配(2023/2024 关联)

函数原型功能
mallocvoid *malloc(size_t size)分配size字节内存
callocvoid *calloc(n, size)分配n×size字节并清零
reallocvoid *realloc(void *ptr, size_t size)重新分配大小
freevoid free(void *ptr)释放内存
c
#include <stdlib.h>

int *p = (int *)malloc(5 * sizeof(int));  // 分配5个int的空间
if (p == NULL) {                          // 检查是否分配成功
    printf("内存不足");
    return -1;
}
for (int i = 0; i < 5; i++)
    p[i] = i * 10;
free(p);      // ⚠️ 用完后必须释放!
p = NULL;     // 避免野指针

🚨 红牌警告

malloc 三要素:

  1. malloc 返回 void* — 需要强制类型转换为需要的类型
  2. 检查是否返回 NULL(内存不足时返回NULL——2023 真题考过)
  3. 用完必须 free,否则内存泄漏(2024 关联)

💡 malloc 失败返回 NULL 是考点:分配不到内存时 malloc 返回 NULL,所以要 if (p == NULL) 检查——不检查直接使用会崩溃(解引用空指针)。


④ 🧪【真题同源例题】

例题 1:入门基础题(先热热身)

c
#include <stdio.h>
int main() {
    int a = 10;
    int *p = &a;
    *p = 20;
    printf("%d\n", a);
    printf("%d\n", *p);
    return 0;
}

逐变量推演:

  1. int a = 10; — a 的值是 10
  2. int *p = &a; — p 存 a 的地址
  3. *p = 20; — 通过 p 找到 a,把 a 改成 20
  4. printf("%d", a)20(a 已被改)
  5. printf("%d", *p)20(p 指向的值)
  6. 输出:
    20
    20

例题 2:2026 真题改编((*p)++ 优先级)

c
#include <stdio.h>
int main() {
    int x = 10;
    int *p = &x;
    (*p)++;
    printf("%d", x);
    return 0;
}

逐层推演:

  1. (*p)++:括号优先 → 先 *p 取 x 的值 10
  2. ++ 作用于这个值 → x 的值 10 → 11
  3. x 变为 11p 不变
  4. 输出:11

✅ 对应 2026 单选7,答案 B。(*p)++ 改的是指向的值

例题 3:2026 真题改编(*p++ 优先级)

c
#include <stdio.h>
int main() {
    int a[3] = {10, 20, 30};
    int *p = a;
    printf("%d\n", *p++);
    printf("%d\n", *p);
    return 0;
}

逐层推演:

  1. 第一条 printf:*p++ 等价于 *(p++)
    • p++ 先用后加:先取 p 旧值(指向 a[0]),p 再自增(指向 a[1])
    • * 取 p 旧值指向的内容 → 10
    • 副作用:p 现在指向 a[1]
  2. 第二条 printf:*p → p 指向 a[1] → 20
  3. 输出:
    10
    20

对比记忆:*p++ 输出 10(指针后移);(*p)++ 是 x 变 11(值自增);*++p 输出 20(先移指针再取值)。

例题 4:2026 真题改编(swap 四选项判断)

下列函数声明中,能够交换调用者两个整型变量的是(  )

A. void swap(int a, int b)  B. void swap(int *a, int *b)  C. int swap(int a)  D. void swap(const int a, const int b)

逐项分析:

  1. A:值传递,形参是实参拷贝 → 换不了 ❌
  2. B:传地址*a/*b 解引用改外部变量 → 能换 ✅
  3. C:单参数返回 int → 换不了 ❌
  4. D:const 禁止修改 → 换不了 ❌

✅ 答案 B(2026 单选5 原题)。交换三步推演见 3.6.2。

例题 5:2021 真题改编(指针合法初始化)

指针变量 p 不能初始化为(  )

A. 地址  B. NULL  C. 0  D. 1

逐项分析:

  1. A 地址:合法 ✅
  2. B NULL:空指针,合法 ✅
  3. C 0:等价 NULL,合法 ✅
  4. D 1:不是合法地址也不是空指针语义 → 非法 ❌

✅ 答案 D(2021 单选6 原题)。


⑤ ⚠️【历年真题高频扣分坑】

#陷阱错误做法正确做法出处
1指针初始化为 1int *p=1; 以为行只能初始化地址/NULL/02021 单选6
2*(p)++ 当 p++混淆改值/改指针(*p)++ 改指向的值;p++ 改指针2026 单选7
3*p++ 优先级以为先解引用再自增指针*p++ = *(p++),先取旧值指针后移2026 关联
4交换用值传递swap(int a,int b) 以为能换必须传指针 swap(int *a,int *b)2026 单选5
5指针形参忘解引用z=x+y 而非 *z=x+y改外部变量必须 *z2024 填空1
6sizeof 数组陷阱函数内 sizeof(arr) 当数组大小形参是指针,sizeof=42024/2026
7数组名当可变对数组名 a++数组名是常量指针,不能修改经典
8返回局部变量地址return &局部变量函数结束局部变量销毁经典
9忘检查 malloc NULL直接使用 malloc 返回值先判 NULL 再使用2023
10忘 free只 malloc 不 free用完必须 free 防泄漏2024
11指针+1 当地址+1int* p+1 以为地址+1p+1 跳 4 字节(sizeof(int))经典
12指针不能作参数以为指针不能传参指针完全可以作函数参数(2021 判断23 对)2021 判断23

📌 最值钱的一条:第 2、3 条((*p)++ vs *p++)是 2026 单选7 的核心——"括号决定改值还是改指针" 一句口诀拿 3 分;第 4 条(swap 传指针)是 2026 单选5,与函数章联动。


⑥ 📝【课后自测练习题】

1. 指针变量 p 不能初始化为(  )(2021 真题风格)

A. &a  B. NULL  C. 0  D. 1


2. 执行下列程序段后,输出结果是(  )(2026 真题风格)

c
int a[3] = {10, 20, 30};
int *p = a;
printf("%d", *p++);

A. 10  B. 20  C. 30  D. 11


3. 能交换调用者两个整型变量的函数声明是(  )(2026 真题风格)

A. void swap(int a, int b)  B. void swap(int *a, int *b)  C. int swap(int a)  D. void swap(const int a, int b)


4. 执行 int x=10; int *p=&x; (*p)++; 后,x 的值是(  )(2026 真题风格)

A. 10  B. 11  C. 指针地址加 1  D. 语法错误


👆 点击展开参考答案与解析

第1题D. 1

  • 地址、NULL、0 都合法;1 不是合法地址/NULL 语义(2021 单选6 原题)

第2题A. 10

  • *p++ = *(p++):先取 p 旧值指向的 a[0]=10,再 p 后移指向 a[1]
  • 输出 10(若第二条再输出 *p 则是 20)

第3题B. void swap(int *a, int *b)

  • 值传递改不了外部变量,必须传地址(2026 单选5 原题)

第4题B. 11

  • (*p)++ 先取 *p(x=10)再自增 → x 变 11(2026 单选7 原题)

📝 原有闭卷真题挑战(保留原内容)

(点击下方空白查看答案)

1. int a[5]; 则 &a[0] 和 a 的关系是?

2. 写出输出:
   int a[5]={10,20,30,40,50}, *p=a;
   p++; printf("%d", *p);

3. 指针+1,地址增加多少?为什么?

4. 为什么不能返回局部变量的地址?

5. malloc 后忘记 free 会怎样?
👆 点击展开答案
  1. 完全相等(a就是首元素地址 &a[0])
  2. 20(p先++指向a[1],再取内容)
  3. 取决于指针类型:int* +1 → 地址+4,char* +1 → 地址+1。因为指针+1是"跳到下一个元素"
  4. 局部变量在函数返回后被销毁,返回的地址指向无效内存
  5. 内存泄漏(memory leak)——程序占用的内存越来越多

📖 教材习题对照

教材习题知识点难度
习题8.1~8.2指针基本概念⭐⭐
习题8.4~8.5指针与数组⭐⭐⭐
习题8.8~8.9指针与函数⭐⭐⭐
习题8.16指针数组⭐⭐⭐⭐
习题8.30动态内存分配⭐⭐⭐⭐

对应教材:谭浩强《C程序设计》第5版 → 第8章 善于利用指针 🌟(最重要的一章!) 对应考试大纲:考点8 — 指针

📺 配套视频

复习到本考点 → 先看视频补讲,再刷上面「闭卷挑战」。

仅供个人学习 · 考生回忆版 · 非考试院原卷