C语言:随笔8--结构体

在学习时我们要知道为什么要用到结构体?

因为有时需要将不同类型的数据组合(封装)成一个有机的整体,以便于引用。(比如类就是将方法和属性封装起来,这里结构就是将不同的变量东西给封装起来)

例如一个学生有学号/姓名/年龄/地址等属性

int num;
char name[20];
char sex;
int age;
char addr[30];

一、结构体

定义一个结构的一般形式为:

struct 结构名
{
   成员列表
};
//成员列表由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:
类型说明符 成员名;//int num;


//比如:定义的student学生类他有6个属性
struct stucent
{
   int num;
   char name[20];
   char sex;
   int age;
   float score;
   char addr[30];

};

可以采用以下三种方法定义结构体类型变量:

(1)先声明结构体类型再定义变量名:

//比如:定义的student学生类他有6个属性
struct stucent//struct 结构体名
{
   int num;
   char name[20];
   char sex;
   int age;
   float score;
   char addr[30];//成员表列

};
struct student  student1,student2;//类型名 结构体  变量名,变量名
//定义了student1和student2为struct student类型的变量,即他们具有struct student类型的结构。

(2)再声明类型的同时定义变量

/比如:定义的student学生类他有6个属性
struct stucent
{
   int num;
   char name[20];
   char sex;
   int age;
   float score;
   char addr[30];

}student1,student2//变量名列表
//在定义结构体变量后,系统会为之分配内存单元。例如studet1和student2在内存中各占?子节
(4+20+1+4+4+30=67)

(3)直接定义结构体类型变量

//即不出现结构体名
struct
{
   成员表列
}变量名表列;

结构的嵌套定义(结构里面的结构)

Num name sex age birthday addr
Month day year

首先应该定义结构data,由Month(月),day(日),year(年),三个成员组成。

在定义并说明变量boy1和boy2时,其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名,互不干扰。(因为他们的作用域不同)

//结构的嵌套
struct data
{
   int month;
   int day;
   int year;
};

struct
{
int num;
char name[20];
char sex;
struct data birthday;
float score;
}boy1,boy2={102,“jane”,'M',98.5};//结构体变量初始化,对变量赋值(有点像数组)
boy1=boy2;//将boy2的值给了boy1,全体成员copy

//如果对boy1使用点"."运算符进行了赋值操作的话,那么boy2=boy1;(将boy1的全体成员赋值给了boy2)这个语句就可以把boy1里边成员变量的值直接都赋值给boy2的成员变量,因此Boy2不用再一个个的进行赋值。

再定义了结构体变量以后,当然可以引用这个变量。应遵守:

(1)不能将一个结构体变量作为一个整体进行输入输出。

应该用点”.“成员运算符。

//比如打印student1中的各个变量的值时,我们不可以直接
printf("%d,%s,%c,%d,%f,%\n",student1);

//正确引用结构体变量中成员的方式为:
//结构体变量名.成员名//点操作符来进行引用
//student1.num//表示student1变量中的num成员。
//student1.num=100;//可以对变量的成员赋值

“.”是成员(分量)运算符,它在所有运算符中优先级最高,因此可以把student1.num作为一个整体看待。上面的赋值语句是将整数100付给student1变量中的成员num。

也不能使用以下语句整体读入结构体变量:(要精确到它最下边的每一个级别)

scanf("%d,%s,%c,%d,%f,%s",&student1);

(2)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的以及的成员。只能对最低级的成员赋值或者存取以及运算。

对上面定义的结构体变量student1,可以这样访问各成员:

student1.num
student1.birthday.month//因为birthday也是一个结构的变量名,他不能代表全体成员,我要再点进去,访问到最下级,最下级才有操作的权力。

(3)结构体变量的成员可以向普通变量一样进行各种运算(根据类型决定可以进行的运算)。

(4)可以引用结构体变量成员的地址(&boy1.num),也可以引用结构体变量(&boy1)的地址。(&boy1和&boy1.num他们两个地址是一样,跟数组类似)

结构体变量的地址主要用做函数参数,传递结构体变量的地址。(其实第一个元素的地址也行,但是一般使用的是结构体变量的地址。名称比较有代表性)

二、结构体数组

一个结构体变量中可以存放一组数据(如一个学生的学号、姓名、成绩等数据)。如果有10个学生的数据需要运算,显然应该用数组,这就是结构体数组。

结构体数组与以前介绍过的数值型数组不同指出在于每个数组元素都是一个结构体类型的数据,他们都分别包括各个成员(分量)项。

下面以一个通讯录的例子做说明:

#include<stdio.h>
#include<stdlib.h>
#define NUM 3

struct person//定义person结构
{
   char name[20];
   char phone[10];
};
void main()
{
   struct person man[NUM];//定义man数组,结构体数组,有三个元素
   int I;
   for(I=0;i<NUM;i++)//一个元素包含了两个项分别是name和phone。
   {
      printf("input name:\n");
      gets(man[i].name);
      printf("input phone:\n");
      gets(man[i].phone);
   }
   printf("\tname\t\t\t\t\tphone\n\n");
   for(i=0;i<NUM;i++)
   {
      printf("%20s\t\t\t%20s\n",man[i],name,man[i].phone);
   }
   system("pause")
}

结构体数组的定义:

//第一种定义方法
struct stucent
{
   int num;
   char name[20];
   char sex;
   int age;
   float score;
   char addr[30];

};
struct student student[3];//定义了一个数组有三个元素,这个数组名称叫student,有三个元素,每一个元素都有上述6个项

//第2种定义方法
struct stucent
{
   int num;
   char name[20];
   char sex;
   int age;
   float score;
   char addr[30];

}studet[3];

结构体数组的初始化:

struct stucent
{
   int num;
   char name[20];
   char sex;
   int age;
   float score;
   char addr[30];

}stu[2]={
            {101,“Li”,'M',18,87.5,"Beijing"},
            {102,“zhang",'F',19,99,"Shanghai"}
        
        };

//或者也可以用以下形式初始化:
struct student
{
   int num;
   ...
};
student student str[]{
   
   {...},{...},{...}};
//即先声明结构体类型,然后定义数组为该结构体类型,再定一数组是初始化。

例题:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出个各人的票结果。并将最高的得票者输出。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define NUM 5
struct person
{
      char name[20];
      int count;
}candidate[NUM] = {
                  {"A", 0},
                  {"B", 0}, 
                  {"C", 0},
                  {"D", 0}, 
                };//定义结构体数组

char *winner();

int main()
{
      int i, j;
      char candidate_name[20];

      printf("欢迎进入2020年火爆网站评选系统:\n\n");
      printf("选择项目:A,B,C,D \n\n");
      
      for( i=1; i <= 6; i++ )
      {
            printf("第 %2d 位投票, 请选择支持的候选人: ", i);
            scanf("%s", candidate_name);
            for( j=0; j < NUM; j++ )//j小于5,所以j只能到4,因为有4个A,B,C,D
            {
                  if( 0 == strcmp(candidate_name, candidate[j].name) )
                  {
                        candidate[j].count++;
                  }
            }
      }
      printf("\n");

      for( i=0; i < 5; i++ )//打印ABCD分别的得票数
      {
            printf("%s 得票数为: %d\n", candidate[i].name, candidate[i].count );
      }
      printf("\n");
      printf("本次评选活动的胜利者的: %s\n", winner() );//调用winner()函数,比较得票最高的是谁

      printf("\n");
      system("pause");
}

char *winner()
{
      int i =0 , winner = i;//相当于winner=0
      
      for( i=1; i < NUM; i++ )
      {
            if( candidate[winner].count < candidate[i].count )//结构体数组的第一个元素的count项依次与后边的元素的count项比较比较
            {
                  winner = i;
            }
      }
      
      return candidate[winner].name;
}

----指向结构体类型数据的指针---

一个结构体变量的指针就是该结构体变量所占据的内存段的其实地址。(指针就是一个存放地址的变量)

可以设一个指针变量,用来指向一个结构体变量,此时该指针变量的值(也就是一个地址)是结构体变量的起始地址。(指针真的很伟大,指啥都行)

指针变量也可以用来指向结构体数组中的元素。

结构体指针变量的一般形式:

struct  结构名  *结构指针变量名//*表示他是指针
//例如前面定义的stu这个结构,说明一个指向stu的指针变量名pstu,可以写为:
struct  stu  *pstu;//*表示它是一个指针,stu表示他是哪一个结构的指针。
//当然也可以在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同,结构指针变量也必须要先赋值后才能使用。(不然就是个无头案)

//赋值是把结构变量的首地址赋予该指针变量,不能把结构名赋予该指针变量。

结构名和结构变量名不是一个东西(一个是student,一个是boy1)。例如

//如果boy是被说明为stu类型的结构变量,则:
struct stu//结构为stu
{
   ...
}boy={...,...,...,};//结构变量为boy,并同时赋初值
//(还可以结构变量名再有一个girl,不止一个boy了这次,所以再声明一个指针指向girl就好了,访问方式跟下边雷同)
struct  stu  *pstu;//*表示它是一个指针,stu表示他是哪一个结构的指针。//定义结构变量指针
pstu=&boy;//是正确的//将boy的初始值地址给了它
(*pstu).name//正确;
(*pstu)->name//正确;
boy.name//正确//通过三种形式可以输出
pstu=&stu;//是错误的
//为什么呢???
//因为结构名stu和结构变量boy是两个不同的概念,不能混淆,结构名只能表示一个结构形式,编译系统并不对它分配内存空间。
//只有当某变量被说明定义为这种类型的结构时,才对该变量分配存储空间。在定义时操作系统才会为它分配空间。只是说明没定义不分配空间。
//因此上面&stu这种写法是错误的,不可能去取一个结构名的首地址。(因为结构名本来是有地址的,他只是让编译器知道,编译器在编译时会申请空间)有了结构指针变量,就能更方便的访问结构变量的各个成员。

//其访问的一般形式为:
(*结构指针变量).成员名
//或者:
结构指针变量->成员名
//例如:
(*pstu).num
//或者:
pstu->num

例子:指向结构体变量的指针变量应用(如上的三种形式输出)。

三、结构指针变量做函数参数

将一个结构体变量的值传递给另外一个函数,有三个方法:

(1)用结构体变量的成员做参数。

(2)用结构体变量作实参。(将结构体变量的名称作为实参)

(3)用指向结构体变量(或数组)的指针作实参,将结构体变量(或数组)的地址传递给形参。

例子:有一个结构体变量stu,内含学生学号、姓名和三门课程的成绩。通过调用函数print中将他们输出。

(要求是先用结构体变量做函数参数(作实参传递给它)(2))

这个例子是将整个结构体复制过去,因为void print(struct student);这里的参数是整个结构体变量。(传值)

#include<stdio.h>
#include<string.h>
struct student//定义结构
{
   int num;
   char name[20];//字符串,(name[20]出现在栈里面,实实在在的空间,而下面的“zhangsan字符串出现在数据常量区”//改变为char *name;的话下边的语句stu.name="zhangsan";是将数据区常量的地址传递给name这个指针而已。
   float score[3];//浮点型数组
};
void print(struct student);//函数的参数是这个结构体,一个结构体作为他的形参
void main()
{
   struct student stu;//定义这个结构体变量的名称为stu
   stu.num=8;
   strcpy(syu.name,"zhangsan");//如果用stu.name="zhangsan";这个语句会错误(复制号的左边必须是一个左值l-value(可以简单的理解l-value就是一个地址,必选是一个指针所以上边就必须改一下定义为char *name;这样的话这句话就没有错误了)),
   stu.score[0]=98.2;
   stu.score[1]=93.2;
   stu.score[2]=98.7;//分别赋值
   print(stu);//把结构体变量作为实参传递给他
}
void print(struct student stu)//print这个函数知道了结构体变量的位置就能够索引到他的名称学号成绩并分别打印
{
    printf("\tnum      :%d\n",stu.num);
    printf("\tname      :%s\n",stu.name);
    printf("\tscore_1      :%5.2f\n",stu.score[0]);
    printf("\tscore_2      :%5.2f\n",stu.score[1]);
    printf("\tscore_3      :%5.2f\n",stu.score[2]);
    printf("\n")
}

(要求用指向结构体变量的指针作实参(3))

第二个方法复制过去的只是一个地址,那么从效率上来说,传递的比较少的还是第二种方法。(传址)

#include<stdio.h>
#include<string.h>
struct student//定义结构
{
   int num;
   char name[20];//字符串,(name[20]出现在栈里面,实实在在的空间,而下面的“zhangsan字符串出现在数据常量区”//改变为char *name;的话下边的语句stu.name="zhangsan";是将数据区常量的地址传递给name这个指针而已。
   float score[3];//浮点型数组
};
void print(struct student *);//函数的参数是指针
void main()
{
   struct student stu;//定义这个结构体变量的名称为stu
   stu.num=8;
   strcpy(syu.name,"zhangsan");
   stu.score[0]=98.2;
   stu.score[1]=93.2;
   stu.score[2]=98.7;//分别赋值
   print(&stu);//因为上边是指针,指针必须接收一个地址嘛,所以这里传递给他的那就是这个结构体的地址了
}
void print(struct student *p)//用指针来接收,p指向的还是这个结构,用p来打印
{
    printf("\tnum      :%d\n",p->num);
    printf("\tname      :%s\n",p->name);
    printf("\tscore_1      :%5.2f\n",p->score[0]);
    printf("\tscore_2      :%5.2f\n",p->score[1]);
    printf("\tscore_3      :%5.2f\n",p->score[2]);
    printf("\n")
}

---动态存储分配----

在数组一章中,曾介绍过数组的长度是预先定义好的(不是直接说明的话,但是也要隐身间接告诉编译器这个数组多长),在整个程序中固定不变。C语言中不允许动态数组类型。

int a[10]={0,1,2,3,4,5,6,7,8,9};//告诉他长度

char a[ ];//编译器会自动计算字符串的长度,然后把[ ]自动的给填充上。(所以数组必须是预先定义好的,规定好有多长的)

int a[n];//这样是不可以的。因为C语言中不允许动态数组类型。(用变量表示长度,想对数组的大小作动态说明,这是错误的。因为编译器不知道你的数组是多大,那他定义的时候就不知道怎么分配空间,但是呢在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定;所以对于这种问题,用数组的办法很难解决(因为数组在定义时就必须规定有多长的数据))

为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态的分配内存空间,也可把不再使用的空间回收待用,为有效地利用内存资源提供了手段。

常用的内存管理函数有以下三个:

(1)分配内存空间的函数malloc、calloc

(2)释放内存空间函数free

----malloc函数

//函数原型为
void *malloc(unsigned int size)//返回一个空指针,参数是要申请的一个尺寸,尺寸是无符号整型
//其作用是在内存的动态存储区中分配一个长度为size的连续空间(size是一个无符号数)。
//此函数的返回值是一个指向分配域的(要分配的这个空间的)起始地址的指针(类型为void)。
//如果此函数未能成功的执行(例如存储空间不足),则返回空指针(NULL)表示0。

----calloc函数

//函数原型为:
void *calloc(unsigned n,unsigned size);
//其作用是在内存的动态存储区中分配n个长度为size的连续空间。
//函数返回一个指向分配域起始地址的指针。
//如果分配不成功,返回NULL。
//用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。
(,这种用法用的少,上个用法用的多,因为这种用法是结合数组来使用的)

----free函数

//函数原型为void free(void *p);
//无返回值,将一个指针(void *p),这个指针就是你将要释放的,刚才申请的那个内存的地址
//起作用是释放由p指向的内存区,使这部分内存区能被其他变量使用
//p是最近一次调用calloc或malloc函数时返回的值
//free函数无返回值

----链表(这是一个数据结构)

什么是链表?

答:链表是一种常见的重要的数据结构,是动态的进行存储分配的一种结构。(所以要说刚才的三个函数)

链表的组成:

头指针:存放一个地址,该地址指向第一个元素。

结点:用户(元素)需要的实际数据和链接节点的指针。

上边ABCD就是四个结点,头指针第一个指针,那么我们发现一个特点,第一个指针存放的是一个地址,地址指的是第一个数据,第一个数据有两部分,第一部分是数据的内容,另一部分是指向下一个数据的地址;然后下一个数据又有两个部分,一个是内容,另一个是再指向下一个数据的地址;依次到最后没有就指向Null,表示这个链表结束。那我们会发现这个链表说白了跟数组相似,数组的数据也是一个挨着一个构成的,而链表呢也是一个挨着一个,但是链表跟数组有一个不同的点,链表一个元素是放两个东西,一个是元素的内容,另一个是下一个元素的地址。那这样的话最简单的一个好处就是链表可以动态的分配。怎么动态的分配呢?1、临时加多一个E,将这个Null改为E的地址,然后将E指向下一个,没有的话就是Null,有的话就指向下一个;2、我们临时去掉B也是可以的,将A存放的地址指向C(直接把B人间蒸发);3、想在A和C之间添插B可以吗?可以的。我将B的地址指向了C,将A的地址指向了B,给B赋值元素,那就实现了在A和C中间插入B;所以说链表这个数据结构使得我们整个分配空间变得更加的灵活。比数组的好处就是体现在这几个方面。

尝试根据下图建立链表

链表有三个数据,三个结点。源代码如下:

//由上图可以看到他有三个结点,每个结点都有元素(一个是number学号、还有score分数、还有地址(存放下一个的地址))
#include<stdio.h>
struct student
{
    long num;//长型存放学号
    float score;//分数
    struct student *next;//(相当于结构体里边有个结构体指针)//因为我们下一个指向的是跟他相同结构的也是student这个结构的一个数据,那我们这里就指向了一个指向结构的指针,指向student这个结构//这是一个指针,存放的地址就是下一个这样的结构,大家结构都是一样的只是位置不同//next这个指针变量,是一个结构体类型的所以它需要指向的是一个结构体(类比于 int *p;相当于int型的这个指针变量,需要指向的是int型的数据)
};
void main()
{
   struct student a,b,c,*head;//定义abc三个结点和一个head是一个结构体指针。
   a.num=10101;
   a.score=89.5;
   b.num=10103;
   b.score=90;
   c.num=10107;
   c.score=85;//赋值
 
   head=&a;//head是链表的头指针它必指向链表的第一元素,我们只需要知道这个链表的第一个元素在哪里,我们接着的第几个元素都可以索引到,因为我们接着的元素都会存放下一个元素的地址。//head里边存放的地址是指向第一个元素
   a.next=&b;//a指向b//因为是指针指向下一个地址
   b.next=&c;//b指向c
   c.next=null//c最后的地址就存放Null,表示链表结束
   do//使用do while依次打印出来,只要head不等于0
   {
       printf("%ld %5.1f\n",head->num,head->score);
       head=head->next;//第一次循环相当于head=a.next;而a.next=&b;//为什么每次执行的都是head,不是就一个head吗?head->a->b->c->Null。(因为我们这里并没有规定head必须指向头结点,所以head的指向是可以变化的)(它这里是把 head 当普通指针使用了,每次改变 head 的值,这样 head 就不指向头结点了,而是每次指向下一个节点)
   }while(head);//这个的意思就是while(head!=0);//while(head!=Null);//NUll就是0//他的地址的下一个值是有指向一个结构的,那么他就可以继续把他打印出来,直到最后一个,因为最后一个他是指向Null,所以这里head=head->next;这个head 是空的,那么整个程序结束。

}//通过链表索引分别将a元素,b元素,c元素打印出来。

---建立动态链表

所谓建立动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据(即根据需要动态的开辟和消除结点,这叫做动态建立链表,这体现了链表比数组的优越性和时效性),并建立起前后相链的关系(链表是一个连着一个)。

根据下面的分析写一个程序建立一个含有学生(学号,成绩)数据的单向动态链表。

(约定:如果学号不为0的话就把他录入;如果输入学号为0,(就表示建立链表完成了)则表示建立链表的过程完成了,该结点不应连接到链表中)

分析如下:

我们把他写成一个最简单的盒子流程图,这个图怎么分析呢?要画画,画圆和三角形来分析。按照顺序来做(三角形表示结点)

head=Null,n=0表示初始化为0,照抄就好了。

算法的实现:

(1)如果输入的p1->num不等于0,则输入的是第一个结点(n=1),令head=p1,即把p1的值赋给head,也就是使head也指向新开辟的结点,p1所指向的新开辟的结点就成为链表中第一个结点。(那么这么说我们必须把三个结点都指向第一个数据,因为只有这一个数据能让我们指向。)

(2)再开辟另一个结点并使P1指向它,接着输入该结点的数据。

(3)如果输入的p1->num不等于0,则应连入第二个结点(n=2),将新结点的地址赋给第一个结点的next成员。

(4)接着使得p2=p1,也就是使p2指向刚才建立的结点。

图(图说明)

刚才建立第一个结点的时候head ,p1,p2都是指向的第一个结点。(图a)当出现第二个结点的时候p1首当其冲就冲了上去,然后又把p2给拉了过去(图b),这时候第一个结点只有head指向它,并且引导第一个结点的next指针指向了第二个结点,这时候完成这一步的时候三个指针都指向了第二个结点(图c)。这样的话第三个第4个也是以此类推。有一个过程的。

(5)再开辟一个结点并使p1指向它,并输入该结点的数据。

(6)在第三次循环中,由于n=3(n不等于1),又将p1的值赋给p2->next,也就是将第3个结点连接到第2 个结点之后,并使p2=p1,使p2指向最后一个结点(也就是第三个结点)。

在第三次循环我们就把他结束掉,如下图:(怎么结束呢,就使它最后的next指向Null)(第三步跟第二步是重复的,先使p1指向它,接着使p2指向它,他的next也指向它)

(7)再开辟一个新结点,并使p1指向它,输入该结点的数据。由于p1->num的值为0,不再执行循环,此新结点不应被连接到链表中。

(8)将Null赋给p2->next.

(9)建立链表过程至此结束,p1最后所指的结点未链入链表中,第三个结点的next成员的值为Null,他不指向任何结点。

下面程序还实现了链表的输出,怎么实现呢?

首先要知道链表第一个结点的地址,也就是要知道head值。(只要知道了地址我们相当于把握了整个链表)

设一个指针变量p,先指向第一个结点(也就是head结点),输出p所指的结点,然后使p后移一个结点(p这时候移向了head->next指针,移向了下一个结点),再输出,直到链表的未结点。如此就可以得到下面流程图

下面程序就是动态输入动态停止

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>

#define LENsizeof(struct student)//结构的大小
struct student *creat();//创建链表
void print(struct student *head)//打印链表(的函数)
struct student
{
    int num;
    float score;//分数
    struct student *next;
};
int n;//全局变量,用来记录存放了多少数据

void main()
{
   struct student *stu;
   stu=create();//12这个stu就是他的头结点,指针指向他的头结点
   print(stu);//然后我把这个头当成参数来print(是自定义的函数,实现的是打印这个链表)接收他的头
   printf("\n\n");
   system("pause");
}

struct student *creat()//creat创建函数
{
   struct student *head;
   struct student *p1,*p2;//在这里只是开辟了四个字节,说明这些指针是指向这个结构的,在内存并没有真正的创建整个结构那么大小的空出来。只是创建了一个指针,说它指向这么一个结构所以在2处就必须动态的给他初始化,给他获取相应的空间。LEN在上边通过宏定义,定义为这个student这个结构的大小
   p1=p2=(struct student *)malloc(LEN);//先定义要给他赋值,我们是动态创建链表,我们在上边声明的都是指针结构,都是指针,指针结构的话他只是开辟了一个四个字节,放一个指针地址//LEN是student结构的大小。3//让p1,p2同时指向我们刚申请的这个大小

   printf("Please enter the num:");
   scanf("%d",&p1->num);
   printf("Please enter the score:");
   scanf("%f",&p1->score);//4接着我们输入,输入都是指向的p1,p1首当其冲
   head=NULL;//head指向NULL
   n=0;//0个节点,还没有任何结点//5开始初始化
   while(p1->num)//6实现循环,当p1的num不等于0的时候,就是没有退出(while(0!=p1->num))
   {
      n++;//7不等于0就n++,表示出现了第一个结点(因为当p1的num成员为0时,就标志着我们整个程序结束,链表创建完成)
      if(1==n)//
      {
         head=p1;//8当n=1时head必须指向它p1,我们整个数据链的头结点指向它
      }
      else
     {
         p2->next=p1;
     }
     p2=p1//p2是跟在p1后边的
     p1=(struct student *)malloc(LEN);//9然后接着p1再次创建一个新的结点
   
     printf("\nPlease enter the num:");
     scanf("%d",&p1->num);
     printf("\nPlease enter the score:");
     scanf("%f",&p1->score);  //10这里要求输入第二个结点//只要输入的值不为0他就在while循环里边慢慢的循环,依次循环,当他在while处判断输入的值为0,到11
   }
   p2->next=NULL;//p2->next就指向NULL,我们刚申请的9到11之间的这个地方就废掉了,他就停留在内存的某个地方
   return head;//然后我们返回的值是head,head是我们整个链表的一个头结点的一个指针(也就是整个链表的领头羊,相当于我们数组的首地址,我们知道了这个数组的首地址,我们知道了数组依次索引,链表是同样的。)(我们之所以发明链表这个数据结构主要就是弥补数组的不足)//return返回的是指向我们创建完的链表的头
}
void print(struct student *head)//11定义接收头
{
   struct student *p;//12我们又定义了p指针,
   printf("\nThere are %d records!\n\n",n);
   p=head;//将头先指向了p
   if(head)//13如果head不等于0的话(if(NULL!=head))(什么都没有也就是一个空指针因为head是一个指针嘛)
   {
       do//14然后用while的形式把p输出出来
       { 
           printf("学号为%d的成绩是:%f\n",p->num,p->score);
           p=p->next;
       }while(p);//这里也是P不等于NULL的时候(while(NULL!=p))他就实现循环
   }

}

结果展示:

-----对链表的删除操作:

比如下边链表想删除B。

(A->B->C->D->E->H)

其实很简单直接把它去掉,把A的指向,指向C就可以了。

删除B之后还想再把B加回来的话,就直接把A和C之间的链断掉,把B加进来就好了(把A指向B,B又指向C就好了)。

(如果是一个数组ABCDE,想要把B删去怎么办?直接删去B,然后C走到B的位置,D走到C的位置,E走到D的位置;那如果想把B再插入进来的话,那就得E再跑回原来的位置,D再走回来,C再走回来,之后把B放进去。所以这就是数组相当于链表不方便的地方)

从一个动态链表中删去一个结点,并不是真正从内存中把它抹掉,(链表我们要链向下一个只是用地址来链,因为链表的next指针指向的是一个结点的指针,我们只需要把那个指针改变一下,那我们感觉他是消失了(那消失的那个结点去哪里了就像宇航员在太空中抛弃一样,迟早会被陨石给撞到的,那他也一样,在内存中的某个地方迟早会被数据给覆盖掉不用管他,并不是真正把他从内存中抹掉,而是))而是把它从链表中分离开来,只要撤销原来的链接关系即可。

猜你喜欢

转载自blog.csdn.net/m0_37957160/article/details/108685364
今日推荐