目录
前言
一、结构体
1.结构体的定义
2.结构体的声明
3.结构体成员类型
4.结构体成员的访问
5.结构体传参
6.结构体的特殊声明
7.结构体内存对齐
8.内存对齐及原因
9.修改默认对齐数
二、位段
1.位段的定义
2.演示
3.位段的应用
三、枚举
1.枚举的定义
2。举例
3.枚举的优点
四、联合(共用体)
1.联合的定义
2.联合体的具体应用
前言
我们终于来到了C语言最自由,最方便的类型----自定义类型
在C语言中,结构体(struct)指的是一种数据结构,是C语言中复合数据类型(aggregate data type)的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问。
struct tag
{
member-list;
}variable-list;
struct tag整个是类型,结构体是全局变量。其中struct是结构体的关键字。
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //不能缺少分号
#include
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
struct Stu stu;
struct Stu* p = &stu;
stu.age = 10;
}
我们通过(.)访问到了结构体Stu中的年龄,并将其赋值为10;我们还定义了一个指向stu的结构体指针,结构体指针当然也可以访问到结构体中的元素
下面是通过指针访问结构体的两种方式
#include
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
struct Stu stu = { {"张三"},{20},{"男"},{1234567} };
struct Stu* p = &stu;
stu.age = 10;
*(*p).id = 12345678;
printf("%s\n", p->name);
return 0;
}
我们这样就通过指针将学号修改,并且将学生张三的姓名打印出来了。
编译器让我们输入标识符,而当我们在main()函数中补充struct Stu stu;时 编译器提醒我们不完整的定义。
我们设想这样一种情况,我们定义两个匿名结构体,其中这两个结构体成员变量都是一模一样的,那么编译器会把它们当成一个结构体吗?
答案是,编译器会把它们当成两个结构体,当你要给其中一个结构体赋值时编译器不知道你要给哪个结构体赋值,所以这是一种不合法的用法。
结构体(struct)的数据成员,第一个数据成员存放的地址为结构体变量偏移量为0的地址处.
其他结构体成员自身对齐时,存放的地址为min{有效对齐值为自身对齐值, 指定对齐值} 的最小整数倍的地址处.
注:自身对齐值:结构体变量里每个成员的自身大小
注:指定对齐值:有宏 #pragma pack(N) 指定的值,这里面的 N一定是2的幂次方.如1,2,4,8,16等.如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8;
注:有效对齐值:结构体成员自身对齐时有效对齐值为自身对齐值与指定对齐值中 较小的一个.
总体对齐时,字节大小是min{所有成员中自身对齐值最大的, 指定对齐值} 的整数倍.
#pragma pack(N) 每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。--------以上摘自百度
我们知道了结构体的对齐规则,那我们看几道题吧
struct S1
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S1));
求s1在内存中所占的字节大小是多少
我们以VS2022环境为例,VS2022默认对齐数是8
Linux没有默认对齐数自身大小就是其对齐数
首先c1是char类型 占1个字节与8比,8大,对齐数是1,放在偏移量为0的位置
c2也是char 类型对其数是1放在1的整数倍处也就是放在偏移量为1的位置
i是int型占4个字节,最大对其数是4,放在偏移量为4的位置
最后s1偏移量为7一共占用了8个字节,8是最大偏移量4的整数倍,所以s1在内存中占用了8个字节
计算结构体成员相对起始位置的偏移量用offsetof-宏
需要引用头文件#include
元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为4或8)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐。
因此我们要尽量将小的成员结合在一起
#pragma pack( )(括号内部写用户自己定义的对齐数)
#pragma pack()括号内部什么都不加表示恢复默认对齐数
位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的好处:
而位域这种数据结构的缺点在于,其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的
代码如下(示例):
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
冒号后面的是设置成员占用的内存大小,单位是比特
普通的int型占用32个比特位,但设置后只占2个比热位,这也导致a只能表示(0,1,2,3)
因此位段可以节省内存,同时它还没有内存对齐
但是它却不能跨平台,因为C语言没有规定位段实现的具体细节,导致每个编译器对位段的实现各有不同。
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。
枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
通俗来说,枚举就是一个对象的所有可能取值的集合
#include
enum sex
{
male,
female,
secret,
};
枚举的关键字是enum 在括号内部注意每个成员名后面要加逗号,同时别忘了括号外面的分号
枚举内部的成员被依次赋值为0,1,2
我们假如将female赋值为1,secret就被赋值为2,male赋值为0
注意:
枚举是应用于具有相关信息的数据,枚举没有限定枚举常量的范围的功能的,所以不能定义几个相同的常量名字。
在计算机科学中,联合体(英语:union)又名共用体,是一种具有多个类型或格式的值,或者把它定义为一种由具有这样的值的变量形成的数据结构。一些编程语言可以支持被称为“联合体”的特殊的资料类型,来表示上述的变量。换句话说,一个联合体的定义(definition)会指定一些允许的可以存储在实例内的原始数据类型(例如整型,浮点)。和记录(record)(或结构,structure)那些可以被定义去包含一个浮点数或整型不同的是,在一个联合体任何时候只有一个值。
在C语言中,一个典型的例子如下:
union name1 { int a; float b; char c; } uvar;
联合体的关键字是union ,其中联合体中的各个成员变量在同一时间只能使用1个。
看到这里我们会有几个疑问?
1.联合体的大小是多少
2.联合体内部会有内存对齐吗
首先,联合体大小至少是最大成员的大小,联合体的成员是共用同一块内存空间。
我们知道数据在存储时是分大小端的,我们在前面的博客中已经写过了判断大小端的代码,这里我们可以用联合体来判断机器是什么存储类型
#include
int judge()
{
union J
{
char m;
int n;
};
J s1;
s1.n = 1;
return s1.m;
}
int main()
{
if (judge())
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
因为联合体共用的是低地址处的空间,所以我们只要看01是否被存入低地址就可以判断,当前环境是什么存储方式了。
总结
以上就是今天要讲的内容,希望大家多多支持。