我正在开展一个正在进行的项目,其中一些工会的定义如下:
/* header.h */
typedef union my_union_t {
float data[4];
struct {
float varA;
float varB;
float varC;
float varD;
};
} my_union;
如果我理解得很好,工会就是为了节省空间,所以sizeof(my_union_t)=其中变量的MAX。使用上面的陈述而不是这个陈述有什么好处:
typedef struct my_struct {
float varA;
float varB;
float varC;
float varD;
};
不会为这两个人分配的空间相同吗?
如何从 my_union 初始化varA,varB ...?
答案 0 :(得分:2)
联盟主要不是为了节省空间,而是为了实施sum types(为此,您将union
放在某些struct
或class
中一个可以保留运行时标记的区别字段。另外,我建议您使用最近的C ++标准,至少C++11,因为它更好地支持联合(例如,允许更容易地联合对象及其构造或初始化)。
使用你的联合的好处是能够将n
- 浮点(0 <= n <= 3)索引为u.data[n]
在声明为my_union u;
的某个变量中指定一个联合字段,只需代码,例如u.varB = 3.14;
在您的情况下与u.data[1] = 3.14;
当之无愧的联合的一个很好的例子是 mutable 对象,它可以包含int
或string
(在这种情况下你不能使用派生类):
class IntOrString {
bool isint;
union {
int num; // when isint is true
str::string str; // when isint is false
};
public:
IntOrString(int n=0) : isint(true), num(n) {};
IntOrString(std::string s) : isint(false), str(s) {};
IntOrString(const IntOrString& o): isint(o.isint)
{ if (isint) num = o.num; else str = o.str); };
IntOrString(IntOrString&&p) : isint(p.isint)
{ if (isint) num = std::move (p.num);
else str = std::move (p.str); };
~IntOrString() { if (isint) num=0; else str->~std::string(); };
void set (int n)
{ if (!isint) str->~std::string(); isint=true; num=n; };
void set (std::string s) { str = s; isint=false; };
bool is_int() const { return isint; };
int as_int() const { return (isint?num:0; };
const std::string as_string() const { return (isint?"":str;};
};
注意显式调用str
字段的析构函数。另请注意,您可以在标准容器(IntOrString
)
std::vector<IntOrString>
在C ++的未来版本中也可以参见std::optional(概念上是与void
标记的联合)
BTW,在Ocaml中,您只需编码:
type intorstring = Integer of int | String of string;;
并且您将使用pattern matching。如果你想让它变得可变,你需要做一个记录或参考它。
您最好以C ++惯用方式使用union
- s(有关一般建议,请参阅this)。
答案 1 :(得分:2)
在实现类似对象(类型字段和数据类型的并集)的变体或实现序列化时经常使用联合。
你使用工会的方式是灾难的一种方法。
您假设struct
中的union
正在打包float
,之间没有差距!
该标准保证float data[4];
是连续的,但不保证结构元素。你知道的另一件事就是varA
的地址;与data[0]
的地址相同。
绝不以这种方式使用联盟。
关于你的问题:&#34;我怎样才能从my_union初始化varA,varB ...&#34;。答案是,通过data[]
数组以正常的冗长方式访问结构成员而不是。
答案 2 :(得分:1)
优点是,使用联合,您可以通过两种不同的方式访问相同的内存。
在您的示例中,union包含四个浮点数。您可以将这些浮点数作为varA,varB ...访问,这可能是更具描述性的名称,或者您可以访问与数组数据[0],数据[1]相同的变量...这可能在循环中更有用。
使用联合,您也可以将相同的内存用于不同类型的数据,您可能会发现这对于编写函数来说是有用的,可以告诉您是否在大端或小端CPU上。
答案 3 :(得分:1)
不,这不是为了节省空间。它能够将一些二进制数据表示为各种数据类型。 例如
#include <iostream>
#include <stdint.h>
union Foo{
int x;
struct y
{
unsigned char b0, b1, b2, b3;
};
char z[sizeof(int)];
};
int main()
{
Foo bar;
bar.x = 100;
std::cout << std::hex; // to show number in hexadec repr;
for(size_t i = 0; i < sizeof(int); i++)
{
std::cout << "0x" << (int)bar.z[i] << " "; // int is just to show values as numbers, not a characters
}
return 0;
}
输出:0x64 0x0 0x0 0x0
相同的值存储在struct bar.y中,但不存储在数组中,而是存储在sturcture成员中。这是因为我的机器有一点endiannes。如果它很大,那么输出就会反转:0x0 0x0 0x0 0x64
您可以使用reinterpret_cast
:
#include <iostream>
#include <stdint.h>
int main()
{
int x = 100;
char * xBytes = reinterpret_cast<char*>(&x);
std::cout << std::hex; // to show number in hexadec repr;
for (size_t i = 0; i < sizeof(int); i++)
{
std::cout << "0x" << (int)xBytes[i] << " "; // (int) is just to show values as numbers, not a characters
}
return 0;
}
它很有用,例如,当你需要读取一些二进制文件时,它写在一个与你的结尾不同的机器上。您只需将值作为bytearray访问,并根据需要交换这些字节。
此外,当你必须处理bit fields时它是有用的,但它是一个完全不同的故事:)
答案 4 :(得分:1)
我认为理解工会的最好方法就是给出两个常见的实例。
第一个例子是使用图像。想象一下,你有和RGB图像排列在一个长缓冲区
大多数人会做的是将缓冲区表示为char*
,然后将其循环3以获得R,G,B。
你可以做的是做一个小联合,然后使用它来遍历图像缓冲区:
union RGB
{
char raw[3];
struct
{
char R;
char G;
char B;
} colors;
}
RGB* pixel = buffer[0];
///pixel.colors.R == The red color in the first pixel.
工会的另一个非常有用的用途是使用寄存器和位域
假设你有一个32位的值,代表一些HW寄存器,或者其他东西
有时,为了节省空间,您可以将32位分成位字段,但您还希望该寄存器的整个表示形式为32位类型。
这显然可以节省许多程序员毫无理由地使用的位移计算。
union MySpecialRegister
{
uint32_t register;
struct
{
unsigned int firstField : 5;
unsigned int somethingInTheMiddle : 25;
unsigned int lastField : 6;
} data;
}
// Now you can read the raw register into the register field
// then you can read the fields using the inner data struct
答案 5 :(得分:0)
联盟主要用于以不同方式表示相同的数据。想象一下,如果您有一个包含各种数据的用户定义结构,例如:
typedef struct
{
int x;
int y;
float f;
} my_struct;
现在您想通过串行总线将此结构发送到另一台计算机。但是您的串行总线硬件一次只能发送1个字节。在接收器端,有一个x字节的硬件缓冲区。为了能够发送数据类型,您可以建立联合:
typedef union
{
my_struct s;
uint8_t bytes[sizeof(my_struct)];
} my_union;
现在,只要您想发送和接收,就可以使用联合的bytes
成员;当您想要访问实际数据时,可以使用s
成员。
上述概念在硬件相关编程中经常使用:数据协议,硬件寄存器定义,内存映射,NVM驱动程序等。
注意:每当你做这样的事情时,要小心填充和对齐!编译器可以在struct / union内的任何位置自由插入填充字节以对齐数据。这可以打破上述概念。
正如其他答案中所指出的,你也可以使用工会来实现&#34;和类型&#34; /&#34;变种&#34;,但是这种用途的实际用途非常有限,如果有这样的用途甚至存在。
答案 6 :(得分:-1)
首先:避免访问访问相同内存但不同类型的联盟!
工会根本没有节省空间。唯一在同一内存区域定义多个名称!并且您只能在联合中一次存储其中一个元素。
如果你有
union X
{
int x;
char y[4];
};
你可以存储一个int OR 4个字符,但不能同时存储两个字符!一般的问题是,没有人知道哪个数据实际存储在一个联合中。如果存储一个int并读取字符,编译器将不会检查它,也没有运行时检查。解决方案通常是在结构中为union提供一个额外的数据元素,它包含实际存储的数据类型作为枚举。
struct Y
{
enum { IS_CHAR, IS_INT } tinfo;
union
{
int x;
char y[4];
};
}
但是在c ++中你总是应该使用类或结构,它们可以从像这样的空父类派生出来:
class Base
{
};
class Int_Type: public Base
{
...
int x;
};
class Char_Type: public Base
{
...
char y[4];
};
所以你可以设置指向base的指针,它实际上可以为你保存Int或Char类型。使用虚函数,您可以以面向对象的编程方式访问成员。
正如Basile的回答所提到的,一个有用的案例可以是通过不同名称访问相同类型。
union X
{
struct data
{
float a;
float b;
};
float arr[2];
};
允许使用与相同类型相同数据的不同访问方式。应该避免使用存储在同一存储器中的不同类型!