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 ; } 
 
通过使用回调函数,你可以将特定的操作从一个函数中分离出来,并通过回调函数的方式动态地执行这些操作。这使得代码更加灵活和可扩展。