内容正文:
C语言程序设计
电子工业出版社
8.6、8.7:C语言趣味程序实例8、项目开发实例
掌握指针在数组和字符串中的核心应用
学习用指针实现循环、判断等复杂逻辑
能用指针解决“分糖果”等经典算法问题
提升阅读和调试复杂指针程序的能力
养成严谨、逻辑严密的编程习惯
激发探索兴趣,感受编程解决问题的成就感
知识目标
能力目标
素养目标
01
02
03
学习目标
目录
Part 01
教学导入
Part 02
知识讲授
Part 03
师生互动
Part 04
课堂练习
Part 05
课堂小结
教学导入:指针是什么?——从门牌号说起
“程序猿公寓”
想象我们生活在一个巨大的公寓楼里,它的名字叫“程序猿公寓”。公寓里有无数个房间,排列得整整齐齐。
“数据居民”
每个房间里都住着一位“居民”,它们就是我们写的各种数据,比如数字100,或者一条字符串"HelloWorld"。
“内存地址”
要找到某位居民,我们需要知道它的房间号。这个独一无二的房间号,在计算机的世界里,就被称为内存地址。
教学导入:两种传递信息的方式
01直接把数字给他
场景:
我直接告诉小王999,然后把这个数字送给他。
✅ 优点:简单直接,接收方立即获得数值。
❌ 缺点:传递的是“副本”,小王修改数字不会同步到我的原始数据。
02告诉他房间号
场景:我告诉小王:“你去302室,那里有我给你留的惊喜。”
✨ 优点:小王找到的是“原始数据”,修改即同步。我并没有给他具体的物品,而是给了他一个找到物品的“地址”或“位置信息”。
💡 启示:第二种方式,正是“指针”在编程中的核心逻辑。
教学导入:指针的本质
什么是“302”?
在第二种访问方式中,“302”它本身不是真正的数据,它就是一个指针!
它就像快递上写的收货地址,是一个指向真实数据的“路标”。
本质是“地址”
指针不直接存储具体数值,它存储的是数据在内存中存放的内存地址。
程序通过这个“地址”,就能间接找到并访问到真实的数据本体。
“地址钥匙”
我们可以把指针变量想象成一把特殊的钥匙:
🔑 钥匙上刻着“门牌号”(内存地址)
🏠 用它打开对应“房间”(内存单元)
📦 最终取出里面的“物品”(数据)
知识讲授
指针应用实例
C语言趣味程序实例8
项目开发实例
第一部分
C语言趣味程序实例8
知识讲授
趣味程序实例8:10个小孩分糖果
📋 初始糖果分配表
10个小孩手拉手围成一个圆圈,老师按顺时针方向,给每位小朋友随机分发了数量不一的糖果:
👶第 1 位:10 块
👶第 2 位:2 块
👶第 3 位:8 块
👶第 4 位:22 块
👶第 5 位:16 块
👧第 6 位:4 块
👧第 7 位:10 块
👧第 8 位:6 块
👧第 9 位:14 块
👧第 10 位:20 块
分配规则 & 求解目标
循环规则
所有小孩同时将手中的糖果分一半给右边的人;若分完后手中的糖果数为奇数,则可向老师再要1块。
待解问题
请问经过多少次这样的分配,所有小孩手中的糖果数量才能变得一样多?最终每人手里有多少块糖果?
问题分析与算法设计
01 问题分析
问题特征
这是一个固定的、机械的、重复执行的操作,直到满足“所有人糖果数量一致”这一特定的停止条件,是典型的循环问题。
核心操作流程
①分配:每人将手中一半的糖果分给右边的人。
②补完:若自己糖果数为奇数,则补1颗,使其变为偶数。
算法设计思路
01. 初始化:定义数组 `sweet[10]` 存储每个小孩的初始糖果数量。
02. 循环逻辑:使用 `while` 循环结构,将分配操作放入循环体,直到所有糖果数相等时跳出。
03. 关键分配:需借助临时数组 `t[10]` 暂存分配过程中的中间值,防止数据覆盖;遇奇数需先加1再均分。
04. 状态判断:封装一个 `judge` 函数,用于检查数组中所有元素的值是否完全一致。
05. 结果输出:每次执行分配操作后,即时打印输出当前所有小孩的糖果数量,以便观察变化过程。
程序实现:变量与函数声明
#include <stdio.h>
引入标准输入输出库,让我们能使用printf等常用函数进行输入和输出操作。
void print(int t[])
声明一个无返回值的打印函数,输出每一轮分配后的糖果数量,方便我们调试代码。
int judge(int t[])
声明一个判断函数,用于检查所有孩子的糖果数是否已完全相等,以决定是否终止循环。
int j = 0;
定义一个全局变量 j 并初始化为0,用来累计循环执行的次数,也就是我们分配了多少轮。
程序文本:变量与函数声明
#include<stdio.h>
//函数声明:负责输出结果
void print(int s[]);
//函数声明:判断分配是否完成
int judge(int c[]);
//全局变量:记录分配的轮数/次数
int j= 0;
程序实现:main函数(初始化)
static int sweet[10]={...}
定义并初始化一个静态数组sweet,用来存储每个小孩的糖果数量。使用static修饰符,是为了让该数组在其他函数中也能被访问。
int t[10]
定义一个临时数组t,专门用来暂存分配过程中的中间数据,从而避免修改和覆盖掉sweet中的原始糖果数量。
print(sweet)
调用自定义的print函数,输出程序开始运行时,所有小孩手中糖果的初始分配情况,方便我们进行观察和验证。
程序文本:main函数(初始化)
void main()
{ static int sweet[10]={10,2,8,22,16,4,10,6,14,20};
/*初始化数组数据*/
int i,t[10],l;
printf(" child
");
printf("round 1 2 3 4 5 6 7 8 9 10
");
printf("..........................................
");
print(sweet);/*输出每个人手中糖果的块数*/
程序实现:main函数(循环分配)
程序文本:main函数(循环分配)
/*若不满足要求则继续进行循环*/
while(judge(sweet))
{ /*将每个人手中的糖果数量分成两半*/
for(i=0;i<10;i++)
/*若为偶数则直接分出一半*/
if(sweet[i]%2==0)
t[i]=sweet[i]=sweet[i]/2;
else /*若为奇数则加1后再分出一半*/
t[i]=sweet[i]=(sweet[i]+1)/2;
循环控制
核心是while(judge(sweet))。judge检查糖果是否一致,不一致则循环执行,直至“人人平等”。
遍历分配
利用for(i=0; i<10; i++)遍历所有小孩,确保每轮分配中,每个小孩按规则同步分糖。
分配操作
偶数:sweet[i] /= 2后分一半;奇数:先+1再均分。分出部分存入t[i]待分配。
程序实现:main函数(分配给右边)
程序文本:main函数(分配给右边)
/*将分出的一半糖果给有(后)边的孩子*/
for(l=0;l<9;l++)
sweet[l+1]=sweet[l+1]+t[l];
sweet[0]+=t[9];
print(sweet);}
/*输出当前每位孩子中手中的糖果的块数*/
}
循环逻辑
遍历前9个小孩:for(l=0; l<9; l++),新糖果数 = 剩余一半 + 右侧分来的一半。
核心计算
执行分配:sweet[l+1] += t[l],将当前孩子分出的糖果加到右侧邻居总数中。
特殊情况
环形闭环:sweet[0] += t[9],第10个小孩的糖果分给第1个小孩。
收尾操作
状态输出:调用print(sweet),打印本轮所有孩子的糖果状态。
程序实现:judge函数
程序文本:judge函数
int judge(int c[])
{ int i;
/*判断每位孩子手中的糖果块数是否相同*/
for(i=0;i<10;i++)
if(c[0]!=c[i])
return 1;/*不相同返回 1*/
return 0;}
函数定义
int judge(int c[])
接收整型数组c,判断元素是否全同,返回整数结果。
循环判断
for(i=0; i<10; i++)
遍历数组0-9号元素,逐个比对检查。
条件判断
if(c[0] != c[i]) return 1;
发现不等则立即返回1(不满足条件)。
结果返回
return 0;
循环正常结束,返回0(所有糖果数一致)。
程序实现:print函数
程序文本:print函数
/*输出数组中每个元素的值*/
void print(int s[])
{ int k;
printf(" %2d ",j++);
for(k=0;k<10;k++)
printf("%4d",s[k]);
printf("
");}
函数定义 | void print(int s[])
接收数组展示10个元素(10个孩子的糖果数)。
打印轮次 | printf(" %2d ", j++)
静态变量j记录轮次,标记当前是第几次分配。
遍历打印 | for(k=0; k<10; k++)
for循环依次访问数组s,遍历所有10个元素。
执行输出 | printf("%4d", s[k])
%4d格式化输出,使数值整齐对齐打印。
换行整洁 | printf("
");
一轮打印结束后换行,分离不同轮次结果。
程序运行结果
分配过程日志
第0次分配后: 10 2 8 22 16 4 10 6 14 20
第1次分配后: 15 6 5 15 19 10 7 8 10 17
第2次分配后: 17 11 6 11 18 15 9 8 9 14
.................................................
第16次分配后: 18 18 18 18 18 18 17 17 18 18
第17次分配后: 18 18 18 18 18 18 18 18 18 18
✅ 分配任务圆满完成!
大家的糖果一样多啦!
所有小朋友围成一圈,开心地展示着手里的糖果,脸上洋溢着满足的笑容。
18次
经过18次循环分配,所有孩子手中的糖果数量终于达成一致,不再有差异。
18块
最终稳定状态下,每个小孩手里都拿着18块糖果,实现了绝对的公平。
运行结果
背诵记忆
01 问题抽象
将实际生活中的“分糖果”转化为程序中的数组操作,是解题的第一步。
02 循环控制
使用 while 循环控制整个过程的重复,直到满足终止条件,确保逻辑闭环。
03 数据暂存
引入临时数组 t 是为了保证所有分配操作是同步进行的,避免分配过程中数据被覆盖。
04 边界处理
利用(i+1)%10巧妙处理环形结构的边界问题,保证第10个小孩的邻居是第1个小孩,实现无缝衔接。
05 函数封装
将 print 和 judge 等功能封装成独立函数,降低耦合度,让主逻辑更简洁清晰,可读性更高。
课堂问答
Q1 比较基准的选择
在judge函数中,if(t[i] != t[i+1]) return 1;这行代码,如果把t[i]换成t[0]可以吗?为什么?
💡 答案:可以。只要确保数组中所有元素都和第一个元素(或者任意一个固定元素)进行比较,就能判断出所有元素是否都相等。这样写逻辑上是正确的,而且是更直接的判断方式。
Q2 static 关键字的作用
代码static int sweet[10]={...};中,static关键字在这里起到了什么作用?如果去掉它,程序还能正常运行吗?
💡 答案:`static` 改变了变量的存储周期(程序运行期)和作用域(仅本函数内)。在这个场景中去掉它,程序依然能正常运行,因为数组是在 `main` 中定义并通过指针传递的,对程序逻辑无决定性影响。
例题分析:指针实现字符串查找
01 / 核心思考
如何查找子串首次出现?
核心:两层指针循环。外层遍历主串,内层逐个比较主串与子串字符,匹配则指针同步后移。
02 / 逻辑分析
查找五步逻辑:
1.外层循环:指针 p 遍历主串
2.内层匹配:指针 i/p, j/sub
3.字符比较:i == *j 则后移
4.匹配成功:j='\0' 返回 p
5.失败处理:p++ 继续外层循环
03 / 得出结论
C语言进阶关键技能
考察指针移动、解引用与嵌套循环。是实现标准库函数、理解字符串操作的基础。
💡 注意循环终止条件!
例题分析
程序文本
#include <stdio.h>
//自定义实现 strstr 函数:查找子串在主串中首次出现的位置
char* my_strstr(const char* main_str,const char* sub_str)
{ if (*sub_str == '\0') return (char*)main_str;// 子串为空时,直接返回主串首地址
const char* p = main_str;
while (*p!='\0') // 外层循环:指针p遍历主串的每一个位置
{ const char* i = p; // 主串当前比较位置
const char* j = sub_str;// 子串的起始位置
while (*i!='\0'&&*j!='\0'&&*i==*j)//内层匹配:逐个比较主串与子串字符
{ i++; j++; }
if (*j == '\0') return (char*)p;//匹配成功:子串遍历结束(遇到'\0'),返回当前p的地址
p++; } //匹配失败:主串指针后移,继续下一轮外层循环
return NULL; } // 主串遍历结束仍未找到,返回NULL
int main()
{ char main_str[]="Hello, welcome to the C language world!";
char sub_str[] = "C language";
char* result = my_strstr(main_str, sub_str);
if (result != NULL)
{ printf("子串\"%s\"在主串中首次出现的位置:
", sub_str);
printf("从这里开始:%s
", result); }
else printf("主串中未找到子串\"%s\"
", sub_str); }
例题分析
课堂练习(一):指针遍历字符串统计字符
练习任务
任务描述:
输入一行文字,用指针遍历字符串,统计大写、小写、空格、数字及其他字符数量。
核心要求:
• 指针遍历字符串
• 分类统计并输出
示例演示
输入 Input:
Today is 2026/6/8
输出 Output:
upper case:1 lower case:6
space:2 digit:6
other:2
解题思路
1. 输入字符串:getchar()读入至数组s。
2. 初始化指针:char *p = s; 指向首地址。
3. 遍历判断:while(*p!='
') 分类计数。
4. 指针移动:每次循环执行 p++。
5. 输出结果:打印各类计数器值。
课堂练习(一):指针遍历字符串统计字符
程序文本
#include <stdio.h>
int main()
{int upper=0,lower=0,digit=0,space=0,other=0,i=0;
char *p,s[20];
printf("input string: ");
while ((s[i]=getchar())!='
') i++;
p=&s[0];
while (*p!='
')
{if (('A'<=*p) && (*p<='Z')) ++upper;
else if (('a'<=*p) && (*p<='z')) ++lower;
else if (*p==' ') ++space;
else if ((*p<='9') && (*p>='0')) ++digit;
else ++other;
p++; }
printf("upper case:%d lower case:%d
",upper,lower);
printf(" space:%d digit:%d
other:%d
",space,digit,other); }
课堂练习(一):指针遍历字符串统计字符
课堂练习(二):指针实现约瑟夫环问题
练习任务
任务描述:
n人围成一圈报数,报到m的人出列,循环直至剩最后一人。
核心要求:
• 指针遍历数组模拟环
• 输出出列顺序与幸存者
示例演示
输入 Input:
n=5, m=3
输出 Output:
编号 3 出列
编号 1 出列
编号 5 出列
编号 2 出列
The last one is NO.4
解题思路
1. 初始化:指针p指向编号数组。
2. 循环报数:while剩余人数>1。
3. 报数判断:非0则计数k++。
4. 处理出列:k=m时置0并重置k。
5. 指针循环:p++至末尾则归零。
6. 输出幸存:找非0元素。
课堂练习(二):指针实现约瑟夫环问题
程序文本
#include <stdio.h>
int main()
{ int i, k, m, n, num[50], *p;
printf("input number of person: n=");scanf("%d", &n); //输入总人数n和报数上限m
printf("input count out number: m=");scanf("%d", &m); p = num;
for (i = 0; i < n; i++) *(p + i) = i + 1; // 初始化数组,存入1~n的编号
i=0;/*数组下标,用于循环遍历*/ k=0;/*报数计数器*/
int outCount = 0; // 已出列人数
while (outCount < n-1)// 循环直到只剩最后1个人
{ if (*(p + i) != 0) // 当前人还未出列,才参与报数
{ k++;
if (k == m) // 报到m的人出列
{ printf("编号 %d 出列
", *(p + i));
*(p + i) = 0; // 标记为已出列
k = 0; // 报数重置
outCount++; // 出列人数+1
} } i++;
if (i == n) i = 0; } // 到数组末尾了,回到开头形成循环
while (*p == 0) p++;// 找到最后剩下的人
printf("The last one is NO.%d
", *p); }
课堂练习(二):指针实现约瑟夫环问题
第二部分
知识讲授
项目开发实例
知识讲授:学生成绩管理系统 — 姓名排序
项目核心任务
利用指针存储学生姓名,实现姓名的字典序排序,并关联输出对应的学号。这是一个指针与字符串结合的经典综合应用,难点在于清晰区分“指针数组”与“二维字符数组”的底层逻辑差异。
方案对比:存储结构分析
不同姓名长度差异大,若使用统一大小的二维数组会造成大量内存碎片。指针数组则可以灵活指向不同长度的字符串,且排序时只需交换指针,无需移动大量字符串数据,效率更高。
❌ 传统方案:二维字符数组
char name[10][20];
开辟固定大小的连续内存块,无法适配姓名长短不一的情况,造成严重的内存浪费,且数据交换成本极高。
✅ 推荐方案:指针数组
char *name[];
元素为指针,指向不同长度的字符串常量,内存利用率高。排序时仅交换指针地址,实现快速、高效的排序操作。
程序实现:初始化与输出
程序文本:初始化与输出
/*学生姓名排序指针实现*/
#include<stdio.h>
#include<string.h>
void main()
{ char *name[]={"One","Two","Three","Four","Five","Six",
"Seven","Eight","Nine","Ten"};/*定义并初始化指针数组*/
int i,j;int max;
char *tmp; int large;
printf("10名学生的姓名分别为:
");
for(i=0;i<10;i++) puts(name[i]);
printf("学号分配结果为:
");
代码解析
循环输出:puts(name[i])
利用 for 循环遍历数组。puts() 函数接收一个字符串指针,将其指向的字符串输出并自动换行。这里的 name[i] 即为指向第 i+1 个学生姓名的指针。
指针数组定义:char *name[] = {...}
定义了一个名为 name 的字符指针数组,其中的每一个元素都是一个指针,分别指向一个代表学生姓名的字符串常量。这种方式高效地管理了一组字符串。
程序实现:选择排序(外层循环)
程序文本:选择排序(外层循环)
/*使用选择排序算法*/
for(i=9;i>0;i--) { max=i;
for(j=0;j<i;j++)
{ large=strcmp(name[max],name[j]);
if(large<0) max=j; }
if(max!=i)
{ tmp=name[i];name[i]=name[max]; name[max]=tmp;
}
}
核心算法类型
此处代码片段使用的是经典的“选择排序”算法逻辑。该算法的核心思想是每一轮从未排序的序列中选出一个极值,放到正确的位置上。
外层循环的作用
`for(i=9; i>0; i--)` 用于控制排序的总轮次。我们从下标 9(即数组末尾)开始向前遍历,逐步确定每一个位置的“最终人选”。
初始化最大值索引
代码中的 `max = i;` 是每一轮的起点。它先做一个“假设”:当前未排序区间里,最后一个元素(下标为 i)就是最大值。
程序实现:选择排序(内层循环)
字符串比较:strcmp 函数
用于比较两个字符串大小,返回值规则:
< 0:前者更小 | = 0:两字符串相等 | > 0:前者更大
更新最大值索引 (max)
遍历未排序区域,如果发现比当前最大值更大的字符串,则更新max = j,记录新的最大值位置。
指针交换:效率的关键
找到最大值后,交换的是name[i]与name[max]的地址,而非复制大量字符串内容,这是指针数组的精髓,极大提升了运行效率。
程序文本:选择排序(内循环)
/*使用选择排序算法*/
for(i=9;i>0;i--) { max=i;
for(j=0;j<i;j++)
{ large=strcmp(name[max],name[j]);
if(large<0) max=j; }
if(max!=i)
{ tmp=name[i];name[i]=name[max]; name[max]=tmp;
}
}
程序实现:输出学号
▍ 代码逻辑解析
排序状态:此时指针数组name[]中的元素已按字符串字典序排好序。
生成学号:利用循环变量i,动态拼接生成学号"2012001"至"2012010"。
打印输出:通过puts(name[i])输出对应位置的学生姓名。
程序文本:输出学号
printf("学号\t\t姓名
");
for(i=0;i<10;i++)
{
printf("%d\t\t",i+2012000);
puts(name[i]); }
}
程序运行结果
背诵记忆
01指针数组
`char *name[];` 一个数组,其每个元素都是一个指针,分别指向一个字符串的首地址。这种方式巧妙地管理了多个长度不一的字符串。
02字符串比较
C语言中,比较字符串不能用 `==`,必须使用 `strcmp` 函数。它比较的是两个字符串中字符的 ASCII 码值,而不是字符串长度。
03指针交换
指针排序的精髓,核心优势在于通过交换指针的指向,而不是交换字符串内容,来实现排序。这一操作开销极小,能显著提升排序的执行效率。
04选择排序
一种入门的简单排序算法,核心的处理逻辑是:每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。
课堂问答
Q1. 内存布局本质区别?
`char *name[]={"Tom", "Jerry"}` 和 `char name[2][20]={"Tom", "Jerry"}` 在内存中存储的本质区别是什么?
答案:`char *name[]` 是一个指针数组,数组里存的是两个地址,分别指向字符串常量区的 `"Tom"` 和 `"Jerry"`。而 `char name[2][20]` 是一个二维数组,它在栈上开辟了一块连续的、大小为 `2*20` 的内存空间,并将字符串内容分别拷贝到这两个连续的区域中。
Q2. strcmp 函数比较的是什么?
`strcmp` 是 C 语言中最常用的字符串比较函数,它的核心比较规则是什么?如果两个字符串长度不同,会发生什么?
答案:它会依次比较两个字符串中对应位置字符的 ASCII 码值,而不是比较字符串的长度。当遇到第一个不匹配的字符时,即可根据该字符的 ASCII 码大小得出结果。若一个字符串是另一个的前缀(如 `"app"` 和 `"apple"`),则较短的字符串被认为更小。
例题分析
题目描述
输入月份号(1-12),输出对应英文月名。例如输入3输出"March"。要求使用指针数组处理月份名称。
💻 示例输入:
3
📤 示例输出:
It is March.
解题思路
01. 定义指针数组
定义 `char *month_name[]` 存储12个月名与索引0的错误提示。
02. 获取用户输入
使用 `scanf` 获取月份号整数 `n`。
03. 输入验证
判断 `n` 是否在 1-12 有效范围。
04. 指针运算输出
有效则用 `*(month_name+n)` 输出月名,否则提示错误。
核心代码与解析
char m[]={"Err","Jan","Feb","Mar"……};
int n;scanf("%d",&n);
if(n>0&&n<13)
printf("%s",*(m+n));
else puts(m[0]);
💡 指针数组定义:索引0存错误提示,1-12存月名。
💡 指针运算:`*(m+n)` 等价于 `m[n]`,通过地址偏移快速定位。
💡 健壮性:判断输入范围,处理无效值。
例题分析
程序文本
#include <stdio.h>
main()
{ char *month_name[13]={"illegal month","January","February",
"March","April","May","June","July","August",
"September","October","November","December"} ;
int n;
printf("input month:
");
scanf("%d",&n);
if((n<=12)&&(n>=1))
printf("Itis %s.
",*(month_name+n));
else
printf("%s.
",month_name[0]);}
例题分析
课堂练习(一)
课堂题目:星期英文显示程序
使用指针数组存储星期英文名称,输入1-7的数字,程序输出对应星期。
示例:输入 4 → 输出 Thursday
思路一:定义指针数组
• 定义 `char *Week[]` 指针数组,元素指向 "Monday" 等字符串常量。
• 优势:利用指针直接访问,避免重复存储,节省内存。
思路二:指针运算与输出
• 核心:通过数组索引与指针关系实现高效访问。
• 实现:输入数字 i,通过 `Week[i-1]` 访问对应字符串。
课堂练习(一)
程序文本:用指针数组构造字符串数组
#include "stdio.h"
int main(void)
{ char *Week[] ={"Monday","Tuesday","Wednesday","Thursday",
"Friday","Saturday","Sunday", };
int i;
printf("Please enter a number for week
");
scanf("%d", &i);
printf("The week is:");
printf("%s
", Week[i - 1]);
}
课堂练习(一)
课堂练习(二)
题目要求
给定4名学生4科成绩(二维数组),找出至少一科不及格(<60)的学生并输出其成绩。
要求:
1. 指针处理二维数组
2. 通过函数search查找
成绩数组:
float score[][4] = {
{60,75,82,91}, // 学生1
{75,81,91,90}, // 学生2
{51,65,78,84}, // 学生3
{65,72,78,72} // 学生4
};
运行示例
📤 输出 Output
The student NO.3 list:
51.0 65.0 78.0 84.0
(学生3有一科不及格)
💡 解题思路提示
01. 定义 search 函数
接收行指针float (*p)[4],遍历该行,不及格则返回行首地址。
02. 主函数遍历
循环调用search(score+i),判断返回值。
03. 指针核心
score+i 指向第i行;
(*(score+i)+j)等价于 score[i][j]。
课堂练习(二)
程序文本:查找成绩不及格的学生
#include<stdio.h>
float *search(float(*p)[4])
{ int i; /*声明变量*/
float *pt; /*声明指针变量*/
pt = *(p + 1); /*获取下一行的首地址*/
for(i=0;i<4;i++)
{ if(*(*p+i)<60) /*判断分数是否小于60*/
pt=*p; } /*指向本行首地址*/
return (pt);} /*返回首地址*/
void main()
{float score[][4]={{60,75,82,91},{75,81,91,90},{51,65,78,84},{65,72,78,72}}; /*声明数组*/
float *p;/*声明指针变量*/ int i, j; /*声明计数变量*/
for(i=0;i<4;i++)
{ p=search(score+i); /*查找有不及格的行*/
if (p==*(score+i))
{ printf("The student NO.%d list:",i+1);
for (j=0;j<4;j++,p++) /*输出成绩*/
printf("%5.1f",*p); }
} }
课堂练习(二)
课堂小结
指针基础
01. 定义
指针本质是一个变量,其存储的值为另一个变量的内存地址。
格式: 类型标识符 * 指针变量名
02. 核心运算符
• 取地址符 & :获取变量内存地址 (如 &var)
• 解引用符 * :访问指针指向地址中的值 (如 *p)
03. 空指针 NULL
表示指针不指向任何有效的内存地址,避免野指针风险。
指针与数组
01. 数组名即指针
在表达式中,数组名会隐式转为指向首个元素的指针。下标访问与指针运算等价:
arr[i] ≡ *(arr + i)
02. 数组指针 (指向数组的指针)
指向一个长度为 n 的数组整体。本质是“指向数组的指针变量”。
格式:类型标识符 (*指针变量名)[长度]
03. 指针数组 (元素为指针的数组)
一个普通数组,但其每一个元素都是指针类型的变量。
格式:类型标识符 *数组名[长度]
课堂小结
指针与字符串
01. 字符串的两种表示方式
•字符数组:char str[] = "hello";(内容可修改,存储在栈区)
•字符串指针:char *p = "hello";(指向只读常量区,内容不可修改)
02. 指针如何操作字符串?
利用指针的移动操作(如p++ / p--) 来改变指向位置,配合解引用(如p) 即可灵活遍历、读取或修改字符串内容。
指针与函数
01. 指针作为函数参数:传址调用
函数参数传递变量的内存地址,允许被调函数直接访问并修改主调函数中实参变量的值。这是实现“交换两个数”等功能的基础。
02. 函数返回指针:关键注意事项
函数可以返回指针类型,但严禁返回指向函数内部局部变量的指针。因为局部变量在函数执行结束后会被系统回收,返回的地址将变成野指针,引发程序错误。
课堂小结
课后任务
01 / 基础题
复习今天课上的两个核心实例:“分糖果”与“姓名排序”。
重点任务:确保彻底理解每一行代码的底层逻辑,并尝试脱离参考,独立完整地写出代码实现。
02 / 进阶题
综合运用今天所学的指针知识,完成两个补充例题:
1.字符串反转:用指针操作实现字符串逆序输出。
2.指针版冒泡排序:改写经典算法,使用指针作为核心操作工具,对字符串按ASCII码值进行冒泡排序。
03 / 挑战题
发散思维,尝试实现一个功能完整的简易计算器程序。
要求:能够处理用户输入的两个数值,并支持加、减、乘、除四种基本算术运算,必须使用指针来管理关键数据的传递。
谢谢观看
THANKS
$