C语言的指针、链表的原理及学生管理系统
指针原理:
创新互联建站主要从事网站设计制作、成都网站制作、网页设计、企业做网站、公司建网站等业务。立足成都服务大通,十余年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:028-86922220
指针就是指向一个特定内存地址的一个变量。简化了的内存空间模型是按照从0到某一个数(比如1048575=1M-1)的一维线性空间,其中的每一个数对应一个存储单元,即1个字节。指针有两个属性:指向性和偏移性。指向性指的是指针一定要有一个确定的指向,偏移性则是体现指针重要应用的方面,即指针可以按程序员的要求向前或向后偏移。
指针的应用往往与数组联系在一起,为了方便说明问题,不妨从数组开始解释指针的偏移。数组就是许多的变量,它的一个重要特征就是在内存空间中连续地存放,而且是按下标顺序存放。比如我们定义一个有100个变量的一维整型数组,它一定从内存的某一个存储单元开始按数组下标顺序存放,连续占用100*4=400字节。当我们定义一个数组时,系统就会自动为它分配一个指针,这个指针指向数组的首地址。(在本文剩余部分的论述中,不加区分地使用“指向数组的首地址”与“指向数组的第一个元素”这两种说法,事实上这两种说法也是一致的。)
为了让系统了解每一次指针偏移的单位,也为了方便程序员进行指针偏移(让程序员记住一个×××变量占用4字节,一个字符型变量占用1字节等等是很麻烦的),不用每次去计算要偏移多少个字节,C语言引入了指针的基类型的概念。基类型的作用就是让系统了解某个指针每次偏移的字节数。比如,对于一个字符型指针,它每次偏移(比如ptr=ptr+1)所起到的作用就是让指针偏移1字节;而对于一个整型指针,它每次偏移就应该是4字节。这样操作数组时就带来了方便。比如对于一个指向某个整型数组起始存储单元(称为首地址)的指针ptr,ptr=ptr+1就表示将该指针指向这个数组的下一个元素的存储单元,即向后移动4字节,而不仅仅是移动一个存储单元(即移动1字节)。
&()、*()、和[ ]运算符的意义
在本文中,将&()、*()和[ ]都看成是运算符。这样可以方便理解这三个概念。简单地说,&()将某个标识符(比如变量)转化为其在内存空间中的地址,而*()是产生一个对应于某个地址的标识符,[ ]就更复杂一点,ptr[i]表示将ptr这个指针虚拟地按其基类型进行i个单位的后移,再进行*(ptr)运算。但这是一个虚拟的后移,即ptr[i]并不改变ptr的指向,只是将其后移i个单位并取*()运算的结果算出来了而已。要改变指针的指向,我们只能通过类似于ptr=ptr+i这样的语句来实现。
实际中,我们往往不愿意经常改变指针的指向,因为指针的移动虽然是自由的,但移动后往往会“移不回来”,因为我们可能无法清楚地确定指针的偏移量。后面我们将看到,对于用指针来表示的数组,其元素的引用和赋值是完全可以不用改变指向这个数组的首地址的指针指向的,而一旦要改变这个指针的指向,问题就会变得复杂一些。
指针类型和系统自动分配的指针
指针可以指向几乎所有我们感兴趣的程序设计要素:函数、数组、结构体、链表节点等等。其中不同函数间往往并不存在严格的线性关系。链表节点可以根据算法需要在逻辑上(或物理上)不按线性连续存储。但数组、结构体的共同特征就是它们在物理上都是线性连续存储的。只要指针指向了它们的首地址,就可以通过简单的偏移来访问各个它们的元素。指针的偏移性在这两种数据结构中发挥着至关重要的作用。这时,我们再回想基类型的定义目的,就会有更深层次的认识了。对于一个数组或结构体,它的基类型长度应当是其元素的长度(这里的长度即指在内存空间中占用的字节数),而不再限于定义为某种简单数据类型的长度。
在我们定义数组和函数时,系统都会为其自动分配一个指向其首地址的指针。其中,指针在数组中的应用是最频繁的,也是最基础的。对于一个数组,其名称就是一个指针变量,亦即假如我们定义“int a[10];”的同时就定义了“int *a=a;”(这只是为了说明问题,这样的语句显然是不合法的)。
数组应用中典型的二级指针
设定一个指向指针的指针,即设定一个二级指针。一般认为,指针不宜超过二级,否则会大大增加逻辑错误出现的可能性。
链表原理:
举个例子,跳马,依下图将每一步跳马之后的位置之后的位置(x,y)放到一个“结点”里,再用“链子穿起来”,形成一条链,相邻两结点间用一个指针将两者连到一起。
为了表示这种既有数据又有指针的情况,引入结构这种数据类型。
依上图有7个结点。
用指针处理链表
链表是程序设计中一种重要的动态数据结构,它是动态地进行存储分配的一种结构。
动态性体现为:
链表中的元素个数可以根据需要增加和减少,不像数组,在声明之后就固定不变;
元素的位置可以变化,即可以从某个位置删除,然后在插入到一个新的地方;
结点里的指针是存放下一个结点的地址
链表中的元素称为“结点”,每个结点包括两个域:数据域和指针域;
单向链表通常由一个头指针(head),用于指向链表头;
单向链表有一个结尾点,该结点的指针部分指向一个空结点(NULL)。
学生信息管理系统代码:
/* *学生信息管理程序, *管理学生的个人信息及各科成绩; */ #include#include #include #include typedef struct NodeNode; //定义成绩信息节点 //分别为语文、数学、英语和总成绩; struct Score { int chinese,math,english,sum; }; //定义学生信息节点 //分别为姓名、班级、学号、成绩和指向下一个节点的指针 //定义了4个全局变量,头节点,和临时节点变量; struct Node { char name[20],classs[20],number[20]; struct Score score; struct Node* next; }*head,*u,*p,*q; //定义多个学生的学生个数及各科平均成绩优秀率及格率; intn,C,M,E,Cj,Cy,Mj,My,Ej,Ey; char num[20]; //进入菜单函数 void Welcome() { printf("\t\t # # # # # # # # # # # # # # # ##\n"); printf("\t\t # 欢迎您使用学生成绩管理系统 #\n"); printf("\t\t # #\n"); printf("\t\t # 1.读取文件 #\n"); printf("\t\t # #\n"); printf("\t\t # 2.保存文件 #\n"); printf("\t\t # #\n"); printf("\t\t # 3.添加学生成绩 #\n"); printf("\t\t # #\n"); printf("\t\t # 4.修改学生成绩 #\n"); printf("\t\t # #\n"); printf("\t\t # 5.删除学生成绩 #\n"); printf("\t\t # #\n"); printf("\t\t # 6.查询个人成绩 #\n"); printf("\t\t # #\n"); printf("\t\t # 7.查询本班成绩 #\n"); printf("\t\t # #\n"); printf("\t\t # 8.查询全校成绩 #\n"); printf("\t\t # #\n"); printf("\t\t # 9.退出管理系统 #\n"); printf("\t\t # #\n"); printf("\t\t # # # # # # # # # # # # # # # ##\n\n"); printf("\t\t 请输入指令:(1-9)"); } //构造节点函数 Node* new_node(Node*uu) { uu = (Node*)malloc(sizeof(Node)); uu->next = NULL; return uu; } //添加学生信息 void Add() { //新建一个节点; u = new_node(u); printf("\n请输入您要加入的学生的信息:\n"); printf("\n姓名: "); scanf("%s",u->name); printf("\n班级: "); scanf("%s",u->classs); printf("\n学号: "); scanf("%s",u->number); printf("\n语文、数学、英语成绩: "); scanf("%d%d%d",&u->score.chinese,&u->score.math,&u->score.english); //计算总成绩; u->score.sum = u->score.chinese +u->score.math + u->score.english; //采用头插法将新节点的尾指针指向第二个节点(掰开) u->next = head->next; //将新节点放在头节点后面; head->next = u; printf("\n--->添加成功!\n"); } //根据学号修改信息 //和查找函数一样,依次从第二个节点开始遍历,如果找到这更新 void Mod() { n = 0; printf("\n请输入您要修改的学号: "); scanf("%s",num); for(u = head; u != NULL;u =u->next) { if(strcmp(u->number,num) == 0) { n = 1; printf("\n请输入新的语文、数学、英语成绩: "); scanf("%d%d%d",&u->score.chinese,&u->score.math,&u->score.english); u->score.sum =u->score.chinese + u->score.math + u->score.english; printf("\n--->修改成功!\n"); break; } } if(!n) printf("\n--->没有这个学生的信息!\n"); } //根据学号删除学生信息, //从头节点开始遍历,如果找到这删除此节点; void Del() { n = 0; printf("\n请输入您要删除的学生的学号: "); scanf("%s",num); for(u = head; u != NULL;u =u->next) { if(strcmp(u->number,num) == 0) { n = 1; p->next = u->next; free(u); printf("\n--->删除成功!\n"); break; } p = u; } if(!n) printf("\n--->没有这个学生的信息!\n"); } void Sort() { int i,j; //记录学生总数; n = 0; for(u = head->next; u != NULL;u =u->next) n++; //采用冒泡法对各个节点按班级升序和总成绩降序排列 for(i=1;i<=n;i++) { u = head; for(j=0;j next; q = p->next; if(strcmp(p->classs,q->classs) > 0 ||strcmp(p->classs,q->classs) == 0 && p->score.sum score.sum) { u->next = q; p->next = q->next; q->next = p; } u = u->next; } } } //按学号查找某一学生成绩; void Que_One() { //标志变量,记录是否查找成功; n = 0; printf("\n请输入您要查询的学生的学号: "); scanf("%s",num); //从第二个节点开始遍历,直到最后一个节点为止; for(u = head->next; u != NULL;u =u->next) { //如果当前节点学号与要查找学号一致这输出此学生信息; if(strcmp(u->number,num) == 0) { n = 1; printf("\n"); puts("班级 姓名 语文 数学 英语总成绩"); printf("%-11s%-15s",u->classs,u->name); printf("%-6d%-6d%-6d%-6d\n",u->score.chinese,u->score.math,u->score.english,u->score.sum); break; } } if(!n) printf("\n--->没有这个学生的信息!\n"); } void Analyze_Sco(Node*uu) { //对查找到的节点进行求各科平均成绩 //求优秀率及格率; C += uu->score.chinese; M += uu->score.math; E += uu->score.english; if(uu->score.chinese >= 60) Cj++; if(uu->score.chinese >= 90) Cy++; if(uu->score.math >= 60) Mj++; if(uu->score.math >= 90) My++; if(uu->score.english >= 60) Ej++; if(uu->score.english >= 90) Ey++; } //打印各科平均成绩及格率优秀率 void Print_Sco() { printf("语文平均成绩: %-6.2f, 及格率: %%%-6.2f , 优秀率:%%%-6.2f.\n\n",(float)C/n,(float)100*Cj/n,(float)100*Cy/n); printf("数学平均成绩: %-6.2f, 及格率: %%%-6.2f , 优秀率:%%%-6.2f.\n\n",(float)M/n,(float)100*Mj/n,(float)100*My/n); printf("英语平均成绩: %-6.2f, 及格率: %%%-6.2f , 优秀率:%%%-6.2f.\n\n",(float)E/n,(float)100*Ej/n,(float)100*Ey/n); } //查找某一班级所以学生的信息; void Que_Cla() { //对链表节点排序; Sort(); n = C = M = E = Cj = Cy = Mj = My = Ej = Ey= 0; printf("\n请输入您要查询的班级: "); scanf("%s",num); printf("\n"); for(u = head->next; u != NULL;u =u->next) { //不是该班的学生则跳过; if(strcmp(u->classs,num)) continue; //如果是第一个学生则打印头信息 if(!n) puts("学号 姓名 语文 数学 英语总成绩"); n++; printf("%-11s%-15s",u->number,u->name); printf("%-6d%-6d%-6d%-d\n",u->score.chinese,u->score.math,u->score.english,u->score.sum); Analyze_Sco(u); } if(!n) { printf("没有这个班级的学生信息!\n"); return ; } //打印该班级学生的各个成绩的特征值; printf("\n该班共有学生 %d 人.\n\n",n); Print_Sco(); } //打印全校所以学生的信息 //具体情况同打印班级学生信息; void Que_All() { Sort(); n = C = M = E = Cj = Cy = Mj = My = Ej = Ey= 0; printf("\n"); if(head->next == NULL) { printf("--->没有学生信息!\n"); return ; } puts("班级 学号 姓名 语文 数学 英语总成绩"); for(u = head->next; u != NULL;u =u->next) { n++; printf("%-12s%-12s%-15s",u->classs,u->number,u->name); printf("%-6d%-6d%-6d%-d\n",u->score.chinese,u->score.math,u->score.english,u->score.sum); Analyze_Sco(u); } printf("\n全校共有学生 %d 人.\n\n",n); Print_Sco(); } //保存文件; void Save() { char c; printf("\n确认保存?(Y/N): "); scanf("%*c%c",&c); if(c == 'N') return ; FILE *fp; if((fp=fopen("C:\\data.txt","w"))==NULL) { printf("\n--->无法打开文件\n"); return ; } //写入数据表头信息; fputs("班级 学号 姓名 语文 数学 英语总成绩",fp); if(head->next != NULL) fputs("\n",fp); //从头节点开始依次写入文件; for(u = head->next; u != NULL;u = u->next) { fprintf(fp,"%-11s%-11s%-15s",u->classs,u->number,u->name); fprintf(fp,"%-6d%-6d%-6d%-d",u->score.chinese,u->score.math,u->score.english,u->score.sum); if(u->next != NULL) fprintf(fp,"\n"); } fclose(fp); printf("\n--->成绩成功存入C:\\\\data.txt中\n"); } //读取文件; void Open() { printf("\n请把数据放到目录C:\\\\data.txt中,按任意键确认.\n"); getch(); FILE *fp; //从c盘根目录下读取文件; if((fp=fopen("C:\\data.txt","r"))==NULL) { printf("\n--->没有找到文件!\n"); return ; } char tmp[100]; //读取65个菜单头字符存入tem字符数组中; fgets(tmp,66,fp); //读到文件结尾处跳出循环; while(!feof(fp)) { u = new_node(u); fscanf(fp,"%s%s%s",u->classs,u->number,u->name); fscanf(fp,"%d%d%d%d",&u->score.chinese,&u->score.math,&u->score.english,&u->score.sum); //头插法建立链表; u->next = head->next; head->next = u; } printf("\n--->成绩读入成功!\n"); fclose(fp); } //退出程序 void Exi() { char c; printf("\n确定退出?(Y/N): "); scanf("%*c%c",&c); if(c == 'N') return ; //打印结束语; system("cls"); printf("\n\n"); printf("\t\t\t %c %c %c %c %c %c %c %c%c\n",4,4,4,4,4,4,4,4,4); printf("\t\t\t %c 谢谢使用%c\n",4,4); printf("\t\t\t %c %c %c %c %c %c %c %c%c\n",4,4,4,4,4,4,4,4,4); printf("\t\t\t Thankyou!\n\n\n"); exit(0); } int main() { //存储指令的变量 int orz; //设置系统文本颜色 system("color 0B"); //新建一个学生信息头节点; head = new_node(head); while(1) { //显示菜单、 Welcome(); //接收用户命令、 scanf("%d",&orz); //调用系统函数清屏; system("cls"); switch(orz) { //根据指令进入相应菜单选项 case 1:Open();break; case 2:Save();break; case 3:Add();break; case 4:Mod();break; case 5:Del();break; case 6:Que_One();break; case 7:Que_Cla();break; case 8:Que_All();break; case 9:Exi();break; default :printf("\n--->无效的指令!\n"); } printf("\n"); //执行系统函数 system("pause"); system("cls"); } return 0; }
网站名称:C语言的指针、链表的原理及学生管理系统
本文URL:http://scyanting.com/article/jjigoo.html