项目三学生成绩管理系统.docx
《项目三学生成绩管理系统.docx》由会员分享,可在线阅读,更多相关《项目三学生成绩管理系统.docx(27页珍藏版)》请在冰豆网上搜索。
![项目三学生成绩管理系统.docx](https://file1.bdocx.com/fileroot1/2023-2/4/952c5578-481e-4ae1-9533-74112f9dd905/952c5578-481e-4ae1-9533-74112f9dd9051.gif)
项目三学生成绩管理系统
项目三学生成绩管理系统
引导文(指针与链表):
一、指针
指针是C语言的灵魂,也是C语言学习的难点之一。
通过指针的使用,充分体现了C语言的灵活方便、功能强大这些特点。
因此学习指针是C语言学习中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志。
1.指针与指针变量
指针就是数据存放单元的首地址。
如:
intiNum=37;
2007,则2006就是变量iNum的指针。
2006
2007
37
指针变量就是用来存放指针的变量。
2.指针变量的定义
指针变量的基类型*指针变量名;
如:
int*piPoint_1;//定义了一个指向int型的指针变量piPoint_1
float*pfPoint_2;//定义了一个指向float型的指针变量pfPoint_2
char*pcPoint_3;//定义了一个指向char型的指针变量pcPoint_3
structstu*psPoint_4;//定义了一个指向结构体类型structstu型的指针变量psPoint_4
“*”表示定义的是指针变量。
3.指针变量的指向
指针变量定义后,只能指向同类型的变量。
设定义如下的变量:
intiNum=9;
floatfScore=34.6;
执行下列语句后:
PiPoint_1=&iNum;
pfPoint_2=&fScore;
则建立了指针变量和对应类型变量的指向关系,如下图所示。
其中指针变量piPoint_1中存放的是变量iNum的地址(指针),pfPoint_2中存放的是变量fScore的地址(指针)。
4.通过指针访问变量的值
直接访问:
通过变量名访问其值的方式,称为直接访问。
间接访问:
通过指针对变量的值进行访问的方式,称为间接访问。
指针运算符“*”:
是一个单目运算符,右结合,运算对象是指针变量。
如:
printf(“%d\n”,iNum);和
printf(“%d\n”,*piPoint_1);
是等价的。
也就是说在使用时,原来是变量名的地方可以替换成*指针变量。
举例如下:
#include
main()
{
intiVar_a
floatfVar_b;
int*pointer_1;
float*pointer_2;
iVar_a=100;
fVar_b=10.8;
pointer_1=&iVar_a;//建立指针和变量的对应关系
pointer_2=&fVar_b;
printf("%d,%f\n",iVar_a,fVar_b);//输出变量的值
printf("%d,%f\n",*pointer_1,*pointer_2);//通过指针输出变量的值
*pointer_1=99 ;//通过指针对变量的值进行修改
*pointer_2=-134.78 ;
printf("%d,%f\n",iVar_a,fVar_b);
printf("%d,%f\n",*pointer_1,*pointer_2);
}
上述程序的输出结果是:
100,10.8000000
100,10.8000000
99,-134.780000
99,-134.780000
5.指针与数组
指针与数组的关系密切,一旦定义了数组,数组名就代表数组的首地址。
(1)指针与一维数组
intiArray[14]={0,1,2,3,4,5,6,7,8,9,10,11,12,13};
int*piPoint;
piPoint=iArray;//将数组的首地址赋给指针变量,则建立了右图所示的数组与指针的关系。
指针的加减运算
指针变量的值可以加减一个整数。
如右图执行:
piPoint=pPpoint+1;语句后,piPoint将指向数组的iArray[1]元素。
其实指针变量加减一个整数,并不是加减一个整数本身,而是加减这个整数*指针基类型的宽度。
如上述加1实际是加上1*2。
指针的比较运算
两个指针变量可以进行比较运算。
如:
iArray>piPoint,iArray==piPoint等比较运算。
下面通过对程序运行结果分析,可以看到建立了指针与数组的关系后,对数组元素的访问就灵活了。
intiArray[14]={0,1,2,3,4,5,6,7,8,9,10,11,12,13};
int*piPoint;
intiRep;
piPoint=iArray;//将数组的首地址赋给指针变量
for(iRep=0;iRep<14;iRep++)//通过指针变量的运算来访问数组,但piPoint的值不变
{
printf(“%d”,*(piPoint+iRep));
}
for(iRep=0;iRep<14;iRep++)//
{
printf(“%d”,*(iArray+iRep));//通过数组名来访问数组
}
for(iRep=0;iRep<14;iRep++)//
{
printf(“%d”,*(piPoint++));//通过指针变量的运算来访问数组,piPoint的值在改变
}
for(;piPoint{
printf(“%d”,*piPoint);//通过指针变量的运算来访问数组,并且运用piPoint的值来作为循环的条件
}
上述程序的四种循环的结果都是输出数组iArray每个元素的值,但是访问的方式不同,但都属于间接访问。
注:
●iArray数组名只代表数组的首地址,可以把它看成是一个符号常量。
●iArray的值与&iArray[0]相同。
(2)指针与二维数组
指针与二维数组的关系相对复杂一些。
如定义下述一个二维数组:
intiStudent[3][10];
则内在分配如右图所示,按行进行排列。
iStudent[0][0]
iStudent[0][1]
……
iStudent[1][0]
iStudent[1][1]
……
iStudent[2]9]
对数组元素的访问可以有以下几种形式:
如对iStuent[1][2]的访问:
①iStudent[1][2]
②*(iStudent[1]+1)
③*(*(iStudent+1)+1)
后两种方式是这样理解,二维数组可以看成是一维数组的数组,所以iStudent[1]表示是一维数组名,而*(iStudent+1)也表示是一维数组的首地址。
因此对于二维数组,可以有两种指针对其进行访问。
一种是前面介绍的简单指针,如:
int*piP;
piP=&iStudent[0][0];
则每个元素的访问可以通过piP进行访问。
这个访问同一维数组。
另一种是定义一个指向一维数组的指针变量。
如:
int(*piPs)[10];
piPs=iStudent;
则piPs和iStudent的概念相同,当然piPs是变量,而iStudent是常量。
因此有个这样的指针变量后,对数组元素的访问可以有如下几种形式:
①piPs[1][2]
②*(piPs[1]+1)
③*(*(piPs+1)+1)
分析下列程序的输出结果:
intiStudent[3][10];
int*piP;
int(*piPs)[10];
intiRep_1,iRep_2;
piP=&iStudent[0][0];
piPs=iStudent;
for(iRep_1=0;iRep<3;iRep++)
for(iRep_2;iRep_2<10;iRep_2++)
iStudent[iRep_1][iRep_2]=iRep_1*10+iRep_2;
for(;piPprintf(“%d”,*piP);
printf(“\n”);
for(iRep_1=0;iRep<3;iRep++)
for(iRep_2;iRep_2<10;iRep_2++)
printf(“%d”,*(*(piPs+iRep_1)+iRep_2));
printf(“\n”);
for(iRep_1=0;iRep<3;iRep++,piPs++)
for(iRep_2;iRep_2<10;iRep_2++)
printf(“%d”,*(*(piPs)+iRep_2));
printf(“\n”);
上述三个for循环都是输出二维数组元素的值,第一个for循环是采用简单的指针变量进行访问的,第二个for循环采用指向一维数组的指针变量进行访问,其间没有改变指针变量piPs的值,第三个for循环也是采用指向一维数组的指针变量进行访问,但是指针变量piPs的值进行了改变。
注意:
piPs加1,实际上是加了1*每行的元素个数*每个元素类型的宽度。
其实就是一个行指针。
6.指针与函数
函数的形参不仅可以是整型、实型、字符型等类型,还可以是指针类型。
当形参是指针类型时,当然也要求实参必须是指针类型。
请分析下列程序的输出结果。
voidswap(int*piPx1,int*piPx1)
{
intiTemp;
iTemp=*piPx1;
*piPx1=*piPx2;
*piPx2=iTemp;
}
main()
{
intiNum_a=15,iNum_b=-10;
int*piP_a,*piP_b;
piP_a=&iNum_a;
piP_b=&iNum_b;
printf(“Num_a=%d,Num_b=%d\n”,iNum_a,iNum_b);
swap(piP_a,piP_b);
printf(“Num_a=%d,Num_b=%d\n”,iNum_a,iNum_b);
}
输出结果如下:
Num_a=15,Num_b=-10
Num_a=-10,Num_b=15
调用过程分析如下图所示:
调用时,将指针变量的值传递给相应的形式参数变量,所以形参变量就指向了主函数中相关变量的地址,因此在swap函数中进行的交换就是对相应地址单元的值进行了交换。
由此可见,用指针变量作函数的形式参数时,由于传递的是指针(地址),所以在被调函数中操作的单元实际上是主调函数中的相关单元。
对于函数调用一定注意是将实际参数的值传递给形式参数变量。
实际应用中,函数的形式参数经常会是数组,因此传递的也是指针,所以在被调函数中实际操作的空间与主调函数中数组是同一空间。
请分析下列程序执行的结果:
voidinverse(intdata[],intiCount)
{
inttemp,iRep;
for(iRep=0;iRep{
temp=data[iRep];
data[iRep]=data[iCount-1-iRep];
data[iCount-1-iRep=temp;
}
}
main()
{
intiArray[14]={0,1,2,3,4,5,6,7,8,9,10,11,12,13};
intiRep;
for(iRep=0;iRep<14;iRep++)
{
printf(“%d”,iArry[iRep]);
}
inverse(iArray,14);
for(iRep=0;iRep<14;iRep++)
{
printf(“%d”,iArry[iRep]);
}
}
输出结果如下:
012345678910111213
13121110987654321
结果分析如下图所示:
iArray[0]
0
iArray[0]
0
data[0]
1
1
2
2
……
……
iArray[13]
13
iArray[13]
13
data[13]
调用之前
参数传递后
iArray[0]
13
data[0]
12
11
……
iArray[13]
0
data[13]
调用返回后
二、链表
在前面的通讯录管理系统中我们使用的是数组来存放每条记录的信息。
C语言规定数组必须先定义后使用,一旦定义了数组的长度后,就不可以更改其长度,也不是说我们的通讯录能存储的记录数就确定了,要多存储记录是不可能的。
这就限制了我们实际使用的灵活性。
并且数组的长度固定了,如果用不完也会出现浪费的现象。
实际使用中,我们希望能够根据我们的需要,即时申请存储空间,用多少申请多少,用完了及时归还给操作系统。
于是人们设计了一种新的存储结构(数据结构)——链表。
链表是一种常用的、能够实现动态存储分配的数据结构。
其结构形式如下图所示。
链表由多个结点组成,每个结点包含数据域和指针域两部分。
1.单向链表的基本概念
(1)链表由多个结点组成;
(2)每个结点由数据域和指针域构成;
(3)每个链表的有一个头结点和一个尾结点;
(4)每个链表有一个头指针;
(5)尾结点的指针域NULL。
单向链表有点象过去的特工们的联系一样,只是单线联系,只有上级找下级,下级是不知道上级的。
2.单向链表结点的结构体类型定义
根据前面的要求,每个结点需要包含数据域和指针域,因此每个结点的结构体定义如下:
structnode
{
intiNum;
charcName[20];
floatfScore;
structnode*pNext;//指针域,指向结点的指针
};
此结点的结构如下:
3.单向链表的建立
建立一个如下图所示,由三个结点组成的单向链表。
应用程序设计三步曲分析如下:
(1)输入(形参):
空;
(2)输出(返回值):
链表的头指针;
(3)算法:
A1:
申请结点并输入相应数据域的值;
A1.1:
申请一个新结点;
A1.2:
输入数据域的值;
A1.3:
将指针域置为NULL;
A1.4:
判断是否为第一个结点;
A1.4.1:
是的,则让关指针指向当前结点;
A1.4.2:
不是,则当前结点加入到链表的尾部;
A2:
三个结点输入完成,结束转A3,否则转A1;
A3:
返回头指针。
存储空间申请与释放函数简介:
(1)空间申请函数——malloc()
原形:
void*malloc(unsignedintsize)
功能:
向操作系统申请size个字节的连续存储空间,并返回所申请空间的首地址。
例:
malloc(5);表示向操作系统申请个字节的连续存储空间,并返回这个空间的首地址(指针)。
(2)内在空间释放函数——free()
原形:
voidfree(void*p)
功能:
向内存释放p所指向的存储空间
(3)sizeof()运算符
计算某个对象的长度,以字节为单位。
(4)指针指向运算符:
->
通过指针访问数据域和指针域。
如p->iNum,p->pNext等。
structnode*creat()
{
structnode*pHead=NULL,*pLast,*p;
intiRep;
for(iRep=0;iRep<3;iRep++)
{
p=(structnode*)malloc(sizeof(structnode));//申请新的结点
scanf(“%d”,&p->iNum);//输入结点数据域的值
scanf(“%s”,p->cName);
scanf(“%f:
&p->fScore);
p->next=NULL;//设置指针域为空
if(pHead==NULL)//头结点的判断
pHead=p;
else
pLast->pNext=p;
pLast=p;
}
return(pHead);
}
4.链表数据域值的输出(链表的遍历)
输出一个单向链表所有结点数据域的值。
用三步曲分析如下:
输入(形参):
链表的头指针;
输出(返回值):
空;
算法:
依次输出一个单向链表每一个结点数据域的值,直到尾结点为止
A1:
输出当前一个结点的值;
A2:
判断是否是尾结点,如果是,则转A3,否则指向下一结点,并转A1;
A3:
返回。
voidoutput(structnode*pH)
{
structnode*p;
p=pH;
while(p)//条件等价于p!
=NULL
{
printf(“%d”,p->iNum);
printf(“%s”,p->cName);
printf(“%f\n”,p->fScore);
p=p->pNext;
}
return;
}
5.结点的删除操作
删除链表中符合条件的某个结点,删除操作过程如下图所示。
用三步曲分析如下:
输入(形参):
链表的头指针和要删除的姓名;
输出(返回值):
新链表的头指针;
算法:
A1:
查找要删除的结点;
A2:
从链表中删除结点;
A3:
释放删除结点所占的存储单元;
A4:
返回新链表的头指针。
structnode*del(structnode*pH,charcName[])
{
structnode*pHead_new,*p1,*p2;
pHead_new=pH;
if(strcmp(pHead_new->cName,cName)==0)
{
pHead_new=pH->pNext;
free(pH);
return(pHead_new);
}
p1=pH;
p2=pH->next;
while(p2)
{
if((strcmp(p2->cName,cName)==0)
{
p1->pNext=p2->pNext;
free(p2);
}
else
{
p1=p2;
p2=p2->pNext;
}
}
return(pHead_new);
}
6.结点的插入操作
要求在某个结点之后插入一个新的结点,其操作过程如下图所示。
用三步曲分析如下:
输入(形参):
链表的头指针;
输出(返回值):
新链表的头指针;
算法:
A1:
申请新结点;
A2:
输入新结点的相关数据信息;
A3:
输入要插入结点的姓名;
A4:
查找要插入的位置;
A5:
插入新结点;
A6:
返回新链表的头指针。
structnode*insert(structnode*pH)
{
structnode*pHead_new,*p2,*p;
charcName[20];
pHead_new=pH;
p2=pH->pNext;
p=(structnode*)malloc(sizeof(structnode));
scanf(“%d”,&p->iNum);
scanf(“%s”,p->cName);
scanf(“%f”,&p->fScore);
scanf(“%s”,cName);
if(strcmp(cName,pHead_new->cName)==0)
{
p->pNext=pHead_new->pNext;
pHead_new->next=p;
return(pHead_new);
}
while(p2)
{
if(strcmp(cName,p2->cName)==0)
{
p->pNext=p2->pNext;
p2->pNext=p;
}
p2=p2->next;;
}
return(pHead_new);
}
7.总结:
(1)链表的基本操作
链表的基本操作有创建、遍历、插入和删除,通过基本操作的组合可以实现多种复杂的操作,如排序、查找等。
(2)单向链表始终是从头指针开始,也就是说从头结点开始,依次对后继结点进行操作,直到指针为NULL为止。
三、局部变量和全局变量
1.局部变量是指在函数内部或复合语句内定义的变量,其有效范围只在本函数或本复合语句内。
如:
floatfun_1(intiVar_1)
{
floatfVar;
……
}
main()
{
intiNum_1;
charcStr;
……
}
(1)形式参数也是局部变量,如函数fun中的iVar_1;
(2)iNum_1、cStr是属于main函数的局部变量,fVar、iVar_1是属于函数fun的局部变量,他们的有效范围分别在各自定义的函数内;
(3)不同函数内可以定义同名的变量,他们代表不同的对象,有效范围也不同,互不干扰。
2.全局变量(外部变量)是在函数之外定义的变量,其有效范围是从其定义之后直到本源程序文件结束。
如:
floatfSum;//全局变量
floatfun_1(intiVar_1)
{
floatfVar;
……
}
intiData;//全局变量
main()
{
intiNum_1;
charcStr;
……
}
变量fSum从定义之后的任何地方以使用,变量iData则从定义之后的任何地方可以使用,上述程序中,iData则可以在main函数中使用,fSum则可以在main和fun_1中使用。
(1)通过全局变量可以增强函数之间的关联。
函数之间通过全局变量进行值的传递,但是全局变量如果用不好,则也会造成使用的混乱。
所以全局变量一般不在必要的时候尽量不要使用。
(2)全局变量降低是程序的通用性。
(3)全局变量使用过多会赞成程序难于阅读与理解。
(4)在同一源程序文件中,如果全局变量和局部变量同名,则在局部变量有效范围内,全局变量被“屏蔽”。
四、变量的存储类型
1.动态与静态存储方式
(1)静态存储方式是指在程序运行期间分配固定的存储空间的方式。
如数组、全局变量就是属于静态存储方式。
(2)动态存储方式是指在程序运行期间根据需要动态地分配存储空间的方式。
如需要时通过malloc函数来进行空间的申请。
存储方式具体包含:
自动的(auto)、静态的(static)、寄存器的(register)、外部的(exter)四种。
自动(auto)变量:
局部变量如不特别说明,都是auto存储类别,属于动态存储类型,如不给定初值,则其初值不确定。
如:
voidfun()
{
autointiData;
}
中,“autointiData;”语句与“intiData;”等价。
静态(static)变量:
在局部变量定义时,加上static之后,则变量即为静态局部变量。
其作用是:
默认变量的初值为0,并且变量的值在函数调用结束后仍然保留,即所占用的存储单元不释放,下次调用函数时,该变量的值就是上次调用结束时的值。
如:
intfun(intiData_a)
{
intiData_b=0;
staticiData_c=3;
iData_b=iData_b+1;
iData_c=iData_c+1;
return(iData_a+iData_b+iData_c);
}
main()
{
intiData=2,iRep