结构解读(下)

一、结构与函数

(一)结构作为函数参数

struct date//定义结构体
{
    
    
   int year;
   int month;
   int day;
};

void prn(struct date p)//函数输出p的各成员
{
    
    
   printf("%d-%d-%d",p.year,p.month,p.day);
}

int main(void)//主函数
{
    
    
   struct date d = {
    
    2020,7,8};
   prn(d);//输出结构体的各成员
   return 0;
}
/*结构体作为函数参数,是复制整个结构体成员的内容,多个值*/
struct date//定义结构体
{
    
    
   int year;
   int month;
};

void prn(struct date p)//函数输出p的各成员
{
    
    
   printf("%d-%d",p.year,p.month);
}

void modify(struct date p)//函数为形参p赋值
{
    
    
   p.year = 2020;
   p.month = 7;
}

int main(void)
{
    
    
   struct date d = {
    
    1979,4};
   printf("调用函数前的日期为:");
   prn(d);
   modify(d);
   printf("调用函数后的日期为:");
   prn(d);
   return 0;
}

/*结构作为函数参数,是值传递,形参是实参的副本,形参内容的修改不影响实参*/

(二)结构指针作为函数参数

在调用函数时,传递给函数的是实参的副本。如果希望结构能够被被调用函数改变,或实参是一个非常大的结构,一般使用结构指针作为实参

struct date//定义结构体
{
    
    
   int year;
   int month;
};

void prn(struct date p)//函数输出p的各成员
{
    
    
   printf("%d-%d",p.year,p.month);
}

void modify(struct date *p)//函数为形参p赋值
{
    
    
   p->year = 2020;
   p->month = 7;
}

int main(void)
{
    
    
   struct date d = {
    
    1979,4};
   printf("调用函数前的日期为:");
   prn(d);
   modify(d);
   printf("调用函数后的日期为:");
   prn(d);
   return 0;
}

/*用结构体指针作为函数参数,在实参和形参之间传递的仅仅是结构体变量的地址,并非结构体的全部内容,因此执行效率比较高,比较常用。如果不希望函数改变主函数中的内容,可加const修饰*/

结论:

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

  1. 用结构体变量的成员作为参数。
  2. 用结构体变量作为参数。
  3. 用指向结构体变量的指针作为参数,将结构体变量的地址传递给形参。

(三)结构作为函数的返回值

结构作为函数的返回值和普通变量作为函数的返回值的主要区别就是可以一次返回多个值,这在有些时候非常方便。

struct date//定义结构体
{
    
    
   int year;
   int month;
   int day;
};

void prn(struct date p)//函数输出p的各成员
{
    
    
   printf("%d-%d-%d",p.year,p.month,p.day);
}
struct date GetData()//函数为形参p赋值
{
    
    
   struct date p;
   p.year = 2020;
   p.month = 7;
   p.day = 8;
   return p;
}

int main(void)
{
    
    
   struct date d = GetData();
   prn(d);
   return 0;

(四)结构数组作为函数参数

题目:输入n并输入n个学生信息(学号、姓名、成绩),按照从高分到低分的顺序输出他们的信息。最后输出平均分。

#include<stdio.h>
void Input(struct student stu[],int n);
void Output(struct student stu[],int n);
void Sort(struct student stu[],int n);
double Avg(struct student stu[],int n);

struct student
{
    
    
	int num;
	char name[20];
	int score;
 };
 
 int main(void)
 {
    
    
 	int n;
 	struct student stu[10];
 	
 	printf("请输入学生的个数:\n");
 	scanf("%d\n",&n);
 	
 	Input(stu,n);
 	Sort(stu,n);
 	Output(stu,n);
 	printf("平均分为:%.2f\n",Avg(stu,n));
 	return 0;
 	
 }
 
 void Input(struct student stu[],int n)
 {
    
    
 	int i;
 	for(i = 0;i < n;i++)
 	{
    
    
 		scanf("%d%s%d",&stu[i].num,&stu[i].name,&stu[i].score);
	 }
 }
 
 void Output(struct student stu[],int n)
 {
    
    
 	int i;
 	for(i = 0;i < n;i++)
 	{
    
    
 		printf("%d%s%d\n",stu[i].num,stu[i].name,stu[i].score);
	 }
 	
 }
 
 void Sort(struct student stu[],int n)
 {
    
    
 	int i,j;
 	struct student temp;
 	
 	for(i = 0;i < n-1;i++)
 	{
    
    
 		for(j = i + 1;j < n;j++)
 		{
    
    
 			if(stu[j].score > stu[i].score)
 			{
    
    
 				temp = stu[j];
 				stu[j] = stu[i];
 				stu[i] = temp;
			 }
		 }
	 }
 }
 
 double Avg(struct student stu[],int n)
 {
    
    
 	int i;
 	int sum = 0;
 	double Avg;
 	
 	for(i = 0;i < n;i++)
 	{
    
    
 		sum += stu[i].score;
	 }
	 Avg = sum * 1.0 / n;
	 
	 return Avg;
 }

二、单链表

(1)线性表的概念
线性表中数据元素之间的关系是一一对应的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表结构有着广泛的实际应用。

(2)线性表的存储:顺序存储结构和链式存储结构。

线性表顺序存储结构的缺点
1、在插入删除某一元素时,需要移动大量元素。
2、预先分配空间需要按照最大空间分配,利用不充分,表的容量难以扩充。
为克服这些缺点,我们引入另一种存储形式:链式存储结构

(一)链表的表示

(1)特点:
用一组任意的存储单元存储线性表的数据元素。
利用指针实现了用不相邻的存储单元存放逻辑上相邻的元素。

结点:
数据域:元素本身信息
指针域:指示直接后继的存储位置

(2)与链式存储有关的术语:
1、结点:数据元素的存储映像。由数据域和指针域组成
2、链表:n个结点由指针链组成一个链表。它是线性表的链式存储映像,称为线性表的链式存储结构。
3、单链表、双链表、多链表、循环链表:
·结点只有一个指针域的链表,叫做单链表或者线性链表
·有两个指针域的链表叫做双链表
·有多个指针域的链表叫做多链表
·首尾相接的链表称为循环链表

(3)何谓头指针、头结点和首元结点?
在这里插入图片描述
头指针:是指向链表中第一个结点(或为头结点首元结点)的指针
头结点:是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息;
首元结点:是指链表中存储线性表第一个数据元素a1的结点。

在链表中设置头结点的好处:
头结点即在链表的首元结点之前附设的一个结点,该结点的数据域中不存储线性表中的数据元素,其作用是为了对链表进行操作时,可以对空表非空表的情况以及对首元结点进行统一处理、编程更方便。

如何表示空表:
无头结点时:当头指针的值为空时表示空表;
有头结点时:当头结点的指针域为空时表示空表。

在这里插入图片描述(4)对于链表的抽象描述:

typedef struct Londe
{
    
    
   ElemType data;   // 数据域
   struct Lnode *next;   //指针域
}Lnode,*LinkList;   //*LinkList为Lnode类型的指针

Lnode s1;//定义结构体类型的变量s1
等价于:
struct Lnode s1;

LinkList p1;//定义该类型的指针变量
等价于:Lnode *p1;
等价于:struct Lnode *p1;

<stdlib.h>中的是哪个库函数
sizeof(x) //计算变量x的长度
malloc(m) //开辟m字节长度的地址空间,并返回这段空间的首地址
free§ //释放指针p所指变量的内存空间,即彻底删除一个变量

(二)链表的实现

(1)单链表的读取
在顺序表中,第i个元素的存储位置很容易计算,但在单链表中,如何读取第i个元素?
难点:单链表中想取得第i个元素,必须从头指针出发,不能随机存取。

思路:
1、声明一个指针p指向首元结点,初始化j为12、当j<i且不为空时,让指针p不断后移,同时j++;
3、若循环结束时p为空,说明第i个元素不存在,否则p所指的即第i个结点,p->data即第i个元素。
/*定义链表*/
typedef struct Londe
{
    
    
   ElemType data;   // 数据域
   struct Lnode *next;   //指针域
}Lnode,*LinkList;   //*LinkList为Lnode类型的指针

//将带头结点的单链表L的第i个元素存入e所指内存
Status GetElem(LinkList L,int i,ElemType *e)
{
    
    
   LinkList p;//声明指针变量
   p = L->next;//指针变量指向首元结点
   int j = 1;

   while(j < i && p)
   {
    
    
   p = p -> next
   j++;
   }

   if(p == NULL)
   return ERROR;
   else
   *e = p-> data;
   return TRUE;
}

(2)单链表的遍历
由于单链表的结构中没有定义表长,所以事先不知道要循环多少次,因此不方便用for循环,其核心思想是“工作指针后移”,直到链表结束。

思路:
1、声明一个指针p指向首元结点;;
2、当p不为空
{
    
    
输出当前元素p->data;
指针p指向下一个元素;
}
void TraverseList(LinkList L)
{
    
    
   LinkList p;
   p = L -> next;//声明指针p指向首元结点

   while(p)
   {
    
    
    printf("%d",p -> data);//输出指针指向的当前元素
    p = p -> next;//指针指向下一个元素
   }
}

(3)单链表的建立
步骤:
1、定义链表的数据结构
2、读取数据
3、生成新节点
4、将数据存入结点的成员变量中
5、将新结点链接到表头和表尾
6、重复2~5操作直到输入结束

方法一:每次从表头插入新结点:先开辟头指针,然后陆续为每个数据元素开辟存储空间并赋值,让该结点作为首元结点,原来的首元结点作为它的后续结点

void CreateList(LinkList L)
{
    
    
	LinkList p;
	int num;
	
	printf("输入若干整数,输入-1表示输入结束\n");
	while(scanf("%d",&num),num != -1)
	{
    
    
		p = (LinkList)malloc(sizeof(Lnode));/*p指向新创建的结点*/
		p->data = num;/*将数据存入新结点的data域*/
		p->next = L->next;/*将原来的首元结点作为新结点的后继*/
		L->next = p;/*将新结点作为首元结点*/ 
	}
 } 
 

方法二:每次从表尾插入新结点:先开辟一个结点,先开辟头指针,然后陆续为每个数据元素开辟存储空间并赋值,并及时将地址送给前面的指针

void CreateList(LinkList L)
{
    
    
	LinkList p,rear = L;/*使rear总是指向链表的最后一个结点*/
	int num;
	
	printf("输入若干整数,输入-1表示输入结束\n");
	while(scanf("%d",&num),num != -1)
	{
    
    
		p = (LinkList)malloc(sizeof(Lnode));/*p指向新创建的结点*/
		p->data = num;/*将数据存入新结点的data域*/
		rear -> next = p;/*将新结点作为rear的后继*/
		rear = p;/*更新rear*/ 
	}
	rear -> next = NULL;/*表尾结点的后继置为NULL*/ 
 } 

(4)单链表的插入

//函数ListInsert()构造一个值为e的结点p,作为链表head的第i个结点
void ListInsert(LinkList head,int i,int e)
{
    
    
	LinkList p,pre = head;/*让pre指向头结点*/
	int j = 0;
	
	while(pre && j < i-1)/*当pre非空且pre不是第i-1个结点*/
	{
    
    
		pre = pre->next;
		j++;
	 } 
	 
	 if(!pre || i < 1)/*如果链表结束或i<1*/
	 {
    
    
	 	printf("i值错误\n");
	 	return;
	 }
	 
	 p = (LinkList)malloc(m);/*生成新结点p*/
	 p->data = e;
	 p->next = pre->next;/*让原来pre的后继做结点p的后继*/
	 pre->next = p;/*结点p作为pre的后继*/ 
 } 
 

(5)单链表的删除

//函数ListDelete()删除链表head的第i个结点,并将第i个结点的值存入形参指针e所指内存 
void ListInsert(LinkList head,int i,ElemType *e)
{
    
    
	LinkList p,pre = head;/*让pre指向头结点*/
	int j = 0;
	
	while(pre && j < i-1)/*当pre非空且pre不是第i-1个结点*/
	{
    
    
		pre = pre->next;
		j++;
	 } 
	 
	 if(!pre || i < 1)/*如果链表结束或i<1*/
	 {
    
    
	 	printf("i值错误\n");
	 	return;
	 }

    p = pre-> next;/*p指向要被删除的结点*/
    pre->next = p-> next;/*绕过要删除的结点p*/
    *e = p-> data;/*把p的数据域存入e所指的内存*/
    free(p);/*释放被删除的结点占用的内存空间*/ 
 } 
 

猜你喜欢

转载自blog.csdn.net/m0_46518461/article/details/107209153