联合(Union)和结构体(Struct)有何不同?其他编程语言是否也有类似的结构?

3
我看到了这段C语言中的联合体代码:

可能是重复问题:
C语言中结构体和联合体的区别

代码如下:

    union time
    {
      long simpleDate;
      double perciseDate;
    } mytime;

在C语言中,联合体和结构体有什么区别?你会在哪里使用联合体,它有什么好处?Java、C++或Python中是否有类似的结构?


3
在C语言中,结构体(structure)和联合体(union)是两种不同的用户定义类型。它们都允许您组合不同类型的变量以创建一个自定义数据类型。但是,它们之间有几个重要的区别。结构体存储每个成员变量的值,并且在内存中按顺序分配空间。相反,联合体只能同时存储一个成员变量的值,并且所有成员变量共享相同的内存空间。这意味着联合体可以节省内存空间,但是也意味着在任何时候只能使用其中一个成员变量的值。 - Michael Todd
1
不是技术上的重复,因为问题还涉及其他语言。 - Alice Purcell
7个回答

14

一个联合体在内存中的行为就像所有成员都在同一位置上,彼此重叠。

当你想要表示某种“通用”值或可以是一组类型中的任何一种类型的值时,这是非常有用的。由于字段重叠,只有在知道先前已初始化了哪个字段时,才能合法地访问字段。尽管如此,由于 C 不会检查这一点,许多编译器会生成允许这样做的代码,因此这是一种常见的技巧,例如:

union {
 int integer;
 float real;
} convert;

convert.real = 3.14;
printf("The float %f interpreted as an integer is %08x", convert.real, convert.integer);

如果您更加注重代码的规范性,需要跟踪联合类型中最后存储的内容,可以像下面这样实现:

typedef enum { INTEGER = 0, REAL, BOOLEAN, STRING } ValueType;

typedef struct {
  ValueType type;
  union {
  int integer;
  float real;
  char boolean;
  char *string;
  } x;
} Value;

需要注意的是,这里的联合实际上是包围结构体 Value 中的一个字段。访问可能看起来像这样:

void value_set_integer(Value *value, int x)
{
  value->type = INTEGER;
  value->x.integer = x;
}

这个记录表明联合体的当前内容是一个整数,并存储给定的值。例如,用于打印Value的函数可以检查type成员,并执行正确的操作:

void value_print(const Value *value)
{
  switch(value->type)
  {
  case INTEGER:
    printf("%d\n", value->x.integer);
    break;
  case REAL:
    printf("%g\n", value->x.real);
    break;
  /* ... and so on ... */
  }
}

Java中没有相应的功能。C++几乎是C的超集,具有相同的功能。它甚至比C的实现更胜一筹,并允许使用匿名联合。在C++中,上述代码可以不命名内部联合(x)而写成,这会使代码变得更短。


重叠有问题吗? - Sajad Bahmani
重叠的问题在于你不知道最后写入了什么。除了少数孤立的情况外,它们使用起来很危险,但在那些情况下非常方便。 - San Jacinto
“overlapping” 可能是描述联合体最优雅的方式,这个用语非常恰当。 - Doug T.

11

所以在你的例子中,当我分配时间时:

int main()
{
    time t;
}

编译器可以将 &t 处的内存解释为 long 类型之一:
t.simpleDate;

或者好像它是一个双精度浮点数:

t.perciseDate;

因此,如果t处的内存原始十六进制值如下:

0x12345678;

那个值可以被“解析”为双精度或长整型,取决于如何访问它。因此,为了使其有用,您必须知道长整型和双精度在内存中的打包和格式化方式。例如,长整型将是2补码有符号整数,您可以在这里阅读有关此内容的信息。您可以在这里了解如何以二进制格式格式化双精度。
然而,结构体只是将单独的变量分组,并将不同的地址间隔组合成一个内存块。
(请注意,您的示例可能存在危险,因为sizeof(long)可能为32位,而sizeof(double)始终为64位)
当您想要“原始”表示(如char数组)和“消息”表示时,通常会使用联合。例如,要通过套接字发送的消息:
struct Msg
{
   int msgType;
   double Val1;
   double Val2;
}; // assuming packing on 32-bit boundary

union
{
   Msg msg;
   unsigned char msgAsBinary[20];
};

希望这有所帮助。

这个答案比目前发布的其他答案好多了。 - San Jacinto
7
要让它有用,你需要知道长整型和双精度浮点数的打包和格式化方式。联合体的一个用途是写入一个成员,然后读取另一个成员,以获得某种重新解释转换。对于这种用途,您需要了解存储表示。但是还有另一种(最初预期的)用途,即存储任何一个成员,记住您所存储的内容,然后读回相同的成员。目的是在永远不需要同时使用两者时节约内存。对于此用途,格式无关紧要,标准并不保证,但大多数编译器都支持。 - Steve Jessop
@Steve Jessop:又一个评论(+1)提供了关键要点,应该合并到答案中。 - TheBlastOne

2

联合体(Union)让你以多种方式解释一个内存位置(原始的,二进制值)。

我曾经使用过一个例子,就是访问 uint32 的各个字节。

union {
  uint32 int;
  char bytes[4];
} uint_bytes;

联合体(union)提供了多种访问同一内存的方式。

联合体类型的大小等于联合体中最大类型的大小。


2
一个联合体是一种节省空间的存储“一种”多种类型的方式。它不提供重新发现存储在其中的类型的机制;这必须在外部确定。从技术上讲,在联合体中访问“错误”的类型(即未初始化的类型)会导致未定义的行为;在实践中,它通常会导致位级转换,并经常被用作执行此操作的一种方式。
虽然“union”类型在C++中存在(C++是C的超集),但大多数C++类型不能安全地存储在其中(具体而言,联合体只能容纳POD类型,即具有默认复制构造函数、默认析构函数和无虚方法的类)。如果你想要一个在C++中等效于联合体的节省空间、基于堆栈的对象存储方式,可以尝试使用Boost.Variant
在其他不太关注堆栈分配的语言中,多态性可以完成联合的工作。在Java中,所有东西都继承自Object,因此可以使用Object*来表示 任何 对象;或者您可以使用一个公共的超类或接口来限制对象集,以支持特定一组操作。
在Python中,任何变量都可以保存任何对象,因此从某种意义上说,所有变量都是联合的。通常情况下,您不需要确定变量中存储的类型;相反,使用 鸭子类型 -- 即查找它支持的方法而不是它实现的类型/接口。

1
在Java和Python(struct模块)中进行位级转换的示例将使其成为最完整的答案。 - Denis Otkidach
既然联合体严格来说不应该用于位级转换,那我想我不再回答这个问题了 ;) - Alice Purcell

1

联合体可以用来存储其任意一个成员,但是(与结构体不同)不能同时存储多个成员。您可以将其视为包含足够的空间以存储其最大的成员,并且重复使用相同的存储空间,用于实际分配值的任何成员。

C++也有联合体。Java没有。Python中的对象成员与C完全不同,它们存储在字典中而不是按顺序在内存中排列。我不知道Python是否有一些方便的库类,可以像C中那样充当联合体,但它不像在C中那样是对象的基本组成部分。


1

使用结构体时,每个数据项都有自己的内存位置,但是使用联合时,一次只使用一个数据项,并且为每个数据项分配的内存位于共享内存中。联合的数据项只会共享一个内存位置。联合的大小将是最大变量的大小。

这可能是有益的,因为有时我们可能不需要复杂数据结构中所有(相关)数据项的数据,并且仅存储/访问一个数据项。在这种情况下,联合有助于解决问题。


1

联合体只能同时使用一个成员,而结构体中所有成员都驻留在内存中。对于联合体,分配的空间大小为其包含的最长元素的大小。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接