C ++的了解联合和结构(C++ understanding Unions and Structs)

2019-10-21 04:12发布

我是来这里上一些工会的定义如下一个正在进行的项目工作:

/* 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;
};

不会被分配为他们两个相同的空间?

我怎么能初始化翻,varB ......从my_union?

Answer 1:

联盟并不多为节省空间,但落实和类型 (对于这一点,你就会把union在某些structclass也有一个鉴别字段,将保持运行时间标记)。 另外,我建议你使用最新的C ++标准,至少C ++ 11 ,因为它有更好的支持工会(例如允许更容易地联合对象和他们的建筑或初始化)。

使用联合的优点是能够以索引n个浮点(0 <= N <= 3)作为u.data[n]

要指定某些变量声明为工会场my_union u; 只是代码例如u.varB = 3.14; 而你的情况具有相同的效果u.data[1] = 3.14;


当之无愧的联盟的一个很好的例子是一个可变的对象,它可以容纳任何一个intstring (你不能使用在这种情况下派生类):

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>

另见的std ::可选在C未来的版本++(这在概念上是一个标签联合使用void

BTW,OCaml中,你只需代码:

 type intorstring = Integer of int | String of string;;

你会使用模式匹配 。 如果你想做出可变的,你需要做一个记录或者它的一个参考。

你会更好地利用union -s在C ++中惯用的方式(请参阅本为一般性咨询)。



Answer 2:

实施状物体的变体(类型字段和数据类型的联合)时工会经常使用,或在执行串行化。

您使用的是联合的方式是一个灾难。

你是假设的structunion被包装的float与当时之间没有间隙s! 标准保证float data[4]; 是连续的,但不是结构元件。 你唯一知道的另一件事是,地址varA ; 是相同的地址data[0]

千万不要以这种方式使用的联合。

至于你的问题:“怎么我可以初始化翻,varB ......从my_union?”。 答案是, 经由访问结构成员在正常冗长方式data[]数组。



Answer 3:

其优点是,与工会就可以在两种不同的方式访问相同的内存。

在您的例子工会包含四个浮动。 您可以访问这些彩车的翻,varB ......这可能是更具描述性的名称,或者您可以访问相同的变量数组数据[0],数据[1] ...这可能是在循环中更加有用。

有了工会,你还可以使用不同类型的数据相同的内存,你可能会发现的东西,有用就像写一个函数来告诉你,如果你是在一个大端或小端的CPU。



Answer 4:

不,它不是以节省空间。 它是代表一些二进制数据的各种数据类型的能力。 例如

#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;
}

它有用,例如,当你需要阅读一些二进制文件,这是写在机器上用不同的字节顺序比你。 你可以只访问值字节组,并根据需要交换这些字节。

此外,它是有用的,当你要处理位域 ,但它的一个完全不同的故事:)



Answer 5:

我想了解工会最好的办法是只给2个普通的实际例子。

第一个例子正在与图像。 想象一下,你有和布置在相当长的缓冲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


Answer 6:

工会主要用来表示不同的方式相同的数据。 想像一下,如果有一个包含各种数据,诸如用户定义的结构:

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驱动程序等。

注意:当你做这样的事情,要小心填充和对齐! 编译器可以自由地在任何地方插入结构/联合内部填充字节对齐数据。 这可以打破上述概念。

在其他的答案中指出,你也可以使用工会实施“求和输入” /“变种”,但是对于这样的实际应用是非常有限的,如果这样的使用,甚至存在。



Answer 7:

首先:避免工会在接入进入相同的内存,但不同类型的!

工会根本不节省空间。 唯一的定义对同一存储区域的多个名字! 而你只能存储在一个工会在一次性的要素之一。

如果你有

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];
};

所以,你可以装置指针基地实际上可以容纳一个整数,或char类型适合你。 有了虚拟功能,您可以访问编程的面向对象的方式成员。

从巴西莱的答案已经提到的,有用的情况下,可以通过不同的名字访问同一个类型

union X 
{
      struct data
      {
          float a;
          float b;
      };
      float arr[2];
 };

这允许不同的接入方式与同一类型的相同数据。 使用不同类型的存储在相同的内存应该不惜一切避免!



文章来源: C++ understanding Unions and Structs