侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 747 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

C语言学习小记(13)-结构体、共用体和枚举

zze
zze
2024-03-29 / 0 评论 / 0 点赞 / 16 阅读 / 31998 字

不定期更新相关视频,抖音点击左上角加号后扫一扫右方侧边栏二维码关注我~正在更新《Shell其实很简单》系列

结构体

定义与概念

在C语言中,结构体(Structure)是一种用户自定义的数据类型,它可以将不同类型的数据组合在一起,形成一种复合数据类型。结构体允许我们将相关联的数据组织在一起,便于管理和使用。

结构体定义的基本语法:

struct structure_tag {
    member_type1 member_name1;
    member_type2 member_name2;
    ...
    member_typen member_namen;
};
  • structure_tag:结构体的标签,可以用于引用结构体类型;

  • member_typeX:成员变量的类型,可以是任何C语言支持的基本类型或复合类型;

  • member_nameX:成员变量的名字,用于访问结构体中的具体数据。

示例:

// 定义一个表示学生信息的结构体
struct Student {
    char name[50]; // 姓名
    int age; // 年龄
    float grade; // 成绩
};

// 定义结构体变量
struct Student student1;

// 初始化结构体变量
strcpy(student1.name, "John Doe");
student1.age = 20;
student1.grade = 85.5;

// 访问结构体成员
printf("Student's name: %s, Age: %d, Grade: %.2f\n", student1.name, student1.age, student1.grade);

定义与初始化

  • 直接定义并初始化结构体变量:

struct Student {
    char name[50];
    int age;
    float grade;
} student2 = {"Jane Smith", 19, 90.0};
  • 通过结构体标签定义变量:

struct Student; // 先声明结构体标签

struct Student student3 = {"Alice", 21, 92.5}; // 使用结构体标签定义变量

// 或者
struct Student {
    char name[50];
    int age;
    float grade;
};

struct Student student4; // 使用结构体标签定义变量,但不立即初始化

strcpy(student4.name, "Bob");
student4.age = 22;
student4.grade = 93.0;

结构体指针与结构体数组

  • 结构体指针:

struct Student *pStudent = &student1; // 定义一个指向结构体变量的指针

// 通过指针访问结构体成员
printf("Student's name via pointer: %s\n", pStudent->name);
  • 结构体数组:

struct Student students[3]; // 定义一个包含3个结构体元素的数组

students[0] = {"Tom", 18, 88.0};
students[1] = {"Jerry", 19, 92.0};
students[2] = {"Spike", 20, 95.0};

// 访问结构体数组元素
printf("First student's name: %s\n", students[0].name);

通过结构体,可以更好地组织和管理复杂的数据结构,实现数据的有效封装和复用。

结构体嵌套结构体

结构体允许在内部定义另一个结构体类型作为成员。这样做可以有效地模拟现实世界的复杂实体间的关系,例如层次结构或关联关系。

#include <stdio.h>
#include <string.h>

int main()
{
    // 定义一个简单的结构体类型
    struct Address
    {
        char street[100];
        char city[50];
        char state[2];
        char zip[10];
    };

    // 定义另一个结构体类型,其中嵌套了Address结构体
    struct Person
    {
        char firstName[50];
        char lastName[50];
        struct Address homeAddress; // 嵌套结构体成员
        int age;
    };

    // 使用嵌套结构体
    struct Person person;
    strcpy(person.firstName, "John");
    strcpy(person.lastName, "Doe");

    // 初始化嵌套的Address结构体
    strcpy(person.homeAddress.street, "123 Main St.");
    strcpy(person.homeAddress.city, "Anytown");
    strcpy(person.homeAddress.state, "CA");
    strcpy(person.homeAddress.zip, "12345");

    // 现在,person结构体包含了姓名和家庭地址信息
}

在上面的例子中,Person 结构体嵌套了一个 Address 结构体作为其成员。这意味着每个 Person 实例都自带一个 Address 实例,用来描述该人的家庭住址信息。这样做的好处是可以将相关的数据紧密结合在一起,便于管理和访问。

结构体中的指针成员

  • 指针指向结构体:

结构体的指针成员允许结构体存储指向其它数据的地址,而非直接存储数据本身。这样可以节省内存空间,并实现灵活的数据引用和动态关联。下面是一个包含指针成员的结构体示例,并通过注释描述其在内存中的存储情况。

#include <stdio.h>

// 定义一个简单的结构体类型
struct SimpleStruct {
    int value;
};

// 定义一个包含指针成员的结构体类型
struct PointerStruct {
    char name[20]; // 储存字符串
    struct SimpleStruct *ptrToSimpleStruct; // 指针成员,存储一个指向SimpleStruct结构体的地址
};

int main() {
    // 创建一个SimpleStruct类型的结构体实例,并赋予其值
    struct SimpleStruct simple;
    simple.value = 42;

    // 创建一个PointerStruct类型的结构体实例
    struct PointerStruct pointerStruct;
    strcpy(pointerStruct.name, "Pointer Member"); // 存储字符串

    // 指针成员指向simple结构体实例
    pointerStruct.ptrToSimpleStruct = &simple;

    // 在内存中,结构体的存储情况如下:
    // |-------------------------|
    // | pointerStruct            |
    // |-------------------------|
    // | name: "Pointer Member"  | <- 字符串存储区域
    // |-------------------------|
    // | ptrToSimpleStruct: 地址 | <- 指针成员存储的是simple结构体的地址
    // |-------------------------|

    // 而simple结构体在内存中的存储情况可能是这样的:
    // |-------------------|
    // | simple            |
    // |-------------------|
    // | value: 42         | <- 数据存储区域
    // |-------------------|

    // 通过指针成员访问SimpleStruct结构体的值
    printf("The value in the pointed-to SimpleStruct is: %d\n", pointerStruct.ptrToSimpleStruct->value);

    return 0;
}

在这个例子中,PointerStruct 结构体在内存中占用一段连续的空间,其中一部分存储 name 字符串,另一部分存储 ptrToSimpleStruct 指针变量。指针变量存储的是另一个结构体 simple 的内存地址,而不是其内容。因此,通过这个指针,我们可以访问到 simple 结构体中存储的 value。在内存布局上,这两个结构体并不一定相邻,但通过指针关联了起来。

  • 指针指向文字常量区:

如果你的结构体指针成员指向一个字符串字面量(文字常量),实际上是指针成员存储了该字符串字面量的地址,这个地址位于程序的静态存储区或文字常量区。由于字符串字面量在程序运行期间是常驻内存的,所以这种指向并不会引起内存泄漏问题。

#include <stdio.h>

// 定义一个包含指针成员的结构体
struct MyStruct {
    char *text;
};

int main() {
    struct MyStruct s;

    // 结构体的指针成员指向字符串字面量
    s.text = "Hello, World!"; // 这里的"s.text"存储的是该字符串在内存中的地址,而非字符串本身

    // 输出字符串内容
    printf("%s\n", s.text);

    // 注意:在此例中,由于字符串字面量是静态存储的,即使程序退出,也不会因为没有释放s.text而导致内存泄漏

    return 0;
}

但是,需要注意的是,如果你通过malloc等方式动态分配内存给结构体指针成员,然后再指向一个字符串字面量,而忘记释放之前分配的内存,这时才会发生内存泄漏。例如:

s.text = malloc(100 * sizeof(char)); // 动态分配内存
strcpy(s.text, "Old String"); // 将一些内容复制到分配的内存中
s.text = "Hello, World!"; // 指针成员改为指向字符串字面量,原来分配的内存未释放,导致内存泄漏
  • 指针指向堆区:

当结构体的指针成员指向堆区时,通常意味着你需要通过动态内存分配函数(如malloccalloc等)为指针成员分配内存,然后才能对其进行写入或读取操作。这样做可以让你在运行时动态控制内存的分配和释放。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个包含指针成员的结构体
struct MyStruct {
    char *text;
};

int main() {
    struct MyStruct s;

    // 为结构体的指针成员动态分配内存
    s.text = malloc(100 * sizeof(char)); // 假设我们分配了100个字符的内存空间

    // 检查内存是否分配成功
    if (s.text == NULL) {
        perror("Memory allocation failed");
        exit(EXIT_FAILURE);
    }

    // 将字符串复制到分配的内存中
    strcpy(s.text, "Hello, World!");

    // 输出字符串内容
    printf("%s\n", s.text);

    // 使用完毕后,记得释放分配的内存
    free(s.text);

    return 0;
}

在这个例子中,s.text 是结构体 MyStruct 的一个指针成员,它指向堆区分配的100个字符的空间。当我们完成对该内存块的使用后,必须调用 free(s.text) 来释放内存,否则将会导致内存泄漏。

结构体的深浅拷贝

结构体的“深拷贝”和“浅拷贝”概念主要体现在结构体中有指针成员时的情况。

浅拷贝(Shallow Copy)

当一个结构体被复制时,它的所有非指针成员都会被直接复制,而指针成员只是简单地复制了指向的地址,而不是复制指针所指向的数据。这意味着原结构体和复制后的结构体的指针成员指向同一个内存区域。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Data
{
    int value;
    char *text; // 指针成员
};

void shallowCopy(struct Data src, struct Data *dest)
{
    dest->value = src.value;
    dest->text = src.text; // 浅拷贝,仅复制指针,不复制指针指向的数据
}

int main()
{
    struct Data original;
    original.value = 42;
    original.text = malloc(sizeof(char) * 10);
    strcpy(original.text, "hello");

    printf("original text: %s\n", original.text);

    struct Data copy;
    shallowCopy(original, &copy);
    printf("copy text: %s\n", copy.text);

    // 这时,original.text 和 copy.text 都指向同一块内存区域
    copy.text[1] = 'E';
    printf("original text: %s\n", original.text);

    free(original.text); // 如果这里释放了内存,那么 copy.text 将成为一个悬挂指针
    // 因为它们共享同一块内存

    return 0;
}

深拷贝(Deep Copy)

在深拷贝中,除了非指针成员外,还会为指针成员分配新的内存,并复制指针所指向的数据。这样,原结构体和复制后的结构体拥有各自独立的数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Data
{
    int value;
    char *text; // 指针成员
};

void deepCopy(struct Data src, struct Data *dest)
{
    dest->value = src.value;
    dest->text = malloc(strlen(src.text) + 1); // 为指针成员分配新内存
    strcpy(dest->text, src.text);              // 复制数据到新分配的内存
}

int main()
{
    // 同样的原始结构体定义和初始化...
    struct Data original;
    original.value = 42;
    original.text = malloc(sizeof(char) * 10);
    strcpy(original.text, "hello");

    struct Data copy;
    deepCopy(original, &copy);

    // 这时,original.text 和 copy.text 指向不同的内存区域,分别存储各自的字符串数据

    free(original.text); // 即使这里释放了original.text,copy.text 仍然有效

    free(copy.text); // 使用完毕后,释放copy.text指向的内存

    return 0;
}

结构体在堆区

结构体对象和结构体的成员都可以在堆区中分配内存。当结构体中包含指针成员时,可以通过动态内存分配函数(如malloccalloc)为结构体对象和指针成员指向的对象都在堆区分配内存。以下是一个示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个包含指针成员的结构体
typedef struct {
    int id;
    char *name;
} StructType;

int main() {
    // 为结构体对象在堆区分配内存
    StructType *obj = (StructType *)malloc(sizeof(StructType));
    if (obj == NULL) {
        perror("Failed to allocate memory for StructType object");
        return EXIT_FAILURE;
    }

    // 为结构体的指针成员在堆区分配内存
    obj->name = (char *)malloc(50 * sizeof(char)); // 假设名字最大长度为50
    if (obj->name == NULL) {
        perror("Failed to allocate memory for name field");
        free(obj); // 释放结构体对象内存
        return EXIT_FAILURE;
    }

    // 给结构体对象的成员赋值
    obj->id = 123;
    strcpy(obj->name, "Example Name");

    // 使用结构体对象
    printf("ID: %d, Name: %s\n", obj->id, obj->name);

    // 释放堆区分配的内存
    free(obj->name);
    free(obj);

    return 0;
}

在这个示例中,结构体对象obj是在堆区通过malloc分配的,其成员name也是一个指向堆区的指针,我们也为它分配了足够的内存来存储字符串。在使用完这些内存后,我们通过free函数分别释放了name指向的内存和obj指向的结构体对象内存,以避免内存泄漏。

结构体数组及成员在堆区

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int id;
    char *name;
} StructType;

int main() {
    // 在堆区分配结构体指针数组
    StructType **structArray = (StructType **)malloc(sizeof(StructType*) * 5); // 假设数组大小为5
    if (structArray == NULL) {
        perror("Failed to allocate memory for array of pointers");
        return EXIT_FAILURE;
    }

    // 分配结构体对象并在堆区初始化每个数组元素指向的结构体
    for (int i = 0; i < 5; ++i) {
        // 为结构体对象在堆区分配内存
        structArray[i] = (StructType *)malloc(sizeof(StructType));
        if (structArray[i] == NULL) {
            perror("Failed to allocate memory for StructType object");
            for (int j = 0; j < i; ++j) { // 释放已分配的结构体对象
                free(structArray[j]);
            }
            free(structArray); // 释放结构体指针数组内存
            return EXIT_FAILURE;
        }

        // 为结构体的指针成员在堆区分配内存
        structArray[i]->name = (char *)malloc(50 * sizeof(char)); // 假设名字最大长度为50
        if (structArray[i]->name == NULL) {
            perror("Failed to allocate memory for name field");
            free(structArray[i]->name); // 释放结构体成员指向的内存
            free(structArray[i]); // 释放结构体对象内存
            for (int j = 0; j < i - 1; ++j) { // 释放已分配的结构体对象
                free(structArray[j]->name);
                free(structArray[j]);
            }
            free(structArray); // 释放结构体指针数组内存
            return EXIT_FAILURE;
        }

        // 给结构体对象的成员赋值
        structArray[i]->id = i + 1;
        sprintf(structArray[i]->name, "Object %d", i + 1);
    }

    // 使用结构体指针数组元素
    for (int i = 0; i < 5; ++i) {
        printf("ID: %d, Name: %s\n", structArray[i]->id, structArray[i]->name);
    }

    // 释放堆区分配的内存
    for (int i = 0; i < 5; ++i) {
        free(structArray[i]->name);
        free(structArray[i]);
    }
    free(structArray); // 释放结构体指针数组内存

    return 0;
}

在这个示例中,结构体指针数组structArray是在堆区通过malloc分配的,数组的每个元素(结构体指针)又指向堆区中的结构体对象,而这些结构体对象的name成员也是在堆区分配的内存。在使用完这些内存后,我们按照分配的逆序释放内存,以确保内存正确释放,避免内存泄漏。

结构体内存对齐

单层结构体

在 C 语言中,结构体的内存对齐是为了提高访问效率和避免访问未对齐内存的问题。具体来说,结构体的内存对齐规则如下:

  • 第一个成员的首地址为 0。

  • 每个成员的首地址是自身大小的整数倍。

  • 结构体的总大小,为其成员中所含最大类型的整数倍。

成员中最大基本类型大小默认为对应结构体的对齐数。

以下是一个示例,展示了结构体的内存对齐方式:

#include <stdio.h>

typedef struct Person {
    char *name;
    int age;
    char *id;
}Person;

int main() {
    // 结构体定义不合理的话,会导致内存浪费
    typedef struct {
        char a; // 1
        char b; // 1
        int c; // 4
        short d; // 2
        double e; // 8
    }Align;

    typedef struct {
        char a; // 1
        char b; // 1
        short d; // 2
        int c; // 4
        double e; // 8
    }OptimizedAlign;

    Align align = {'a', 'b', 3, 4, 5.0};
    OptimizedAlign optimized_align = {'a', 'b', 4, 3, 5.0};

    printf("Size of Align: %lu\n", sizeof(Align)); // 24
    printf("Size of OptimizedAlign: %lu\n", sizeof(OptimizedAlign)); // 16

    return 0;
}

在这个例子中,Align 结构体的内存对齐方式会导致一定的内存浪费,而OptimizedAlign结构体则通过合理地调整成员的顺序,避免了这种情况的发生。

总的来说,结构体的内存对齐是 C 语言中一个比较重要的概念,合理地利用内存对齐规则可以提高程序的性能。

嵌套结构体

嵌套结构体的对齐数默认为结构体本身以及其内部所有结构体成员中最大基本类型大小。

结构体嵌套结构体时,内存对齐的原则会继续应用于嵌套的结构体。嵌套结构体的起始地址也会遵循对齐规则,即从一个对齐数的整数倍地址开始。同时,整个外部结构体的大小也需要满足内部和外部结构体中最大对齐数的整数倍。

#include <stdio.h>
int main()
{
    // 假设有如下的结构体定义:
    struct Inner
    {
        char i1; // size = 1 byte
        int i2;  // size = 4 bytes, alignment = 4 bytes
    };

    struct Outer
    {
        char o1;            // size = 1 byte
        struct Inner inner; // 内嵌结构体
        double o2;          // size = 8 bytes, alignment = 8 bytes
    };

    printf("Size of Inner: %lu\n", sizeof(struct Inner)); // 8
    printf("Size of Outer: %lu\n", sizeof(struct Outer)); // 24
    return 0;
}

上述代码内存结构如下图:

Inner 作为 Outer 的成员时,其起始地址为其自身对齐数(结构体内最大基本类型大小)整数倍。

实际上,不同编译器和平台的对齐策略可能有所不同,但基本原理是一致的:嵌套结构体内部和外部都要遵循对齐原则,确保高效访问的同时合理利用内存。在实际编程中,可以使用sizeof运算符确定结构体的具体大小,以及使用预处理器宏offsetof来获取成员相对于结构体起始地址的偏移量。

强制对齐

编译器会根据系统的架构和编译选项自动为结构体成员分配合适的内存地址,以满足CPU对内存访问的效率要求。你也可以通过预处理器指令#pragma pack(n)来显式设置结构体的对齐数,其中n是你指定的对齐字节数。

当手动指定对齐数为 n 后,对齐数 = min(成员中最大类型大小, n)。

依旧以上面代码为例,仅修改对齐数:

#include <stdio.h>
#pragma pack(4)
int main()
{
    // 假设有如下的结构体定义:
    struct Inner
    {
        char i1; // size = 1 byte
        int i2;  // size = 4 bytes, alignment = 4 bytes
    };

    struct Outer
    {
        char o1;            // size = 1 byte
        struct Inner inner; // 内嵌结构体
        double o2;          // size = 8 bytes, alignment = 4 bytes
    };

    // pragma pack(1)
    printf("Size of Inner: %lu\n", sizeof(struct Inner)); // 6
    printf("Size of Outer: %lu\n", sizeof(struct Outer)); // 16

    // pragma pack(2)
    printf("Size of Inner: %lu\n", sizeof(struct Inner)); // 6
    printf("Size of Outer: %lu\n", sizeof(struct Outer)); // 16

    // pragma pack(4)
    printf("Size of Inner: %lu\n", sizeof(struct Inner)); // 8
    printf("Size of Outer: %lu\n", sizeof(struct Outer)); // 20
    return 0;
}

可以看到通过修改数会影响结构体所占用的大小。

位域

结构体的位域(Bit Fields)是C语言中的一种特性,允许在结构体成员中以二进制位(bit)为单位定义和访问变量,这样可以有效地在有限的存储空间中编码多个小规模的信息。位域特别适用于存储一组标志位或压缩存储数据的情况。

位域的基本语法:

struct BitFieldExample {
    unsigned int bit1 : 1; // 一个位宽为1的无符号整型位域
    unsigned int bit2 : 2; // 一个位宽为2的无符号整型位域
    signed int flag : 3; // 一个位宽为3的有符号整型位域
    unsigned int data : 4; // 一个位宽为4的无符号整型位域
    // ... 其他位域或常规成员变量...
};

位域的注意事项:

  • 位域的宽度必须是 1 到相应的整型类型的大小(如unsigned int通常是16位或32位,取决于平台)之间的整数。

  • 位域在内存中是连续存放的,但编译器会根据需要插入额外的位来确保后续的位域或普通成员变量的自然对齐。

  • 不同位域可以共用一个存储单元,但其内部顺序和具体如何映射到内存由编译器决定。

  • 位域的初始化可以通过位级操作符(如<<|)或者直接赋值的方式实现。

示例:

  • 例 1:

struct structBitFields
{
    unsigned int status : 3; // 包含3位的状态标志
    unsigned int count : 5;  // 包含5位的计数器
};

struct structBitFields bf;

// 设置位域值
bf.status = 0b101;  // 设置status位域为5(二进制101)
bf.count = 0b11111; // 设置count位域为最大值31(二进制11111)

// 访问位域值
if (bf.status & 0b001)
{
    // 检查status的第一位是否为1
}

// 注意:实际读取或写入位域时,需要考虑到位域所在的整体变量(在这里是unsigned int类型)
unsigned int rawValue = *((unsigned int *)&bf); // 获取位域打包成的整数值
  • 例 2:

在嵌入式系统或低层编程中,结构体位域经常用于节省存储空间和高效表示一组标志位。例如,考虑一个硬件接口,它有几个独立的功能标志,每个标志只需一位即可表示开启或关闭状态。我们可以使用结构体位域来表示这些标志:

#include <stdio.h>

// 定义一个表示设备功能状态的结构体
struct DeviceStatus {
    unsigned int powerOn : 1;    // 设备电源开关,0表示关闭,1表示开启
    unsigned int modeA : 1;      // 模式A,0表示禁用,1表示启用
    unsigned int modeB : 1;      // 模式B,类似
    unsigned int errorDetected : 1; // 错误检测标志
    unsigned int reserved : 28;   // 保留位,未使用的位可以用来填充,确保整个结构体字节对齐
};

int main() {
    struct DeviceStatus devStatus;

    // 设置设备状态
    devStatus.powerOn = 1;    // 设备开机
    devStatus.modeA = 0;       // 关闭模式A
    devStatus.modeB = 1;       // 开启模式B
    devStatus.errorDetected = 0; // 没有检测到错误

    // 检查并操作设备状态
    if (devStatus.powerOn) {
        printf("Device is powered on.\n");
    }
    if (!devStatus.modeA && devStatus.modeB) {
        printf("Mode B is enabled while Mode A is disabled.\n");
    }

    return 0;
}

在这个示例中,DeviceStatus结构体使用位域表示了四个布尔状态标志,这些标志原本可能需要4个独立的布尔变量来存储,但现在只需要一个整数大小的空间(通常是32位或16位,取决于系统架构)。这样大大减少了存储空间的使用,并且可以通过位操作快速设置和查询这些标志。

共用体

共用体(Union)是C语言中的一种数据类型,它允许在同一内存区域中存储不同类型的多个数据成员,但一次只能存储其中一个成员的值。共用体中的所有成员共享相同的内存空间,因此改变其中一个成员的值会影响到其他成员。

共用体的基本语法:

union UnionName {
    type1 member1;
    type2 member2;
    // ...
    typen membern;
};

共用体的特点:

  1. 共用体的所有成员在内存中重叠,占用相同的空间,大小由其中最大的成员决定。

  2. 任何时候只有一个成员的值是有效的,当你改变了某个成员的值,其他成员的值会被覆盖。

  3. 共用体提供了通过一个单一的内存区域来以多种方式进行解释的方法。

示例:

  • 例 1:

#include <stdio.h>

// 定义一个共用体
union DataUnion {
    int intValue;
    float floatValue;
    char charArray[10];
};

int main() {
    union DataUnion myUnion;

    // 存储整数值
    myUnion.intValue = 42;
    printf("Integer Value: %d\n", myUnion.intValue);

    // 修改后,浮点值区域也被修改
    myUnion.floatValue = 3.14f;
    printf("Float Value: %.2f\n", myUnion.floatValue);

    // 修改后,字符数组区域也被修改
    strcpy(myUnion.charArray, "Hello, World!");
    printf("Character Array: %s\n", myUnion.charArray);

    // 请注意,此时intValue和floatValue的值不再是先前的值,因为它们所在的内存区域已被覆盖

    return 0;
}

在这个示例中,DataUnion共用体包含一个整型变量、一个浮点型变量和一个字符数组。每当我们改变其中一个成员的值时,同一内存区域内的其他成员的值也随之改变,因为它们共享同一块内存。共用体通常用于处理那些底层硬件寄存器(一个寄存器具有多种功能)或者需要在不同数据类型之间转换的情况。

  • 例 2:

共用体(union)常常用于处理底层硬件接口、网络协议解析、或者需要在多种数据类型之间切换的情况。下面是一个典型的使用共用体处理网络字节序转换的场景示例:

#include <stdio.h>
#include <netinet/in.h> // 提供htonl和ntohl函数

// 定义一个共用体,用于表示既可以是整型也可以是网络字节序的32位整数
union IntegerOrNetwork {
    uint32_t integer; // 无符号32位整型
    struct {
        uint8_t byte1;
        uint8_t byte2;
        uint8_t byte3;
        uint8_t byte4;
    } bytes;
};

int main() {
    union IntegerOrNetwork number;
    
    // 设置一个本地主机字节序的整数
    number.integer = 0x01020304; // 本地机器可能是小端或大端字节序

	printf("%d\n", number.bytes.byte1); // 1
    printf("%d\n", number.bytes.byte2); // 2
    printf("%d\n", number.bytes.byte3); // 3
    printf("%d\n", number.bytes.byte4); // 4

    // 输出原始整数值
    printf("Original integer value: %u\n", number.integer);

    // 将整数转换为网络字节序(大端字节序)
    number.integer = htonl(number.integer); // htons用于短整型,htonl用于长整型

    // 输出网络字节序对应的各个字节
    printf("In network byte order: ");
    for (int i = 0; i < 4; ++i) {
        printf("%02X ", number.bytes.byte4 - i);
    }
    printf("\n");

    // 现在可以将这个网络字节序的整数发送到网络,接收方可以使用ntohl将其转换回本地字节序

    return 0;
}

// Original integer value: 16909060
// In network byte order: 04 03 02 01 

在这个示例中,IntegerOrNetwork共用体可以表示一个32位整数或者表示为四个独立的8位字节。在网络通信中,通常要求数据采用网络字节序(Big Endian),通过共用体,我们可以方便地将整数转换为字节序列,并可以反向转换回来。这就是共用体在处理网络数据包中的一个实际应用场景。

枚举

枚举(Enumeration)是一种用户定义的整数类型,它允许程序员定义一组命名的整数常量,这些常量具有可读性强的名字,方便在代码中表示特定的状态或类别。枚举成员的值默认从0开始递增,但程序员也可以自定义初始值。

枚举的基本语法:

enum EnumTypeName {
    ConstantName1,
    ConstantName2 = InitialValue, // 可以自定义初始值
    ConstantName3,
    ...
};

// 使用typedef简化枚举类型的声明和定义
typedef enum {
    EnumValue1,
    EnumValue2,
    ...
} EnumTypeName;

示例:

#include <stdio.h>

// 定义枚举类型DaysOfWeek
enum DaysOfWeek {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

int main() {
    // 声明并初始化枚举变量
    enum DaysOfWeek today = Monday;

    // 根据枚举类型打印今天的日期
    switch(today) {
        case Monday:
            printf("Today is Monday.\n");
            break;
        case Tuesday:
            printf("Today is Tuesday.\n");
        // 其他case分支...
        default:
            printf("Unknown day.\n");
    }

    // 使用typedef定义的枚举类型示例
    typedef enum {
        Red,
        Green,
        Blue
    } Color;

    Color myFavoriteColor = Green;
    if (myFavoriteColor == Green) {
        printf("My favorite color is green.\n");
    }

    return 0;
}

在上述示例中,我们定义了一个名为DaysOfWeek的枚举类型,它有7个枚举成员,分别代表一周的七天。然后我们声明了一个枚举变量today并初始化为Monday。通过switch语句,我们可以根据枚举变量的值执行相应的代码块。

同时,我们展示了如何使用typedef定义枚举类型,并声明了一个Color类型的枚举变量myFavoriteColor,其值为Green。这样我们就可以用更具描述性的名称代替整数值来表示颜色,使得代码更加清晰易懂。

0

评论区