一 分类
①结构体 位段(位段算是一种特殊的结构体)
②枚举
③联合体(共用体)
二 结构体的探究
为什么要有结构体呢?结构体是对一个复杂对象的描述。如果有结构体的话,数据之间就相当于建立了联系。
1结构体的声明
一般在主函数前单独声明,这样就可以看作是一个全局变量,在函数中直接。如果在函数中使用的话,出了这个函数的作用域结构体变量的生命周期也就结束了。使用基本格式:
struct TAG
{
M_list;
}V_list;
注意:
① 声明结构体的时候,末尾的;不能省略
②一般结构体名是要使用大写字母的
③结构体类型包括两部分: struct (指定该类型为结构体)+结构体变量名。因此结构体类型是用户自定义的。
④结构体声明的时候系统并不会为他开辟空间。只有当结构体初始化的时候才会为他开辟空间吉安。因此在声明结构体的时候不可以对他进行初始化。
⑤结构体也可以嵌套。只不过接下来初始化的时候要在大括号内再加上大括号。
⑥可以使用typedef重命名。若使用的话,初始化可以直接使用s1进行初始化。
特殊:
2结构体的定义
结构体可以在声明结构体的时候创建变量,也可以在使用结构体的时候再创建变量,但是此时一个是全局变量,一个是局部变量
struct STUDENT
{
char name;
int age;
float score;
}s1;//全局变量
int main()
{
struct STUDENT s1;//局部变量
return 0;
}
3 结构体的初始化。
创建了结构体变量之后可以同时赋值,也可以先创建再赋值。
接下俩展示两种方式:
struct STUDENT
{
char name;
int age;
float score;
}s1;
int main()
{
//struct STUDENT s1= { 'zs',18,85.5 };//创建的同时赋值
struct STUDENT s1;
s1.name = 'zs';
s1.age = 18;
s1.score = 85.5;//先创建后赋值
return 0;
}
4 结构体操作符
①. 访问结构体的成员,但是有缺陷,不能对指针进行直接访问。如果非要用的话,可以写成(*p),name的形式。(p为指向结构体的指针)
②-> 适用范围比较广。可以先将结构体的地址保存到一个指针变量中。(节省空间)
struct STUDENT
{
char name;
int age;
float score;
};
int main()
{
struct STUDENT s1= { 'zs',18,85.5 };//创建的同时赋值
struct STUDENT* p = &s1;
printf("%d\n", p->age);
return 0;
}
5 匿名结构体
结构体使用的时候有时候为了避免重名,会使用。但是这种结构体没有结构体名称,因此在创建的时候应该在后跟一个变量列表。
需要注意的是,可能两个匿名结构体的成员完全一致,但是编译器却会把它当作里啷个完全不同的结构体。跟设计的初衷——避免重名其实也是吻合的。
匿名结构体只能使用一次。
6 结构体的自引用
相当于一个链式访问。
7 结构体内存对齐
① 如何进行结构体内存对齐操作
a.对于第一个结构体成员来说,偏移量为0。直接进行存储。
b.对于中间的各个变量来说,要将变量的大小和默认对其数进行对比,取较小的作为对齐数。此时变量存放的位置的对齐数的整数倍。(vs中默认为8)
c.对于结构体变量整体大小的确认,是最大对齐数的整数倍
d.如果有嵌套结构体,作为结构体成员的结构体对齐数是这个结构体中的最大对齐数。
我们来举例子体会一下
#define _CRT_SECURE_NO_WARNINGS 1
#include
struct STUDENT
{
char name;
int age;
float score;
};
int main()
{
printf("%d\n", (int)sizeof(struct STUDENT));
return 0;
}
②结构体成员的设置
由此可以看出来,结构体成员的不同顺序可能会开辟不同的空间。
那么怎么做才能减小空间的浪费?
将占用空间小的结构体成员排放在一起。
③为什么会存在内存对齐?
1. 平台原因 ( 移植原因 ) :
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因 :
数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
总体来说:
结构体的内存对齐是拿 空间 来换取 时间 的做法。
④如何修改默认对齐数?
#pragma pack();
在括号内加入自己所需的对齐数就好。
三 位段
1 什么是位段?
段位其实是一种特殊的结构体。但是他的成员只能是int,unsigned int,signed int,char类型。并且他所占空间是按照比特位进行计算的。
2 为什么会有位段?
刚才了解了结构体之后,发现结构体为了提升效率,有时候会牺牲空间。而段位的设计恰好就可以节省空间。
3位段的存储方式
struct S
{
char a : 3;//所占比特位
char b : 4;
char c : 5;
char d : 4;
};
对于这个位段来说,需要占用三个字节大小。
我们来分析一下:一个char类型共占用1个字节(8bit),因此先存放了a,b,剩下的一个空间不够存放c,因此再开辟一个字节,对于开辟的第二个空间来说,剩下3bit显然不足以放下d,因此需要再次开辟一个字节,这样一来一共开辟了3字节。
注意:位段中的对齐数是根据其中成员最大类型来定的。如果同时开辟int char两个类型的变量,如果char不够存放,那么此时开辟32个bit位(8个字节),因为int是相较于char成员类型大小更大。
4 位段在内存中的分配
我们先对上面的位段进行初始化。
struct S
{
char a : 3;//所占比特位
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;//2^3+2^1 (1)010->截断
s.b = 12;//2^3+2^2 1100
s.c = 3;//2^1+2^0 00011
s.d = 4;//2^2 0100
return 0;
}
我么通过一张图来了解一下。由于vs中是小端字节存储模式,因此如图所示开辟了3字节的空间。
对于a来说,由于存储的数字超过了空间,因此要发生截断。截断高位数字。
对于b来说,接着a进行存放
对于c来说,由于第一个字节的空间已经放不下了,因此开辟了一个字节存放,没有用到的位置存放0.
对于d来说,同c。
5 位段的跨平台问题
根据上面的描述,其实可以知道,位段是不跨平台的。
为什么?
①位段被当成有符号的还是无符号数是不确定的。
②位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
③位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。上述例子假设是从右到左在内存中分配。
④是否利用剩余的空间无法确定。上述的例子是舍弃。
四 枚举
1 声明与定义
#define _CRT_SECURE_NO_WARNINGS 1
#include
enum SEX
{
MALE,//0
FEMALE,//1
SECRET//2
};//上面是可能取值
int main()
{
enum SEX s = MALE;//直接用上述的变量进行初始化
return 0;
}
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。
也可以对MALE进行赋值,如果赋值1,那么FEMALE,SECRET变成2,3.
2 使用
用来定义常量
3 优点
①增强代码可读性。比如上述例子中MALE,FEMALE,SECRET就是性别中的三种取值。相当于进行了封装
②传递参数错误。不能直接进行常数的传递,避免使用的过程中混淆了常数代表的含义。
③去除equals两者判断。由于常量值地址唯一,使用枚举可以直接通过“==”进行两个值之间的对比,性能会有所提高。
④编译优势
五 联合体(共用体)
1 定义和初始化。
其实自定义类型函数的定义和初始化差不多,可以参照结构体
#define _CRT_SECURE_NO_WARNINGS 1
#include
union STUDENT
{
char name;
int age;
float score;
}s;
int main()
{
printf("%d\n", (int)sizeof(s));//4
return 0;
}
2 特点
共用体中成员共用一块内存空间。
3 内存计算规则
①至少是最大成员的大小
②当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
4 联合体使用
#define _CRT_SECURE_NO_WARNINGS 1
#include
int check_system()
{
union C
{
char n;
int m;
}c;
c.m = 1;//共用一块内存空间,对m进行修改也是对n进行修改
return c.n;
};
int main()
{
if (check_system() == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
以判断大小端存储为例子,这样一来相当于进行了封装。代码风格更加优秀。