一、指针及其应用

1、指针、引用和取值

什么是指针?什么是内存地址?什么叫做指针的取值?指针是一个存储计算机内存地址的变量。从指针指向的内存读取数据称作指针的取值。指针可以指向某些具体类型的变量地址,例如intlongdouble。指针也可以是void类型、NULL指针和未初始化指针。

根据出现的位置不同,操作符* 既可以用来声明一个指针变量,也可以用作指针的取值。当用在声明一个变量时,*表示这里声明了一个指针。其它情况用到*表示指针的取值。

&是地址操作符,用来引用一个内存地址。通过在变量名字前使用&操作符,我们可以得到该变量的内存地址。

#include<stdio.h>

int main()

{

   int*ptr;   // 声明一个int指针

   int val =1;  // 声明一个int

   ptr =&val;  // 为指针分配一个int值的引用

   int deref =*ptr;  // 对指针进行取值,打印存储在指针地址中的内容

  printf("deref地址=%ld,=%d\n",ptr,deref);

}

2行,我们通过*操作符声明了一个int指针。接着我们声明了一个int变量并赋值为1。然后我们用int变量的地址初始化我们的int指针。接下来对int指针取值,用变量的内存地址初始化int指针。最终,我们打印输出变量值,内容为1

6行的&val是一个引用。在val变量声明并初始化内存之后,通过在变量名之前使用地址操作符&我们可以直接引用变量的内存地址。

8行,我们再一次使用*操作符来对该指针取值,可直接获得指针指向的内存地址中的数据。由于指针声明的类型是int,所以取到的值是指针指向的内存地址存储的int值。

这里可以把指针、引用和值的关系类比为信封、邮箱地址和房子。一个指针就好像是一个信封,我们可以在上面填写邮寄地址。一个引用(地址)就像是一个邮件地址,它是实际的地址。取值就像是地址对应的房子。我们可以把信封上的地址擦掉,写上另外一个我们想要的地址,但这个行为对房子没有任何影响。

2、指针和数组

C语言的数组表示一段连续的内存空间,用来存储多个特定类型的对象。与之相反,指针用来存储单个内存地址。数组和指针不是同一种结构因此不可以互相转换。而数组变量指向了数组的第一个元素的内存地址。

一个数组变量是一个常量。即使指针变量指向同样的地址或者一个不同的数组,也不能把指针赋值给数组变量。也不可以将一个数组变量赋值给另一个数组。然而,可以把一个数组变量赋值给指针,这一点似乎让人感到费解。把数组变量赋值给指针时,实际上是把指向数组第一个元素的地址赋给指针。

#include<stdio.h>

int main()

{

         int myarray[4] = {1,2,3,0};

    int *ptr = myarray;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

    ptr++;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

    ptr++;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

    ptr++;

    printf("ptr地址=%ld,*ptr=%d\n", ptr,*ptr);

}

 

3、指针与结构体

就像数组一样,指向结构体的指针存储了结构体第一个元素的内存地址。与数组指针一样,结构体的指针必须声明和结构体类型保持一致,或者声明为void类型。

#include<stdio.h>

struct person {

  int age;

  char *name;

};

 

int main()

{

  struct person first;

  struct person *ptr;

  first.age = 21;

  char *fullname = "full name";

  first.name = fullname;

  ptr = &first;

  printf("age=%d, name=%s\n",first.age, ptr->name);

}

16行声明了一个person结构体,一个变量指向了一个person结构体和指向person结构体的指针。第8行为age成员赋了一个int值。第910行我们声明了一个char指针并赋值给一个char数组并赋值给结构体name成员。第11行我们把一个person结构体引用赋值给结构体变量。

13行我们打印了结构体实例的agename。这里需要注意两个不同的符号,’. -> 。结构体实例可以通过使用 . 符号访问age变量。对于结构体实例的指针,我们可以通过 -> 符号访问name变量。也可以同样通过(*ptr).name来访问name变量。

 

第二部分、静态内存分配和动态内存分配

 

一、为什么用动态内存分配

我们未学习链表的时候,如果要存储数量比较多的同类型或同结构的数据的时候,总是使用一个数组。比如说我们要存储一个班级学生的某科分数,总是定义一个float型(存在0.5分)数组:
float score[30];
但是,在使用数组的时候,总有一个问题困扰着我们:数组应该有多大?
在很多的情况下,你并不能确定要使用多大的数组,比如上例,你可能并不知道该班级的学生的人数,那么你就要把数组定义得足够大。这样,你的程序在运行时就申请了固定大小的你认为足够大的内存空间。即使你知道该班级的学生数,但是如果因为某种特殊原因人数有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错误,甚至导致严重后果。
那么有没有其它的方法来解决这样的外呢体呢?有,那就是动态内存分配。
所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。从以上动、静态内存分配比较可以知道动态内存分配相对于静态内存分配的特点:
1
、不需要预先分配存储空间;
2
、分配的空间可以根据程序的需要扩大或缩小。


二、如何实现动态内存分配及其管理

要实现根据程序的需要动态分配存储空间,就必须用到以下几个函数
1
malloc函数
malloc
函数的原型为:
void *malloc (unsigned int size)
其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号×××数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。
下例是一个动态分配的程序:

#include<stdio.h>

#include<stdio.h> 

#include<conio.h> 

#include<string.h> 

#include<stdlib.h>  

main()

{

   int count,*array; /*count是一个计数器,array是一个整型指针,也可以理解为指向一个整型数组的首地址*/

   if((array=(int *)malloc(10*sizeof(int)))==NULL)

   {

     printf("不能成功分配存储空间。");

     exit(1);

   }

   for (count=0;count<10;count++) /*给数组赋值*/

      array[count]=count;

   for(count=0;count<10;count++) /*打印数组元素*/

      printf("%d-",array[count]);

}

上例中动态分配了10个整型存储区域,然后进行赋值并打印。例中if((array=(int *) malloc(10*sizeof(int)))==NULL)语句可以分为以下几步:
1
)分配10个整型的连续存储空间,并返回一个指向其起始地址的整型指针
2
)把此整型指针地址赋给array
3
)检测返回值是否为NULL
2
free函数
由于内存区域总是有限的,不能不限制地分配下去,而且一个程序要尽量节省资源,所以当所分配的内存区域不用时,就要释放它,以便其它的变量或者程序使用。这时我们就要用到free函数。
其函数原型是:
void free(void *p)
作用是释放指针p所指向的内存区。
其参数p必须是先前调用malloc函数或calloc函数(另一个动态分配存储区域的函数)时返回的指针。给free函数传递其它的值很可能造成死机或其它灾难性的后果。
注意:这里重要的是指针的值,而不是用来申请动态内存的指针本身。例:
int *p1,*p2;
p1=malloc(10*sizeof(int));
p2=p1;
……
free(p1) /*
或者free(p2)*/
malloc
返回值赋给p1,又把p1的值赋给p2,所以此时p1p2都可作为free函数的参数。
malloc函数是对存储区域进行分配的。
free
函数是释放已经不用的内存区域的。
所以由这两个函数就可以实现对内存区域进行动态分配并进行简单的管理了。

 

三、单链表的建立

有了动态内存分配的基础,要实现链表就不难了。链表由一系列不必在内存中相连的结构组成。每一个结构均含有表元素和指向包含该元素后继元的结构指针。我们称之为next指针。最后一个单元的next指针指向NULL;该值由C定义并且不能与其它指针混淆。ANSI C规定NULL为零。
指针变量是包含存储另外某个数据的地址的变量。因此,如果P被声明为指向一个结构的指针,那么存储在P中的值就被解释为内存中的一个位置,在该位置能够找到一个结构。该结构的一个域可以通过P->FileName访问,其中FileName是我们要考察的域的名字。如图1所示,这个表含有五个结构,恰好在内存中分配给它们的位置分别是1000,800,712,992和692。第一个结构的指针含有值800,它提供了第二个结构所在的位置。其余每个结构也都有一个指针用于类似的目的。当然,为了访问该表,我们需要知道在哪里能够找到第一个单元。

spacer.gif

                      图1

为了执行打印表PrinList(L)或查找表Find(L,key),只要将一个指针传递到该表的第一个元素,然后用一些Next指针穿越该表即可。
typedef struct node
{
char name[20];
struct node *link;
}stud;
这样就定义了一个单链表的结构,其中char name[20]是一个用来存储姓名的字符型数组,指针*link是一个用来存储其直接后继的指针。
定义好了链表的结构之后,只要在程序运行的时候在数据域中存储适当的数据,如有后继结点,则把链域指向其直接后继,若没有,则置为NULL。下面就来看一个建立带表头(若未说明,以下所指链表均带表头)的单链表的完整程序。

#include<stdio.h> 

#include<conio.h> 

#include<string.h> 

#include<stdlib.h>

#define N 3 /*N为人数*/

typedef struct node

{

   char name[20];

   struct node *link;

}stud;

stud * creat(int n)/*建立单链表的函数,形参n为人数*/

{

   stud *p,*h,*s; /* *h保存表头结点的指针,*p指向当前结点的前一个结点,*s指向当前结点*/

   int i; /*计数器*/

   if((h=(stud *)malloc(sizeof(stud)))==NULL)/*分配空间并检测*/

   {

     printf("不能分配内存空间!");

     exit(0);

   }

   h->name[0]='\0'; /*把表头结点的数据域置空*/

   h->link=NULL; /*把表头结点的链域置空*/

   p=h; /*p指向表头结点*/

   for(i=0;i<N;i++)

   {

     if((s= (stud *)malloc(sizeof(stud)))==NULL) /*分配新存储空间并检测*/

     {

        printf("不能分配内存空间!");

        exit(0);

     }

     p->link=s; /*s的地址赋给p所指向的结点的链域,这样就把ps所指向的结点连接起来了*/

     printf("请输入第%d个人的姓名",i+1);

     scanf("%s",s->name); /*在当前结点s的数据域中存储姓名*/

     s->link=NULL;

     p=s; //要把s的数据保存到p中,要不下次循环s就又指向一个空的新地址了。

   }

   return(h);

}

int main()

{

   int number; /*保存人数的变量*/

   stud *head; /*head是保存单链表的表头结点地址的指针*/

   number=N;

   head=creat(number); /*把所新建的单链表表头地址赋给head*/

}

 

这样就写好了一个可以建立包含N个人姓名的单链表了。
写动态内存分配的程序应注意,请尽量对分配是否成功进行检测。

四、单链表的基本运算
建立了一个单链表之后,如果要进行一些如插入、删除等操作该怎么办?所以还须掌握一些单链表的基本算法,来实现这些操作。单链表的基本运算包括:查找、插入和删除。下面我们就一一介绍这三种基本运算的算法,并结合我们建立单链表的例子写出相应的程序。


1
、查找
对单链表进行查找的思路为:对单链表的结点依次扫描,检测其数据域是否是我们所要查好的值,若是返回该结点的指针,否则返回NULL。
因为在单链表的链域中包含了后继结点的存储地址,所以当我们实现的时候,只要知道该单链表的头指针,即可依次对每个结点的数据域进行检测。
以下是应用查找算法的一个例子:

#include<stdio.h> 

#include<conio.h> 

#include<string.h> 

#include<stdlib.h>

#define N 3

typedef struct node

{

char name[20];

   struct node *link;

}stud;

stud * creat(int n)/*建立链表的函数*/

{

   stud *p,*h,*s;

   int i;

   if((h=(stud *)malloc(sizeof(stud)))==NULL)

   {

      printf("不能分配内存空间!");

      exit(0);

   }

   h->name[0]='\0';

   h->link=NULL;

   p=h;

   for(i=0;i<N;i++)

   {

      if((s= (stud *)malloc(sizeof(stud)))==NULL)

      {

        printf("不能分配内存空间!");

        exit(0);

      }

      p->link=s;

      printf("请输入第%d个人的姓名",i+1);

      scanf("%s",s->name);

      s->link=NULL;

      p=s;

   }

   return(h);

}

 

//以上为原插入函数部分


 number=N;

   head=creat(number);

   printf("请输入你要查找的人的姓名:");

   scanf("%s",fullname);

   searchpoint=search(head,fullname); /*调用查找函数,并把结果赋给searchpoint指针*/

   printf("查找的姓名是:%s\n",searchpoint->name);

}

2、插入(后插)


假设在一个单链表中存在2个连续结点p、q(其中p为q的直接前驱),若我们需要在p、q之间插入一个新结点s,那么我们必须先为s分配空间并赋值,然后使p的链域存储s的地址,s的链域存储q的地址即可。(p->link=s;s->link=q),这样就完成了插入操作。

插入命令需要使用一次malloc调用从系统得到一个新单元并在此后执行两次指针调整。想法通过图3给出,其中虚线表示原来的指针。

spacer.gif

图3

//原创建部分

#include<stdio.h> 

#include<conio.h> 

#include<string.h> 

#include<stdlib.h>

#define N 3

typedef struct node

{

   char name[20];

   struct node *link;

}stud;

stud * creat(int n)/*建立单链表的函数*/

{

   stud *p,*h,*s;

   int i;

   if((h=(stud *)malloc(sizeof(stud)))==NULL)

   {

      printf("不能分配内存空间!");

      exit(0);

   }

   h->name[0]='\0';

   h->link=NULL;

   p=h;

   for(i=0;i<N;i++)

   {

     if((s= (stud *)malloc(sizeof(stud)))==NULL)

     {

        printf("不能分配内存空间!");

        exit(0);

     }

     p->link=s;

     printf("请输入第%d个人的姓名:",i+1);

     scanf("%s",s->name);

     s->link=NULL;

     p=s;

   }

   return(h);

}

 

 

 

//原搜索部分

stud * search(stud*h,char *x) /*查找函数*/

{

   stud *p;

   char *y;

   p=h->link;

   while(p!=NULL)

   {

     y=p->name;

     if(strcmp(y,x)==0)

       return(p);

     else p=p->link;

   }

   if(p==NULL)

     printf("没有查找到该数据!");

}

 

stud * list(stud*h) /*查找函数*/

{

   stud *p;

   char *y;

   p=h->link;

   printf("所有的姓名列表\n");

   while(p!=NULL)

   {

     printf("%s \n",p->name);

          p=p->link;

   }

  

}


main()

{

   int number;

   char fullname[20]; /*保存输入的要查找的人的姓名*/

   stud *head,*searchpoint;

   number=N;

   head=creat(number); /*建立新链表并返回表头指针*/

   printf("请输入你要查找的人的姓名:");

   scanf("%s",fullname);

   searchpoint=search(head,fullname); /*查找并返回查找到的结点指针*/

   insert(searchpoint); /*调用插入函数*/

   list(head);

}

3、删除
假如我们已经知道了要删除的结点p的位置,那么要删除p结点时只要令p结点的前驱结点的链域由存储p结点的地址该为存储p的后继结点的地址,并回收p结点即可。

删除命令可以通过修改一个指针来实现。图2给出在原表中删除第三个元素的结果。

spacer.gif

图2


以下便是应用删除算法的实例:

//原创建部分

#include<stdio.h> 

#include<conio.h> 

#include<string.h> 

#include<stdlib.h>

#define N 3

typedef struct node

{

   char name[20];

   struct node *link;

}stud;

stud * creat(int n)/*建立单链表的函数*/

{

   stud *p,*h,*s;

   int i;

   if((h=(stud *)malloc(sizeof(stud)))==NULL)

   {

      printf("不能分配内存空间!");

      exit(0);

   }

   h->name[0]='\0';

   h->link=NULL;

   p=h;

   for(i=0;i<N;i++)

   {

     if((s= (stud *)malloc(sizeof(stud)))==NULL)

     {

        printf("不能分配内存空间!");

        exit(0);

     }

     p->link=s;

     printf("请输入第%d个人的姓名:",i+1);

     scanf("%s",s->name);

     s->link=NULL;

     p=s;

   }

   return(h);

}

 

   char *y;

   p=h->link;

   printf("所有的姓名列表\n");

   while(p!=NULL)

   {

     printf("%s \n",p->name);

          p=p->link;

   }

  

}

 

//新增加的搜索部分和删除部分

stud * search2(stud*h,char *x)

/*另一个查找函数,返回的是上一个查找函数的直接前驱结点的指针,

h为表头指针,x为指向要查找的姓名的指针

其实此函数的算法与上面的查找算法是一样的,只是多了一个指针s,并且s总是指向指针p所指向的结点的直接前驱,

结果返回s即是要查找的结点的前一个结点*/

{

   stud *p,*s;

   char *y;

   p=h->link;

   s=h;

   while(p!=NULL)

   {

     y=p->name;

     if(strcmp(y,x)==0)

       return(s);

     else

     {

       p=p->link;

       s=s->link;

     }

   }

   if(p==NULL)

    printf("没有查找到该数据!");

}

void del(stud*x,stud *y) /*删除函数,其中y为要删除的结点的指针,x为要删除的结点的前一个结点的指针*/

{

  stud *s;

  s=y;

  x->link=y->link;

  free(s);

}

 

//新增加的搜索部分和删除部分

stud * search2(stud*h,char *x)

/*另一个查找函数,返回的是上一个查找函数的直接前驱结点的指针,

h为表头指针,x为指向要查找的姓名的指针

其实此函数的算法与上面的查找算法是一样的,只是多了一个指针s,并且s总是指向指针p所指向的结点的直接前驱,

结果返回s即是要查找的结点的前一个结点*/

{

   stud *p,*s;

   char *y;

   p=h->link;

   s=h;

   while(p!=NULL)

   {

     y=p->name;

     if(strcmp(y,x)==0)

       return(s);

     else

     {

       p=p->link;

       s=s->link;

     }

   }

   if(p==NULL)

    printf("没有查找到该数据!");

}

void del(stud*x,stud *y) /*删除函数,其中y为要删除的结点的指针,x为要删除的结点的前一个结点的指针*/

{

  stud *s;

  s=y;

  x->link=y->link;

  free(s);

}

 

第三部分、完整的学生姓名管理系统

#include <stdio.h> 

#include <conio.h> 

#include <string.h> 

#include <stdlib.h>

#define N 3

typedef struct node

{

   charname[20];

   struct node*link;

}stud;

stud * creat(int n) /*建立单链表的函数*/

{

   stud*p,*h,*s;

   int i;

   if((h=(stud*)malloc(sizeof(stud)))==NULL)

   {

     printf("不能分配内存空间!");

      exit(0);

   }

  h->name[0]='\0';

  h->link=NULL;

   p=h;

  for(i=0;i<N;i++)

   {

     if((s=(stud *) malloc(sizeof(stud)))==NULL)

     {

       printf("不能分配内存空间!");

        exit(0);

     }

    p->link=s;

    printf("请输入第%d个人的姓名:",i+1);

    scanf("%s",s->name);

    s->link=NULL;

     p=s;

   }

   return(h);

}

stud * search(stud *h,char *x) /*查找函数*/

{

   stud *p;

   char *y;

   p=h->link;

  while(p!=NULL)

   {

    y=p->name;

    if(strcmp(y,x)==0)

      return(p);

     elsep=p->link;

   }

   if(p==NULL)

     printf("没有查找到该数据!");

}

 

stud * search2(stud *h,char*x)

/*另一个查找函数,返回的是上一个查找函数的直接前驱结点的指针,

h为表头指针,x为指向要查找的姓名的指针

其实此函数的算法与上面的查找算法是一样的,只是多了一个指针s,并且s总是指向指针p所指向的结点的直接前驱,

结果返回s即是要查找的结点的前一个结点*/

{

   stud *p,*s;

   char *y;

   p=h->link;

   s=h;

  while(p!=NULL)

   {

    y=p->name;

    if(strcmp(y,x)==0)

      return(s);

     else

     {

      p=p->link;

      s=s->link;

     }

   }

   if(p==NULL)

   printf("没有查找到该数据!");

}

void insert(stud *p) /*插入函数,在指针p后插入*/

{

   charstuname[20];

   stud *s; /*指针s是保存新结点地址的*/

   if((s= (stud*) malloc(sizeof(stud)))==NULL)

   {

    printf("不能分配内存空间!");

     exit(0);

   }

   printf("请输入你要插入的人的姓名:");

  scanf("%s",stuname);

  strcpy(s->name,stuname); /*把指针stuname所指向的数组元素拷贝给新结点的数据域*/

  s->link=p->link; /*把新结点的链域指向原来p结点的后继结点*/

   p->link=s;/*p结点的链域指向新结点*/

}

 

void del(stud*x,stud *y) /*删除函数,其中y为要删除的结点的指针,x为要删除的结点的前一个结点的指针*/

{

  stud *s;

  s=y;

 x->link=y->link;

  free(s);

}

 

 

void print(stud *h)

{

   stud *p;

   p=h->link;

   printf("数据信息为:\n");

  while(p!=NULL)

   {

    printf("%s \n",&*(p->name));

    p=p->link;

   }

}

 

 

void quit()

{

  exit(0);

}

void menu(void)

{

   system("cls");

   printf("\t\t\t单链表C语言实现实例\n");

   printf("\t\t|----------------|\n");

   printf("\t\t| |\n");

    printf("\t\t|[1] |\n");

   printf("\t\t| [2] |\n");

   printf("\t\t| [3] |\n");

   printf("\t\t| [4] |\n");

   printf("\t\t| [5] |\n");

   printf("\t\t| [6] 退 |\n");

   printf("\t\t| |\n");

   printf("\t\t| 如未建立新表,请先建立! |\n");

   printf("\t\t| |\n");

   printf("\t\t|----------------|\n");

   printf("\t\t 请输入你的选项(1-6):");

}

main()

{

    int choose;

    stud*head,*searchpoint,*forepoint;

    charfullname[20];

 

    while(1)

    {

      menu();

     scanf("%d",&choose);

     switch(choose)

      {

        case 1:

          head=creat(N);

          break;

        case 2:

                     printf("输入你所要查找的人的姓名:");

          scanf("%s",fullname);

          searchpoint=search(head,fullname);

          printf("你所查找的人的姓名为:%s",*&searchpoint->name);

          printf("\n按回车键回到主菜单。");

          getchar();getchar();

          break;

        case 3:printf("输入你要在哪个人后面插入:");

          scanf("%s",fullname);

          searchpoint=search(head,fullname);

          printf("你所查找的人的姓名为:%s",*&searchpoint->name);

          insert(searchpoint);

          print(head);

          printf("\n按回车键回到主菜单。");

          getchar();getchar();

          break;

        case 4:

                     print(head);

          printf("\n输入你所要删除的人的姓名:");

           scanf("%s",fullname);

          searchpoint=search(head,fullname);

          forepoint=search2(head,fullname);

           del(forepoint,searchpoint);

          break;

        case 5:

                     print(head);

          printf("\n按回车键回到主菜单。");

           getchar();getchar();

          break;

        case6:quit();

          break;

        default:

                     printf("你输入了非法字符!按回车键回到主菜单。");

          system("cls");

           menu();

           getchar();

      }

   }

}

 

第四部分、完整的学生信息管理系统

 

/*

*学生信息管理程序,

*管理学生的个人信息及各科成绩;

*/

#include<stdio.h> 

#include<conio.h> 

#include<string.h> 

#include<stdlib.h> 

   

 

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<n-i;j++) 

        { 

            p = u->next; 

            q = p->next; 

           if(strcmp(p->classs,q->classs) > 0 ||strcmp(p->classs,q->classs) == 0 && p->score.sum <q->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; 

  }