第八章 善于利用指针

谭浩强《C程序设计》第五版 | 交互式教学演示

📚 本章学习目标

指针是C语言的精髓,正确和灵活地使用指针可以使程序更加高效。本章将深入学习指针的概念、定义、使用方法,以及指针与数组、函数的关系。

章节知识体系

知识点主要内容重要程度
地址与指针概念变量的地址、指针的含义⭐⭐⭐⭐⭐
指针变量定义指针变量的声明、初始化⭐⭐⭐⭐⭐
指针运算取地址&、间接访问*、加减运算⭐⭐⭐⭐⭐
指针与数组数组名是指针、指针数组、下标运算⭐⭐⭐⭐⭐
指针与字符串字符指针、字符串处理函数⭐⭐⭐⭐
指针与函数指针作为参数、返回指针、函数指针⭐⭐⭐⭐
指向指针的指针二级指针、多级指针⭐⭐⭐

🔍 指针的核心概念可视化

?
变量 a
?
指针 p

点击"播放演示"查看指针如何指向变量

📍 什么是地址?什么是指针?

每个变量在内存中都占用一定的存储单元,每个单元都有唯一的地址。指针就是变量的地址,通过指针可以间接访问和修改变量的值。

内存地址可视化

🧠 理解内存地址

点击按钮查看内存布局

地址运算符演示

& 和 * 运算符

int a = 100; int *p = &a; printf("a的值: %d\n", a); // 输出 100 printf("a的地址: %p\n", &a); // 输出 &a printf("*p的值: %d\n", *p); // 输出 100

📌 重要概念

  • &运算符:取地址运算符,获得变量的内存地址
  • *运算符:解引用运算符,通过地址访问变量的值
  • 指针变量:专门用来存储地址的变量

🔧 指针变量的定义与使用

指针变量必须先定义后使用,定义时需要指定指针的类型。指针的类型决定了它能指向什么类型的数据。

指针变量的定义

// 定义指针变量的一般形式 类型名 *指针变量名; // 示例 int *p1; // 指向int类型的指针 float *p2; // 指向float类型的指针 char *p3; // 指向char类型的指针 double *p4; // 指向double类型的指针

交互式练习:指针类型识别

🧩 判断指针类型

int *p; // p是什么类型? float *pf; // pf是什么类型? char **ppc; // ppc是什么类型?

指针大小可视化

📏 不同类型指针的大小

📌 初始化规则

  • 指针变量在使用前必须初始化
  • 可以将变量地址赋给指针变量(使用 & 运算符)
  • 可以赋值为 NULL 表示空指针
  • 同类型的指针才能相互赋值

➕ 指针运算

指针是一种特殊的变量,支持有限的算术运算。指针运算主要用于遍历数组元素。

指针运算一览

运算含义效果
p + n指针加整数向下移动n个元素的位置
p - n指针减整数向上移动n个元素的位置
p++ / ++p指针自增指向下一个元素
p-- / --p指针自减指向上一个元素
p1 - p2指针相减两个指针间的元素个数

指针算术运算可视化

🔢 int* 指针 +1 增加多少?

int* p = &a[0]; // 地址: 0x100

💡 int占4字节,所以p+1实际上增加了4个字节的地址

指针与数组下标的关系

int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; // 以下四种写法完全等价 arr[2] == *(arr + 2) == p[2] == *(p + 2) // 以上都等于 30 // 结论:数组名是指向首元素的指针常量

等价性可视化

🔄 arr[i] == *(arr+i) == p[i] == *(p+i)

📊 指针与数组的密切关系

数组名在大多数情况下相当于指向数组首元素的指针常量。指针可以用于遍历和操作数组元素,这是C语言高效处理数组的基础。

数组名的本质

⚠️ 数组名的两个例外

📌 数组名 ≠ 普通指针的情况

  1. sizeof(数组名):得到整个数组的大小,不是首元素地址
  2. &数组名:得到的是指向整个数组的指针,类型为 int(*)[n]

用指针遍历数组

🔄 指针遍历 vs 下标遍历

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

指针数组

// 指针数组:数组的元素是指针 int a = 1, b = 2, c = 3; int *arr[3] = {&a, &b, &c}; // 指针数组 for (int i = 0; i < 3; i++) printf("%d ", *arr[i]); // 输出:1 2 3

📞 指针作为函数的参数和返回值

指针使函数能够访问和修改外部变量,实现"传址调用"。函数也可以返回指针类型,实现返回数据的地址。

指针作为函数参数

🔄 传值 vs 传址

选择一种方式查看演示

返回指针的函数

// ⚠️ 注意:不能返回局部变量的地址 int *wrong(void) { int x = 10; return &x; // 错误!x在函数结束后被销毁 } // ✅ 正确:返回静态变量或参数的地址 int *correct(int *p) { return p; // OK!返回参数地址 }

const与指针

指向常量的指针
const int *p = &a; // *p = 100; // 错误! p = &b; // OK! // 不能通过p修改a
指针常量
int *const p = &a; *p = 100; // OK! // p = &b; // 错误! // p不能指向其他变量

🧠 随堂测验

1. 以下关于指针的说法,正确的是?

A. 指针变量的大小与它指向的数据类型有关
B. int *p 表示p是指向int类型的指针变量
C. 可以将float类型的地址赋给int*类型的指针
D. 所有指针变量的大小都是8字节

2. 设有语句:int a=10, *p=&a; 以下哪个表达式不能访问a的值?

A. a
B. *p
C. p
D. *(&a)

3. 若有定义:int a[5]={1,2,3,4,5}, *p=a; 则下列表达式正确的是?

A. *(p+2) 等于 a[2]
B. sizeof(p) 等于 sizeof(a)
C. 数组名可以进行++运算
D. &数组名和数组名值相同但意义相同

4. 关于const和指针,说法正确的是?

A. const int *p 和 int *const p 完全相同
B. const int *p 不能通过p修改指向的值
C. int *const p 不能修改指向的值
D. const关键字对指针没有影响

5. 关于指针运算,说法错误的是?

A. p+n 使指针向下移动n个元素
B. p1-p2 得到两个指针间的元素个数
C. p1+p2 是合法的指针运算
D. p++ 使指针指向下一个元素