一、结构体类型
C语言的数据类型有多种,其中,构造数据类型是把多个数据结合在一起,每一个数据被称为构造类型的“成员”。数组就是构造类型中的一种,只是数组是由多个相同数据类型的“成员”组成;而结构体可以由多个不同数据类型的“成员”构成。
1.结构体类型的定义
(1)结构体类型定义的一般形式
使用结构体类型时,首先要“构造”它,如同在调用函数之前要先定义函数一样。结构体类型的定义方式是:
struct 结构体类型名称{
数据类型 成员名1;
数据类型 成员名2;
......
数据类型 成员n;
};
说明:
(1)struct是关键字,结构体类型名称的命名规则满足标识符命名规则。
(2)结构体类型中的“成员”由大括号“{ }”括起来,用来说明该结构体有哪些成员以及各成员的数据类型。
(3)结构体类型定义末尾括号后的分号“ ; ”并不可少。
(2)类型定义
C语言允许用户使用typedef语句定义新的数据类型名代替已有的数据类型名。类型定义的一般形式为: typedef 类型名 新类型名
其中,typedef是关键字,类型名是标准类型名或用户自定义的构造类型名,新类型名是对已有类型重新定义的名称。例如: typedef int INTEGER;
将int类型重新定义为INTEGER,在程序中就可以用INTEGER定义变量。例如: INTEGER x,y; 其功能与“ int x,y; ”是等价的。
使用typedef只是定义了一个新的类型名称来代替已有的类型名,并没有建立一个新的数据类型。
2.结构体变量的定义
结构体类型定义之后,就可以作为一种已存在的数据类型使用。但是,它只是一个“模型”,并没有具体的数据,编译器也没有在内存中为它分配存储空间。为了在程序中可以使用结构体类型的数据,必须定义结构体变量。结构体变量的定义有如下3种方式。
(1)先定义结构体类型,再定义结构体变量
语法结构:
struct 结构体类型名称{
数据类型 成员名1;
......
数据类型 成员n;
};struct 结构体类型名 变量名;
例如:
struct student{
int sno;
char name[20];
char classname[20];
int grade[3];
};
struct student stu;
(2)在定义结构体类型的同时定义结构体变量
语法结构:
struct 结构体类型名称{
数据类型 成员名1;
......
数据类型 成员n;
}结构体变量;
例如:
struct student{
int sno;
char name[20];
char classname[20];
int grade[3];
}stu;
(3)直接定义结构体变量
采用这种方式定义的结构体没有类型名称,如果需要在后面的程序中定义同类型的变量就不方便了。
语法结构:
struct {
数据类型 成员名1;
......
数据类型 成员n;
}结构体变量;
例如:
struct {
int sno;
char name[20];
char classname[20];
int grade[3];
}stu;
结构体变量定义后,编译器就会为其分配内存。它所占用的实际字节数,就是其各个“成员”所占用字节数的总和。上述例子的变量stu中各成员所占用内存如下代码输出的运行结果:
#include <stdio.h>
struct{
int sno;
char name[20];
char classname[20];
int grade[3];
}stu;
int main(){
printf("内存占用的字节数:%d\n",sizeof(stu));
return 0;
}
3.结构体变量的初始化
为结构体变量初始化的过程,就是为结构体各个成员初始化的过程。结构体变量的初始化有以下两种方式。
(1)在定义结构体类型和结构体变量的同时为结构体变量初始化。
例如:
struct student{
int sno;
char name[20];
char classname[20];
int grade[3];
}stu1={ 2022,"李华","软件1班",{95,86,85} };
上述实例定义了结构体变量stu1,并且进行了初始化,此时变量stu1的存储结构如图:
(2)定义结构体类型后为结构体变量初始化
例如:
struct student{
int sno;
char name[20];
char classname[20];
int grade[3];
};
struct student stu1={ 2022,"李华","软件1班",{95,86,85} };
4.结构体变量的引用
为结构体变量的初始化,完成了对结构体变量中所有“成员”的赋值操作。那么,如何引用结构体变量中的一个成员。
引用结构体变量的语法结构是: 结构体变量名.成员名
例如: stu1.sno=2020; stu1.name="李华"
其中“ . ” 是一个运算符,表示对结构体变量的成员进行访问,它的优先级最高,结合性是从左到右。
例题:定义一个名称为student的结构体类型来描述学生信息,该信息的成员由学号、姓名、班级、课程成绩组成。通过键盘输入一个学生信息,并且输出这个学生的所有信息。
#include <stdio.h>
struct student{
int sno;
char name[20];
char classname[20];
float grade[3];
};
int main(){
struct student stu1;
printf("请输入学生信息:学号、姓名、班级、三门课程的成绩\n");
scanf("%d",&stu1.sno);
scanf("%s",&stu1.name);
scanf("%s",&stu1.classname);
scanf("%f%f%f",&stu1.grade[0],&stu1.grade[1],&stu1.grade[2]);
printf("\n结构体变量stu1的信息为:\n");
printf("学号:%d\n",stu1.sno);
printf("姓名:%s\n",stu1.name);
printf("班级:%s\n",stu1.classname);
printf("课程1:%.2f\n",stu1.grade[0]);
printf("课程2:%.2f\n",stu1.grade[1]);
printf("课程3:%.2f\n",stu1.grade[2]);
return 0;
}
运行结果
二、结构体数组
描述10个学生的信息,就需要定义一个长度为10的student类型的数组,这个数组就是结构体数组。
struct student{
int sno;
char name[20];
char classname[20];
float grade[3];
};
struct student stu1[10];
本例中,定义了一个包含10个元素的数组stu1,数组元素stu1[0]、stu[1]...的类型都是结构体类型student。
三、结构体指针
当一个指针变量指向一个结构体变量时,被称为结构体指针变量。结构体指针变量的定义方式与一般指针类似。
例如:
struct student s;
struct student *p=&s;
上述代码中,定义了一个结构体指针变量p,并且将结构体变量s的地址赋值给p,也就是说,p就是指向结构体变量s的指针。
通过结构体指针变量访问结构体变量中的成员,有两种方式,其语法结构如下:
(1) (*结构体指针变量).成员名
(2)结构体指针变量->成员名
例题:改写上面的例子,用指针变量引用结构体变量的成员。
#include <stdio.h>
struct student{
int sno;
char name[20];
char classname[20];
float grade[3];
};
int main(){
struct student stu1;
struct student *p=&stu1;
printf("请输入学生信息:学号、姓名、班级、三门课程的成绩\n");
scanf("%d",&(*p).sno);
scanf("%s",&(*p).name);
scanf("%s",&(*p).classname);
scanf("%f%f%f",&(*p).grade[0],&(*p).grade[1],&(*p).grade[2]);
printf("\n结构体变量stu1的信息为:\n");
printf("学号:%d\n",(*p).sno);
printf("姓名:%s\n",(*p).name);
printf("班级:%s\n",(*p).classname);
printf("课程1:%.2f\n",(*p).grade[0]);
printf("课程2:%.2f\n",(*p).grade[1]);
printf("课程3:%.2f\n",(*p).grade[2]);
return 0;
}
运行结果 :和上面一样的。
四、结构体与函数
函数的参数和返回值类型可以是基本数据类型的变量、指针、数组,也可以是结构体类型的变量、指针、数组。结构体与函数的关系只要分为3种:结构体变量作为函数参数,结构体指针作为函数参数,函数的返回值是结构体类型。
例题:以结构体指针作为函数参数,定义一个函数,其功能是输出学生信息。
#include <stdio.h>
struct student{
int sno;
char name[20];
char classname[20];
float grade[3];
};
void show(struct student stu){
printf("学号:%d\n",stu.sno);
printf("姓名:%s\n",stu.name);
printf("班级:%s\n",stu.classname);
printf("课程1:%.2f\n",stu.grade[0]);
printf("课程2:%.2f\n",stu.grade[1]);
printf("课程3:%.2f\n",stu.grade[2]);
}
int main(){
struct student a={2022,"李华","软件1班",{100,90,80}};
show(a);
return 0;
}
结构体变量作为函数参数时,实参传递给形参的方式时值传递,即传递的是结构体各成员的值。如果结构体的成员较多,则传递的字节数就很多,这样会降低程序的执行效率。因此,建议使用结构体指针作为函数参数,这时参数传递给形参的方式就是地址传递,实参和形参指向同一块内存空间。
五、链表
1.链表的概念
链表是一种常见的数据结构,由两部分组成。
(1)头指针。图中以h表示,存储的是第一个结点的首地址。
(2)结点。链表中每一个元素称为一个结点,每一结点包含以下两个部分。
· 数据域:存储用户需要的数据。
· 指针域:存储下一个结点的地址。
链表中最后一个结点称为尾结点,其指针域为NULL,表示空地址,即不指向其他元素,表示链表的结束。可以看出,h指向第1个结点,第1个结点又指向第2个结点,直到最后一个结点。这就是一个“链”,一环扣一环。
链表和数组都可以存储大批量的数据,它们之间的区别如下。
(1)数组定义时必须指定数组长度,所占用的内存空间大小固定。链表则是在执行过程中根据需要动态申请空间,结点的个数可以根据需要增加或减少。
(2)数组元素的查找通过数组下标和数组首地址确定。链表的查找必须从第一个结点开始依次查找。
2.链表的实现
1.链表的构成
链表是由若干个结点通过指针域连接起来的。结点的成员包含数据域和指针域,可以使用结构体类型定义结点。
struct node{
int data;
struct node *next; //指针变量,指向结构体变量,存储下一个结点
};
其中,结构体成员data存储整型数据,指针类型的成员next指向自己所在的结构体类型struct node的数据,存储下一个结点的地址。
2.链表的建立
(1)初始化链表
程序代码:
struct node *h,*s,*p;
h=NULL; //空链表
s=(struct node*)malloc(sizeof(struct node)); //为链表的一个结点申请存储空间
s->.next=NULL; //结合体指针s访问成员next赋NULL
h=s;
(2)在尾节点后插入新结点
程序代码:
p=(struct node*)malloc(sizeof(struct node));
p->data=i;
p->next=NULL;
s->next=p;
s=p;
(3)根据链表中所需结点的个数,循环执行步骤(2)所说明的操作,建立链表结构。
程序代码:
for(i=0;i<3;i++){
p=(struct node*)malloc(sizeof(struct node));
p->data=i+1;
p->next=NULL;
s->next=p;
s=p;
}
3、处理链表所需要的函数
C语言提供了一些内存管理函数,这些内存管理可以按需动态地分配内存空间,也可以把不在使用的空间回收,为有效地利用内存资源提供了手段。
常用的内存管理函数有malloc(),calloc(),free(),realloc()这4个函数。
malloc(),calloc(),free(),realloc()这4个函数的声明在stdlib.h头文件中,在用到这些函数时需要用“#include <stdlib.h>”命令将头文件包含到程序文件中。
1. malloc()函数
malloc()函数的原形为: void *malloc(unsigned size);
其调用形式为: (类型说明符*)malloc(size);
功能:在内存的动态存储区中分配一块长度为size字节的连续区域。函数的返回值为该区域的首地址,这个地址是void类型,因此,在调用时要用“类型说明符”进行强制类型转换。如果分配失败,则返回一个NULL指针。例如:
char *p;
p=(char *)malloc(100);
表示分配100个字节的内存空间,函数的返回值为指向该内存空间的指针,把该指针强制转换为字符指针并赋值给变量p。
可用如下语句为链表的一个结点申请存储空间:
p=(struct node *)malloc(sizeof(struct node));
2. calloc()函数
calloc()函数的原形为: void *calloc(int n,int size);
其调用形式为: (类型说明符*)calloc(n,size);
功能:在内存的形态存储区中分配n个长度为size的连续空间。函数返回所分配存储空间的首地址;如果分配失败,则返回NULL。
calloc()函数与malloc()函数的区别仅在于一次可以分配n块连续存储区域。例如:
p=(struct node*)calloc(2,sizeof(struct node));
其中的sizeof(struct node)是求node结构体的长度。该语句的意思是:按node的长度分配2块连续区域,强制转换为node指针类型,并赋值给指针变量p。
3. realloc()函数
realloc()函数的原形为: void *realloc(void *ptr,unsigned newsize);
其调用形式为: (类型说明符*)realloc(p,newsize);
功能:重新分配内存,将p所指向的已分配内存区的大小改为newsize,newsize可以比原来分配的空间大或小。它的返回值为该内存区域的地址;如果分配失败,则返回NULL。
4. free()函数
free()函数的原形为: void free(void *p);
其调用形式为; free(p);
功能:释放p所指向的一块内存空间,p是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区是由malloc()或calloc()函数所分配的区域。
六、共用体类型
C语言中,共用体类型同结构体类型一样,也属于构造类型,它的类型定义和变量定义与结构体的类型定义和变量定义和变量定义方法相似。但是,它们的成员在内存中的存储结构不同。结构体类型的成员分别占用存储空间,而共用体类型的成员使用同一块存储空间。
1.共用体类型的定义
定义共用体类型的语法结构是:
union 共用体类型名称{
数据类型 成员名1;
......
数据类型 成员名n;
};
例如:
union data{
int x;
double y;
char z;
};
上述代码定义了一个名为data的共用体类型,该类型由3个成员组成,它们共享同一块存储空间。
2.共用体变量的定义
共用体变量定义和结构体变量的定义类似,可以采用3种方式。本节中仅就“先定义共用体类型,再定义共用体变量”方式进行说明。
例如:
union data{
int x;
double y;
char z;
};
union data d2;
上述代码定义了一个共用体变量d2。该变量的3个成员分别需要占用内存4字节、8字节、1字节,编译器为共用体变量d2分配空间是按照其成员中字节数最大的数目分配,即为变量d2分配了8字节的存储空间。
3.共用体变量的初始化和引用
在定义共用体变量的同时,只能对其中一个成员进行初始化操作,这与它的内存分配方式是对应的。
例如:
union data{
int x;
double y;
char z;
};
union data d2={6};
上述语句用于对data类型的共用体变量d2进行初始化,而且只对成员x进行了赋值6的操作。
共用体变量成员引用与结构体变量成员的引用方法相同。其语法结构是:
共用体变量名.成员名;
例如: d2.x=6;
由于共用体变量的成员在内存中共用同一块存储空间,所有在某一个时刻只能存放一个成员的值。