在C
语言中,可以使用结构体(Struct
)来存放一组不同类型的数据。结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member
)。结构体的定义形式为:
struct 结构体名{
结构体所包含的变量或数组
};
1. 定义结构体变量
结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间。结构体变量才包含了实实在在的数据,需要内存空间来存储。
- 方式一:先定义结构体,再定义结构体变量
//定义stu结构体
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
};
//定义两个结构体变量
struct stu stu1, stu2;
stu
为结构体名,里面包含name、num、age、group、score这5个成员。stu1
和stu2
则为两个stu类型的结构体变量。
- 方式二:在定义结构体的同时定义结构体变量
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
} stu1 stu2;
直接将变量放在结构体的最后即可。
如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名。
- 注意:在结构体内部定义结构体
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
struct sub1{
char group; //所在学习小组
} sub1;
struct sub2{
float score; //成绩
};
};
如上所示,在stu
结构体里还定义了『结构体变量sub1』和『结构体sub2』,由于sub2
没有定义变量,所以其内部成员score
即为母结构体stu的成员变量。
2. 成员的获取和赋值
使用点号.
获取结构体变量的单个成员,然后再进行赋值操作。
//给结构体成员赋值
stu1.name = "Tom";
stu1.num = 12;
stu1.age = 18;
stu1.group = 'A';
stu1.score = 135;
//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
//打印结果
Tom的学号是12,年龄是18,在A组,今年的成绩是135!
也可以在定义结构体变量时整体赋值:
struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };
3. 结构体的内存分配
结构体中各成员在内存中是按顺序依次存储的,成员之间不互相影响,各占用不同的内存空间。结构体变量占用的内存大于等于所有成员占用的内存的总和,因为成员在存储时需要遵循结构体的内存对齐规则,成员之间可能会存在裂缝。
结构体内存对齐
4. 计算结构体的内存大小
先来看看结构体的内存对齐规则:
1. 数据成员对⻬规则:结构体的第⼀个数据成员会存放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组, 结构体等)的整数倍开始存储(⽐如int为4字节,则要从4的整数倍地址开始存储)。
2. 结构体作为成员:如果⼀个结构里有某些结构体成员,则结构体成员要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储。(struct a⾥存有struct b,b⾥有char, int, double等元素,那b应该从8的整数倍开始存储.)
3. 总内存对齐:结构体的总⼤⼩,也就是sizeof的结果,必须是其内部最⼤成员所占内存大小的整数倍,不⾜的要补⻬。若结构体a里包含结构体成员b,则需要将a的其他成员和b里的成员相比,得到最大成员内存,再按照最大内存的倍数进行补齐。
看完内存对齐规则是不是感觉有点绕?不急,接下来通过分析具体例子来理解这个规则。
示例1:含有多种数据类型成员
struct Struct {
double a; //8 (0-7)
char b; //1 [8 1] (8)
int c; //4 [9 4] 9 10 11 (12 13 14 15)
short d; //2 [16 2] (16 17)
} struct1;
//输出内存大小
printf("struct1 = %lu \n", sizeof(struct1));
//输出结果
struct1 = 24
输出结果分析:
double a
:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。char b
:b只占用1个字节内存,会按顺序存放在位置8处。int c
:c占用4字节内存,本该从位置9开始存放,但根据规则1可知,c必须从4的整数倍开始存储,而9不是4的整数倍,需要后移到位置12才开始存储,所以c存放在结构体内存中的12-15的位置。short d
:d占用2字节内存,可以接着从位置16开始存储,所以存放在16-17的位置。
根据上面的分析可知,struct1的成员总共需要18字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为24。
sizeof:是一个运算符,用来计算传进来的数据类型占用多大的内存,在编译时即可完成运算。比如:sizeof(int)为4字节,sizeof(double)为8字节。
示例2:交换成员位置
struct Struct {
double a; //8 (0-7)
int c; //4 (8 9 10 11)
char b; //1 (12)
short d; //2 [13 2] (14 15) - 16
} struct2;
//输出内存大小
printf("struct2 = %lu \n", sizeof(struct2));
//输出结果
struct2 = 24
这次在示例1中struct的基础上交换了成员b和c的位置,输出结果就不一样了,分析如下:
double a
:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。int c
:c占用4字节内存,可以接着从位置8开始存储,所以c存放在结构体内存中的8-11的位置。char b
:b只占用1个字节内存,可以直接存放在位置12处。short d
:d占用2字节内存,本该从位置13开始存放,根据规则1可知,由于13不是2的倍数,需要后移到位置14开始存储,所以存放在14-15的位置。
根据上面的分析可知,struct2的成员总共需要16字节内存,根据规则3,struct2的内存大小必须是8(double a)的整数倍,所以最后内存大小为16。
示例3:结构体嵌套结构体
直接在struct1里加上一个struct2成员,然后输出内存大小
//在最后加上成员e
struct Struct1 {
double a; // 8
char b; // 1
int c; // 4
short d; // 2
struct Struct2 e; //16
} struct1;
//输出内存大小
printf("struct1 = %lu \n", sizeof(struct1));
//输出结果
struct1 = 40
从之前struct1的分析可知,a、b、c、d实际占用18字节(位置0-17),那成员e就需要从位置18开始存放。由于e是个结构体,根据规则2,当结构体作为成员时,需要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储。结构体e中内存占用最大的元素是double a
,为8字节,所以e就需要从8的整数倍地址开始存储,即后移到位置24开始存储,e本身占用16字节内存,所以存放位置是24-39。
根据上面的分析可知,struct1的成员总共需要40字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为40。
对于这个示例,大家可以改变成员e在Struct1中的位置,或者更改struct1和e里的成员类型,再看下输出结果和自己计算的结果是否相同。
作者:东篱采桑人
链接:https://www.jianshu.com/p/60b64935b0a5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。