1.前言
在前期的C语言中,我们已经学习了很多的数据类型,必须int(整型),char(字符类型),float(单精度浮点型),double(双精度浮点型)等等。然而在实际的应用中,我们常常要对许多不同类型的变量进行定义,例如我们在学生管理系统中需要定义:姓名(char),学号(int),成绩(float)等许多变量,如果单独定义这些变量会显得冗杂。这时如果我们可以将这些变量规整为一个整体,则显得代码有序整洁,此时就要引入结构体这一数据类型。
2.何为结构体
struct Student
{
char Name[10];
int Id;
float Grade;
}Student = {"张三",2020,95.5};
int main()
{
printf("学生姓名:%s\n学号:%d\n成绩:%.2f\n", Student.Name, Student.Id, Student.Grade);
return 0;
}
struct为结构体类型的关键字
Student为此结构体的名字
{}中时成员列表
注意!!!结构体中的成员列表不能被初始化!!!
3.结构体的基础结构
(1)直接定义结构体类型
struct Student //结构体名
{
char Name[10]; //姓名
int Id; //学号
float Grade; //成绩
};
(2)定义结构体类型的同时定义结构体变量
struct Student //结构体名
{
char Name[10]; //姓名
int Id; //学号
float Grade; //成绩
}stu; //stu为结构体变量
(3)对结构体进行重命名
在此之前,我们先来学习一下重命名的知识!!
重命名的关键字是 typedef ,它的作用是将变量名提升成为类型名
例如:
typedef int INT; //将int类型重命名为INT
int a; //定义整型变量a
INT a; //定义整型变量a
其中的INT等价于int,所以最后两行代码的含义是一样的。
既然 typedef 可以重命名int类型,自然也可以重命名char类型,double类型以及struct类型等等
例如:
typedef struct Student //结构体名
{
char Name[10]; //姓名
int Id; //学号
float Grade; //成绩
}Student; //这时的Student将提升为类型名
此时的Student就是一个结构体类型。
那我们为什么要对结构体进行重命名???
struct Bank
{
char Name[10];
float Money;
};
struct Bank bank; //此方式正确
Bank bank; //此方式错误
typedef struct Student
{
char Name[10];
int Id;
float Grade;
}Student;
Student stu; //此方式正确
在C语言中,对于Bank结构体来说,没有进行重命名,则访问时需要引用结构体的名字后再定义
而Student结构体进行了重命名则直接可以调用Student进行定义
注意!!!C++中则可以直接定义
struct Bank
{
char Name[10];
float Money;
};
struct Bank bank; //此方式正确
Bank bank; //此方式正确
4.结构体的初始化
结构体变量可以进行初始化
!!!但是结构体成员列表不可被初始化!!!
例如:
struct Student
{
char Name[10];
int Id = 2020; //错误
int Id; //正确
float Grade;
}Student = {"张三",2020,95.5};
int main()
{
printf("学生姓名:%s\n学号:%d\n成绩:%.2f\n", Student.Name, Student.Id, Student.Grade);
return 0;
}
输出结果:
5.结构体的成员访问
结构体成员的访问使用”点“访问 使用"."进行
访问方式:(结构体类型变量名). (结构体成员)
例如:
typedef struct Student
{
char Name[10];
int Id;
float Grade;
}Student;
int main()
{
Student stu;
stu.Grade = 95.5; //对结构体成员进行赋值
stu.Id=2020;
strcpy(stu.Name,"zs");//使用strcpy函数对stu.Name赋值
printf("学生姓名:%s\n学号:%d\n成绩:%.2f\n", stu.Name, stu.Id, stu.Grade);
return 0;
}
输出结果:
6.结构体的设计 ----- 位段
(1)结构体内存分配
typedef struct Student
{
char Name[10];
int Id;
float Grade;
}Student;
编译器执行代码创建了一个结构变量Student并为其中的成员列表分配空间:
然而不难发现我们经常很难将每个空间都占满,导致内存空间的浪费,所以我们引入位段的概念,将结构体内存空间合理分配
(2)位段(位域)
位段(位域):位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的好处:
1)可以使数据单元节省储存空间,当程序需要成千上万个数单元时,这种方法就显得尤为重要。
2)位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。
(3)位段的使用
typedef struct S
{
//int占4Byte,32个bit
int _a : 3; //给_a分配3bit
int _b : 4; //给_b分配4bit
int _c : 7; //给_c分配7bit
};
int main()
{
printf("%d\n", sizeof(S));
return 0;
}
_a和_b同时占用了第一个Byte的空间,而_c因为第一个Byte只剩1bit不够存放,则新开辟一个Byte的空间。
所以一共需要2Byte的空间存储
但为什么输出结果是4呢???
这牵扯到字节对齐的知识,稍后会解答!!
!!!注意!!!:
1)一个位段必须存储在同一存储单元(即字)之中,不能跨两个单元。如果其单元空间不够,则剩余空间不用,从下一个单元起存放该位段。
2)使用位段必须保证是同一数据类型
7.字节对齐(计算结构体的大小)
(1)字节对齐的产生
struct A
{
int a;
char b;
char c;
};
struct B
{
char b;
int a;
char c;
};
对于以上两个结构体的区别,其实就是结构体的成员列表的顺序不同,这样对于结构体的内存空间的大小有什么区别呢???
对于以上两个结构体的大小,不妨计算一下:
不难看出仅仅是改变了结构体成员列表的顺序,就会导致结构体大小的不同!!!
这是因为编译器对数据进行了优化,做了字节对齐操作,可以增加内存的使用率和数据高效得传输。
(2)字节对齐的规则
1)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍。如有不满足,则需要在成员之间加上相应的填充字节。
2)结构体的总大小必须为最大对齐数的整数倍,如不满足,则需要在最后一个成员之后加上填充字。
struct A
{
int a; //4Byte
char b; //1Byte
char c; //1Byte
//总共是6Byte,但是不是最大数据类型int的整数倍
//进行字节填充,总大小:6Byte + (2Byte) = 8Byte
};
struct B
{
char b; //1Byte + (3Byte)= 4Byte
int a; //4Byte,前面总和的内存大小不是当前的整数倍
//进行字节填充
char c; //1Byte + (3Byte)= 4Byte,前面总和的内存大小不是当前的整数倍
//进行字节填充
//总大小:4Byte + 4Byte + 4Byte = 12Byte
};
图形解释:
(3)为什么要进行字节对齐
1) 便于cpu的快速访问,使cpu的性能达到最佳
2)节省内存的大小,合理利用存储空间
8.结构体的扩容 ----- 柔性数组
(1)什么是柔性数组
柔性数组又称为变长数组
与普通数组不同的是,柔性数组不需要明确定义数组大小,而是可以在程序运行时动态分配内存大小和容量
柔性数组通常为结构体成员列表的最后一个变量
struct A
{
int a;
char b;
char c[0]; //柔性数组
};
(2)柔性数组特点
1)用sizeof计算含有柔性数组的结构体时,不考虑柔性数组的内存大小
typedef struct A
{
int a;
char b;
char c[0]; //柔性数组
}A;
int main()
{
printf("%d\n", sizeof(A));
return 0;
}
输出结果:
2)使用柔性数组时,需要用malloc,calloc,realloc函数对柔性数组进行手动动态分配内存空间
typedef struct A
{
int a;
char b;
char c[0]; //柔性数组
}A;
int main()
{
A *ps = (A*)malloc(sizeof(A)); //动态分配内存空间
printf("%d\n", sizeof(A));
return 0;
}
9.结构体数组
(1)定义
结构体数组指在C语言中,是一个由结构体类型的元素组成的数组。这种数组允许我们存储多个结构体实例,并可以通过索引来访问每个结构体。
struct Student stu[10]; //结构体数组
(2)对结构体数组进行初始化
1)定义的同时进行初始化
struct Student
{
char Name[10];
int Age;
char Sex[10];
}students[10] =
{
{ "zs",15,"man" },
{ "ls",20,"man" },
{ "wmz",25,"woman" }
};
2)定义完成后通过下标进行初始化
typedef struct Student
{
char Name[10];
int Age;
char Sex[10];
}students[10];
int main()
{
Student stu[10];
strcpy(stu[0].Name, "zs");
stu[0].Age = 15;
strcpy(stu[0].Sex, "man");
printf("%s\n%d\n%s\n", stu[0].Name, stu[0].Age, stu[0].Sex);
return 0;
}
输出结果: