版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
学习目标理解指针的含义,掌握指针的定义、初始化和使用方法掌握数组、结构和函数的指针概念及应用掌握线性链表的基本操作使用指向结构数组的指针来优化学生成绩管理系统增强安全意识,安全大于天,责任重于山案例导入【问题描述】弘扬工匠精神,精益求精,改进学生成绩管理系统,用指针来优化学生成绩管理系统中的主要功能模块,提高程序执行效率。【问题分析】案例8中开发的学生成绩管理系统,对数据的操作是通过结构成员访问实现的,显然执行效率低,对于处理批量数据会表现速度太慢。为此,我们将利用指针知识优化程序主要功能模块,用指针来访问结构成员,处理批量数据,达到提高系统的访问效率。本案例的任务:①指向结构的指针作为函数的参数;②在函数体内直接用指针访问结构成员。案例9学生成绩管理系统的优化知识学习【知识准备】指针是C语言的精髓,用好了指针,可以事半功倍的效果。要完成上面的任务,必须熟练掌握指针的基本概念,熟悉指针变量的类型说明,掌握用指针处理数组和结构,以及指针作为函数的参数等知识点。本章学习内容如下:9.1指针相关概念9.2指针变量的定义与引用9.3用指针处理数组9.4指针与结构9.5线性链表*9.6指针与函数9.7综合应用第二知识空间:指针的安全使用*9.1指针相关概念1.内存地址计算机内存是以字节为单位进行管理的,假设某PC机上配置的内存是1GB,即1024X1024X1024个字节,因此没有办法也不可能为每一个字节单元起名字。采用编号方式标记和管理内存是最方便的。2.变量的地址和变量的值在程序中定义变量时,计算机就按变量的类型,为其分配一定长度的存储单元。例如,短整型分配了2个字节,单精度分配4个字节。shortx,y;floatz;2.变量的地址和变量的值变量x,一是它内存单元的数值10,另一个是内存单元的起始地址:0x2000。简称为变量x的地址。【例9.1】输出变量的值和变量起始地址。#include<stdio.h>intmain(){shortx,y;floatz;x=10;y=x+2;z=5.6F;printf("x=%d,y=%d,z=%f\n",x,y,z);printf("%#x,%#x,%#x\n",&x,&y,&z);return0;}
运行结果如下:x=10,y=12,z=5.6000000x62fe1e,0x62fe1c,0x62fe183.变量的指针与指针变量变量x所分配的内存空间首地址称该变量的指针,记作&x。例如,变量x的首地址是0x2000,我们就说x的指针是0x2000。用来存放变量地址的变量称指针变量。当指针变量中存放着另一个变量的地址时,就称这个指针变量指向那一变量。例如,假设px是指针变量,并存放x的地址0x2000,见图9-2(a),简称为px指向x,见图9-2(b)。9.2指针变量的定义与引用9.2.1指针变量的定义及初始化1.针变量的定义格式:类型标识符*指针变量名;例如,声明语句int*p1;/*定义指向int型变量的指针p1*/float*p2;/*定义指向float型变量的指针p2*/char*p3;/*定义指向char型变量的指针p3*/说明:(1)“*”表示定义的是指针变量,与其它变量的定义相比,多了一个“*”号外。(2)“类型标识符”表示该指针所指向的变量的类型。星号“*”和前面的类型标识符之间,以及和后面的变量名之间可以有0个或多个空白字符。2.指针变量的赋值
指针变量必须先定义且赋值后使用,使用未经赋值的指针变量将引起灾难性破坏。
地址运算符“&”来表示变量的地址(&变量名),指针变量也可以定义时进行初始化。指针的定义和初始化形式:类型标识符*指针变量名=&变量名;【例9.2】从键盘输入两个数,利用指针方法将这两个数输出。#include<stdio.h>intmain(){intx,y;int*px;int*py=&y;//定义指针变量py并赋初值&y,从而py指向ypx=&x;//将x地址赋给指针变量px,从而px指向xprintf("x的地址=%#x,px的值=%#x\n”,&x,px);printf("y的地址=%#x,py的值=%#x\n”,&y,py);return0;}9.2.2指针运算符及指针运算1.与指针有关的两个运算符(1)&:取地址运算符。(2)*:指针运算符(或称“间接访问”运算符),取其指向单元的内容。
p指向x后,就可以通过p间接访问它所指向的变量x,也就是*p就等价于x,所以,以下两条赋值语句:*p=10;//将10赋值p所指向的变量x,即将10间接赋给变量xx=10;//将10直接赋值给变量x是等价的,都是将10赋给x。同样,下两条语句是等价的:printf("%d\n",x);//直接方式输出变量x的值printf("%d\n",*p);//间接方式输出变量x的值9.2.2指针运算符及指针运算
【例9.3】通过指针变量访问简单变量。#include<stdio.h>intmain(){intx;/*定义一个简单变量x*/int*px;/*定义一个指针变量px*/px=&x;/*使px指向变量x*/x=10;/*直接方式给变量x赋值*/printf("x=%d\t",x);/*直接方式输出变量x*/printf("*px=%d\n",*px);/*间接方式输出变量x*/*px=100;/*间接方式给变量x赋值*/printf("x=%d\t",x);/*直接方式输出变量x,*/printf("*px=%d\n",*px);/*间接方式输出变量x,*/return0;}运行结果如下:x=10*px=10x=100*px=1009.2.2指针运算符及指针运算2.指针算术运算指针加、减整数运算不同于普通变量的加、减整数的运算。设p为指针变量,n为整数,则表达式p+n为p的当前值向后移动到第n项数据的地址。p与p+n之间有多少个单元,与p所指向的数据类型有关。intx,*px;doubley,*py;px=&x;py=&y;px=px+2;//px向后移动8个字节,因为int类型变量占4个字节py=py+2;//py向后移动16个字节,因为double类型变量占8个字节同理:px=px-2;//px向前移动8个字节py=py-2;//py向前移动16个字节9.2.2指针运算符及指针运算【例9.5】指针变量地址输出。#include<stdio.h>intmain(void){shortx,*px;floaty,*py;px=&x;py=&y;printf("px=%#x,py=%#x\n",px,py);//移动前地址输出px=px+2; //px向后移动2*sizeof(int)个字节py=py+2;//py向后移动2*sizeof(float)个字节printf("px+2=%#x,py+2=%#x\n",px,py);//移动后地址输出return0;}9.2.2指针运算符及指针运算【例9.5】指针变量地址输出。说明:
短整型变量x占2个字节,指针px是指向x的地址的,这里的px+2不是简单地在地址上加2,而是指向下两个存放短整型数的地址。如图9-4所示,px+2后px的值增加4(4个字节)。图9-5所示的结果是因为y被定义单精度型实数变量,所以py+2的值增加了8(8个字节)。9.2.3指针变量作为函数参数函数的参数不仅可以是基本类型(整数、实数、字符),还可以是指针类型。指针变量作为函数的参数,其作用是将将一个变量的地址传递到另一个函数中。【例9.7】用fnSwap()函数交换实参变量的值(传地址方式)。#include<stdio.h>voidfnSwap(int*x,int*y);/*fnSwap函数原型声明,形参为指针变量*/intmain(){inta=6,b=9;fnSwap(&a,&b);/*传地址调用,以变量的地址作函数的实参*/printf("在main()函数中:a=%d,b=%d\n",a,b);}voidfnSwap(int*x,int*y)/*fnSwap函数定义,形参为指针变量*/{intt;t=*x;/*交换指针变量所指向的存储单元中的值*/*x=*y;*y=t;}9.3用指针处理数组9.3.1数组的指针和指向数组的指针变量1.数组的指针数组的指针是指数组在内存中的起始地址,数组元素的指针是数组元素在内存中的起始地址。例如,intdata[6];则C语言规定:(1)数组名data是指针常量,它代表的是数组的首地址,也就是数组第一个分量data[0]元素的首地址。(2)data+i就是data[i]的首地址(i=0,1,2,…,5),即data+i与&data[i]等价。与简单变量类似,数组元素data[i]的首地址&data[i]就称data[i]的指针。因为地址就是指针,所以data+i又称为指向data[i]的指针,简称为data+i指向data[i]。(3)可以用*data、*(data+1)、*(data+2)、…、*(data+5)方式引用数组元素,如图9-9(a)所示。以下两循环输出语句完全等价。9.3用指针处理数组9.3.1数组的指针和指向数组的指针变量1.数组的指针数组的指针是指数组在内存中的起始地址,数组元素的指针是数组元素在内存中的起始地址。例如,“intdata[6];”,则C语言规定:(1)数组名data是指针常量,是数组的首地址,也就是data[0]的首地址。(2)data+i是data[i]的首地址(i=0,1,2,…,5),即data+i与&data[i]等价。data[i]的首地址&data[i]就称data[i]的指针。因为地址就是指针,所以data+i又称为指向data[i]的指针,简称为data+i指向data[i]。(3)可以用*data、*(data+1)、…、*(data+5)方式引用数组元素,见图9-6(a)。下两循环输出语句完全等价。for(i=0;i<6;i++)printf("%d",data[i]);for(i=0;i<6;i++)printf("%d",*(data+i));2.指向数组的指针变量类似于指向变量的指针。例如:intdata[6];//定义data为整型数据的数组int*p,*q;//定义p,q为指向整型变量的指针则语句:p=&data[0];(或p=data;)//p指向data数组的第0号元素q=&data[i];//q指向data数组的第i号元素注意:(1)如果p的初值为&data[0],则p+i就是data[i]的地址&data[i](i=0,1,2,…,5)。(2)如果p指向数组中的一个元素,则p+1就指向同一数组的下一个元素。p+1所代表的地址实际上是p+1*d,d是一个数组元素所占字节数(对短整型,d=2;对实型,d=4;对字符型,d=1),见图9-6(b)9.3用指针处理数组3.指向数组的指针变量,在使用中应注意的问题
(1)几种指针运算形式①*p++等价于*(p++),作用是先得到p所指向的变量的值(即*p),然后再使p加1。②*(p++)与*(++p)作用不同,前者是先取*p的值,后使p加1;后者是先使p加1,再取*p的值。若p初值为&data[0],输出*(p++)时,得data[0]的值,而输出*(++p),则得到data[1]的值。(2)(*p)++表示p所指向的元素值加1,注意是元素值加1。比如,如果p所指向的元素为data[3],且data[3]的值为9,则(*p)++表示将data[3]单元中的值加1,变成10,而p仍指向元素data[3],也就是说,p中的地址值并没有改变。(3)p±n:将指针从当前位置前进(+n)或回退(-n)n个元素,而不是n个字节。显然,p++、p--(或++p、--p)是p±n的特例(n=1)。(4)p2-p1表示两指针之间的数组元素个数,而不是指针的地址之差,见图910-10(a)。9.3用指针处理数组(5)两指针之间可进行关系运算,如果p1指向data[i],p2指向data[j],并且i<j,则p1<p2为“真”,反之亦然,即图9-10(b)中,p1<p2为真。图9-10指针减法、关系运算9.3.2数组元素的引用若有如下变量定义语句:
intdata[6];int*p=data;则p是指向数组data的指针变量,指针和数组之间有如下恒等式:
•data+i==&data[i]==p+i(i=0,1,…,5)•data[i]==*(data+i)==*(p+i)==p[i](i=0,1,…,5)引用数组第i个元素,有以下几种访问方式:(1)标法data[i]//①数组名下标法:p[i]//②指针变量下标法:(2)指针法*(data+i)//①数组名指针法:*(p+i)//②指针变量指针法:9.3用指针处理数组【例9.8】用下标法和指针法引用数组元素。#include<stdio.h>intmain(void){ intdata[6]={10,3,6,9,12,15}; int*p=data,i; printf("数组名下标法:\t");for(i=0;i<6;i++) printf("%3d",data[i]);/*数组名下标法*/ printf("\n数组名指针法:\t"); for(i=0;i<6;i++) printf("%3d",*(data+i));/*数组名指针法*/ printf("\n指针变量下标法:\t");for(i=0;i<6;i++) printf("%3d",p[i]);/*指针变量下标法*/printf("\n指针变量指针法:\t");for(i=0;i<6;i++) printf("%3d",*(p+i));/*指针变量指针法*/return0;}运行结果如下。数组名下标法:103691215数组名指针法:103691215指针变量下标法:103691215指针变量指针法:1036912159.3.3数组名作为函数参数当数组名作为函数参数时,在函数调用时,实际传递给函数的是该数组的起始地址,即指针值。所以,实参可以是数组名或指向数组的指针变量。而被调函数的形参,既可以说明为数组也可以说明为指针。函数的实参和形参都可以使用指向数组的指针或数组名,于是函数实参和形参的配合上有4种等价形式,这4种等价形式本质上是一种,即指针数据作函数参数。见表9-1。9.3.3数组名作为函数参数【例9.9】编写一个字符变换函数,其功能是将大写字母变成小写,小写变成大写,要求数组名为函数的参数。然后,从键盘输入一串英文字母,调用该字符变换函数,验证结果。
【算法设计】(1)以字符数组作为函数形参,自定义一个字符变换函数(2)在函数体内,对形参字符数组,循环对每一个字符进行变换
如果是小写字符,自减32转换成大写字符;
如果是大写字符,自加32转换成小写字符;(3)在主函数中,定义一个字符数组;(4)输入一串字符存储在字符数组中;(5)以字符数组名为实参,调用自定义的字符变换函数;(6)输出字符数组。9.3.3数组名作为函数参数#include<stdio.h>voidchChange(chara[]);intmain(void){charchA[80];printf("请输入一串英文字符:");scanf("%s",chA);printf("你输入的是:%s",chA);chChange(chA);printf("\n变化后是:%s",chA);return0;}voidchChange(chara[]){inti=0;while(a[i]!='\0’){ if(a[i]>='a'&&a[i]<='z')a[i]-=32;//如果是小写字母,变换成大写字母elsea[i]+=32;//否则,大写字母变成小写字母 i++;//下一个字符}}9.4指针与结构9.4.1指向结构变量的指针结构变量的首地址就是该结构变量的指针。用地址运算符“&”就可获得结构变量的指针。指向一个结构变量的指针变量称为结构指针变量。【例9.16】利用结构体指针变量访问结构体中的成员。#include<stdio.h>structtime{inthour;intminute;intsecond;};structtimet={2,34,56};//定义结构体变量t并初始化intmain(void){ structtime*pt;//定义结构体指针变量ptpt=&t;//使pt指向结构体变量printf("1.用结构体变量访问各成员:");printf("%d时%d分%d秒\n",t.hour,t.minute,t.second);printf("2.用“*”运算符访问各成员:");printf("%d时%d分%d秒\n",(*pt).hour,(*pt).minute,(*pt).second);return0;}9.4.1指向结构变量的指针运行结果如下:1.用结构体变量访问各成员:2时34分56秒2.用“*”运算符访问各成员:2时34分56秒
可见两个printf()函数输出结果是相同的,正是初始化设置的值。
在C语言中,为了便于使用和直观,通常使用指针运算符“->”访问结构体中的成员,可以把(*pt).hour改用pt->hour来代替。
一般地说,如果指针变量pt已指向了结构体变量t,则访问结构体体成员三种形式:
(1)t.成员/*结构体变量名.成员名*/
(2)(*pt).成员/*(*结构体指针变量名).成员名*/
(3)pt->成员/*结构体指针变量名->成员名*/9.4.2指向结构体数组的指针类似于用指向数组的指针来访问数组元素,也可以用指向结构体数组的指针来访问结构体数组。【例9.17】利用指向结构体数组的指针来访问结构体数组。#include<stdio.h>structstudent{charname[13];charsex;intscore;};structstudentstu[3]={ {"Zhang",'M',100}, {"Li",'F',67}, {"Wang",'M',90}};intmain(void){structstudent*p;for(p=stu;p<stu+3;p++)printf("%6s,%3c,%4d\n",p->name,p->sex,p->score);return0;}9.4.3指向结构体的指针作为函数参数类似于指针变量作为函数参数,用指向结构体的指针变量作实参时,属于“地址传递”方式。【例9.18】用函数调用方式,改写例9.17,编写一个显示结构体成员的函数,主函数调用时,用指向结构体的指针变量作实参。#include<stdio.h>structstudent{charname[13];charsex;intscore;};structstudentstu[3]={ {"Zhang",'M',100}, {"Li",'F',67}, {"Wang",'M',90}};voidfnPrint(structstudent*p){printf("%6s,%3c,%4d\n",p->name,p->sex,p->score);}intmain(void){structstudent*p;for(p=stu;p<stu+3;p++)fnPrint(p);return0;}9.5线性链表*9.5.1链表概述1.链表的结构线性链表是由称为结点的元素组成的,结点的多少根据需要而定。每个结点都应包括两部分的内容:一是数据部分,该部分可以根据需要由多个成员组成,它存放的是需要处理的数据;二是指针部分,该部分存放的是下一个结点的地址,链表中的每个结点通过指针链接在一起,图9-16是一个有四个结点的链表。图9-16单链表结构示意图9.5线性链表*2.链表的相关操作链表的操作主要包含创建、输出、插入和删除等。不失一般性,假设某一链表有三个结点,分别由p1,p2,p3指向(不妨设结点名仍记为p1,p2,p3),如图9-17(a)所示,删除结点p2所得链表如图9-17(b)所示。删除步骤如下:(1)把p3指向的结点的首地址赋给p1的指针域:p1->next=p2->next;(2)释放p2指向的结点空间:free(p2);9.5线性链表*9.5.2静态链表【例9.18】建立一存储3个学生信息的简单链表,并输出各结点中的数据。程序的运行结果如图9-18所示。图11-18静态链表9.5线性链表*9.5.3链表创建所谓动态链表,就是在程序执行过程中从无到有地建立起来的链表,即一个一个地开辟结点和输入结点数据,并建立起前后相链的关系。1.动态分配和释放函数(1)malloc函数用法:#include<malloc.h>void*malloc(unsignedsize);功能:动态分配一块参数大小的连续的存储空间,并返回空间的首地址。9.5线性链表*(2)free函数用法:#include<malloc.h>voidfree(void*ptr);功能:释放指针变量ptr指向的空间块,交还给系统。9.5线性链表*2.动态链表的建立对于N个结点链表的创建,关键是处理第一个结点时,必须使头指针(head)指向,而后的结点完全类似于上面第二个结点一样,链接到链表的最后结点,并注意使链表的最后结点的指针域赋空(NULL)。注意在链接过程中,必须设定一指针变量(如例图9.-19中的pEnd)指向链表的最后结点,便于链接添加结点。如图9-19所示。9.5线性链表*9.5.4链表输出链表输出也称遍历,就是根据已给的链表头指针,按由前向后的顺序依此访问链表的各个结点。因为链表最重要的就是头指针和结束标志。所以,只要定义一个指针变量pTemp指向第一个结点,输出所指结点数据,然后使pTemp后移一个结点,再输出。直到链表的尾结点。9.6指针与函数9.6.1函数的返回值是指针一个函数不仅可以返回int型、float型、char型和结构类型等数据类型,也可以返回指针类型的数据。返回指针类型的函数定义格式为:类型名*函数名([参数表]){函数体;}例如:int*func(){int*p;.../*省略的其它操作语句*/return(p);}func()函数的返回值是一个指向整型变量的指针9.6指针与函数9.6.2指向函数的指针1.指向函数的指针变量的定义函数在内存中也占据一定的存储空间并有一个入口地址(函数开始运行的地址),这个地址就称为该函数的指针。可以用一个指针变量来存放函数的入口地址,这时称该指针指向这个函数,并称该指针变量为“指向函数的指针变量”,简称为“函数的指针变量”或“函数指针”,可以通过函数指针来调用函数,这是函数指针的主要用途。函数指针定义的一般形式为:类型标识符(*指针变量名)();9.6指针与函数2.指向函数的指针变量的赋值与其它指针的定义一样,函数指针定义后,应给它赋一个函数的入口地址,即使它指向一个函数,才能使用这个指针。C语言中,函数名代表该函数的入口地址。因此,可用函数名给指向函数的指针变量赋值:指向函数的指针变量=函数名;3.用函数指针变量调用函数通过函数指针来调用函数的一般格式是:(*函数指针)(实参表)9.6指针与函数9.6.3指向函数的指针作函数参数假设函数A在运行过程中要根据不同情况多次调用下列函数:B、C、D和E之一来协助它进行处理,按照以往的做法是,用条件选择语句将上述函数写在函数A的函数体中的多个位置上,这是一种笨拙的、灵活性较差的处理方法。现在,可以通过使用函数指针作为函数参数的方式向函数A传递其它函数的入口地址,从而灵活地调用其他函数。9.6指针与函数9.6.3指向函数的指针作函数参数【例9.23】输入两个整数,利用指向函数的指针分别指向求最大值、最小值及和数三个函数来实现运算。算法设计:(1)编写一个求两数最大值、最小值、及和数的通用函数,其形式参数为:参加运算的两个数和指向函数的指针变量;(2)编写求最大值、最小值、及和数的三个功能函数。(3)在主函数中,输入两个整数,分别用求最大值、最小值、及和数的函数名作为函数实参,调用通用函数。9.6指针与函数9.6.3指向函数的指针作函数参数intfnMax(int,int);intfnMin(int,int);intfnAdd(int,int);voidfnProcess(int,int,int(*fun)(int,int));voidmain(){ intx,y; printf("请输入两个整数x和y:"); scanf("%d%d",&x,&y); printf("Max="); fnProcess(x,y,fnMax); printf("Min="); fnProcess(x,y,fnMin); printf("Sum="); fnProcess(x,y,fnAdd); }voidfnProcess(intx,inty,int(*fun)(int,int)){ intresult; result=(*fun)(x,y); printf("%d\n",result);}intfnMax(intx,inty){ intz; if(x>y)z=x; elsez=y; returnz;}intfnMin(intx,inty){ intz; if(x<y)z=x; elsez=y; returnz;}intfnAdd(intx,inty){ intz; z=x+y; returnz;}9.7综合应用案例9-1函数多值带回
当指针作为函数的参数时,函数体内对形参的改变,影响实参的改变,这一特性可实现函数多值带回,从而弥补了函数单值返回缺陷。
在窗体上输入10个整数,求其平均值、最大值和最小值,并显示在窗体上,程序运行结果如图9-18所示。图9-18函数多值带回
案例9-1函数多值带回【算法设计】(1)自定义函数fnMut(),返回平均值,
带回最大值和最小值。floatfnMut(int*a,intnum,int*max,int*min){inti;intsum=a[0];*max=a[0];*min=a[0
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年生产销售部培训计划方案
- 2026年民航安全质量管理体系标准
- 2026年国庆节加油站活动策划方案
- 2026年小学生教学研究与实践
- 2026年户外亲子活动策划流程及方案
- 2026年过年超市活动策划书
- 输电线路杆项目工程项目投资估算范文
- 2026关于医院面试题及答案
- 兽医微生物学复习题+答案
- 首都医科大学内科题库-内分泌
- 2026年湖北武汉警务辅助人员招聘考试试卷-含答案解析
- 2026工会社会化工作者综合能力测试题库及答案
- DB32∕ 3962-2020 绿色建筑设计标准
- (完整版)输变电国网缺陷库
- 设计思维与表现课件
- DG型高压锅炉给水泵安装使用说明书
- 肾部分切除术患者的护理查房
- SVW大众FormelQ之QPNTP审核说明
- 万科集团公司工程管理手册
- GB/T 17285-2022电气设备电源特性的标记安全要求
- GB/T 7512-2006液化石油气瓶阀
评论
0/150
提交评论