C++了解联合和结构
C++ understanding Unions and Structs
我来从事一个正在进行的项目,其中一些工会的定义如下:
/* 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;
};
分配给他们俩的空间不是一样吗?
我怎样才能初始化varA,varB...从my_union?
实现对象(类型字段和数据类型的联合(等变体或实现序列化。
你使用工会的方式是灾难的秘诀。
您假设union
中的struct
正在包装float
,然后之间没有间隙!该标准保证float data[4];
是连续的,但不能保证结构元素。您唯一知道的另一件事是 varA
的地址;与data[0]
的地址相同。
切勿以这种方式使用工会。
至于你的问题:"我怎样才能初始化varA,varB......从my_union?答案是,以正常的冗长方式访问结构成员,而不是通过data[]
数组。
Union 主要不是为了节省空间,而是为了实现 sum 类型(为此,您将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;
相同的效果
当
之无愧的联合的一个很好的例子是一个可变对象,它可以容纳一个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
的标记联合(
顺便说一句,在 Ocaml 中,您只需编写代码:
type intorstring = Integer of int | String of string;;
您将使用模式匹配。如果你想让它可变,你需要做一个记录或引用它。
您最好以C++惯用的方式使用 union
-s(有关一般建议,请参阅此处(。
我认为理解工会的最好方法是举出两个常见的实际例子。
第一个示例是处理图像。想象一下,你有和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 位值,它代表某个硬件寄存器或其他东西。
有时,为了节省空间,您可以将 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
优点是使用联合,您可以通过两种不同的方式访问相同的内存。
在您的示例中,联合包含四个浮点数。您可以将这些浮点数作为varA,varB...这可能是更具描述性的名称,或者您可以访问与数组 data[0]、data[1]...这在循环中可能更有用。
通过联合,您还可以对不同类型的数据使用相同的内存,您可能会发现这对于编写函数以告诉您使用的是大端序还是小端序 CPU 之类的事情很有用。
不,这不是为了节省空间。它用于将一些二进制数据表示为各种数据类型的能力。例如
#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
相同的值存储在结构 bar.y 中,但不存储在数组中,而是存储在结构成员中。这是因为我的机器有一个小端序。如果它很大,则输出将被反转: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;
}
例如,当您需要读取一些二进制文件时,它很有用,该文件是在与您的字节序不同的计算机上编写的。您可以只访问字节数组形式的值,并根据需要交换这些字节。
此外,当您必须处理位字段时,它很有用,但这是一个完全不同的故事:)
首先:避免访问相同内存但访问不同类型的联合!
工会根本没有节省空间。只在同一内存区域定义多个名称!而且您一次只能在联合中存储其中一个元素。
如果你有
union X
{
int x;
char y[4];
};
您可以存储 int 或 4 个字符,但不能同时存储两者!一般的问题是,没有人知道哪些数据实际存储在联合中。如果您存储 int 并读取字符,编译器将不会检查该字符,也不会进行运行时检查。解决方案通常是在联合的结构中提供额外的数据元素,该联合包含实际存储的数据类型作为枚举。
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];
};
因此,您可以设备指向实际上可以为您保存 Int 或 Char 类型的基的指针。使用虚函数,您可以通过面向对象的编程方式访问成员。
正如Basile的回答中已经提到的,一个有用的情况是通过不同的名称访问同一类型。
union X
{
struct data
{
float a;
float b;
};
float arr[2];
};
这允许对具有相同类型的相同数据的不同访问方式。应完全避免使用存储在相同内存中的不同类型!
- 如何循环打印顶点结构
- 通过方法访问结构
- 使用不带参数的函数访问结构元素
- 正在尝试了解输入验证循环
- 预处理器:插入结构名称中的前一个行号
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 孤立代码块在结构中引发异常
- 有什么方法可以遍历结构吗
- 如何在 C# 中映射双 C 结构指针?
- 如何在C++中使用结构生成映射
- 无法将结构注册为增强几何体3D点
- 在学习数据结构之前对STL有一个了解是好的吗?
- 了解具有显式和默认值C++结构
- 了解类型特征的体系结构
- C Koala图库 - 了解访问数据结构的语法
- 了解 VC++ 项目/解决方案资源管理器文件层次结构
- C++了解联合和结构
- 了解C++结构语法
- 了解数据结构和类 C++.
- 了解隐式声明的默认协结构体