自定义类型 - 联合

2024 - 10 - 13 - 笔记 - 29
作者(Author): 郑龙浩 / 仟濹(CSDN 账号名)

联合(共用体)

① 联合的概念

在C语言中,联合(Union)是一种自定义类型,这种(联合)类型定义的变量包含一系列的成员,其显著特征是这些成员共享(或公用)同一块内存空间,这意味着在同一时间点上,这块内存空间只能存储联合中某个成员的数据(因此联合也被称为共用体)。

联合需要注意的问题:联合类型的成员共享同一个内存空间,因此不应同时对两个或更多成员进行赋值,否则会导致数据相互覆盖。在使用联合时,需要确保在任意时刻只存储和访问一个成员的数据。

② 联合类型的定义
//联合类型的声明
union Un
{
 char c;
 int i;
};
//联合变量的定义
union Un AA;
③ 联合占多大空间?成员占多大空间?联合变量与成员地址是一样的?
#include 
union Un
{
    char c;
    int i;
};
int main()
{
    union Un AA;
    AA.c = 'a';
    printf("%d\n", sizeof(AA));
    printf("%d\n", sizeof(AA.c) );
    printf("%d\n", sizeof(AA.i) );

    //两个地址打印出来是否一样?
    //是一样的
    printf("%p\n", &AA);
    printf("%p\n", &(AA.c));
    printf("%p\n", &(AA.i));

}

打印结果:

4
1
4
000000000061FE1C
000000000061FE1C
000000000061FE1C
④ 我的疑惑,联合中成员都赋值,会相互覆盖吗?

是的,会覆盖

下面是AI对我所问问题的回答:

  1. 对于联合,如果我对一个成员赋值,是不是就没法对另一个成员进行赋值了,我的疑惑点是,联合是公用一个内存空间,如果是给一个成员赋值了,给另一个成员赋值的时候,会被覆盖吗?

    您的理解是正确的。在C语言中,联合(Union)的所有成员共享同一块内存空间。因此,当您对联合中的一个成员赋值时,这块内存区域的内容会被该成员的值所占据。如果您随后对联合中的另一个成员进行赋值,那么之前存储的数据(即之前赋值的成员的数据)确实会被新赋值的数据所覆盖。

    这是因为联合的内存布局是重叠的,所有成员都位于相同的内存起始地址。所以,当您访问或修改联合中的任何一个成员时,实际上都是在操作这块共享的内存区域。

    以下是一个简单的例子来说明这一点:

    #include   
      
    union MyUnion {  
        int intValue;  
        float floatValue;  
    };  
      
    int main() {  
        union MyUnion myUnionVar;  
      
        // 给intValue赋值  
        myUnionVar.intValue = 42;  
        printf("intValue: %d\n", myUnionVar.intValue); // 输出: intValue: 42  
      
        // 给floatValue赋值,这会覆盖之前intValue的值  
        myUnionVar.floatValue = 3.14f;  
        printf("intValue after floatValue assignment: %d\n", myUnionVar.intValue); // 输出可能是垃圾值,因为floatValue的值覆盖了intValue的内存  
        printf("floatValue: %f\n", myUnionVar.floatValue); // 输出: floatValue: 3.140000  
      
        return 0;  
    }
    

    在上面的例子中,当 myUnionVar.floatValue 被赋值时,之前存储在 myUnionVar.intValue 中的值(42)被覆盖了。因此,当您再次尝试访问 myUnionVar.intValue 时,您可能会得到一个不可预测的值(通常被称为“垃圾值”),因为这个内存区域现在存储的是 floatValue 的值(3.14f)。

    需要注意的是,由于整数和浮点数在内存中的表示方式不同,将一个浮点数赋值给原本存储整数的联合成员可能会导致整数成员读取时得到不可预测的结果。这进一步强调了在使用联合时需要小心处理数据类型的转换和访问。

  2. 那岂不是写联合的时候,如果有多个成员,我只能给一个成员赋值?

    是的,您的理解是正确的。在C语言中,当您定义一个联合(Union)并声明一个联合类型的变量时,由于联合的所有成员共享同一块内存空间,因此您通常只能在该内存空间中存储联合中某个成员的数据。这意味着,在任意给定的时间点,您通常只能安全地对联合中的一个成员进行赋值。

    如果您尝试对联合中的另一个成员进行赋值,那么之前存储的数据(即之前赋值的成员的数据)会被新赋值的数据覆盖。这可能会导致数据丢失或不一致,特别是当不同成员的数据类型在内存中的表示方式不同时。

    因此,在使用联合时,您需要仔细考虑何时以及如何对联合的成员进行赋值和访问。通常,您会在程序的某个阶段确定要存储和访问哪个成员的数据,并在该阶段内只对该成员进行操作。

    以下是一个使用联合的示例,其中我们根据某个条件来选择对哪个成员进行赋值:

    #include   
      
    union MyUnion {  
        int intValue;  
        float floatValue;  
    };  
      
    int main() {  
        union MyUnion myUnionVar;  
        int choice;  
      
        printf("Enter 1 to set intValue, or 2 to set floatValue: ");  
        scanf("%d", &choice);  
      
        if (choice == 1) {  
            myUnionVar.intValue = 42;  
            printf("intValue set to: %d\n", myUnionVar.intValue);  
        } else if (choice == 2) {  
            myUnionVar.floatValue = 3.14f;  
            printf("floatValue set to: %f\n", myUnionVar.floatValue);  
        } else {  
            printf("Invalid choice!\n");  
        }  
      
        // 注意:此时我们只能安全地访问之前设置的成员  
        // 如果再次设置另一个成员,之前的数据将被覆盖  
      
        return 0;  
    }
    

    在这个例子中,我们根据用户的输入来选择对联合的哪个成员进行赋值。在赋值之后,我们只能安全地访问之前设置的成员,因为再次设置另一个成员会覆盖之前的数据。

  3. 有什么方法可以给联合变量的多个成员赋值吗?

    在C语言中,直接给联合变量的多个成员赋值通常是不安全的,因为联合的所有成员共享同一块内存空间。一旦您对一个成员赋值,之前存储在其他成员中的数据就会被覆盖。因此,传统的做法是在任意时刻只对一个联合成员进行赋值和访问。

    然而,如果您确实需要在联合中存储多个值(尽管这违背了联合的初衷),有几种可能的解决方法,但它们都有各自的限制和潜在问题:

    1. 使用更大的数据类型
      如果联合的成员都是相同类型或者可以安全地转换为更大的类型,您可以定义一个足够大的成员来存储所有数据。然后,您可以使用类型转换和内存操作技巧来在这个大成员中存储和检索多个值。这种方法非常复杂且容易出错,通常不推荐。
    2. 使用结构体与联合的组合
      您可以定义一个结构体,其中包含联合以及其他必要的成员或变量。这样,您可以在结构体中存储额外的信息,同时使用联合来存储需要根据条件访问的数据。
    3. 手动管理内存
      在某些情况下,您可能需要在堆上分配内存来存储多个值,并在需要时手动复制这些数据到联合中。这种方法需要仔细管理内存以避免泄漏,并且增加了程序的复杂性。
    4. 使用位字段
      如果联合的成员是整数类型,并且您只需要存储少量的位,可以考虑使用位字段。位字段允许您在结构体中定义跨越多个字节的位级成员。然而,这种方法在跨平台或编译器时可能会有差异,并且不适合存储浮点数或其他复杂类型。
    5. 重新设计数据结构
      通常,最好的方法是重新考虑您的数据结构设计。如果您需要存储多个值,并且这些值之间没有直接的互斥关系,那么使用结构体而不是联合可能更合适。结构体允许您同时存储多个成员,而不会相互覆盖。

    请记住,联合的主要用途是在同一内存位置存储不同类型的值,但每次只能安全地访问其中一个成员。如果您需要同时存

  4. 如何避免这种情况发生呢,简答回答?

    要避免联合(Union)中成员赋值相互覆盖,可以:

    1. 使用一个额外的成员(如枚举或整数)作为“标签”来跟踪当前存储的是哪个成员的数据。
    2. 在每次访问联合成员之前,检查这个“标签”以确定应该访问哪个成员。
    3. 避免在不需要时随意对联合成员进行赋值。
    4. 对使用联合的代码进行仔细的审查和测试。

    这些措施可以帮助您更安全地使用联合,减少数据覆盖的风险。

⑤ 联合体的大小是最大成员的大小?

要注意:结构体和联合体计算大小的时候都存在**【对其】**

疑惑 :既然成员是公用内存空间,那么联合体的大小就是最大成员的那个大小吗???

不是的

联合体大小的基本原则:

  1. 至少容纳最大成员:联合体的大小至少要能够容纳其最大的成员。也就是说,联合体的总大小不能小于其最大成员的大小。
  2. 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

Eg:

下面程序打印联合体大小是5,还是8?

是8,因为也要进行对齐

#include 
union Un
{
    char arr[5];
    int i;
};
int main()
{
    printf("%d\n", sizeof(union Un));
    
	return 0;
}

分析:

  1. 数组类型与大小:
    • char arr[5] 是一个包含5个char类型元素的数组,它本身不是char类型,而是一个数组类型(char[5])。这个数组在内存中占用了5个字节。
  2. 联合体大小的计算:
    • 联合体的大小不是简单地取类型占字节数最大的那个成员的整数倍。实际上,联合体的大小需要满足其所有成员的对齐要求,并且可能包含一些填充字节(padding)。
    • 在这个例子中,char arr[5]占用了5个字节,而int i(假设int为4字节)的对齐要求通常是4字节(这取决于编译器和平台)。
    • 由于char arr[5]的大小不是4的整数倍,为了满足int i的对齐要求,编译器可能会在char arr[5]之后添加填充字节(padding),使得int i可以从一个4字节对齐的地址开始。
    • 因此,联合体Un的大小可能是8字节(5字节用于char arr[5],3字节用于填充,以满足int i的4字节对齐要求,然后是4字节用于int i本身)。但是,具体的大小和对齐方式取决于编译器和平台。
  3. 与结构体大小的比较:
    • 联合体的内存布局与结构体的内存布局有相似之处,都需要考虑对齐和填充字节。然而,它们的不同之处在于:联合体只考虑其成员中最大的对齐要求,并且所有成员共享同一块内存空间;而结构体则需要考虑每个成员的对齐要求,并为每个成员分配独立的内存空间。
  4. 输出结果的解释:
    • printf("%d\n", sizeof(union Un)); 将输出联合体Un的大小,这个大小可能是8字节(如上所述,但具体取决于编译器和平台)。

打印结果:

8

计算联合体大小【结论】

  • 联合体的大小不是类型占字节数最大的那个成员的整数倍。
  • 联合体的大小需要满足其所有成员的对齐要求,并可能包含填充字节。
  • 联合体Un的大小可能是8字节(但具体取决于编译器和平台)。
  • 联合体的内存布局与结构体的内存布局有相似之处,但它们的对齐方式和计算方式有所不同。
  • 当成员有数组的时候,该成员最大对齐数不是整个数组的大小,而是单个数组元素的大小。
⑥ 测试各种联合体大小
#include 
union Un
{
    char arr[8];
    int i;
};
int main()
{
    printf("%d\n", sizeof(union Un));
    
	return 0;
}
//8
#include 
union Un
{
    char arr[8];
    int i;
};
int main()
{
    printf("%d\n", sizeof(union Un));
    
	return 0;
}
//8
#include 
union Un
{
    char arr[4];
    int i;
};
int main()
{
    printf("%d\n", sizeof(union Un));
    
	return 0;
}
//4
#include 
union Un
{
    char arr[3];
    int i;
};
int main()
{
    printf("%d\n", sizeof(union Un));
    
	return 0;
}
//4
#include 
union Un
{
    short arr[7];
    int i;
};
int main()
{
    printf("%d\n", sizeof(union Un));
    
	return 0;
}
//16

你可能感兴趣的:(C语言学习笔记,算法,笔记,c语言,c++,c#)