文章目录
第九章:结构体
9.1-定义结构体类型
-
定义:结构体建立可存放多种不同数据类型,由若干数据项组成;数据类型不同是结构体与数组的根本区别
-
定义形式:
struct 结构体名 { 类型名1 成员名1; 类型名2 成员名2; ............. 类型名n 成员名n; }; # struct为关键字,结构体名为标识符。 # “struct 结构体名“为结构体类型名称 # 以分号”;“结尾 eg: struct Student { int num; char name[20]; char sex; int age; float score; char addr[30]; };
-
结构体可以嵌套使用
struct Date { int year, month, day; }; struct Student { int num; char name[20]; char sex; int age; struct Date birthday; char addr[30]; };
-
注意:编译系统结构体没有给结构体定义的各成员分配内存空间,占有内存的空间是具有结构体类型的变量----结构体变量
9.2-定义结构体变量
三种形式:
1.先定义结构体类型,再定义结构体类型的变量(较为灵活)
struct Student
{
char name[20];
short age;
char addr[30];
long tel;
};
struct Student stu1, stu2;
2.在声明类型的同时定义变量(不建议)
struct Student
{
char name[20];
short age;
char addr[30];
long tel;
}stu1,stu2;
3.直接定义结构体变量,无结构体名,称无名结构体
struct
{
char name[20];
short age;
char addr[30];
long tel;
}stu1,stu2;
结构体类型与结构体变量的区别
- 定义时先定义结构体类型,然后定义结构体变量
- 结构体类型不分配内存空间,变量分配空间,可以赋值、存取或运算
- 结构体类型中的成员名可以与程序的变量名相同
9.3-结构体变量的初始化
-
方法1:
struct Student { char name[20]; short age; char addr[30]; long tel; }stu1={“Wangwei”, 20, “Mingyuan Road”, 87532854};
-
方法2:
struct 结构体名 结构体变量={初始数据};
9.4-结构体变量的引用
-
结构体成员运算符
struct Student { char name[20]; short age; char addr[30]; }stu1; printf(“name:%s\nage:%d\naddress:%s\n”,stu1.name, stu1.age, stu1.addr); # stu1后面的.为结构体运算符
-
规则:
- 不能对结构体变量的整体进行赋值、输入,只能对结构体中的各个成员分别进行
- 允许一个结构体变量直接赋值给另一个具有相同结构的结构体变量
struct Student { char name[20]; short age; char addr[30]; }stu1,stu2; 错误示范:stu1={“Wangwei”,20, “Mingyuan Road”}; 正确示范: stu1.age=20; strcpy(stu1.name,”Zhang san”); stu2=stu1;
-
如果是嵌套定义,需要对最低一级的成员操作
stu1.birthday.month=10 stu1.birthday.day=24
-
结构体成员:同普通变量一样进行各种操作和运行
-
成员变量stu1.name是字符数组,可以对它进行字符串所允许的任何操作,包括直接引用其数组名进行输入和输出
scanf("%s",stu1.name); strcpy(stu1.name, "张杨");
-
可以对结构成员进行同类型普通变量所允许的任何操作
stu1.num=102; stu1.num++; stu1.sex='M'; stu1.sex=getchar( ); stu1.birthday.year=1993;
-
可以引用结构体变量成员的地址,也可以引用结构体变量的地址
scanf(“%d”, &stu1.num); //输入stu1.num的值 printf(“%o”, &stu1); //输出结构体变量stu1的起始地址
-
9.5-结构体数组
-
结构体数组:数组中每个元素都是一个结构体类型的数据
-
定义:
-
定义结构体类型的同时定义结构体数组
-
先定义结构体类型,然后再定义结构体数组
struct Student { char name[20]; char class[6]; float maths, computer, english; }; struct Student class1[20];
-
-
结构体数组的初始化:类似于多维数组初始化
struct Student { char name[20]; char class[6]; float maths, computer, english; }; struct Student class1[3]={ {”Wangwei”,”200107”,87,78,90}, {”Haoge”,”200108”,88,82,89}, {”Liumin”,”200109”,89,90,92}};
- 结构体数组在内存中连续存放
-
结构体数组成员的引用
-
表示方法:
结构体数组元素.结构体成员名 eg: printf("%s",class[0].name);
-
说明:结构体数组实际上是一种二维数据结构,第一维是数组元素,第二维是结构体成员。
-
9.6-结构体指针
指向结构体变量的指针
-
基本概念:
-
结构体类型变量定义后,系统就会为其在内存中分配一片连续的存储单元。该片存储单元的起始地址就称为该结构体类型变量的指针,可以定义一个指针变量来存放这个地址。
-
当把一个结构体类型变量的首地址存放到一个指针变量时,就称该指针变量指向这个结构体类型变量
-
-
定义方式:
struct Student *pt struct 结构体名 *指针名
-
用结构体变量间接访问结构体变量
stu1.成员名; (*pt).成员名; pt->成员名 eg: #include <stdio.h> #include <string.h> int main( ) { struct Student { char name[20], char sex; float score; }; struct Student stu1; struct Student *pt; //定义结构指针 pt=&stu1; //将pt指向结构体变量stu1 strcpy(stu1.name,“Lilin”) stu1.sex=‘M’; stu1.score=89.5; printf(“name:%s\nsex:%c\nscore:%.2f\n”, stu1.name, stu1.sex,stu1.score); printf(“name:%s\nsex:%c\nscore:%.2f\n”, (*pt).name, (*pt).sex, (*pt).score); printf(“name:%s\nsex:%c\nscore:%.2f\n”, pt->name, pt->sex, pt->score);
指向结构体数组的指针
#include <stdio.h>
struct Worker
{
int num;
char name[20],sex;
float salary;
}company[3]={{9901,"Limei",'F',1000.0},{9803,"Wangxing",'M',1200.0},{2006,"Xiafang",'F',980.0}};
void main()
{
struct Worker *p;
printf("the length of struct worker is:%d bytes.\n",sizeof(struct worker));
for(p=company; p<company+3; p++)
printf("%5d\t%-20s %2c %8.2f\n",p->num,p->name,p->sex,p->salary);
}
结构体变量和结构体指针作函数参数
- 值传递
- 用结构体变量的成员做参数
- 用结构体变量做参数
- 址传递
- 结构体变量或结构体数组作实参,将地址传给形参
9.7-用指针处理链表
结构体数组: struct student class[N]; 如何确定数组长度N?
N过小,学生超过N会导致数据溢出
N过大,造成系统资源的浪费
解决方法:链表和动态分配内存
链表概念
- 链表是由若干同样类型的元素通过依次串接方式构成的一种动态数据结构
- 链表每个元素称为结点,每个结点由程序中用到的数据和用来链接下一个结点的指针两部分组成
- 单向链表的最后一个结点中链接指针的值为NULL,以表示链表的末尾
- 链表用一个”头指针“变量来指向链表的开始
利用结构体变量构成链表
-
链表中结点的数据类型是结构体类型。利用结构体变量可构成链表这种数据结构
struct Student { char name[8]; float score; struct Student *next; };
-
链表中的结点可以不连续存放
-
链表的长度往往不确定,可以动态地创建结点,并为其分配存储空间
动态内存分配
-
malloc与free函数:
-
头文件:
stdlib.h
-
函数原型:
void *malloc(unsigned size); void free(void *ptr);
-
-
由malloc( )函数所分配的内存空间放在数据区的堆(Heap)中。堆是一个自由存储区域。堆地址在函数调用结束后仍然存在,可作为函数的返回值
-
在程序整个运行期间,堆区和堆栈区都处在动态的、不断变化的状态,统称为”动态存储区”,堆栈区由系统支配,而堆由编程者编写程序来管理
malloc()函数
void *malloc(unsigned size);
-
形参size: size用来指定所分配内存空间的大小,以字节为单位。各种数据类型都可以采用表达式”sizeof(数据类型)“作为实参指定内存空间大小
-
malloc()返回值:
- 执行成功:返回值为大小为size的连续内存空间首地址
- 执行失败:返回一个空指针NULL
-
若使用空指针,通常回引起系统破坏(破坏内存管理机制),因此在使用malloc函数时,必须检测其返回值是否不为空指针,否则可能因为堆区的内存资源耗尽而出错
-
检测程序的一般格式:
if((指针名=(类型*)malloc(空间大小))==NULL) { 出错处理操作 } or if(!(指针名=(类型 *)malloc(空间大小)) { 出错处理操作 }
-
-
将指针函数malloc()函数的返回值赋给其他各种非void型指针时,必须使用强制类型转换
-
常见格式:
- 用sizeof运算符计算分配给指定数据类型的存储单元字节数,并返回该存储单元的首地址
(数据类型 *) malloc(sizeof(数据类型)); p=(struct Student *)malloc(sizeof(struct Student));
calloc()函数
-
函数原型:
void *calloc(unsigned n, unsigned size);
-
作用:在内存的动态存储区中分配n个长度为size连续空间
-
返回值:
- 调用成功:函数返回一个指向分配域起始地址的指针
- 调用失败:返回NULL
free()函数
-
free()函数用来释放malloc()函数在堆区中所分配的内存空间,以便这些内存空间称为再分配时的可用空间
-
free函数的形参ptr也是通用类型指针,专门用来接受malloc()函数在堆区所分配的内存空间首地址
-
free()函数和malloc()、calloc()函数的配对函数
p=(struct Student *)malloc(sizeof(struct Student)); free(p); // 释放由指针变量p所指向的存储单元
尾插法建立动态链表
- 动态链表的建立是指在程序执行过程中从无到有地建立起一个链表、
- 基本思想:该方法从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表尾上,直到读入结束标志为止
# 写函数建议一个有3名学生的单向动态链表
struct Student
{
long num;
int score;
struct Student *next;
};
- 流程图:
struct Student *create(void)
{
struct Student *head,*pnew,*pend;
pnew=pend=(struct Student*)malloc(LEN);
if(pnew==NULL)
{
printf("No enough memory to alloc");
exit(0);
}
printf("input num,score:(num=0 stop)\n");
scanf("%ld,%d",&pnew->num,&pnew->score);
head=NULL;
while(pnew->num!=0)
{
n++;
if(n==1) head=pnew;
else pend->next=pnew;
pend=pnew;
pnew=(struct Student*)malloc(LEN);
if (pnew==NULL)
{
printf("No enough memory to alloc");
exit(0);
}
printf("input num,score:(num=0stop)\n");
scanf("%ld,%d",&pnew->num,&pnew->score);
}
pend->next=NULL;
free(pnew);
return(head);
}
输出链表
- 流程图:
void print(struct Student *head)
{
struct Student *p;
printf("Now:There %d records:\n",n);
p=head;
if(head==NULL) printf("It's empty!\n");
else
while(p)
{
printf("%ld,%d\n",p->num,p->score);
p=p->next;
}
}
链表的删除操作
- 过程:
(1)找到要删除的结点及其前驱结点
(2)修改指针,将其从链表中分离并释放其占用的空间
struct Student *del(struct Student *head,long num)
{
struct Student *p1,*p2;
if(head==NULL)
printf("\n null list !\n");
else if(head->num==num)
{ head=head->next;
n--;
}
else
{
p1=head;
while(p1->num!=num&&p1->next!=NULL)
{
p2=p1;
p1=p1->next;
}
if(p1->num==num)
{
p2->next=p1->next;
free(p1);
n--;
}
else
printf("%ld is not found!\n",num);
}
return head;
}
链表的插入操作
- 过程:
(1)找到插入位置及其前驱结点
(2)为插入的数据生成新结点
(2)修改指针,将新结点链入链表
9.8-共用体(联合体)
-
概念:多个变量共同占用一段内存的结构,称为共用体
union Data { char c; short i; float f; }a;
-
定义:
union 共用体名 { 成员列表; }变量表列;
-
定义形式:
1. union Data { char c; int i; float f; }a; 2. union Data { char c; int i; float f; }; union Data a; 3. union { char c; int i; float f; }a;
-
结构体、共用体比较
-
共用体变量的初始化:可以对共用体变量初始化,但初始化表中只能有一个常量
union Data { int i; char c; float f; }a={16}; 错误形式: union Data { int i; char c; float f; }a={16,’a’,1.5};
-
引用共用体变量的方式
-
先定义,再引用
-
只能引用成员,不能引用变量
union Data { int i; char c; float f; }a,b; a.c=‘a’; a.f=1.5; a.i=65; #错误示范 a=1; b=a; //C99允许 printf(“%d”,a);
-
共用体在某一瞬时,只有一个成员的值有效,即最近存入的成员变量的值,而原来的成员变量的值被覆盖
-
9.9-枚举类型
-
概念:如果一个变量只有几种可能的取值,则可定义为枚举类型。即:把可能的值一一举例出来,变量的值只限于出来的值的范围内
-
声明枚举类型
enum [枚举名]{元素名1,元素名2,... ,元素名n}; enum Wekdays{sun,mon,tue,wed,thu,fri,sat};
-
枚举变量的定义
- 先声明类型,再定义变量
- 声明类型的同时定义定义变量
enum Weekdays{ sun, mon,tue, wed, thu, fri, sat};
enum Weekdays workday, weekend;
enum Weekdays{ sun, mon,tue, wed, thu, fri, sat} workday, weekend;
-
说明:
-
系统将枚举元素作为符号常量来处理,故称枚举常量。不能看成变量对其赋值
eg:错误展示 enum Weekdays{ sun, mon, tue, wed, thu, fri, sat} workday, weekend; sun=0; mon=1;//不能对枚举常量赋值
-
每个枚举常量都代表一个整数,默认取值从0开始,依次增1
enum Weekdays{ sun, mon, tue, wed, thu, fri, sat} workday, weekend; workday=mon; printf(“%d”,workday);//输出整数1
-
也可在定义时人为指定枚举元素的值
enum Weekdays{ sun=7, mon=1, tue, wed, thu, fri, sat};
-
枚举元素可以用来作判断
enum Weekdays{ sun, mon, tue, wed, thu, fri, sat} workday; if(workday==mon)…
-
9.10-用typedef 声明新类型名
-
作用:使用typedef指定一种新的类型名来代替已有的类型名
-
两种情况:
-
简单地用一个新的类型名代替原有的类型名
typedef int Integer; //int->原有类型名,Integer->新类型名
-
命名一个简单的类型名代替复杂的类型表示方法
typedef struct { int month; int day; int year; }Date; Date birthday,*p; //用新类型名来定义变量 声明前: struct student { long num; int score; struct student *next; }; struct student *head,p1,p2; 声明后: typedef struct { long num; int score; struct student *next; }STUDENT; STUDENT *head,p1,p2;
-
-
通用形式:
typedef 类型名 标识符;
- 不产生新的数据类型,只为现有的类型定义了一个新的名字
-
使用typedef有利于程序的移植:
如:int 在不同系统字节存放数可能不一样,程序从某一个以4字节存放整数的计算机系统移植到2个字节存放整数的系统。
未使用typedef时:所有int改为long
采用typedef时:typedef int integer->typedef long integer;
即可
9.11-位段
对计算机而言,节省计算机内存资源是编程者需要考虑的问题。如年月日
typedef struct date
{
int year:12;
int month:4;
int day:5;
}Date;
-
定义形式
struct 结构名 { 类型 成员名:长度 }变量名;
-
访问形式
-
用成员运算符
结构体变量名.成员名 Date dt,*pd=&dt; dt.year=2010; (*pd).month=6;
-
用指向运算符
结构体指针名->成员名 pd->day=2;
-
-
注意事项
- 同一位段必须存储在同一存储单元中,不能跨单元
- 位段在表达式中引用时,会被系统自动转换为整型
C语言五种确立常规数据类型的途径
- 结构:同一名字下各种变量的组合
- 共用:使得两个或两个以上的不同的变量类型共用同一内存块
- 枚举:是符号目录表,按ANSI C新标准扩充
- typedef:为已存在类型产生一个新的类型名
- 位段(位域):一种结构体变量,允许访问一个字节的各个位