- 联合体 {#1-联合体} ===============
在 C 语言中,联合体又叫共用体(Union)是一种特殊的数据类型,定义联合体的语法如下:
|---------------------|---------------------------------------------------|
| 1 2 3 4 5 6
| union 联合体名 { 成员类型 成员名1; 成员类型 成员名2; ... };
|
定义联合体变量的语法如下:
|-----------|-------------------------|
| 1
| union 联合体名 变量名;
|
从语法上来看联合体和结构体非常的类似只是将关键字struct
替换成了union
,但是,二者在使用的时候有很大区别,其特点如下:
联合体的不同成员共享同一块内存,它们的值互相覆盖。
联合体的大小由最大成员的大小确定。
下面是一个示例,展示如何定义联合体并使用联合体变量:
|------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| // 定义一个联合体类型 union Data { unsigned char c; unsigned int i; unsigned short s; char str[20]; }; int main() { // 定义联合体变量 union Data data; // 打印所有成员的地址 printf("data.c address = %p\n", &data.c); printf("data.i address = %p\n", &data.i); printf("data.s address = %p\n", &data.s); printf("data.str address = %p\n", &data.str); // 打印联合体大小 printf("union size = %zd\n", sizeof(data)); // 设置联合体成员的值并打印 data.i = 10; printf("整数值: %d\n", data.i); data.s = 314; printf("浮点数值: %d\n", data.s); strcpy(data.str, "Hello, Dabing!"); printf("字符串值: %s\n", data.str); data.i = 0xffabcd98; printf("data.c = %x\n", data.c); printf("data.s = %x\n", data.s); printf("data.i = %x\n", data.i); printf("========================\n"); data.c = 0; printf("data.c = %x\n", data.c); printf("data.s = %x\n", data.s); printf("data.i = %x\n", data.i); return 0; }
|
程序输出的结果如下:
|---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| data.c address = 000000875EEFFB38 data.i address = 000000875EEFFB38 data.s address = 000000875EEFFB38 data.str address = 000000875EEFFB38 union size = 20 整数值: 10 浮点数值: 314 字符串值: Hello, Dabing! data.c = 98 data.s = cd98 data.i = ffabcd98 ======================== data.c = 0 data.s = cd00 data.i = ffabcd00
|
通过输出的结果可以证明以下结论:
- 联合体中各个数据成员共用同一块内存,因为他们的起始地址是相同的
- 联合体各个数据成员之间会发生数据覆盖
- 联合体的大小等于占用存储空间最大的成员的大小
另外,还有一个细节需要说明:联合体中的各个成员的类型可能不同,所以在取数据的时候操作的内存大小也不尽相同。
data.i = 0xffabcd98;
给整形成员赋值,使用了4个字节的内存printf("data.c = %x\n", data.c);
从一个字节的内存中读数据0xffabcd98
低地址位的一个字节中存储的数据是0x98
printf("data.s = %x\n", data.s);
从两个字节的内存中读数据0xffabcd98
低地址位的两个字节中存储的数据是0xcd98
printf("data.i = %x\n", data.i);
从四个字节的内存中读数据
联合体应用:验证当前主机的大小端(字节序)
大小端(Endianness)指的是在多字节数据类型(如整数)在内存中存储时的字节顺序。
- 大端字节序(Big Endian)是指高位字节存储在低地址
- 小端字节序(Little Endian)是指低位字节存储在低地址。
举个例子,假设我们有一个 4 字节的整数值
0x12345678
在内存中存储的情况如下:
- 大端字节序:地址由低到高的顺序依次存储为
12
34
56
78
- 小端字节序:地址由低到高的顺序依次存储为
78
56
34
12
不同的处理器架构在存储数据时可能采用不同的字节序。例如,x86 架构使用小端字节序,而 PowerPC 架构使用大端字节序。因此,在处理跨平台数据交换时需要注意字节序的问题。
示例代码如下:
|------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdio.h> union MyData { unsigned int data; struct { unsigned char byte0; unsigned char byte1; unsigned char byte2; unsigned char byte3; }byte; }; int main() { union MyData num; num.data = 0x12345678; if (0x78 == num.byte.byte0) { printf("Little endian\n"); } else if (0x78 == num.byte.byte3) { printf("Big endian\n"); } return 0; }
|
由于联合体内部的data
和byte
成员是共用同一块内存,并且二者占用的内存大小相同,所以通过结构体成员byte
就可以非常轻松的取出data
中各个字节的值,程序中是对最高位的字节值(byte.byte3
)和最低位的字节值(byte.byte0
)进行了判断。
- 枚举 {#2-枚举} =============
枚举(Enumeration)是一种在编程语言中表示一组具名常量的数据类型。枚举常常用于定义一组相关的离散值,比如颜色、星期几、月份等。在 C 语言中,可以使用 enum
关键字定义枚举类型。
定义枚举类型的语法如下:
|-----------|-----------------------------------|
| 1
| enum 枚举名 { 值1, 值2, ... };
|
枚举值默认从 0 开始,依次递增。可以显式指定枚举值的值,如 MONDAY = 1, TUESDAY = 2, ...
。并且枚举值是常量,不能在程序中用赋值语句再对它赋值。
下面是一个示例,展示如何定义和使用枚举:
|---------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <stdio.h> // 定义一个枚举类型 enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }; int main() { // 定义枚举变量 enum Weekday today; // 设置枚举变量的值 today = WEDNESDAY; // 使用 switch 语句根据枚举变量的值进行处理 switch (today) { case MONDAY: printf("Today is Monday.\n"); break; case TUESDAY: printf("Today is Tuesday.\n"); break; case WEDNESDAY: printf("Today is Wednesday.\n"); break; case THURSDAY: printf("Today is Thursday.\n"); break; case FRIDAY: printf("Today is Friday.\n"); break; case SATURDAY: printf("Today is Saturday.\n"); break; case SUNDAY: printf("Today is Sunday.\n"); break; default: printf("Invalid day.\n"); } return 0; }
|
在上述示例中,使用 enum
关键字定义了一个名为 Weekday
的枚举类型,其中包含了一周的七个枚举值。然后,在 main
函数中,声明了一个名为 today
的枚举变量,并通过赋值将其设置为 WEDNESDAY
。接着,使用 switch
语句根据枚举变量的值进行处理,并打印出对应的结果。
其实枚举类型就是一种特殊的整形,使用枚举类型在程序中可以增加代码的可读性,使得相关常量的含义更加清晰。
- typedef {#3-typedef} =======================
typedef
是 C 语言中的一个关键字,用于为已存在的数据类型定义新的名称(alias)
,不能创建新类型
。它能够简化复杂的类型声明,提高代码的可读性和可维护性。
typedef
关键字的语法:
|-----------|---------------------------|
| 1
| typedef 原类型 新类型名;
|
typedef
和 #define
用法上有相似之处,但是也有很多不同:
typedef
仅限于数据类型,而不能是表达式或具体的值#define
发生在预处理,typedef
发生在编译阶段
3.1 基础类型 {#3-1-基础类型}
下面是一个示例,展示如何使用 typedef
来创建新的类型别名:
|------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <stdio.h> typedef int INT; typedef char BYTE; typedef BYTE T_BYTE; typedef unsigned char UBYTE; typedef struct type { UBYTE a; INT b; T_BYTE c; }TYPE, * PTYPE; int main() { TYPE t; t.a = 254; t.b = 10; t.c = 'c'; PTYPE p = &t; printf("%u, %d, %c\n", p->a, p->b, p->c); return 0; }
|
在上面的示例代码中给一些基础数据类型定义了别名,然后再基于这些别名定义了相关的变量,很容易理解,不再过多进行解释。
3.2 复合类型 {#3-2-复合类型}
除此之外还可以给结构体、联合体和枚举等数据类型创建新的类型别名。
当使用typedef
来定义结构体时,可以为结构体类型提供一个简短、易于使用的别名。下面是一个示例,展示如何使用typedef
定义结构体:
|------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <stdio.h> typedef struct { int x; int y; } Point; int main() { // 使用结构体别名创建变量 Point p1; p1.x = 10; p1.y = 20; // 输出结构体变量的值 printf("Point: (%d, %d)\n", p1.x, p1.y); return 0; }
|
在上述示例中,使用了匿名的结构体,即在定义结构体时未指定结构体名字。然后,我们使用typedef
关键字将这个匿名结构体定义的结构体类型起一个别名Point
。
使用tpedef
定义联合体别名和定义结构体别名语法完全相同,这里就不再举例子了,下面在列举一个定义枚举别名的例子,示例代码如下:
|---------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <stdio.h> typedef enum { RED, GREEN, BLUE } Color; int main() { // 使用枚举别名创建变量 Color c = RED; // 输出枚举变量的值 switch (c) { case RED: printf("Color: RED\n"); break; case GREEN: printf("Color: GREEN\n"); break; case BLUE: printf("Color: BLUE\n"); break; } return 0; }
|
在上述示例中,我们使用typedef关键字将enum
定义的枚举类型起一个别名Color
。枚举类型中包含了三个枚举常量RED
、GREEN
和BLUE
。
通过使用typedef
来定义结构体的别名,可以使代码更加简洁和可读性更高,特别是在结构体类型较为复杂或频繁使用时。
3.3 函数指针 {#3-3-函数指针}
在C语言中,typedef
可以用来给一个已有类型起别名,其中也包括函数指针类型。使用typedef
来定义函数指针类型可以简化代码,并使其更加易读。下面是一种常见的使用方法:
|-----------|-------------------------------------------|
| 1
| typedef int (*FuncPtr)(int, int);
|
上述代码定义了一个名为FuncPtr
的函数指针类型,该函数指针可以指向返回类型为int
,接受两个int类型参数
的函数。可以根据实际需求调整返回类型和参数类型。
可以使用该函数指针类型来定义函数指针变量并赋值,例如:
|------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <stdio.h> typedef int (*FuncPtr)(int, int); int add(int a, int b) { return a + b; } int main() { FuncPtr ptr = add; int result = ptr(3, 4); return 0; }
|
第11行
:定义函数指针变量,并将add
函数的地址赋值给函数指针ptr
第12行
:调用函数指针ptr
,相当于调用add
函数
回调函数是一种常见的编程技术,它允许你将一个函数作为参数传递给另一个函数,并在需要的时候被调用。
在C语言中,回调函数通常与函数指针一起使用
。你可以将一个函数的指针作为参数传递给另一个函数,在适当的时候调用该函数指针,以执行特定的操作。
下面是一个简单的示例,演示了回调函数的用法:
|------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <stdio.h> // 定义函数指针别名 typedef int (*funcPtr)(int, int); int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int result(funcPtr func, int a, int b) { int res = func(a, b); res += 100; return res; } int main() { int res = result(add, 100, 200); printf("Result of addition: %d\n", res); res = result(subtract, 500, 200); printf("Result of subtraction: %d\n", res); return 0; }
|
通过使用回调函数,你可以将特定的操作从一个函数中分离出来,并通过回调函数的方式动态地执行这些操作。这使得代码更加灵活和可扩展。