C语言结构体变量与链表知识总结
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
结构体与链表
11.1 结构体类型的定义
结构体是由C语言中的基本数据类型构成的、并用一个标识符来命名的各种变量的组合,其中可以使用不同的数据类型。
1.结构体类型的定义
Struct结构体名
{ 类型标识符1 成员名1;
类型标识符2 成员名2;
……
类型标识符n 成员名n;
};
Struct结构体名——结构体类型名
2.关于结构体类型的说明:
(1)“struct 结构体名”是一个类型名,它和int、float等作用一样可以用来定义变量。
(2)结构体名是结构体的标识符不是变量名,也不是类型名。
(3)构成结构体的每一个类型变量称为结构体成员,它像数组的元素一样,单数组中元素以下标来访问,而结构体是按结构体变量名来访问成员的。
(4)结构体中的各成员既可以属于不同的类型,也可以属于相同的类型。
(5)成员也可以是一个结构体类型,如:
Struct date
{
Int month;
Int day;
Int year;
};
Struct person
{
Float num;
Char name[20];
Char sex;
Int age;
Struct date birthday;
Char address[10];
};
11.2 结构体类型变量
11.2.1 结构体类型变量的定义
1.先定义结构体类型,再定义结构体变量
形式:
Struct 结构体名
{类型标识符1 成员名1;
类型标识符2 成员名2;
……
类型标识符n 成员名n;
};
Struct 结构体名变量名表;
例如:
Struct student
{char name[20];
Char sex;
Int age;
Float score;
};
Struct student stu1,stu2;
2.在定义结构体类型的同时定义变量形式:
Struct 结构体名
{
类型标识符1 成员名1;
类型标识符2 成员名2;
……
类型标识符n 成员名n;
}变量名表;
例如:
Struct student
{
Char name[20];
Char sex;
Int age;
Float score;
}stu1,stu2;
3.用匿名形式直接定义结构体类型变量形式:
Struct
{
类型标识符1 成员名1;
类型标识符2 成员名2;
……
类型标识符n 成员名n;
}变量名表;
例如:
Struct
Char naem[20];
Char sex;
Int age;
Float score;
}stu1,stu2;
11.2.2 结构体变量的使用
结构体是一种新的数据类型,因此结构体变量也可以像其它类型的变量一样赋值、运算,不同的是结构体变量以成员作为基本变量。
结构体成员的表示方式为:结构体变量名.成员名
其中的圆点运算符称为成员运算符,它的运算级别最高。
如果将“结构体变量名.成员名”看成一个整体,则这个整体的数据类型与结构体中该成员的数据类型相同,这样就可以像前面所讲的变量那样使用,但应遵循以下规则:
(1)不能将一个结构体变量作为一个整体进行输入和输出,而只能对结构体变量中的各个成员分别进行输入和输出。
Struct date
{
Int month;
Int day;
Itn year;
}day;
Scanf(“%d%d%d”,day);(错误)
Scanf(“%d%d%d”,&day.year,&day.month,&day.day);(正确)
Printf(“%d%d%d”,day);(错误)
Printf(“%d%d%d”,day.year,day.month,day.day);(正确)
(2)如果成员本身又属于一个结构体类型,则要用若干个成员运算符,一级一级地找到最底的一级的成员,只能对最底级的成员进行赋值或存取运算。
Struct date
{
Int month;
Int day;
Int year;
};
Struct student
{
Long num;
Char naem[20];
Char sex;
Int age;
Struct date birthday;
Char depart[10];
}stu1;
如:stu1.birthday.year=2004;
Stu1.birthday.month=12;
(3)对结构体变量的成员可以像同类型普通变量一样进行各种运算。
11.2.3 结构体变量的初始化与存储
1.结构体变量的初始化:在定义结构体变量的同时给它赋以初值。
Struct student
{
Char name[20];
Char sex;
Int age;
Float score;
}stu1,stu2={“wangwu”,’m’,20,88.5};
2.结构体变量所占内存的字节数
·struct 类型用内存字节数=?
·是所有成员变量的内存总和吗?
用运算符sizeof获得结构体大小
Sizeof(变量或表达式)
Sizeof(类型)
Typedef struct sample
{
Char m1;
Int m2;
Char m3;
}SAMPLE;
Printf(“%d\n”,sizeof(struct sample));
Printf(“%d\n”,sizeof(SAMPLE));
并非所有成员变量的内存总和
事实上,所有数据类型在内存中都是从偶数地址开始存放的且结构所占的实际空间一般是按照机器字长对齐的不同编译器、平台,对齐方式会有变化
结构体变量的成员存储对齐规则是与机器相关的
具有特定数据类型的数据项大小也是与机器相关的
所以一个结构体在内存中的存储格式也是与机器相关的
3.结构体变量存储分配示意图
11.3 结构体类型数组
11.3.1 结构体数组的定义与使用
结构体是一种新的数据类型,同样可以有结构体数组。
1、结构体数组的定义
结构体数组就是具有相同结构体类型的变量集合。
Struct 结构体名
{
类型标识符1 成员名1;
类型标识符2 成员名2;
……
类型标识符n 成员名n;
}数组名[整型常量表达式];
加入要定义一个班级100个同学的学号、姓名、性别、年龄,可以定义成一个结构体数组。
如下所示:
Struct{
Long num;
Char name[20];
Char sex;
Int age;
}stu[100];
11.3.2 结构体数组的初始化
一般形式是在定义的数组后面加上={初始值表列};
Struct student
{
Long num;
Char name[20];
Char sex;
Int age;
Float score;
Char add[5];
}stu[3]={
{101,”WGJ”,’M’,28,88.5,”CS”}
{102,”DYH”,’F’,26,88.0,”CS”}
{103,”DYC”,’M’,24,78.5,”CS”}
};
11.3.3 结构体数组的使用
结构体数组成员的访问十一数组元素为结构体变量的,其形式为:结构体数组元素名.成员名
如:stu[2].age
【例11.1】候选人得票的统计。
设有三个候选人,每次输入一个得票的候选人名字,要求最后输出各人得票结果。
Struct person
{
Char name[20];
Int count;
}leader[3]={“Zhang”,0,”Li”,0,”Wang”,0};
Main()
{
Int i,j;
Char leader_name[20];
For(i=1;i<=10;i++){
Scanf(“%s”,leader_name);
For(j=0;j<3;j++)
If(strcmp(leader_name,leader[j].name)==0)
Leader[j].count++;
}
Printf(“\n”);
For(i=0;i<3;i++)
Printf(“%5s:%d\n”,leader[i].name,leader[i].count);
}
11.4 结构体类型指针
一个结构体变量的指针就是该变量所占据的内存段的起始地址。
可以定义一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。
11.4.1 指向结构体变量的指针
1.指向一个结构体变量的指针定义形式:
Struct 结构体名*指针变量名;
例如:
Struct student
{
Long int num;
Char name[20];
Char sex;
Float score;
};
Struct student *pstu,stu;
Pstu=&stu;
2.由指向结构体变量的指针变量引用结构体成员的形式为:
(*指针变量).成员名或指针变量名->成员名
->为指向运算符
如:上面示例用指针变量引用其成员的方式为:
(*pstu).num , (*pstu).name
(*pstu).sex , (*pstu).score
可以等价表示为:
Pstu->num,pstu->name,pstu->sex,pstu->score
例11.2 比较结构体成员的几种引用方式
#include “stdio.h”
Main(){
Struct student{
Long int num;
Char name[20];
Char sex;
Float score;
};
Struct student stu_1;
Struct student *p;
P=&stu_1;
Stu_1.num=200601;
Strcpy(stu_,”liping”);
Stu_1.sex=’M’;
Stu_1.score=811.5;
Printf(“No.:%ld\nname:%ssex:%c\nscore:%f\n”,stu_1.num,sut_,stu_1.score);
Printf(“\nNo.:%ld\nname:%s\nsex:%cscore:%f\n”,(*p).num,(*p).name,(*p).sex,(*p).score); Printf(“\nNo.:%ld\nname:%s\nsex:%c\nscore:%f\n”,p->num,p->name,p->sex,p->score);
}
11.4.2 指向结构体数组元素的指针
一个指针变量可以指向一个结构体数组元素(将该结构体数组的数组元素地址赋给此指针变量)。
例如:
Struct
{
Int a;
Float b;
}arr[3],*p;
P=arr;
此时使p指向arr数组的第一个元素,“p=arr;”等价于“p=&arr[0];”。
若执行“p++;”则此时指针变量p此时指向arr[1]。
例11.3 输出数组中各元素中各成员的值。
Struct student
{
Int num;
Char name[20];
Char sex;
Int age;
};
Struct student stu[3]={
{10101,”zhang”,’m’,18},
{10102,”li”,’m’,19},
{10103,”wang”,’f’,20}
};
Main(){
Struct student *p;
Printf(“ sex age\n”);
For(p=stu;p<stu+3;p++)
Printf(“%5d%-20s%2c%4d\n”,p->num,p->name,p->sex,p->age);
}
注意:
(1)如果p的初值为stu,即指向第一个元素,则p+1后指向下一个元素。
请区别:(++p)->num
和(p++)->num
(2)指针p已定义为指向struct student类型的数据,它只能指向该结构体类型数据,而不能指向一元素的某以成员(即p的地址不能是成员的地址)。
如下面的赋值是错误的:p=&stu.num 编译时将给出“警告”信息,表示地址的类型不匹配。
不要认为反正p是存放地址的,可以将任何地址赋给它。
11.5 结构体与函数
11.5.1 结构体变量作函数参数
将一个结构体变量的值传递给另一个函数,可以采用以下两种方式:
·用结构体变量的成员作参数。
用法和普通变量作实参是一样的,属“值传递”方式。
·形参与实参都用结构体变量
直接将实参结构体变量的各个成员之全部传递给形参的结构体变量。
注意:实参和形参类型应当完全一致。
例11.4 有一个结构体变量stu,内含学生学号、姓名和三门课的成绩。
要求在main函数中赋值,在另一函数print中将它们打印输出,程序如下。
Struct stu
{
Int num;
Char name[20];
Int score[3];
};
Main(){
V oid print(struct stu p);
Struct stu stu;int j;
Scanf(“%d”,&stu.num);
Scanf(“%s”,);
For(j=0;j<3;j++)
Scanf(“%d”,&stu.score[j]);
Print(stu);
}
V oid print(struct stu p){
Int j;
Printf(“%d\n%s\n”,p.num,);
For(j=0;j<3;j++)
Printf(“%d\n”,p.score[j]);
}
注意:ANSI C允许用整个结构体作为函数的参数传递,但是必须保证实参与形参的类型相同。
·值得指出的是,把一个完整的结构体变量作为参数传递,虽然合法,但要将全部成员一个一个传递,既费时间又费空间,开销大,因此一般不采用。
11.5.2 用指向结构体的指针作函数参数
用指向结构体变量(或数组)的起始地址传给形参。
形参为指针变量,实参为结构体变量的地址或指向结构体变量的指针。
例11.4的print函数形参改用指向结构体指针后程序如下:
#include “stdio.h”
Struct student
{
Int num;
Char name[20];
Int score[3];
};
Main()
{void print(struct student *p);
Struct student stu;int j;
Scanf(“%d”,&stu.num);
Strcpy(,”liping”);
For(j=0;j<3;j++)
Scanf(“%d”,&stu.score[j]);
Print(&stu);
}
V oid print(struct student *p)
{
Int j;
Printf(“%d\n%s\n”,p->num,p->name);
For(j=0;j<3;j++)
Printf(“%d\n”,p->score[j]);
}
11.5.3 返回结构体类型值的函数
ANSI C还允许函数返回结构体类型的值。
设有struct student 类型,结构体变量定义如下:Struct student stud;
若函数input()的功能是输入一个学生结构体数据,并将其返回给学生记录stud,即:
#include “stdio.h”
Struct student
{int num;
Char name[10];
Int score[3];
};
Struct student input()
{
Int k;
Struct student stud;
Scanf(“%d”,&stud.num);
Getchar();
Gets();
For(k=0;k<3;k++)
Scanf(“%d”,&stud.score[k]);
Return stud;
}
Main(){
Struct student stud;
Int k;
Stud=input();
Pritnf(“%d%s”,stud.num,);
For(k=0;k<3;k++)
Pritnf(“%d”,stud.score[k]);
}
11.6 链表
11.6.1 链表概述
链表是一种常见的重要的数据结构,它是动态地进行存储分配的一种结构,根据需要开辟内存单元,使用链表可以减少内存空间的浪费。
所谓链表是指若干个结构体变量(每个结构体变量称为一个“结点”)按一定的原则连接起来。
每个结构体变量都包含有若干个数据和一个指向下一个结构体变量的指针,依靠这些指针将所有的结构体变量连接成一个链表。
例:建立和输出一个简单的链表
Struct student
{
Long num;
Char name[20];
Struct student *next;
};
Main(){
Struct student a,b,c,*head,*p;
A.num=2002001;b.num=2002002;c.num=2002003;
Strcpy(,”zhang”);
Strcpy(,”sun”);
Strcpy(,”li”);
Head=&a;
A.next=&b;
B.Next=&c;
C.Next=NULL;
P=head;
Do
{
Printf(“%ld,%s”,p->num,p->name);
P=p->next;
}while(p!=NULL);
}
11.6.2 内存管理库函数
链表结构是动态分配存储的,即在需要时才开辟一个结点的存储单元,怎样开辟呢?C语言编译系统中提供了以下有关的函数:
1、分配存储空间函数malloc()
Malloc()函数的原型为:
V oid *malloc(unsigned int size);
函数的作用是在内存自由空间开辟一块大小为size字节的空间,并将此存储空间的起始地址作为函数值带回。
例如,malloc(10)的结果是分配了一个长度为10字节的内存空间,若系统设定的此内存空间的起始地址为1800,则malloc(10)的函数返回值就为1800.
例11.6 malloc函数的使用
Main(){
Int j,n,*p;
Printf(“\nplease input a data to n:”);
Scanf(“%d”,&n);
P=(int *)malloc(n*sizeof(int));
Printf(“\nplease input %d datas:”,n);
For(j=0;j<n;j++)
Scanf(“%d”,p+j);
For(j=0;j<n;j++)
Printf(“%4d”,*(p+j));
}
2.calloc函数
函数原型为:
V oid *calloc(unsigned int num,unsigned int size);
功能为:分配num个size字节的空间。
3.重新分配空间函数realloc()
函数原型为:
V oid *realloc(void *ptr ,unsigned int size);
函数用于使已分配的空间改变大小,即重新分配。
其作用是将ptr指向的存储区(是原先用malloc函数分配的)大小改变为size个字节,可以使原先的分配区扩大也可以缩小。
它的返回值是一个指针,即新的存储区的首地址。
4.释放空间函数free()
该函数的原型为:
V oid free(void *ptr);
该函数的功能为:
将指针ptr指向的存储空间释放,交还给系统,系统可以另行分配作它用。
必须指出,ptr值不能是随意的地址,而是只能是程序在运行时通过动态申请分配到的存储空间的首地址。
下面的做法是正确的:
Pt=(int *)malloc(10);
……
……
Free(pt);
11.6.4 链表的基本操作
链表的基本操作包括建立动态链表、链表的插入、删除、输出和查找等。
1.建立动态链表
所谓建立动态链表是指一个一个地输入各结点数据,并建立起各结点前后相链的关系。
建立动态链表有两种方式:一种是在链表尾插入节点,一种是在链表头插入结点。
例:写一个函数建立一个若干学生数据的单向动态链表。
(要求按学号从小到大的顺序建立) #define NULL 0
Struct student
{
Long num;
Struct student *next;
};
Struct student *creat()
{
Struct student *head,*p,*q;
Long x;
q=head=(struct student *)malloc(sizeof(struct student)); Head->next=NULL;
Printf(“\nplease input datas to the list:”);
Scanf(“%ld”,&x);
While(x!=0){
P=(struct student *)malloc(sizeof(struct student));
P->num=x;
P->next=NULL;
q->next=p;
q=p;
Scanf(“%ld”,&x);
}
Return (head);
}
2.输出链表
V oid print(struct student *head)
{
Struct student *p;
P=head->next;
Printf(“\nthe list is:”);
If(p==NULL)
Printf(“the list is NULL!\n”);
While(p!=NULL)
{
Printf(“%6ld”,p->num);
P=p->next;
}
Printf(“\n”);
}
3.删除链表中的一个结点
V oid del(struct student *head)
{
Struct student *p,*q;
Long num;
Printf(“\nplease input the student’ num you want to delete:”); Scanf(“%ld”,&num);
P=head->next;
While(num!=p->num && p->next!=NULL){
Q=p;
P=p->next;
}
If(num==p->num)
{q->next=p->next;
Free(p);}else
Printf(“\n%ld is not found!\n”,num);
}
4.在链表中插入一个结点
V oid insert(struct student *head)
{
Struct student *p,*q,*t;
P=(struct student *)malloc(sizeof(struct student));
Printf(“\nplease input the student you want to insert:”);
Scanf(“%ld”,&p->num);
Q=head;
While(q-next!=NULL && q->next-num<q->num)
Q=q->next;
P->next=q->next;
Q->next=p;
}
5.对链表的综合操作
V oid main(){
Struct student *la;
La=creat();
Print(la);
Del(la);
Print(la);
Insert(la);
Print(la);
}
11.6.3 链表与结构体数组的主要区别
1、数组的元素个数是固定的,而组成链表的结点个数可按需要增减;
2、数组中的元素顺序关系由元素在数组中的位置(即下标)确定,链表中的结点顺序关系由结点所包含的指针来体现。
3、对于不是固定长度的列表,用可能最大长度的数组来描述,会浪费许多内存空间。
另外,对于元素的插入、删除操作非常频繁的列表处理场合,用数组表示列表也是不适宜的。
若用链表实现,会使程序结构清晰,处理的方法也较为简单。