什么是自定义类型?
在C语言中,我们可以使用结构体(Struct),枚举(Enum),联合体(Union)来创建自定义的类型。
1.结构体
1.1结构体的介绍
结构体(Struct):结构体是一种用户定义的数据类型,用于将不同类型的数据组合在一起,形成一个新的复合类型。结构体由一组成员(member)组成,每个成员可以是不同的数据类型,如整型、字符型、浮点型、指针等。结构体的定义使用 struct 关键字,然后指定结构体的名称以及成员列表。
1.2结构体的声明
struct student { char name[20]; int age; char sex[5]; char id[20] };
在上述代码中,我们定义了一个名为student的结构体,包含了一个整形类型和三个字符类型的数组。
1.3特殊声明(匿名结构体类型)
struct { int a; float b; float c; }x;
在上述代码中,省略了结构体的标签——这就是匿名结构体类型。
1.4结构体自引用
我们可以在结构体中包含一个结构体,如果包含的这个结构体是该结构体本身的结构体指针即为结构体的自引用。
struct student { int age; char name[20]; struct student* s1; };
1.5结构体变量的定义和初始化
struct student { int age; char name[20]; }s1,s2;//在声明结构体类型时定义变量s1,s2 struct student s3;//定义结构体变量s3 struct point { int x; int y; }p1={1,1};//结构体嵌套初始化 struct point p2 = { 1,2 };//初始化
1.6结构体内存对齐
结构体内存对齐是用来计算结构体的大小的
如何计算?
首先得掌握结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
Linux中没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
实例:
struct S1 { char c1; int i; char c2; }; int main() { printf("%d \n", sizeof(struct S1)); return 0; }
为什么存在内存对齐?
大部分的参考资料都是如是说的:
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法
1.7修改默认对其数
#pragma pack(1)//设置默认对齐数为1 struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; double d; }; #pragma pack ()//取消设置默认对齐数,还原为默认
2.位段
2.1什么是位段
位段(Bitfields)是C语言中一种用于定义结构体成员的技术,它允许我们对结构体中的数据进行位级别的控制。
位段的目的是在结构体成员中存储各个字段(或位)的数据,并利用较少的内存空间。通常,在处理一些较小范围的整数值时,使用完整的字节或更大的数据类型可能会浪费内存。
位段的声明和结构体是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字
比如:
struct student { int _a : 2; int _b : 5; int _c : 10; int _d : 30; };
student就是一个位段类型。那student的大小呢?
2.2位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
实例:
struct S { char a:3; char b:4; char c:5; char d:4; }; struct S s = {0}; s.a = 10; s.b = 12; s.c = 3; s.d = 4;
3.枚举
3.1枚举的定义
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,当然在声明枚举类型的时候也可以赋初值。
例如:
enum Color//颜色 { RED=1, GREEN=2, BLUE=4 };
3.2枚举的优点
为什么使用枚举?
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 便于调试
4. 使用方便,一次可以定义多个常量
4.联合体(共用体)
联合体是一种特殊的数据类型,允许在相同的内存位置存储不同类型的数据。与结构体不同的是,联合体中的各个成员共享相同的内存空间。
4.1联合体类型的定义
//联合体类型的声明 union Un { char c; int i; }; int main() { //联合体变量的定义 union Un un; //计算联合体变量的大小 printf("%d\n", sizeof(un)); //打印结果为4 }
4.2联合体的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。
union Un { int i; char c; }; int main() { union Un un; printf("%d \n", &(un.i)); printf("%d \n", &(un.c)); un.i = 1; un.c = 2; printf("%d \n", un.i); return 0; }
实例:判断当前机器的大小端?
int Pd() { union { int i; char c; }un = { .i = 1 }; return un.c; } int main() { int ret = Pd(); if (ret == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
4.3联合体大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。