Skip to content

共用体

共用体(Union)是C语言中的一种特殊数据类型,它与结构体类似,但所有成员共享同一块内存空间。共用体可以在不同的时间存储不同类型的数据,这使得它在某些场景下非常有用。在本章节中,我们将学习C语言中共用体的定义、使用和应用场景。

1. 什么是共用体?

共用体是一种特殊的数据类型,它允许在同一块内存空间中存储不同类型的数据。与结构体不同,结构体的每个成员都有自己的内存空间,而共用体的所有成员共享同一块内存空间。

这意味着:

  • 共用体的大小等于其最大成员的大小
  • 同一时间只能使用共用体的一个成员
  • 修改一个成员会影响其他成员的值

2. 共用体的定义

定义共用体就是创建一个新的共用体类型,指定其名称和包含的成员。共用体定义的基本语法如下:

c
union 共用体名称 {
    数据类型 成员1;
    数据类型 成员2;
    // 更多成员...
};

2.1 说明

  • union:共用体定义的关键字
  • 共用体名称:共用体的类型名,遵循标识符命名规则
  • 成员:共用体包含的变量,可以是不同的数据类型
  • 分号:共用体定义结束时的分号是必需的

2.2 示例

c
// 定义一个表示不同类型数据的共用体
union Data {
    int i;
    float f;
    char c;
};

// 定义一个表示不同类型指针的共用体
union Pointer {
    int *i_ptr;
    float *f_ptr;
    char *c_ptr;
};

// 定义一个表示不同类型值的共用体
union Value {
    int integer;
    float floating;
    char string[20];
};

3. 共用体变量的声明和初始化

3.1 声明共用体变量

声明共用体变量就是创建一个共用体类型的变量。可以在定义共用体的同时声明变量,也可以在定义共用体后单独声明变量。

3.2 示例

c
#include <stdio.h>

// 定义一个共用体
union Data {
    int i;
    float f;
    char c;
};

int main() {
    // 方法1:在定义共用体的同时声明变量
    union Data data1;
    
    // 方法2:在定义共用体后单独声明变量
    union Data data2, data3;
    
    return 0;
}

3.3 初始化共用体变量

初始化共用体变量就是给共用体的成员赋初始值。由于共用体的所有成员共享同一块内存空间,所以只能初始化其中的一个成员。

3.4 示例

c
#include <stdio.h>

// 定义一个共用体
union Data {
    int i;
    float f;
    char c;
};

int main() {
    // 初始化共用体的第一个成员
    union Data data1 = {10};
    
    // 使用指定初始化器(C99及以上)
    union Data data2 = {.f = 3.14};
    union Data data3 = {.c = 'A'};
    
    // 打印初始化的值
    printf("data1.i = %d\n", data1.i);  // 10
    printf("data2.f = %.2f\n", data2.f);  // 3.14
    printf("data3.c = %c\n", data3.c);  // A
    
    return 0;
}

4. 访问共用体成员

访问共用体成员就是获取或修改共用体的某个成员的值。在C语言中,使用点运算符(.)来访问共用体成员,使用箭头运算符(->)来访问共用体指针指向的成员。

4.1 示例

c
#include <stdio.h>

// 定义一个共用体
union Data {
    int i;
    float f;
    char c;
};

int main() {
    // 声明共用体变量
    union Data data;
    
    // 访问并修改第一个成员
    data.i = 100;
    printf("data.i = %d\n", data.i);  // 100
    
    // 访问第二个成员(注意:这里会读取相同内存位置的内容,但解释为float)
    printf("data.f = %.2f\n", data.f);  // 不确定的值
    
    // 修改第二个成员
    data.f = 3.14;
    printf("data.f = %.2f\n", data.f);  // 3.14
    
    // 访问第一个成员(注意:这里会读取相同内存位置的内容,但解释为int)
    printf("data.i = %d\n", data.i);  // 不确定的值
    
    // 修改第三个成员
    data.c = 'X';
    printf("data.c = %c\n", data.c);  // X
    
    // 访问其他成员(注意:这里会读取相同内存位置的内容,但解释为不同类型)
    printf("data.i = %d\n", data.i);  // 不确定的值
    printf("data.f = %.2f\n", data.f);  // 不确定的值
    
    return 0;
}

注意:由于共用体的所有成员共享同一块内存空间,所以修改一个成员会影响其他成员的值。当访问未被修改的成员时,会读取相同内存位置的内容,但解释为不同的类型,因此得到的值是不确定的。

5. 共用体指针

共用体指针是指向共用体变量的指针,它存储共用体变量的内存地址。使用共用体指针可以更高效地访问和修改共用体成员。

5.1 示例

c
#include <stdio.h>

// 定义一个共用体
union Data {
    int i;
    float f;
    char c;
};

int main() {
    // 声明共用体变量
    union Data data;
    
    // 声明共用体指针并指向共用体变量
    union Data *p = &data;
    
    // 使用箭头运算符访问成员
    p->i = 200;
    printf("p->i = %d\n", p->i);  // 200
    
    p->f = 6.28;
    printf("p->f = %.2f\n", p->f);  // 6.28
    
    p->c = 'Y';
    printf("p->c = %c\n", p->c);  // Y
    
    return 0;
}

6. 共用体的内存布局

6.1 共用体的大小

共用体的大小等于其最大成员的大小,因为所有成员共享同一块内存空间。

6.2 示例

c
#include <stdio.h>

// 定义一个共用体
union Data {
    int i;      // 4字节
    float f;    // 4字节
    char c;     // 1字节
};

// 定义另一个共用体
union Value {
    int i;      // 4字节
    double d;   // 8字节
    char s[20]; // 20字节
};

int main() {
    printf("sizeof(int): %zu\n", sizeof(int));        // 4
    printf("sizeof(float): %zu\n", sizeof(float));    // 4
    printf("sizeof(char): %zu\n", sizeof(char));      // 1
    printf("sizeof(union Data): %zu\n", sizeof(union Data));  // 4
    
    printf("\nsizeof(double): %zu\n", sizeof(double));  // 8
    printf("sizeof(char[20]): %zu\n", sizeof(char[20]));  // 20
    printf("sizeof(union Value): %zu\n", sizeof(union Value));  // 20
    
    return 0;
}

6.3 内存对齐

与结构体类似,共用体也会进行内存对齐,以提高内存访问效率。共用体的对齐要求等于其所有成员的对齐要求的最大值。

6.4 示例

c
#include <stdio.h>

// 定义一个共用体
union Test {
    char c;  // 对齐要求为1字节
    int i;   // 对齐要求为4字节
    double d;  // 对齐要求为8字节
};

int main() {
    printf("sizeof(char): %zu\n", sizeof(char));    // 1
    printf("sizeof(int): %zu\n", sizeof(int));      // 4
    printf("sizeof(double): %zu\n", sizeof(double));  // 8
    printf("sizeof(union Test): %zu\n", sizeof(union Test));  // 8
    
    return 0;
}

7. typedef与共用体

使用typedef可以为共用体类型创建别名,使代码更加简洁易读。

7.1 示例

c
#include <stdio.h>

// 使用typedef为共用体创建别名
typedef union {
    int i;
    float f;
    char c;
} Data;

int main() {
    // 使用别名声明共用体变量
    Data data;
    
    // 访问成员
    data.i = 100;
    printf("data.i = %d\n", data.i);  // 100
    
    data.f = 3.14;
    printf("data.f = %.2f\n", data.f);  // 3.14
    
    data.c = 'A';
    printf("data.c = %c\n", data.c);  // A
    
    return 0;
}

8. 共用体的应用场景

共用体在C语言中有特定的应用场景:

8.1 节省内存空间

当需要在不同的时间存储不同类型的数据时,使用共用体可以节省内存空间。

c
// 表示不同类型的消息
typedef struct {
    int type;  // 消息类型
    union {
        int i_value;     // 整数类型的消息
        float f_value;   // 浮点数类型的消息
        char s_value[50];  // 字符串类型的消息
    } value;
} Message;

// 使用示例
void process_message(Message msg) {
    switch (msg.type) {
        case 0:  // 整数类型
            printf("处理整数消息: %d\n", msg.value.i_value);
            break;
        case 1:  // 浮点数类型
            printf("处理浮点数消息: %.2f\n", msg.value.f_value);
            break;
        case 2:  // 字符串类型
            printf("处理字符串消息: %s\n", msg.value.s_value);
            break;
    }
}

8.2 类型转换

共用体可以用于在不同类型之间进行转换,特别是在底层编程中。

c
// 用于类型转换的共用体
typedef union {
    int i;
    float f;
    char bytes[sizeof(float)];
} FloatConverter;

// 示例:获取浮点数的字节表示
void print_float_bytes(float f) {
    FloatConverter converter;
    converter.f = f;
    
    printf("浮点数 %.2f 的字节表示: ", f);
    for (int i = 0; i < sizeof(float); i++) {
        printf("%02X ", (unsigned char)converter.bytes[i]);
    }
    printf("\n");
}

8.3 位操作

共用体可以用于位操作,特别是在嵌入式系统编程中。

c
// 用于位操作的共用体
typedef union {
    unsigned int value;
    struct {
        unsigned int bit0: 1;
        unsigned int bit1: 1;
        unsigned int bit2: 1;
        unsigned int bit3: 1;
        unsigned int bit4: 1;
        unsigned int bit5: 1;
        unsigned int bit6: 1;
        unsigned int bit7: 1;
    } bits;
} Byte;

// 示例:设置和清除位
void set_bit(Byte *b, int position) {
    b->value |= (1 << position);
}

void clear_bit(Byte *b, int position) {
    b->value &= ~(1 << position);
}

int get_bit(Byte b, int position) {
    return (b.value >> position) & 1;
}

8.4 变体类型

共用体可以用于实现变体类型,即可以存储不同类型数据的变量。

c
// 变体类型的标签
typedef enum {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
} TypeTag;

// 变体类型
typedef struct {
    TypeTag tag;
    union {
        int i;
        float f;
        char *s;
    } data;
} Variant;

// 创建变体类型
Variant create_int_variant(int value) {
    Variant v;
    v.tag = TYPE_INT;
    v.data.i = value;
    return v;
}

Variant create_float_variant(float value) {
    Variant v;
    v.tag = TYPE_FLOAT;
    v.data.f = value;
    return v;
}

Variant create_string_variant(char *value) {
    Variant v;
    v.tag = TYPE_STRING;
    v.data.s = value;
    return v;
}

// 打印变体类型
void print_variant(Variant v) {
    switch (v.tag) {
        case TYPE_INT:
            printf("整数: %d\n", v.data.i);
            break;
        case TYPE_FLOAT:
            printf("浮点数: %.2f\n", v.data.f);
            break;
        case TYPE_STRING:
            printf("字符串: %s\n", v.data.s);
            break;
    }
}

9. 共用体与结构体的区别

特性结构体共用体
内存分配每个成员都有自己的内存空间所有成员共享同一块内存空间
大小等于所有成员大小之和(考虑内存对齐)等于最大成员的大小(考虑内存对齐)
初始化可以初始化所有成员只能初始化一个成员
成员访问可以同时访问所有成员同一时间只能访问一个成员
修改影响修改一个成员不影响其他成员修改一个成员会影响其他成员
应用场景表示具有多个属性的对象表示在不同时间使用的不同类型的数据

10. 示例:综合运用

让我们看一个综合运用共用体的例子:

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

// 定义一个表示不同类型值的共用体
typedef union {
    int i;
    float f;
    char s[20];
} Value;

// 定义一个表示带类型的值的结构体
typedef struct {
    int type;  // 0: int, 1: float, 2: string
    Value value;
} TypedValue;

// 创建带类型的值
TypedValue create_int_value(int i) {
    TypedValue tv;
    tv.type = 0;
    tv.value.i = i;
    return tv;
}

TypedValue create_float_value(float f) {
    TypedValue tv;
    tv.type = 1;
    tv.value.f = f;
    return tv;
}

TypedValue create_string_value(const char *s) {
    TypedValue tv;
    tv.type = 2;
    strcpy(tv.value.s, s);
    return tv;
}

// 打印带类型的值
void print_typed_value(TypedValue tv) {
    switch (tv.type) {
        case 0:
            printf("整数: %d\n", tv.value.i);
            break;
        case 1:
            printf("浮点数: %.2f\n", tv.value.f);
            break;
        case 2:
            printf("字符串: %s\n", tv.value.s);
            break;
        default:
            printf("未知类型\n");
            break;
    }
}

// 计算带类型的值的大小
size_t size_of_typed_value(TypedValue tv) {
    switch (tv.type) {
        case 0:
            return sizeof(int);
        case 1:
            return sizeof(float);
        case 2:
            return strlen(tv.value.s) + 1;
        default:
            return 0;
    }
}

int main() {
    // 创建不同类型的值
    TypedValue tv1 = create_int_value(100);
    TypedValue tv2 = create_float_value(3.14);
    TypedValue tv3 = create_string_value("Hello, Union!");
    
    // 打印值
    printf("tv1: ");
    print_typed_value(tv1);
    printf("tv2: ");
    print_typed_value(tv2);
    printf("tv3: ");
    print_typed_value(tv3);
    
    // 计算大小
    printf("\ntv1大小: %zu字节\n", size_of_typed_value(tv1));
    printf("tv2大小: %zu字节\n", size_of_typed_value(tv2));
    printf("tv3大小: %zu字节\n", size_of_typed_value(tv3));
    
    // 共用体的大小
    printf("\nValue共用体大小: %zu字节\n", sizeof(Value));
    printf("TypedValue结构体大小: %zu字节\n", sizeof(TypedValue));
    
    return 0;
}

11. 共用体的注意事项

  1. 成员共享内存:共用体的所有成员共享同一块内存空间,修改一个成员会影响其他成员的值
  2. 初始化限制:只能初始化共用体的一个成员
  3. 类型安全:访问未被修改的成员可能会得到不确定的值,需要确保访问的成员是最近修改的那个
  4. 内存对齐:共用体的大小和对齐要求取决于其最大成员
  5. 指针访问:使用共用体指针时,需要确保指针类型与访问的成员类型匹配
  6. 字符串处理:当共用体包含字符串时,需要注意字符串的长度和内存分配
  7. 类型标签:通常需要一个额外的变量(如枚举)来跟踪当前使用的成员类型

12. 小结

共用体是C语言中一种特殊的数据类型,它允许在同一块内存空间中存储不同类型的数据。在本章节中,我们学习了:

  1. 共用体的定义:使用union关键字定义共用体类型
  2. 共用体变量的声明和初始化:创建共用体类型的变量并赋值
  3. 访问共用体成员:使用点运算符(.)或箭头运算符(->)访问共用体成员
  4. 共用体的内存布局:共用体的大小等于其最大成员的大小,所有成员共享同一块内存空间
  5. typedef与共用体:为共用体类型创建别名,使代码更加简洁易读
  6. 共用体的应用场景:节省内存空间、类型转换、位操作、变体类型等
  7. 共用体与结构体的区别:内存分配、大小计算、初始化方式等方面的差异

共用体在C语言中虽然不如结构体常用,但在特定场景下非常有用,特别是当需要在不同的时间存储不同类型的数据,或者需要节省内存空间时。通过合理使用共用体,可以编写出更加灵活、高效的C程序。