我最近遇到了一本很棒的数据结构书,“Data Structures Using C”(c)1991,当地图书馆的图书销售仅$ 2 。正如本书的标题所暗示的,本书涵盖了使用C编程语言的数据结构。
我知道这本书已经过时但可能会包含许多我在其他地方都不会遇到的高级C主题。
在5分钟内我发现了一些我不知道的关于C.的事情。我遇到了一个关于union
关键字的部分,我意识到我从未使用它,也没见过任何代码。我很高兴能够学到一些有趣的东西并迅速买下这本书。
对于那些不了解工会是什么的人,本书用一个好的比喻来解释:
要完全理解a的概念 工会,有必要审视它 实现。结构可能是 被视为一个地区的路线图 记忆。它定义了内存的方式 被解释。工会提供 几个不同的路线图 相同的记忆区域,它是 程序员的责任 确定当前的路线图 使用。在实践中,编译器 分配足够的存储空间 包含最大的成员 联盟。不过,这是路线图 这决定了存储的方式 解释。
我可以很容易地想出我会使用联盟的人为的情况或黑客。 (但我对人为的情况或黑客不感兴趣......)
您是否曾使用或看过使用Union解决问题的实现**比不使用Union更优雅**
如果你快速解释为什么使用union比不使用union更好/更容易,那么就增加了奖励。
答案 0 :(得分:24)
UNION在非OOP世界中实现某种多态性。通常,您有一个常见的部分,根据该部分,您使用其余的UNION。因此,在没有OOP语言并且想要避免过多指针算术的情况下,在某些情况下,联合会更加优雅。
答案 1 :(得分:18)
这对于设置寄存器而不是移位/掩码操作非常有用:
typedef union {
unsigned int as_int; // Assume this is 32-bits
struct {
unsigned int unused1 : 4;
unsigned int foo : 4;
unsigned int bar : 6;
unsigned int unused2 : 2;
unsigned int baz : 3;
unsigned int unused3 : 1;
unsigned int quux : 12;
} field;
} some_reg;
注意:打包的方式与机器有关。
some_reg reg;
reg.field.foo = 0xA;
reg.field.baz = 0x5;
write_some_register(some_address, reg.as_int);
我可能在那里的某个地方吹了一些语法,我的C生锈了:)
修改强>
顺便提一下,这也是相反的方式:
reg.as_int = read_some_register(some_address);
if(reg.field.bar == BAR_ERROR1) { ...
答案 2 :(得分:10)
实际上,当您编写设备驱动程序(struct
要发送到可能具有多种相似但不同格式的设备)并且您需要精确的内存排列时,它是一个很棒的工具......
答案 3 :(得分:8)
你应该知道在C ++中它们不是一个很好的解决方案,因为只有POD(普通旧数据)类型可以放在一个联合中。如果你的类有一个构造函数,析构函数,包含具有构造函数和/或析构函数的类(以及大约一百万个其他陷阱),它就不能成为一个联合的成员。
答案 4 :(得分:6)
我认为,Union是在C / C ++中实现VARIANT-like data types的最简单方法。
答案 5 :(得分:5)
它经常用于数据传输协议的规范中,您希望避免在数据结构中浪费空间。它允许通过为多个互斥选项使用相同的空间来保存存储空间。
例如:
enum PacketType {Connect, Disconnect};
struct ConnectPacket {};
struct DisconnectPacket {};
struct Packet
{
// ...
// various common data
// ...
enum PacketType type;
union
{
ConnectPacket connect;
DisconnectPacket disconnect;
} payload;
};
ConnectPacket和DisconnectPacket结构占用相同的空间,但这没关系,因为数据包不能同时是两种类型。枚举值用于确定联合的哪个部分正在使用中。使用union可以避免重复Packet结构的公共部分。
答案 6 :(得分:4)
考虑访问大变量中的单个字节的情况:
UInt32 x;
x = 0x12345678;
int byte_3 = x & 0x000000FF; // 0x78
int byte_2 = (x & 0x0000FF00) >> 8; // 0x56
int byte_1 = (x & 0x00FF0000) >> 16; // 0x34
int byte_0 = (x & 0xFF000000) >> 24; // 0x12
对于工会来说,这可以更加优雅:
typedef union
{
UInt32 value; // 32 bits
Byte byte[4]; // 4 * 8 bits
}
UInt32_Bytes;
UInt32_Bytes x;
x.value = 0x12345678;
int byte_3 = x.byte[3]; // 0x78
int byte_2 = x.byte[2]; // 0x56
int byte_1 = x.byte[1]; // 0x34
int byte_0 = x.byte[0]; // 0x12
使用union意味着您不再需要使用位掩码和移位运算符来访问各个字节。它还使字节访问显式。
答案 7 :(得分:4)
这是获取浮点数的IEEE位值的一种很好的方法(当然假设浮点数是您系统上的IEEE)。涉及将float *转换为int *的任何内容都有可能超越严格的别名规则。这不仅仅是理论上的 - 高水平的优化实际上会破坏你的代码。
从技术上讲,工会没有处理这个问题。在实践中,所有已知编译器将(a)允许您编写联合的一个成员并读回另一个成员,以及(b)执行写入后执行读取。 GCC至少能够将并集转换为一个寄存器,将整个事务转变为无操作(假设浮点数存储在寄存器中)。
答案 8 :(得分:2)
我们在很多代码中使用了联合来进行网络数据包解析。
Union分配最大元素的大小。您将使用最大消息大小的缓冲区元素创建联合,然后您可以轻松访问数据包中的值。
想象一下,数据“c123456”已联机,您需要解析并访问这些值:
#include <iostream>
using namespace std;
struct msg
{
char header;
union
{
char a[3];
char b[2];
char c[5];
char d[6];
char buf[10];
} data;
} msg;
int main()
{
struct msg m;
memcpy(&m, "c123456", sizeof("c123456"));
cout << "m.header: " << m.header << endl;
cout << "m.data.d: " << string(m.data.d,sizeof(m.data.d)) << endl;
cout << "m.data.b: " << string(m.data.b,sizeof(m.data.b)) << endl;
switch (m.header)
{
case 'a': cout << "a: " << string(m.data.a, sizeof(m.data.a)) << endl; break;
case 'b': cout << "b: " << string(m.data.b, sizeof(m.data.b)) << endl; break;
case 'c': cout << "c: " << string(m.data.c, sizeof(m.data.c)) << endl; break;
default: break;
}
}
输出如下:
m.header: c
m.data.d: 123456
m.data.b: 12
c: 12345
答案 9 :(得分:2)
我知道这已经重复了,但我会发布一个代码示例,看看联盟在阅读网络流量时如何增加优雅和效率:
#pragma packed(1)
struct header_t {
uint16_t msg_id;
uint16_t size;
};
struct command_t {
uint8_t cmd;
};
struct position_t {
uint32_t x;
uint32_t y;
uint32_t z;
};
// ... Rest of the messages in an IDS
struct message {
header_t header;
union {
command_t command;
position_t position;
} body;
};
#pragma packed(0)
message read( int socket ) {
message data;
unsigned int readed = read( socket, &data, sizeof(header_t) );
// error checks... readed bytes smaller than header size and such
readed = read( socket, &(data.body), data.header.size );
// error checks...
}
在上面的代码段中,您可以执行就地读取的消息,而无需关心接收到的具体对象类型。如果你没有使用联合,你将留下阅读标题,提取大小和类型,实例化一个适当类型的对象(在层次结构中或包含在变量类型内作为boost :: any / boost :: variant),并在新创建的空间上执行第二次读取。
我们广泛使用此解决方案来控制模拟器(一些公司不欣赏像DDS或HLA这样的'新'技术,仍然依赖于模拟器的原始UDP / TCP数据)。在网络层中,我们使用转换为内部数据结构(网络到主机转换,数据扩展......)的联合,然后将其提供给应用程序层。如前所述,您必须始终小心填充。
答案 10 :(得分:1)
我以类似markh44's回答的方式使用过一次粗略的数据多态性。我想要使用几种不同类型的数据。我创建了所有这些类型的联合以及包含union的结构和定义要使用哪种类型的代码。
union
{
data_type_1;
data_type_2;
data_type_3;
} data_union;
typedef struct _TAG_DATA_WRAPPED_
{
data_union data;
int data_type; //better an enum
} WRAPPED_DATA;
WRAPPED_DATA loads_of_data[1024];
回答你关于为什么这有利的问题:
这使您可以轻松分配不同类型数据的列表或数组,并以编程方式管理其类型。最大的问题当然是存储空间,因为如果类型具有非常不同的存储大小,您可能会浪费大量空间。
答案 11 :(得分:0)
我认为这是一个很好的例子:
struct fieldsv4{
unsigned int ip4 : 8;
unsigned int ip3 : 8;
unsigned int ip2 : 8;
unsigned int ip1 : 8;
};
typedef union {
unsigned int ip32; // Assume this is 32-bits
struct fieldsv4 part;
} ipv4;
ipv4 dir1;
struct fieldsv4 f1 = {1, 1, 168, 192}; //for little endian depending OS for big endian do not invert
dir1.part = f1;
ipv4 dir2= dir1;
dir2.part.ip4 = 2;
printf("%d.%d.%d.%d\n", dir2.part.ip1, dir2.part.ip2, dir2.part.ip3, dir2.part.ip4);
printf("%d.%d.%d.%d\n", dir1.part.ip1, dir1.part.ip2, dir1.part.ip3, dir1.part.ip4);
printf("%X\n", dir1.ip32 ^ dir2.ip32);