C语言 C语言 联合体和枚举 苏丙榅 2023-10-05 2023-10-05 1. 联合体 在 C 语言中,联合体又叫共用体(Union)是一种特殊的数据类型,定义联合体的语法如下:
1 2 3 4 5 6 union 联合体名 { 成员类型 成员名1 ; 成员类型 成员名2 ; ... };
定义联合体变量的语法如下:
从语法上来看联合体和结构体非常的类似只是将关键字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 = 000000875 EEFFB38 data.i address = 000000875 EEFFB38 data.s address = 000000875 EEFFB38 data.str address = 000000875 EEFFB38 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 (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
语句根据枚举变量的值进行处理,并打印出对应的结果。
其实枚举类型就是一种特殊的整形,使用枚举类型在程序中可以增加代码的可读性,使得相关常量的含义更加清晰。
3. typedef typedef
是 C 语言中的一个关键字,用于为已存在的数据类型定义新的名称(alias)
,不能创建新类型
。它能够简化复杂的类型声明,提高代码的可读性和可维护性。
typedef
关键字的语法:
typedef
和 #define
用法上有相似之处,但是也有很多不同:
typedef
仅限于数据类型,而不能是表达式或具体的值
#define
发生在预处理,typedef
发生在编译阶段
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 复合类型 除此之外还可以给结构体、联合体和枚举等数据类型创建新的类型别名。
当使用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 函数指针 在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 ; }
通过使用回调函数,你可以将特定的操作从一个函数中分离出来,并通过回调函数的方式动态地执行这些操作。这使得代码更加灵活和可扩展。