内容正文:
8.1、8.2节:指针的基本概念、指针变量的使用与指针运算
C语言程序设计
电子工业出版社
理解指针概念,掌握指针变量的定义、初始化及引用
理解并掌握指针的算术运算和关系运算
能够运用指针进行基本的编程操作
能够阅读和理解包含指针基础操作的C语言代码
培养底层抽象思维,建立计算机内存模型认知
养成严谨细致的编程习惯,主动规避指针常见错误
知识目标
能力目标
素养目标
01
01
01
学习目标
目录
Part 01
教学导入
Part 02
知识讲授
Part 03
师生互动
Part 04
课堂练习
Part 05
课堂小结
教学导入:一个寻宝故事
🎮 场景设定:想象一下,我们正在玩一个寻宝游戏。我把一份宝藏藏在了教室里的某个秘密地点,现在有两种方式告诉你宝藏在哪:
📦 方式一:直接给你
我直接把宝藏交到你手里。
💻 类比:
直接使用变量值,如int money = 100;
📜 方式二:间接指引
我给你一张写着宝藏位置的纸条。
💡 类比:
“纸条”就是C语言中的指针变量
💡 指针核心思想:
指针存储的不是数据本身,而是数据在计算机内存中的“地址”。就像那张写着地址的纸条,它不包含宝藏,却能准确指引你找到宝藏。
概念类比:指针到底是什么?
比喻一:图书馆的索引卡
📚 书 (数据):图书馆里的书放在固定位置,不会随意移动。
📇 索引卡 (指针):你不需要记住书的位置,查索引卡就能找到它。
📍 地址 (指针的值):索引卡上写着“3楼东A区12号书架”这样的具体位置。
比喻二:酒店的门牌号
🛏房间 (内存空间):你预订了一间实体的酒店房间,它有固定的物理位置。
🗝房卡 (指针变量):前台给你的房卡,里面包含了打开房门的信息。
🔢 房间号 (内存地址):房卡上印着的“508”,就是指向房间的关键标识。
指针就是一个存储内存地址的变量。它就像一个“向导”,不直接存储数据,而是指引程序找到数据真正存放的物理位置。
教学导入
01
8.1 指针的基本概念
理解内存地址与指针的映射关系,建立底层逻辑思维。
02
8.2.1 指针变量的定义
掌握指针变量的声明语法,理解类型与指向对象的匹配规则。
03
8.2.2 指针变量的初始化
学习为指针变量正确赋初始值,规避常见的“野指针”陷阱。
知识讲授
认识指针
指针的定义与初始化
指针的运算与引用
第一部分
指针的定义与初始化
知识讲授
知识讲授
知识讲授:计算机的内存地址
内存模型:巨大的“储物柜”阵列
计算机的内存就像一个巨大的、连续编号的储物柜阵列。
• 内存单元:每一个“储物柜”就是一个独立的内存单元。
• 内存地址:每个单元都有一个独一无二的数字编号,就是地址。
变量与内存:当我们声明int a = 10;
系统会自动执行以下步骤,将数据和变量名关联:
1. 搜索并找到一个空闲的“储物柜”(内存单元)。
2. 将数值10存入该单元中。
3. 记录下这个单元的“编号”(地址),并将其绑定到变量名a上。
知识讲授:指针变量的定义
定义格式
类型说明符 * 指针变量名 ;
格式解析
•类型说明符:指针指向的变量类型。 •:表示该变量是指针变量。 •指针变量名:符合命名规则的标识符。
💡 重要提示
一个指针变量,在定义时就决定了它只能指向同一种数据类型的变量。
知识讲授:指针变量的定义
程序文本:定义不同类型的指针
#include<stdio.h>
main() {
// 定义普通变量 与 指向它们的指针
int age = 20; //定义整型变量age,并对其赋值20
int *p_age = &age; //定义指针p_age存储整型变量age的地址
float h = 1.75f;//定义浮点型变量h,并对其赋值1.75
float *p_h = &h;//定义指针p_h存储浮点型变量h的地址
printf("%d,%d
",age,*p_age);//*p_age指的是引用指针p_age存储的地址中的值
printf("%f,%f
",h,*p_h);//*p_h指的是引用指针变量p_h存储的地址中的值
}
知识讲授:指针变量的定义
知识讲授:指针变量的初始化
⚠️ 警惕“野指针”
刚定义的指针变量,内存地址是随机的,此时被称为“野指针”。如果直接使用,可能会导致程序崩溃或破坏系统数据,必须先让它指向一个**确定的内存地址**。
❓ 如何获取变量地址?
在C语言中,使用“取地址运算符”来获取一个变量的内存地址。
符号:&
💡 核心:赋值格式
指针变量 = & 普通变量;
知识讲授:指针变量的初始化
程序文本:指针变量初始化
#include<stdio.h>
main() {
int a = 100;// 定义变量a
int *p;// 此时p是野指针,极其危险!
p=&a;// 初始化指针:指向变量a的地址
printf("a=%d,*p=%d",a,*p);
}
知识讲授:指针变量的初始化
知识讲授:指针初始化的多种形式
先定义,后赋值
int a = 10;
int *p;
p = &a;
先声明指针变量 p,
再将变量 a 的地址赋值给 p。
定义的同时赋值
int a = 10;
// 推荐写法
int *p = &a;
在一行代码内完成指针的
定义和地址绑定,简洁高效。
指向空 (NULL)
• 指针暂时不指向变量时,应初始化为NULL。
• NULL是一个宏,代表地址 0。
• 示例:int *p = NULL;
重要提示
使用未初始化的野指针,
或直接解引用 NULL 指针,
程序会崩溃或产生严重后果!
背诵记忆
01
指针是一个专门用来存储地址 的变量。
02
在 C 语言中,获取一个变量地址的运算符是:
取地址符 &
03
定义一个指向 float 类型变量的指针 pf,语句是:
float *pf;
04
将变量 score 的地址赋值给指针 p_score,语句是:
p_score = &score;
例题分析:火眼金睛
程序文本
#include<stdio.h>
main()
{
int value = 100;
int *p;
p = value;
//这里对吗?请说出错误原因!
printf("%d", *p);
}
这段代码存在什么问题?
仔细观察指针变量 p 的赋值操作,思考类型匹配。
诊断与修正
❌ 错误点:`p = value;`类型不匹配。
💡 解析:指针变量 p 存储的是内存地址,而 value 是整数 100。
✅ 正确写法:`p = &value;`(使用取地址符 `&` 获取变量地址)。
例题分析:火眼金睛
课堂问答
Q1. 含义辨析
问题:“int *p;” 中的 “*”和 “*p” 中的 “*” 含义一样吗?
Q2. 内存占用
问题:指针变量占用多大的内存空间?
答案:不一样。
“int *p;” 中的 “*” 是类型说明符,用于声明变量 p 的类型为指针。而 “*p” 中的 “`*” 是解引用运算符,表示访问指针 p 所指向的内存地址中存储的值。
答案:取决于系统架构。
指针存储的是内存地址,所以其大小由系统能支持的最大寻址空间决定。在32位系统中,指针通常占用4个字节;在64位系统中,通常占用8个字节。
课堂练习(一)
练习任务
变量定义与赋值
定义一个double类型的变量pi,并赋值为3.14159。
指针变量声明
定义一个指向double类型的指针变量,命名为p_pi。
指针赋值
将变量pi的内存地址赋值给指针变量p_pi。
结果输出
打印输出:变量pi的值、pi的地址,以及指针p_pi存储的地址。
课堂练习(一)
程序文本
#include<stdio.h>
main()
{
double pi=3.14159;
double *p_pi;
p_pi= π
printf("pi的值: %f
",pi);
printf("pi的地址: %p
", &pi);
printf("指针变量p_pi地址中的值: %f
",*p_pi);
printf("指针变量p_pi指向的地址: %p
",p_pi);
}
/* 注:%p是指针地址格式符,用以十六进制形式输出变量的内存地址 */
课堂练习(一)
课堂练习(二)
练习任务
1. 定义一个char类型的变量grade,并赋值为'B'。
2. 在定义指针p_grade的同时,将其初始化为grade的地址。
3. 打印出grade的值、grade的地址、p_grade中存储的地址及变量grade和指针p_grade所占内存空间。
课堂练习(二)
程序文本
#include<stdio.h>
main()
{
char grade='B';
char *p_grade=&grade;
printf("grad的值: %c
",grade);
printf("grad的地址: %p
", &grade);
printf("指针变量p_grade地址中的值: %c
",*p_grade);
printf("指针变量p_grade指向的地址: %p
",p_grade);
printf("grade所占内存空间: %d
",sizeof(grade));
printf("p_grade所占内存空间: %d
",sizeof(p_grade));
}
课堂练习(二)
内容小结
基本概念
❓ 指针是什么?
一个专门用来存储内存地址的特殊变量类型。
💡 核心思想
通过“地址”去访问变量,即间接访问。
语法规则
📝 如何定义?
类型 *指针变量名
// 例如: int *p;
🔧 如何初始化?
pointer_name = &var;
// 或: type *p = &var;
关键符号
& 取地址运算符
作用:获取变量在内存中的地址编号。
例:&a 就是变量 a 的地址。
* 星号运算符
场景1:定义指针时,表示这是一个指针变量。
场景2:解引用操作,访问指针指向的变量。
第二部分
知识讲授
指针的运算与引用
知识讲授:指针的运算与引用
解引用运算符(*)
通过这个符号,我们可以“解”开指针的外壳,直接访问指针变量中存储的地址所指向的内存空间中的实际数据。
语法格式
在代码中,直接在指针变量名称前加上星号:
*指针变量名
核心作用
它是连接“指针”与“真实数据”的桥梁。简单来说,就是告诉计算机:“请根据这个地址,去找到并使用它里面存的值”。
💡 极易混淆的关键点:“星号 * ”的“双重身份”
在定义指针变量时,它是“类型说明符”,告诉系统这是个指针;在后续使用时,它是“解引用运算符”,表示去访问指向的数据。
知识讲授:指针的运算与引用
程序文本:通过指针访问和修改变量
#include <stdio.h>
main()
{
int a = 100;
int *p = &a;
printf("*p 的值 (即a的值): %d
", *p); // 输出100
*p = 200; // 修改p指向的变量的值,等价于 a = 200;
printf("修改后 a 的值: %d
", a); // 输出200
}
知识讲授:指针的运算与引用
知识讲授:“*”和“&”的互逆关系
01
“*p” 等价于 “a”
指针 p 指向变量 a 的地址,对 p 取值即得到变量 a 的内容。
02
“&(*p)” 等价于 “p”
对指针取值后再取地址,抵消了取值操作,最终结果还是指针 p 本身。
03
“*(&a)” 等价于 “a”
对变量取地址后再取值,抵消了取地址操作,结果回到变量 a。
💡 记忆口诀
`&` 是“取值的地址”,`*` 是“取地址的值”。它们互为“逆运算”,连续使用时可以相互抵消。
知识讲授:“*”和“&”的互逆关系
程序文本
#include <stdio.h>
main() {
int a = 5;
int *p =&a;
printf(" *p 等价于 a :%d,%d
",a,*p);
printf(" &(*p) 等价于 p : %p,%p
", &(*p),p);
printf(" *(&a) 等价于 a : %d,%d
", *(&a),a);
*p = 100; // 修改a的值
printf("修改后,a 的值: %d
", a);
}
知识讲授:“*”和“&”的互逆关系
知识讲授:指针的移动——算术运算
基础认知:指针支持的算术运算类型有限,仅包含加法 (+)、减法 (-)以及自增 (++)、自减 (--) 运算,不可直接做乘法或除法。
核心规则:按“数据类型大小”跳跃移动
指针的算术运算不是简单的数值加减,而是按指向类型的内存大小(sizeof)来决定移动的“步长”。例如执行p++或p = p + 1时,指针向后移动一个“单位”,单位长度等于sizeof(指针指向的类型)。
图示:指针遍历数组
利用指针的算术运算特性,我们可以高效地遍历数组元素。每一次指针自增,都自动定位到下一个数组元素的起始地址,无需手动计算偏移量。
a[0] a[1] a[2] a[3] a[4]
地址: p p+1 p+2 p+3 p+4
知识讲授:指针的移动——算术运算
程序文本
#include<stdio.h>
main() {
int arr[5] = {1, 2, 3, 4, 5},i;
int *p = arr;// 数组名arr本身就是指向数组首元素的指针
for(i = 0; i < 5; i++)
{ printf("%d ", *p);p++; /*指针向后移动一个int大小的内存空间*/ }
printf("
");
for(i=1;i<=5;i++)
printf("%d ",*(p-i)); //p-i:指向当前所指元素前面的第i个元素
printf("
");
for(i = 0; i < 5; i++) //注意第2轮输出时并未改变p指针中的地址
{ p- -; /*指针向后移动一个int大小的内存空间*/
printf("%d ", *p); }
printf("
");
for(i=0;i< 5;i++)
printf("%d ",*(p+i));} //p+i:指向当前所指元素后面的第i个元素
知识讲授:指针的移动——算术运算
知识讲授:指针的移动——算术运算
程序文本【8.3】
#include<stdio.h>
main()
{
int a=10,b=20,s,t,*pa,*pb; /*说明pa,pb为整型指针变量*/
pa=&a; /*给指针变量pa赋值,pa指向变量a*/
pb=&b; /*给指针变量pb赋值,pb指向变量b*/
s=*pa+*pb; /*求a+b,(*pa就是a,*pb就是b)*/
t=*pa**pb; /*求a*b*/
printf("a=%d
b=%d
a+b=%d
a*b=%d
",a,b,a+b,a*b);
printf("s=%d
t=%d
",s,t);
}
知识讲授:指针的移动——算术运算
知识讲授:指针的移动——算术运算
程序文本【8.4】
#include<stdio.h>
main()
{ float f,*pf=&f;
int i,*pn=&i;
char c,*pc=&c;
printf("
pf=%X,pn=%X,pc=%X",pf,pn,pc);
pf=pf+2; // 指针移动 2*sizeof(float)字节
pn++; // 指针移动 1*sizeof(int)字节
pc+=3; // 指针移动 3*sizeof(char)字节
printf("
pf=%X,pn=%X,pc=%X",pf,pn,pc);
} //注意:不同运行环境下内存地址值可能有所不同
知识讲授:指针的移动——算术运算
知识讲授:指针的比较——关系运算
比较的是地址的大小
核心概念:指针支持关系运算(==, !=, <, >),本质是比较指针存储的地址值大小。
运算逻辑:地址值大的指针,指向内存中更靠后的存储单元。
典型应用场景
场景1:判等同:用 == / != 判断两个指针是否指向同一变量。
场景2:比先后:用 < / > 判断地址在内存中物理位置的前后。
知识讲授:指针的比较——关系运算
程序文本【8.5】
#include<stdio.h>
main()
{ int a,b,c,*pmax,*pmin; /*pmax,pmin为整型指针变量*/
printf("input three,numbers:
"); /*输入提示*/
scanf("%d%d%d",&a,&b,&c); /*输入3个数字*/
if(a>b){ /*如果第1个数字大于第2个数字*/
pmax=&a; pmin=&b;} /*指针变量赋值*/
else{
pmax=&b; pmin=&a;} /*指针变量赋值*/
if(c>*pmax) pmax=&c; /*判断并赋值*/
if(c<*pmin) pmin=&c; /*判断并赋值*/
printf("max=%d
min=%d
",*pmax,*pmin); /*输出结果*/
} /*注意:在进行关系运算之前,指针必须指向确定的变量或存储区域,
另外,只有相同类型的指针才能进行比较*/
知识讲授:指针的比较——关系运算
背诵记忆
01. 解引用运算符 `*`
作用:访问指针所指向的变量的值。
示例:若 p 指向 a,则 `*p` 等价于 a。
02. 通过指针修改值
作用:修改指针指向变量的值。
示例:`*p = 100;` 将100赋值给p指向的变量。
03. 指针算术运算
规则:步长为指向类型的大小(sizeof)。
示例:double指针 pd++ 地址增加8字节。
04. 指针关系运算
作用:比较地址值的大小或前后位置。
应用:判断是否指向同一变量或数组元素。
例题分析
程序文本
#include <stdio.h>
main()
{
int x = 5, y = 10;
int *px=&x,*py=&y;
*px=*py; // 将y的值赋给x
*py=20; // 将20赋给y
printf("x=%d,y=%d
",x,y);
printf("*px=%d,*py=%d
",*px,*py);
}
思考:程序的最终输出是什么?
提示:关注指针指向的变量值的变化过程
▍ 逐步拆解分析
1. 赋值操作 *px = *py:指针px指向x,指针py指向y。将y的值(10)赋给x。此时x = 10, y = 10。
2. 赋值操作 *py = 20:直接修改指针py指向的变量y的值。此时x = 10, y = 20。
3. 最终打印:变量的值决定了输出,最终结果如下:
x=10,y=20
*px=10,*py=20
例题分析
课堂问答
01. 表达式解析 &*p
已知:int a = 10; int *p = &a;
请问:&*p 等于什么?
02. 表达式解析 *&a
已知:int a = 10; int *p = &a;
请问:*&a 等于什么?
答案:&a
解析:先解引用 p 得到 a,再取 a 的地址。
答案:a
解析:先取 a 的地址,再对地址解引用。
课堂练习
1. 定义变量
定义两个整型变量,初始化赋值:
`int m = 5, n = 8;`
2. 建立指针指向关系
定义指针变量 `pm` 和 `pn`,分别让它们指向变量 `m` 和 `n`。
3. 指针操作:交换数值
不直接操作变量本身,仅通过指针间接访问,完成两个变量值的交换。
4. 验证结果
最后使用打印语句,输出 `m` 和 `n` 的值,检查交换是否成功。
课堂练习
程序文本【8.4】
#include<stdio.h>
main()
{
int m = 5, n = 8, temp;
int *pm = &m, *pn = &n;
temp = *pm;// temp = m,将指针pm指向的值赋给temp
*pm = *pn;// m = n,将指针pn指向的值赋给指针pm指向的变量
*pn = temp;// n = temp,完成交换
printf("m=%d,n=%d
",m,n);// 输出 m = 8, n = 5
}
课堂练习
课堂小结
基础篇
💡 指针的本质
一个专门用来存储“内存地址”的特殊变量,就像是数据的“门牌号”。
📝 核心语法
•定义:类型 *指针名; (如 int *p;)
•初始化:指针名 = &变量名;
操作篇
🔑 取地址符 `&`
作用于变量前,获取该变量在内存中的地址。
示例: int a=5; p = &a; // 将a的地址存入指针p
🔑 解引用符 `*`
作用于指针前,访问指针指向的地址中存储的数据值。
示例: *p = 10; // 修改指针指向地址的值为10
运算篇
➕ 算术运算 (移动指针)
支持 p++, p--, p+i 等操作。
⚠️ 关键:移动步长取决于指针类型(int/char等),而非固定1字节。
⚖️ 关系运算 (比较指针)
支持 ==, !=, <, > 等比较。
通常用于判断指针是否指向同一数组中的不同位置。
课堂小结
指针的“坑”:常见陷阱与避坑指南
01. 野指针
指未初始化或指向已释放/非法内存地址的指针,访问此类指针会导致程序崩溃或不可预知的行为。
💡 避坑指南:定义指针后立即初始化,或直接赋值为 NULL;释放内存后及时置空。
02. 空指针解引用
试图访问指针指向的内容,但该指针的值是 NULL(即不指向任何有效内存),这是最常见的崩溃原因之一。
💡 避坑指南:养成“先检查,后使用”的习惯,在进行解引用操作前,务必判断指针是否为 NULL。
03. 指针越界
指针运算超出了数组或动态分配内存的合法范围,导致非法访问内存,可能破坏其他变量数据。
💡 避坑指南:时刻关注数组的边界条件,使用循环时确保索引不越界;管理好动态内存的大小。
04. 类型不匹配
错误地将一种类型的指针赋值给另一种类型的指针,可能导致数据在解释时出现错误,破坏内存结构。
💡 避坑指南:尽量保持指针类型一致;若需转换,显式使用强制类型转换,并确保转换逻辑的安全性。
作业布置:
1. 指针交换变量:
要求:①定义int变量a,b并赋值;②使用指针交换a,b的值(禁止临时变量);③可尝试加减或异或运算实现;④打印交换后的结果。
2. 三数排序:要求:①输入整数存入x,y,z;②使用指针操作变量;③将三数按从小到大排序;④输出排序后的结果。
3. 预习第8.3节 指针与数组。
课后任务
谢谢观看
THANKS
$