我学到了,但并没有真正得到工会。我遍历的每一个C或C ++文本都对它们进行了介绍(有时会进行介绍),但是它们却很少给出为什么或在何处使用它们的实际示例。工会在现代(甚至是传统)案例中什么时候有用?我仅有的两个猜测是,当您的工作空间非常有限时,或者当您开发API(或类似的东西)时,您想强迫最终用户在多个对象/类型中只有一个实例时,对微处理器进行编程一度。这两个猜测是否接近正确?
我学到了,但并没有真正得到工会。我遍历的每一个C或C ++文本都对它们进行了介绍(有时会进行介绍),但是它们却很少给出为什么或在何处使用它们的实际示例。工会在现代(甚至是传统)案例中什么时候有用?我仅有的两个猜测是,当您的工作空间非常有限时,或者当您开发API(或类似的东西)时,您想强迫最终用户在多个对象/类型中只有一个实例时,对微处理器进行编程一度。这两个猜测是否接近正确?
Answers:
联合通常与鉴别公司一起使用:一个变量,指示联合的哪些字段有效。例如,假设您要创建自己的Variant类型:
struct my_variant_t {
int type;
union {
char char_value;
short short_value;
int int_value;
long long_value;
float float_value;
double double_value;
void* ptr_value;
};
};
然后,您将使用它,例如:
/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
v->type = VAR_FLOAT;
v->float_value = initial_value;
}
/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
switch (v->type) {
case VAR_FLOAT:
v->float_value += n;
break;
case VAR_INT:
v->int_value += n;
break;
...
}
}
这实际上是一个非常常见的习惯用法,尤其是在Visual Basic内部。
有关真实示例,请参见SDL的SDL_Event联合。(此处为实际源代码)。type
联合的顶部有一个字段,并且在每个SDL_ * Event结构上重复相同的字段。然后,要处理正确的事件,您需要检查该type
字段的值。
好处很简单:只有一种数据类型可以处理所有事件类型,而无需使用不必要的内存。
struct object
在github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
我发现C ++联合非常酷。似乎人们通常只想到用例,该用例是要“就地”更改联合实例的值(似乎仅用于节省内存或执行可疑的转换)。
实际上,即使您从未更改任何工会实例的值,工会也可以作为软件工程工具发挥巨大作用。
使用并集,您可以将多个任意类重新组合为一个面额,这与基类及其派生类的情况并非没有相似之处。但是,对于给定的联合实例,可以做或不能做的是:
struct Batman;
struct BaseballBat;
union Bat
{
Batman brucewayne;
BaseballBat club;
};
ReturnType1 f(void)
{
BaseballBat bb = {/* */};
Bat b;
b.club = bb;
// do something with b.club
}
ReturnType2 g(Bat& b)
{
// do something with b, but how do we know what's inside?
}
Bat returnsBat(void);
ReturnType3 h(void)
{
Bat b = returnsBat();
// do something with b, but how do we know what's inside?
}
似乎程序员在要使用给定的联合实例时必须确定其内容的类型。f
上面的函数就是这种情况。但是,如果函数要接收联合实例作为传递的参数,例如g
,则它将不知道该如何处理。返回联合实例的函数也是如此,请参阅h
:调用方如何知道内部内容?
如果联合实例永远不会作为参数或返回值传递,那么它必然具有非常单调的生活,当程序员选择更改其内容时会兴奋不已:
Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;
这是工会最(不受欢迎)的用例。另一个用例是,联合实例附带一些可以告诉您其类型的东西。
object
来自Class
”假设一个程序员选择总是将一个联合实例与一个类型描述符配对(我将由读者自行决定想象一个这样的对象的实现)。如果程序员想要的是节省内存,并且类型描述符的大小相对于联合的大小不可忽略,则这会破坏联合本身的目的。但是,让我们假设至关重要的是,在被调用者或调用者不知道内部内容的情况下,可以将联合实例作为参数或返回值进行传递。
然后,程序员必须编写一个 switch
控制流语句,以告知Bruce Wayne除了木棍或类似的东西。当联合体中只有两种类型的内容时,还算不错,但显然,联合体不再扩展。
正如ISO C ++标准建议书的作者早在2008年就指出的那样,
许多重要的问题域需要大量的对象或有限的内存资源。在这些情况下,节省空间非常重要,而合并通常是实现此目的的理想方法。实际上,一个常见的用例是工会在其生命周期内从未更改其活动成员的情况。可以像仅包含一个成员的结构一样对其进行构造,复制和销毁。此方法的典型应用是创建不相关类型的异构集合,这些不相关类型不会动态分配(也许它们是在映射中或数组成员中就地构造的)。
现在,举一个带有UML类图的示例:
用简单的英语来说就是这样的:类A的对象可以具有B1,...,Bn中的任何类的对象,并且每种类型中最多可以有一个对象,其中n是一个很大的数字,至少为10。
我们不想像这样向A添加字段(数据成员):
private:
B1 b1;
.
.
.
Bn bn;
因为n可能会有所不同(我们可能想在混合中添加Bx类),并且因为这会导致构造函数混乱,并且因为A对象会占用很多空间。
我们可以使用带有void*
指针的古怪指针容器来Bx
强制转换以检索它们,但这很笨拙,所以采用C风格……但是更重要的是,这将使我们可以管理许多动态分配的对象。
相反,可以执行以下操作:
union Bee
{
B1 b1;
.
.
.
Bn bn;
};
enum BeesTypes { TYPE_B1, ..., TYPE_BN };
class A
{
private:
std::unordered_map<int, Bee> data; // C++11, otherwise use std::map
public:
Bee get(int); // the implementation is obvious: get from the unordered map
};
然后,要从中获取联合实例的内容data
,请使用a.get(TYPE_B2).b2
等,其中a
类A
实例为。
一个示例是在嵌入式领域,其中寄存器的每一位可能表示不同的含义。例如,使用8位整数和具有8个单独的1位位域的结构的并集,可以更改一位或整个字节。
void*
s或掩码和移位。
REG |= MASK
和的简单陈述REG &= ~MASK
。如果那容易出错,则将它们放在#define SETBITS(reg, mask)
和中#define CLRBITS(reg, mask)
。不要依赖编译器来获得一定的顺序位(stackoverflow.com/questions/1490092/...)
“但是,不要以为联盟只是早先的保留。联盟可能通过允许数据重叠来节省空间最有用,而这在C ++和当今的现代世界中仍然是理想的。例如,其中一些最高级C ++现在,世界上的标准库实现都只使用这种技术来实现“小字符串优化”,这是一种伟大的优化选择,它可以重用字符串对象本身内部的存储:对于大字符串,字符串对象内部的空间通常存储动态指向的指针。分配的缓冲区和整理信息,例如缓冲区的大小;对于小字符串,取而代之的是重复使用相同的空间直接存储字符串内容,并完全避免任何动态内存分配。有关小字符串优化(以及其他相当深入的字符串优化和悲观化)的更多信息,请参阅...。”
关于一个不太有用的示例,请参见gcc,严格混叠和通过union进行转换这一冗长但不确定的问题。
好吧,我能想到的一个示例用例是:
typedef union
{
struct
{
uint8_t a;
uint8_t b;
uint8_t c;
uint8_t d;
};
uint32_t x;
} some32bittype;
然后,您可以访问该32位数据块的8位单独部分;但是,请准备好被字节序咬住。
这只是一个假设的示例,但是每当您想要将字段中的数据拆分为这样的组成部分时,都可以使用并集。
也就是说,还有一种字节顺序安全的方法:
uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;
例如,由于该二进制操作将由编译器转换为正确的字节序。
工会的一些用途:
union { unsigned char byte_v[16]; long double ld_v; }
使用此声明,可以很容易地显示a的十六进制字节值
long double
,更改指数的符号,确定它是否为非正规值或对不支持它的CPU实施long double运算等。
当字段取决于某些值时,节省存储空间:
class person {
string name;
char gender; // M = male, F = female, O = other
union {
date vasectomized; // for males
int pregnancies; // for females
} gender_specific_data;
}
Grep包含文件,供您的编译器使用。您会发现数十种到数百种用途union
:
[wally@zenetfedora ~]$ cd /usr/include
[wally@zenetfedora include]$ grep -w union *
a.out.h: union
argp.h: parsing options, getopt is called with the union of all the argp
bfd.h: union
bfd.h: union
bfd.h:union internal_auxent;
bfd.h: (bfd *, struct bfd_symbol *, int, union internal_auxent *);
bfd.h: union {
bfd.h: /* The value of the symbol. This really should be a union of a
bfd.h: union
bfd.h: union
bfdlink.h: /* A union of information depending upon the type. */
bfdlink.h: union
bfdlink.h: this field. This field is present in all of the union element
bfdlink.h: the union; this structure is a major space user in the
bfdlink.h: union
bfdlink.h: union
curses.h: union
db_cxx.h:// 4201: nameless struct/union
elf.h: union
elf.h: union
elf.h: union
elf.h: union
elf.h:typedef union
_G_config.h:typedef union
gcrypt.h: union
gcrypt.h: union
gcrypt.h: union
gmp-i386.h: union {
ieee754.h:union ieee754_float
ieee754.h:union ieee754_double
ieee754.h:union ieee854_long_double
ifaddrs.h: union
jpeglib.h: union {
ldap.h: union mod_vals_u {
ncurses.h: union
newt.h: union {
obstack.h: union
pi-file.h: union {
resolv.h: union {
signal.h:extern int sigqueue (__pid_t __pid, int __sig, __const union sigval __val)
stdlib.h:/* Lots of hair to allow traditional BSD use of `union wait'
stdlib.h: (__extension__ (((union { __typeof(status) __in; int __i; }) \
stdlib.h:/* This is the type of the argument to `wait'. The funky union
stdlib.h: causes redeclarations with either `int *' or `union wait *' to be
stdlib.h:typedef union
stdlib.h: union wait *__uptr;
stdlib.h: } __WAIT_STATUS __attribute__ ((__transparent_union__));
thread_db.h: union
thread_db.h: union
tiffio.h: union {
wchar.h: union
xf86drm.h:typedef union _drmVBlank {
联合在处理字节级(低级)数据时很有用。
我最近的用法之一是IP地址建模,如下所示:
// Composite structure for IP address storage
union
{
// IPv4 @ 32-bit identifier
// Padded 12-bytes for IPv6 compatibility
union
{
struct
{
unsigned char _reserved[12];
unsigned char _IpBytes[4];
} _Raw;
struct
{
unsigned char _reserved[12];
unsigned char _o1;
unsigned char _o2;
unsigned char _o3;
unsigned char _o4;
} _Octet;
} _IPv4;
// IPv6 @ 128-bit identifier
// Next generation internet addressing
union
{
struct
{
unsigned char _IpBytes[16];
} _Raw;
struct
{
unsigned short _w1;
unsigned short _w2;
unsigned short _w3;
unsigned short _w4;
unsigned short _w5;
unsigned short _w6;
unsigned short _w7;
unsigned short _w8;
} _Word;
} _IPv6;
} _IP;
我使用工会的一个例子:
class Vector
{
union
{
double _coord[3];
struct
{
double _x;
double _y;
double _z;
};
};
...
}
这使我可以将数据作为数组或元素进行访问。
我使用了一个联合,使不同的术语指向相同的值。在图像处理中,无论我是在处理列还是在X方向上的宽度或大小,它都可能会造成混淆。为了解决这个问题,我使用了一个并集,所以我知道哪些描述一起使用。
union { // dimension from left to right // union for the left to right dimension
uint32_t m_width;
uint32_t m_sizeX;
uint32_t m_columns;
};
union { // dimension from top to bottom // union for the top to bottom dimension
uint32_t m_height;
uint32_t m_sizeY;
uint32_t m_rows;
};
联合的一个出色用法是内存对齐,这是我在PCL(点云库)源代码中找到的。API中的单个数据结构可以针对两种体系结构:具有SSE支持的CPU和不具有SSE支持的CPU。例如:PointXYZ的数据结构是
typedef union
{
float data[4];
struct
{
float x;
float y;
float z;
};
} PointXYZ;
3个浮子用一个额外的浮子填充以进行SSE对齐。因此对于
PointXYZ point;
用户可以访问point.data [0]或point.x(取决于SSE支持)以访问x坐标。以下链接提供了更多类似的更好的用法详细信息:PCL文档PointT类型
的union
关键字,同时仍然在C中使用++ 03 1,是大多的C-天的残余。最明显的问题是它仅适用于POD 1。
但是,联合的想法仍然存在,并且Boost库确实具有类似联合的类:
boost::variant<std::string, Foo, Bar>
它具有union
(如果不是全部)的大部分优点,并添加:
在实践中,已经证明,这是相当于组合union
+ enum
,和基准,这是因为快速(而boost::any
更的境界dynamic_cast
,因为它使用RTTI)。
1联合在C ++ 11中进行了升级(不受限制的联合),现在可以包含带有析构函数的对象,尽管用户必须手动调用析构函数(在当前活动的联合成员上)。使用变体仍然容易得多。
boost::variant
不是试图对自己使用的工会。工会周围存在太多未定义的行为,以至于您无法正确实现工会的机会。
联合的主要用途是节省空间,因为它提供了一种让许多不同类型存储在同一空间中的方法。联合还 提供了粗略的多态性。但是,没有类型检查,因此程序员必须确保在不同的上下文中访问正确的字段。并集变量的相关字段通常由其他变量的状态(可能在封闭结构中)确定。
一种常见的C编程习惯用法通过将联合赋值给联合的一个字段并从另一个字段中读取,来执行C ++称为reinterpret_cast的操作,就像在代码中所做的那样,这取决于值的原始表示形式。
在C的早期(例如1974年记录),所有结构都为其成员共享一个公共名称空间。每个成员名称都与一个类型和一个偏移量相关联;如果“ wd_woozle”是偏移量12处的“ int”,则给定p
任何结构类型的指针p->wd_woozle
都将等效于*(int*)(((char*)p)+12)
。该语言要求所有结构类型的所有成员都必须具有唯一的名称,只是在使用它们的每个结构将它们视为共同的初始序列的情况下,它明确允许成员名称的重用。
可以混杂使用结构类型的事实使结构的行为好像它们包含重叠的字段一样。例如,给定的定义:
struct float1 { float f0;};
struct byte4 { char b0,b1,b2,b3; }; /* Unsigned didn't exist yet */
代码可以声明“ float1”类型的结构,然后使用“成员” b0 ... b3访问其中的各个字节。当更改语言以使每个结构为其成员接收一个单独的命名空间时,依赖于以多种方式访问事物的能力的代码将被破坏。为不同的结构类型分隔出名称空间的值足以要求更改此类代码以容纳它,但是此类技术的价值足以证明扩展语言以继续支持它是合理的。
这已经被写入利用到内的访问存储的能力代码struct float1
,就好像是一个struct byte4
:可以在新的语言文字工作通过增加宣发union f1b4 { struct float1 ff; struct byte4 bb; };
,声明的对象类型union f1b4;
,而不是struct float1
和替换访问f0
,b0
,b1
,等随着ff.f0
,bb.b0
,bb.b1
等。虽然有更好的方法这样的代码可能已被支持,该union
方法是至少在某种程度上可行的,至少的别名规则C89时代的解释。
严格的别名规则对工会的重要性已经有了新的提高。最近版本的C标准引入新的提高。
您可以在不违反C标准的情况下使用union do进行类型修饰。
该程序具有未指定的行为(因为我已经假设了float
并且unsigned int
具有相同的长度),但是没有未定义的行为(请参见此处)。
#include <stdio.h>
union float_uint
{
float f;
unsigned int ui;
};
int main()
{
float v = 241;
union float_uint fui = {.f = v};
//May trigger UNSPECIFIED BEHAVIOR but not UNDEFINED BEHAVIOR
printf("Your IEEE 754 float sir: %08x\n", fui.ui);
//This is UNDEFINED BEHAVIOR as it violates the Strict Aliasing Rule
unsigned int* pp = (unsigned int*) &v;
printf("Your IEEE 754 float, again, sir: %08x\n", *pp);
return 0;
}
我想添加一个使用联合的良好实践示例-实现公式计算器/解释器或在计算中使用某种形式(例如,您想在计算公式的运行时部分使用可修改的方法-数字求解方程式-仅例如)。因此,您可能需要定义不同类型的数字/常量(整数,浮点数甚至是复数),如下所示:
struct Number{
enum NumType{int32, float, double, complex}; NumType num_t;
union{int ival; float fval; double dval; ComplexNumber cmplx_val}
}
因此,您可以节省内存,更重要的是-避免对可能数量极多(如果使用大量运行时定义的数量)的小型对象(与通过类继承/多态性实现的对象相比)进行动态分配。但是更有趣的是,对于这种类型的结构,您仍然可以使用C ++多态性的功能(例如,如果您喜欢双重分派;)。只需将“虚拟”接口指针添加到所有数字类型的父类作为此结构的字段,而不是原始类型(除了原始类型)/ 指向此实例,或使用良好的旧C函数指针即可。
struct NumberBase
{
virtual Add(NumberBase n);
...
}
struct NumberInt: Number
{
//implement methods assuming Number's union contains int
NumberBase Add(NumberBase n);
...
}
struct NumberDouble: Number
{
//implement methods assuming Number's union contains double
NumberBase Add(NumberBase n);
...
}
//e.t.c. for all number types/or use templates
struct Number: NumberBase{
union{int ival; float fval; double dval; ComplexNumber cmplx_val;}
NumberBase* num_t;
Set(int a)
{
ival=a;
//still kind of hack, hope it works because derived classes of Number dont add any fields
num_t = static_cast<NumberInt>(this);
}
}
因此,如果需要,您可以使用多态性来代替switch(type)的类型检查-具有内存有效的实现(没有动态分配小对象)。
从http://cplus.about.com/od/learningc/ss/lowlevel_9.htm:
工会的用途很少而且相差甚远。在大多数计算机上,指针和int的大小通常是相同的-这是因为两者通常都适合CPU中的寄存器。因此,如果您想对int指针进行快速而肮脏的转换,或者相反,请声明一个并集。
union intptr { int i; int * p; }; union intptr x; x.i = 1000; /* puts 90 at location 1000 */ *(x.p)=90;
联合的另一种用法是在命令或消息协议中发送和接收大小不同的消息。每种消息类型将保存不同的信息,但是每种消息将具有固定部分(可能是结构)和可变部分位。这就是您可能会实现的方式。
struct head { int id; int response; int size; }; struct msgstring50 { struct head fixed; char message[50]; } struct
struct msgstring80 {struct head fixed; 字符消息[80]; }
struct msgint10 {struct head fixed; int讯息[10]; } struct msgack {struct head fixed; 好的 } union messagetype {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }实际上,尽管并集大小相同,但仅发送有意义的数据而不浪费空间是有意义的。msgack的大小仅为16个字节,而msgstring80的大小为92个字节。因此,在初始化messagetype变量时,将根据其类型设置其size字段。然后其他功能可以使用它来传输正确数量的字节。
联合提供了一种在单个存储区域中处理不同类型数据的方式,而无需在程序中嵌入任何与机器无关的信息。它们类似于pascal中的变体记录
例如,在编译器符号表管理器中可能找到一个示例,假设常量可以是int,float或字符指针。特定常数的值必须存储在适当类型的变量中,但是,如果该值占据相同的存储量并且无论其类型如何都存储在相同的位置,则对于表管理最方便。这是联合的目的-单个变量可以合法地容纳几种类型之一。语法基于以下结构:
union u_tag {
int ival;
float fval;
char *sval;
} u;
变量u足够大,可以容纳三种类型中的最大值。具体大小取决于实现方式。这些类型中的任何一种都可以分配给u,然后在表达式中使用,只要用法一致