常见的自定义类型有结构体、枚举、联合体,本篇博客总结了这些自定义类型,方便自己的学习。也希望文档能帮助大家。
结构体
在C语言第七课,就讲解了结构体的内容。主要有结构体的声明、结构体的传参、结构体的成员访问。
结构体在数据结构中应用的非常广泛。比如最基本的单链表就可以用如下的结构体表示。
struct Node
{
int data;
struct Node* next;
};
可以发现对于一个数组,如果要对它的元素进行增删改查,就需要对它前后的元素进行移动;而单链表只需要将插入位置相邻的指针,指向一个新的位置就可以了。
结构体的大小
规定: 1.结构体的第一个成员,存放在结构体变量开始位置的0偏移处。
2.第二个成员开始,都要对齐到整数倍的地址处对齐数:成员自身大小和默认对齐数(8的较小值。
3.结构体的总大小,必须是最大对齐数的整数倍。最大对齐数:成员的对齐数中最大的那个。
3.1.Linux环境下没有默认对齐数。对齐数就是成员自身的大小。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举例说明
使用offsetof可以看到每一个成员的偏移量。
例一:
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", offsetof(struct S1, c1));
printf("%d\n", offsetof(struct S1, i));
printf("%d\n", offsetof(struct S1, c2));
return 0;
}
由1可知:第一个char类型的变量,放在位置零。 int成员的大小为四,默认对齐数为八,由2可知,要对齐到4的倍数的位置,所以int占的空间为4~7。 第三个char类型的变量只占一个比特位。然而由3可知,最大对齐数是四,所以结构体的大小必须是4的整数倍,所以会浪费三个空间。
例二
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S2));
printf("%d\n", offsetof(struct S2, c1));
printf("%d\n", offsetof(struct S2, c2));
printf("%d\n", offsetof(struct S2, i));
return 0;
}
由1可知:第一个char类型的变量,放在位置零。 第二个char类型的变量只占一个比特位。 int成员的大小为四,默认对齐数为八,由2可知,要对齐到4的倍数的位置,所以int占的空间为4~7。 然而由3可知,最大对齐数是四,所以结构体的大小必须是4的整数倍,所以会浪费三个空间。 对比这两个结构体,我们可以知道将占用空间小的结构体成员放在一起,可以使结构体的大小变小
例三
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S3));
printf("%d\n", offsetof(struct S3, d));
printf("%d\n", offsetof(struct S3, c));
printf("%d\n", offsetof(struct S3, i));
return 0;
}
由1可知:第一个double类型的变量,从位置零开始一直到位置七。 第二个char类型的变量只占一个比特位,由2可知,只用放到一的整数倍处。 int成员的大小为四,默认对齐数为八,由2可知,要对齐到4的倍数的位置,所以int占的空间为12~14。 然而由3可知,最大对齐数是八,所以结构体的大小必须是八的整数倍,所以会浪费一个空间。
例四
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
printf("%d\n", offsetof(struct S4, c1));
printf("%d\n", offsetof(struct S4, s3));
printf("%d\n", offsetof(struct S4, d));
return 0;
}
由1可知:第一个char类型的变量,从位置零开始。 第二个属于嵌套结构体类型,由4可知:需要对齐到八偏移,从八开始到二十三字节。 最后一个double成员的大小为八,默认对齐数为八,由2可知,要对齐到八的倍数的位置,所以double占的空间为24~31。
为什么会有内存对齐
-
平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 -
性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
修改默认对齐数
使用 #pragma 这个预处理指令,可以改变我们的默认对齐数。 把默认对齐数修改一下,改大了没有任何意义,要改小!
#pragma pack(1)
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
位段
位段的声明和结构是类似的,有两个不同:
- 位段的成员必须是 int、unsigned int 或signed int 。
- 位段的成员名后边有一个冒号和一个数字。
假设表示年龄 int age:10;甚至这个都能够表示0~1023了,很明显如果使用16bit的int存储,任然很浪费内存。 也就是说有些数据不需要使用那么多的空间,那么就采用位段的方式。
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
printf("%d\n", sizeof(struct A));
printf("%d\n", sizeof(struct S));
return 0;
}
在网络上传输一个信息时,比如我要发送一个“多喝热水”,就需要在这个数据上添加发送端的地址和接收端的地址,除此之外还有其他的一些变量。想象一下如果不加上这些东西,我发的消息是不是网上的人都知道了?那不就乱套了吗? 而网络上传输数据的时候,如果数据包过大或数据包太多,就会造成拥堵。那么最好就是要压缩数据,比如标志位只需要三个比特位就够了,我就没有必要创建一个char变量。 1.在早期的16位机器下,sizeof(int)是16,现在sizeof(int)是32。2.关于机器是大端存储还是小端存储完全取决于编译器。3.第二个位段比较大,无法容纳于第一个位段,开辟一个后,舍弃还是利用第一个位段的剩余空间? 基于这几个方面的问题,体现出了位段的跨平台性不够好。
枚举
枚举类型的定义
枚举顾名思义就是一一列举。 把可能的取值一一列举。 比如我们现实生活中: 一周的星期一到星期日是有限的7天,可以一一列举。
enum Day
{
Mon,
Tues,
Wed,
Thir,
Fri,
Sta,
Sun
};
枚举的使用
枚举变量的默认值从上到下是0 1 2 ……
enum Sex
{
MALE,
FEMALE,
SECRET
};
int main()
{
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
return 0;
}
枚举类型是一个常量,如果觉得枚举的值不妥,还可以自己给他赋值。比如下面的代码: 对枚举类型使用typedef定义为一个新的变量Sex。 枚举变量的大小是一个整型。
typedef enum Sex
{
MALE=1,
FEMALE=3,
SECRET=5
}Sex;
int main()
{
enum Sex s = SECRET;
Sex s2 = MALE;
printf("%d\n", sizeof(s2));
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
return 0;
}
枚举的优点
与 #define 相比,用枚举有什么优点? 枚举的优点:
- 增加代码的可读性和可维护性
- #define定义的标识符只是做一个替换,并没有表示这个值是什么类型,而枚举是整型。
- 防止了命名污染(封装)
- 便于调试。#define是不能进行调试的,在预处理的时候,两条代码就合成了一条代码。
- 使用方便,一次可以定义多个常量。
做项目的时候就可以感受带枚举的优点。
联合体
使用方法:比如学校的信息管理系统 学生:名字 年龄 身份 老师:名字 年龄 职称 身份和职称就可以作为一个联合体变量
联合类型的定义
联合体有一个更加贴切的名字:共用体。
union Un
{
char c;
int i;
};
int main()
{
union Un u = {0};
u.c = 'w';
u.i = 0x11223344;
printf("%d\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
return 0;
}
可以看出它的两个成员的地址都是相同的,也就是说:它的两个成员共用一个空间,同一时间上,只能用一个。
联合大小的计算
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。 比如第一个联合体,默认对齐数为8,最大对齐数是 默认对齐数 和 变量大小 的较小值,所以最大对齐数是4。需要对齐到最大对齐数的整数倍处也就是8的位置。
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
}
再比如第二个联合体,默认对齐数为8,最大对齐数是 默认对齐数 和 变量大小 的较小值,所以最大对齐数是4。需要对齐到最大对齐数的整数倍处也就是16的位置。
|