1:结构体类型创建
2:结构体初始化
3:结构体内存对齐
4:位段,位段计算机大小。
5:枚举+联合。
struct 结构体名{
结构体所包含的变量或数组
};
结构体的成员包括: 标量,数组,指针,其他结构体。
结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
};
stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化
结构体变量名.成员名;
通过这种方式可以获取成员的值,也可以给成员赋值
#include<stdio.h>
int main()
struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
} stu1;
//给结构体成员赋值
stu1.name = "Tom";
stu1.num = 12;
stu1.age = 18;
stu1.group = 'A';
stu1.score = 136.5;
//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
return 0;
}
Tom,学号12,18岁,A组136.5分
如果结构体访问指向变量的成员,有时候我们得到的不是一个结构体变量,而是一个只想结构体的指针。
struct S
{
char name[29];
int age;
}s;
void print(struct S* ps)
{
printf("name = %s,age=%d\n",(*ps).name,(*ps).age);
printf("name = %s,age=%d\n",ps->name,ps->age);
}
结构体的自引用
struct Node
{
int data;
struct Node* next;
};
用指针的方式进行自引用
struct B// //对结构体B进行不完整声明
struct A结构体A中包含指向结构体B的指针
{
int _a;
struct B* pb;
};
struct B
{
int _b;
struct A* pa;
};
有时候,必须声明一些相互之间存在依赖的结构。即:其中一个结构包含了另一个结构的一个成员或多个成员。
问题在于声明部分:如果每个结构都引用了其他结构的标签,哪个结构应该首先被声明呢?
该问题采用不完整声明来解决。它声明一个作为结构标签的标识符。
然后,把这个标签用在不需要知道这个结构的长度的声明中,声明指向这个结构的指针。接下来的声明把这个标签与成员列表联系在一起。
结构体内存对齐
结构体的内存对⻬是拿空间来换取时间的做法
结构体内存对齐规则:
1.第一个成员在与结构体变量偏移为0的地址处。
2.其他成员变量对对齐到某个数字的(对齐数)的整数倍的地址处。
//对齐数=编译器默认对齐数与该成员的较小值。
//vs默认对齐数为8,linux默认对齐数为4。
3.结构体总大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐原因:
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.性能原因::经过内存对齐之后,CPU的内存访问速度大大提升 ,内存不是一个个字节组成的,而是有2,4,6,,区分成的内存块称为内存读取粒度,内存对齐之后系统取数据时可以直接取出完整的数据,如果不对齐,可能需要在两块区读取数据,剔除并合并,额外操作降低cpu性能
结构体内存对齐理解及应用
1、数据成员对齐规则:结构的数据成员,第一个数据成员放在偏移地址0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
例1: struct {
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;
sizeof(A) = 6; 这个很好理解,三个short都为2。
sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。
例2:struct A{
int a;
char b;
short c;
};
struct B{
char b;
int a;
short c;
};
sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。
sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。
深究一下,为什么是这样,我们可以看看内存里的布局情况。
a b c
A的内存布局:1111, 1*, 11
b a c
B的内存布局:1***, 1111, 11**
其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。
B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。
再看一个结构中含有结构成员的例子:
例3:struct A{
int a;
double b;
float c;
};
struct B{
char e[2];
int f;
double g;
short h;
struct A i;
};
sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。
sizeof(B) = 48; 看看B的内存布局。
e f g h i
B的内存布局:11* *, 1111, 11111111, 11 * * * * * *, 1111* * * *, 11111111, 1111 * * * *
结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
讲解函数栈帧的时候,我们讲过函数传参的时候,参数是需要压栈的。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致
性能的下降。
###位段
声明
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有1个冒号和1个数字
位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
- 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位 时,是舍弃剩余的位还是利⽤,这是不确定的
总结:
跟结构相⽐,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存
在。
###枚举
枚举顾名思义就是⼀⼀列举。
把可能的取值⼀⼀列举
enum Day//星期
位段的跨平台问题
枚举
枚举类型的定义
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜⾊
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量 。这些可能取值都是有值的,默认从0开始,
⼀次递增1,当然在定义的时候也可以赋初值
需要注意的两点是:
- 枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的(严格来说是 main() 函数内部),不能再定义与它们名字相同的变量。
- Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。
重点内容
枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。
枚举的优点:
- 增加代码的可读性和可维护性
- 很#define定义的标识符⽐较枚举有类型检查,更加严谨。
- 防⽌了命名污染(封装)
- 便于调试
###(共⽤体)
联合也是⼀种特殊的⾃定义类型
这种类型定义的变量也包含⼀系列的成员,特征是这些成员公⽤同⼀块空间(所以联合也叫共⽤
体)。
特点 联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩ (因为联合⾄少得有能⼒保存最⼤的那个成员)。
联合⼤⼩的计算
联合的⼤⼩⾄少是最⼤成员的⼤⼩。
当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍