文章目录
- 一、前言
- 二、结构体
- 2.1 概念
- 2.2 定义
- 2.2.1 通常情况下的定义
- 2.2.2 匿名结构体
- 2.3 结构体的自引用和嵌套
- 2.4 结构体变量的定义与初始化
- 2.5 结构体的内存对齐
- 2.6 结构体传参
- 2.7 结构体实现位段
- 三、枚举
- 3.1 概念
- 3.2 定义
- 3.3 枚举的优点
- 3.3.1 提高代码的可读性
- 3.3.2 防止非法值
- 3.3.3 方便维护
- 3.4 枚举的使用
- 3.4.1 基础使用
- 3.4.2 搭配switch使用
- 3.4.3 使用typedef简化枚举类型
- 四、联合体
- 4.1 概念
- 4.2 定义
- 4.3 联合体的特点
- 4.4 联合体的使用
- 4.4.1 基础使用
- 4.4.2 联合体的内存大小
- 4.5 应用场景
一、前言
本文主要是讲解C语言中的自定义类型,包括:结构体、枚举、联合体。
二、结构体
2.1 概念
在C语言中,结构体是一种强大的数据组织工具,它允许我们将不同类型的数据组合在一起,形成一个逻辑上的整体。通过合理使用结构体,可以提高代码的可读性、可维护性和复用性。
2.2 定义
2.2.1 通常情况下的定义
struct Book
{char name[20];int price;char id[12];
}b1,b2;
2.2.2 匿名结构体
匿名结构体通常用于一些临时性的、不需要多次使用的数据结构。由于没有类型名称,匿名结构体变量只能在声明它的作用域内使用。
struct
{char name[20];int price;char id[12];
}s;
2.3 结构体的自引用和嵌套
结构体可以包含自身类型的成员,这种特性称为自引用。例如,我们可以定义一个链表节点结构体,其中包含一个指向相同结构体类型的指针:
struct Node
{int data;struct Node* next;
};
2.4 结构体变量的定义与初始化
struct Book b1 = {"C Programming", 50, "123456789012"};
这里,b1是一个Book类型的结构体变量,并且在定义时初始化了它的所有成员。如果只初始化部分成员,未初始化的成员将自动初始化为零(对于数值类型)或空字符串(对于字符数组)。
2.5 结构体的内存对齐
在C语言中,结构体的内存布局受到内存对齐规则的影响。内存对齐的目的是提高数据访问的效率。以下是结构体内存对齐的一些基本规则:
- 第一个成员的对齐:结构体的第一个成员放在结构体变量在内存中存储位置的0偏移处开始。
- 后续成员的对齐:从第二个成员往后的所有成员,都放在一个对齐数(成员的大小和默认对齐数的较小值)的整数倍的地址处。
- 结构体总大小的对齐:结构体的总大小是结构体的所有成员的对齐数中最大的那个对齐数的整数倍。
- 嵌套结构体的对齐:如果结构体中嵌套了其他结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
第二点规则的例子:
struct Example
{char a; // 1字节short b; // 2字节int c; // 4字节char d; // 1字节
};
此时,内存应该如下图对齐:
把成员调换一下
struct Example
{char a; // 1字节int c; // 4字节short b; // 2字节char d; // 1字节
};
如下图:
2.6 结构体传参
在函数调用中,结构体变量可以作为参数传递。由于结构体可能包含多个成员,直接传递结构体变量可能会导致较大的内存拷贝开销。因此,建议在传递结构体时使用指针。例如:
void printBook(struct Book* book)
{printf("Name: %s\n", book->name);printf("Price: %d\n", book->price);printf("ID: %s\n", book->id);
}struct Book b1 = {"C Programming", 50, "123456789012"};
printBook(&b1);
2.7 结构体实现位段
位段是一种特殊的结构体成员,它允许我们指定成员占用的位数。位段通常用于硬件编程或需要精确控制内存布局的场景。例如:
struct S
{char a : 3; // a占3个bitchar b : 4;char c : 5;char d : 4;
};struct S s;
s.a = 7; // 二进制为0111
s.b = 15; // 二进制为1111
在这个例子中,a、b、c和d是位段成员,分别占用3、4、5和4个位。位段的空间是按照需要以4个字节(int)或者一个字节(char)的方式来开辟的。
三、枚举
3.1 概念
在C语言中,枚举(enum)是一种用户自定义的整数类型,它允许为整数值赋予有意义的名称。枚举类型通常用于表示一组相关的常量,使代码更具可读性和易维护性。
3.2 定义
默认是从0开始。
enum 枚举名 {枚举值1,枚举值2,枚举值3,...
};
例如,以下代码定义了一个表示一周七天的枚举类型Day,也可以自定义mon的值,这样它会根据最上面的值往下递增:
enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
};
3.3 枚举的优点
3.3.1 提高代码的可读性
枚举类型通过为整数值赋予有意义的名称,使代码更易于理解和维护。例如,使用枚举类型Day时,代码可以写成:
enum Day today = Fri;
3.3.2 防止非法值
枚举类型限制了变量可以取的值,从而防止非法值的赋值。例如,today变量只能取Mon到Sun中的某个值,而不能是其他任意整数。这有助于减少潜在的错误。
3.3.3 方便维护
当需要添加新的枚举值时,只需在枚举定义中添加新的名称,而无需修改其他代码。例如,如果需要添加一个表示“节假日”的枚举值,只需在Day中添加:
enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun,Holiday
};
3.4 枚举的使用
3.4.1 基础使用
enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
};int main()
{enum Day today = Mon;printf("today is %d\n", today);return 0;
}
3.4.2 搭配switch使用
enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
};int main()
{/*模拟键盘输入 */enum Day today = Mon;switch (today) {case Mon:printf("Today is Monday\n");break;case Tues:printf("Today is Tuesday\n");break;case Wed:printf("Today is Wednesday\n");break;case Thur:printf("Today is Thursday\n");break;case Fri:printf("Today is Friday\n");break;case Sat:printf("Today is Saturday\n");break;case Sun:printf("Today is Sunday\n");break;default:printf("Unknown day\n");}return 0;
}
3.4.3 使用typedef简化枚举类型
typedef enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
}Day_t;int main()
{/*模拟键盘输入 */Day_t today = Sat;switch (today) {case Mon:printf("Today is Monday\n");break;case Tues:printf("Today is Tuesday\n");break;case Wed:printf("Today is Wednesday\n");break;case Thur:printf("Today is Thursday\n");break;case Fri:printf("Today is Friday\n");break;case Sat:printf("Today is Saturday\n");break;case Sun:printf("Today is Sunday\n");break;default:printf("Unknown day\n");}return 0;
}
四、联合体
4.1 概念
在C语言中,联合体也叫共用体,联合体(Union)是一种特殊的数据结构,它允许在同一内存位置上存储不同类型的变量。换句话说,联合体中的所有成员都共享同一块内存空间,因此,联合体的大小是它最大成员所需的内存空间。联合体是一种有效的内存使用方式,尤其是在我们需要在不同时间点使用不同类型的数据时。
4.2 定义
在C语言中,联合体的定义使用关键字 union。与结构体不同,结构体中的每个成员都有自己的内存空间,而联合体中的所有成员共用同一块内存。其基本定义格式如下:
union Un {char c;int i;
};
在上述代码中,Un 是联合体的名字,c 和 i 是该联合体的成员。虽然它有两个成员,一个是字符型 char,另一个是整型 int,但是这两个成员是共享内存的。也就是说,它们存储的数据会覆盖彼此。
4.3 联合体的特点
- 共用内存:联合体的成员共享同一块内存,所有成员的起始地址相同。因此,每次只能存储一个成员的数据。修改一个成员的值会影响其他成员,因为它们共用同一块内存。
- 大小:联合体的大小由其最大成员的大小决定。即使联合体中有多个成员,它的大小将是最大的成员所占内存的大小。举个例子,在上述定义中,char 通常占 1 字节,int 通常占 4 字节,因此该联合体的大小通常是 4 字节(由 int 决定)。
- 内存优化:联合体提供了内存优化的功能,尤其适用于当某个变量需要存储不同类型的数据,但在同一时刻只需要存储其中一个数据时。例如,保存一个变量可能是整数,也可能是字符值,根据需要使用不同类型的数据时,使用联合体可以节省内存空间。
4.4 联合体的使用
4.4.1 基础使用
union Un
{char c;int i;
};int main()
{union Un u;u.c = 'A';u.i = 100;printf("c=%c i=%d\n", u.c, u.i);return 0;
}
此时,c=d i=100。
分析一下代码执行过程,首先u.c = ‘A’;此时联合体的内存内容是0x41。然后,u.i = 100;假设此时是小端模式则内存的内容是0x64。联合体是共享内存的所以u.c的内容已经被覆盖掉,char类型是一个字节,所以此时这个字节里面的内容用十进制表示就是100用ascii值表示就是d。
4.4.2 联合体的内存大小
#include <stdio.h>union Un {char c;int i;
};int main()
{union Un u;printf("Size of union Un: %zu bytes\n", sizeof(u));return 0;
}
根据上述代码,输出的联合体大小通常为 4 字节(这取决于系统架构)。这是因为 int 类型通常占 4 字节,而 char 占 1 字节。由于联合体共享内存,所以联合体的大小是最大成员(int 类型)所占的字节数。
4.5 应用场景
- 节省内存:当我们需要存储多种数据类型,但在某一时刻只需要其中之一时,使用联合体可以有效节省内存。例如,在编写操作系统或硬件驱动时,联合体常用于存储可能是不同类型的寄存器值。
- 类型转换:在一些需要类型转换的场合,联合体常被用作一种简便的方式来查看和修改不同数据类型的内存表示。例如,可以用联合体来将一个 float 类型的值解释为 int 类型的位表示,反之亦然。
#include <stdio.h>union Converter {float f;int i;
};int main()
{union Converter c;// 将 float 类型的值存储在联合体中c.f = 3.14f;// 打印 float 类型的值printf("Float value: %f\n", c.f);// 打印相应的 int 类型位表示printf("Int representation (as bits): %d\n", c.i);// 将 int 类型值赋给联合体的 i 成员c.i = 1078523331; // 通过 int 位表示修改 float 值// 打印修改后的 float 值printf("Modified float value: %f\n", c.f);return 0;
}