Bootstrap

C语言之韵:枚举与联合的和谐共舞

大家好,这里是小编的博客频道
小编的博客:就爱学编程

很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!!

引言

C语言中的枚举(Enum)和联合(Union)是两种强大的数据结构,它们在程序设计中扮演着重要角色。枚举提供了一种定义命名常量的方式,使得代码更加清晰和易于管理。而联合则允许在相同的内存位置存储不同的数据类型,这在需要节省空间或处理不同数据格式时非常有用。那就跟着小编一起来看看吧!!!

在这里插入图片描述


那接下来就让我们开始遨游在知识的海洋!

正文


枚举(Enum)

1.枚举的定义和基本使用

枚举是一种用户定义的类型,它允许程序员为一组整数值定义一个名字。这使得代码更加可读和易于维护。枚举通过enum关键字定义,基本格式如下:

enum 枚举类型名 {
    枚举常量1,
    枚举常量2,
    ...
};

如果省略枚举类型名,可以定义一个匿名枚举:

enum {
    枚举常量1,
    枚举常量2,
    ...
};

注意:

  • 枚举的每个常量都是整数,它们从0开始自动赋值,除非显式指定。

这可以用一个题目体会一下:

判断下列代码运行的结果

#include<stdio.h>
int main() {
	enum ENUM_A
	{
		X1,
		Y1,
		Z1 = 255,
		A1,
		B1,
	};
	enum ENUM_A enumA = Y1;
	enum ENUM_A enumB = B1;
	printf("%d %d\n", enumA, enumB);
	return 0;
}

宝子们可以根据注意猜一下运行结果。






好,跟着小编一起看看这个代码的运行结果:

在这里插入图片描述
为什么是这个结果?

原因: 枚举默认从0开始,所以X1是0,故Y1是1,给了数字后会根据数字向后推,那么Z1是255,A1是256,所以B1是257。

所以我们可以知道:

  • 枚举的每个常量都是整数,它们从0开始自动赋值,若前一个枚举常量被显式指定,后面的枚举常量依次递增1

其实我们在之前的学习已经知道:#define也可以用来定义一个常量,那C语言为什么又要创建枚举类型的语法呢?
存在即合理!枚举有着自己的独一无二的优势,这是#define无法比拟的,那就让我们一起来看看吧!!!

2.枚举的优点

  • (1)提高代码可读性:使用枚举可以代替硬编码的数字,使代码更易读。

我们每个优点都用一个例子来佐证一下:

enum TrafficLight {
    RED, YELLOW, GREEN
};

void controlLight(enum TrafficLight light) {
    if (light == RED) {
        printf("Stop at the red light.\n");
    } else if (light == YELLOW) {
        printf("Prepare to stop at the yellow light.\n");
    } else if (light == GREEN) {
        printf("Go at the green light.\n");
    }
}
  • (2)类型安全:枚举提供了一种类型安全的方式,防止将错误的值赋给变量。
enum TrafficLight {
    RED, YELLOW, GREEN
};

void controlLight(enum TrafficLight light) {
    if (light == RED) {
        printf("Stop at the red light.\n");
    } else if (light == YELLOW) {
        printf("Prepare to stop at the yellow light.\n");
    } else if (light == GREEN) {
        printf("Go at the green light.\n");
    }
}
  • (3)便于维护:如果需要更改枚举常量的值,只需在定义处更改,而不需要搜索整个代码库。
enum Status {
    SUCCESS = 0,
    FAILURE = 1,
    ERROR = 2
};

// 假设我们需要将SUCCESS的值改为1
enum Status {
    SUCCESS = 1,
    FAILURE = 2,
    ERROR = 3
};

学习了以上内容,那我们再来看看枚举具体使用在哪些场景。

3.枚举的使用场景

枚举常用于表示有限的、命名的常量集合,例如:

  • 状态码(如:打开、关闭)

  • 方向(如:北、南、东、西)

  • 优先级(如:低、中、高)


事实上,除了以上枚举的用法,枚举还可以与swtich语句结合使用,一起来看看吧!!!

4.枚举与switch语句

  • 枚举常与switch语句结合使用,处理基于不同枚举值的条件分支,提高代码的可读性和可维护性。

以下列代码为例感知:

enum Day {
    Sunday,
    Monday,
    // ...
};

void printDay(enum Day day) {
    switch (day) {
        case Sunday:
            printf("Sunday");
            break;
        case Monday:
            printf("Monday");
            break;
        // ...
    }
}

简单对枚举的定义与使用,进行了解后,我们再来看看联合这一节的知识。

联合(Union)

1.联合的定义和基本使用

联合是一种特殊的数据类型,允许在同一内存位置存储不同的数据类型。联合通过union关键字定义,基本格式如下:

union 联合类型名 {
    类型1 成员1;
    类型2 成员2;
    ...
};

如果省略联合类型名,可以定义一个匿名联合:

union {
    类型1 成员1;
    类型2 成员2;
    ...
};

注意:

  • 联合的大小等于其最大成员的大小,且所有成员共享同一块内存空间。

这同样也可以用一个题目来体会一下:

判断下面代码的运行结果

#include <stdio.h>
union Un
{
	short s[7];
	int n;
};
int main()
{
	printf("%d\n", sizeof(union Un));
	return 0;
}

宝子们同样可以根据注意猜一下运行结果。






好,跟着小编一起看看这个代码的运行结果:

在这里插入图片描述

小编猜想很多宝子们会猜答案是14,实际的结果却是16,这似乎与我们上面说的注意相违背。

但实际上,是不违背的,因为小编还有一个知识点没有和宝子们介绍:

  • 实际上,联合作为一种自定义类型,它在内存中的存储也是存在对齐的。

那我们接下来就走进**联合与内存对齐**这一章的知识。

2.联合与内存对齐

联合的内存对齐取决于最大成员的对齐要求。编译器可能会对联合进行内存对齐,以提高访问效率。


内存对齐是指在创建数据结构时,按照某些规则(通常是硬件平台的要求)将数据成员存放在内存中,以提高访问效率和避免访问异常。对于联合(union)来说,内存对齐同样重要,以下是联合内存对齐规则:

数据成员对齐规则:

  • 结构体或联合的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始。

  • 结构体作为成员:如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储

  • 结构体的总大小:即sizeof的结果,必须是其内部最大成员长度的整数倍,不足的要补齐。


(1)联合体大小

联合体的大小取决于他所有成员中占用空间最大的一个成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数类型的整数倍。


(2)联合体内存分配

联合体变量的各个成员都是从低字节开始公用的。即:所有的成员都是从低字节开始的。

举例说明

示例1:考虑一个联合体,包含一个char数组、一个int和一个float

union Example {
    char a[6];
    int b;
    float c;
};

分析:
char a[6]占据6个字节。

int b通常占据4个字节。

float c通常占据4个字节。

根据联合体大小的规则,联合体的大小取决于最大成员的大小,即char a[6]的6个字节。但由于6不是4的整数倍,根据内存对齐规则,联合体的大小需要被调整为8个字节,以满足最大对齐数(通常是4或8,取决于平台)。

示例2:内嵌联合体的情况。

union InnerUnion {
    char a[6];
    int b;
};

struct OuterStruct {
    short a;
    union InnerUnion uu1;
};

分析:

short a占据2个字节。

union InnerUnion uu1根据之前的解释,占据8个字节。

因此,struct OuterStruct的总大小需要是其内部最大成员长度的整数倍。在这个例子中,最大成员是union InnerUnion uu1,占据8个字节。所以,struct OuterStruct的总大小为12个字节,以满足内存对齐的要求。

通过这些规则和例子,我们可以看到:

  • 联合的内存对齐是为了确保数据在内存中的存储位置符合处理器的访问要求,同时提高内存访问效率和程序性能。

3.联合的使用场景

联合常用于需要存储多种数据类型但在同一时间只能使用其中一种的场景,例如:

  • 变量的多种表示形式(如:整数或浮点数)

  • 内存管理(如:不同数据类型的缓冲区)


4.联合的优点

  • 节省内存:由于所有成员共享内存,联合可以节省存储空间。
union Data {
    int integer;
    float floating;
};

int main() {
    union Data data;
    data.integer = 10; // 使用整数存储
    // 此时,内存中存储的是整数10
    data.floating = 3.14f; // 覆盖内存,现在存储浮点数3.14
    // 此时,内存中存储的是浮点数3.14
}
  • 提高效率:联合允许在不同数据类型之间进行高效的转换。
union IntFloat {
    int asInt;
    float asFloat;
};

int main() {
    union IntFloat value;
    value.asInt = 0x40490FDB; // 以整数形式存储
    printf("As float: %f\n", value.asFloat); // 以浮点数形式读取
}

最后,我们再来看看枚举和联合的高级使用

枚举和联合的高级使用

1.枚举的位字段

枚举可以与位字段结合使用,以存储多个标志位。这种方法在需要节省内存时非常有用。

typedef enum {
    FLAG_A = 1 << 0,
    FLAG_B = 1 << 1,
    FLAG_C = 1 << 2
} Flags;

struct MyStruct {
    unsigned int flags : 3;
};

int main() {
    struct MyStruct myStruct;
    myStruct.flags = FLAG_A | FLAG_C; // 设置标志A和C
    if (myStruct.flags & FLAG_A) {
        printf("FLAG_A is set.\n");
    }
    if (myStruct.flags & FLAG_B) {
        printf("FLAG_B is set.\n");
    }
    if (myStruct.flags & FLAG_C) {
        printf("FLAG_C is set.\n");
    }
    return 0;
}

2.联合的类型转换

联合可以用作不同数据类型之间的转换工具。特别是,当需要将相同内存中的数据以不同格式进行解释时,联合可以提供一种有效的方法。


typedef union {
    float f;
    uint32_t i;
} FloatIntUnion;

int main() {
    FloatIntUnion data;
    data.f = 3.14159; // 设置浮点数
    // 打印浮点数及其对应的整数位模式
    printf("Float value: %.5f\n", data.f);
    printf("Integer value: 0x%08X\n", data.i);
    return 0;
}

3.枚举和联合的组合使用

枚举和联合可以组合使用,以创建复杂的数据结构,这些结构可以根据需要存储不同的数据类型。

typedef enum {
    INT_TYPE,
    FLOAT_TYPE,
    STRING_TYPE
} DataType;

typedef union {
    int intValue;
    float floatValue;
    char *stringValue;
} DataUnion;

typedef struct {
    DataType type;
    DataUnion data;
} DataHolder;

int main() {
    DataHolder holder;
    holder.type = INT_TYPE;
    holder.data.intValue = 42;
    printf("Integer value: %d\n", holder.data.intValue);

    holder.type = FLOAT_TYPE;
    holder.data.floatValue = 3.14;
    printf("Float value: %.2f\n", holder.data.floatValue);

    holder.type = STRING_TYPE;
    holder.data.stringValue = "Hello, World!";
    printf("String value: %s\n", holder.data.stringValue);
    return 0;
}

4.注意事项

• 联合成员的互斥性:由于联合的所有成员共享同一块内存,因此在同一时间只能访问一个成员。修改一个成员会覆盖其他成员的数据。

• 内存对齐:编译器可能会对联合进行内存对齐,这可能会影响联合的大小和性能。

• 类型安全:虽然枚举提供了类型安全,但联合的使用可能会导致类型不安全的操作,特别是在不同成员之间进行赋值和访问时。

通过上述详细介绍:

  • 我们了解枚举和联合在C语言中的重要性和灵活性。它们为程序员提供了强大的工具,以处理各种复杂的数据类型和内存管理问题。正确使用枚举和联合可以显著提高程序的性能和可维护性。

快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!!

;