struct vs union

本文深入解析C语言中Union的定义、使用场景及注意事项,并通过示例演示如何结合Enum实现类型安全。
从定义上来看struct和union定义的风格都差不多.
但是union只能存储一个元素, 与struct 一样也是固定长度. 长度取决于union中定义的所有ITEM中占用空间最大的那个.
为什么C这么多固定长度的东西呢, 原因是指针, 如果不定长, 存储这些类型的数组及数组型指针将使用起来非常困难. 因为需要定长, 地址运算的时候才比较方便. 如char a[10], a+1 地址加1字节. int a[10], a+1 则地址加4字节. 如果使用下面这个union 定义的 test a[10], a+1 地址加104字节. 如果不是定长, 那就不好搞了.
例如 : 
typedef union test {
  int a;
  long b;
  float c;
  short d;
  char a[100];
} test;
这个union的长度取决于a[100], 理论上应该是100, 但是由于alignment, 需要一些pad空间, 所以是104.

例如 : 
[root@db-172-16-3-150 ~]# cat f.c
#include <stdio.h>

typedef union test {
  int a;
  long b;
  float c;
  short d;
  char e[100];
} test;

int main() {
  fprintf(stdout, "sizeof(test):%lu\n", sizeof(test));
  return 0;
}
结果
[root@db-172-16-3-150 ~]# gcc -g ./f.c -o f && ./f
sizeof(test):104


union用在什么场景呢? 例如要存储一个数量, 可能是个数(int), 也可能是重量(float), 也可能是体积(float).
如下面的例子 : 


[root@db-172-16-3-150 ~]# cat e.c
#include <stdio.h>

typedef union quantity {
  int count;
  float kg;
  float ml;
} quantity;

typedef struct goods {
  const char *name;
  const char *country;
  quantity amount;
} goods;

int main() {
  goods apple = {"apple", "china", {.kg = 10}};
  goods watermelon = {"watermelon", "china", {5}};
  goods mango = {"mango", "china", {.kg = 6}};
  goods wine = {"wine", "franch", {.ml=10000}};
  fprintf(stdout, "apple.amount.kg:%f, watermelon.amount.count:%i, mango.amount.kg:%f, wine.amount.ml:%f\n", apple.amount.kg, watermelon.amount.count, mango.amount.kg, wine.amount.ml);
  // 如果把wine.amount改成count存储, 那读取的时候也要修改. wine.amount.count, 输出为%i. 
  wine.amount.count=999;
  fprintf(stdout, "apple.amount.kg:%f, watermelon.amount.count:%i, mango.amount.kg:%f, wine.amount.count:%i\n", apple.amount.kg, watermelon.amount.count, mango.amount.kg, wine.amount.count);
  wine.amount.kg=888.88;
  fprintf(stdout, "apple.amount.kg:%f, watermelon.amount.count:%i, mango.amount.kg:%f, wine.amount.ml:%f\n", apple.amount.kg, watermelon.amount.count, mango.amount.kg, wine.amount.kg);
  wine.amount.ml=777.77;
  fprintf(stdout, "apple.amount.kg:%f, watermelon.amount.count:%i, mango.amount.kg:%f, wine.amount.ml:%f\n", apple.amount.kg, watermelon.amount.count, mango.amount.kg, wine.amount.ml);
  return 0;
}
结果 : 
[root@db-172-16-3-150 ~]# gcc -O3 -Wall -Wextra -Werror -g ./e.c -o e && ./e
apple.amount.kg:10.000000, watermelon.amount.count:5, mango.amount.kg:6.000000, wine.amount.ml:10000.000000
apple.amount.kg:10.000000, watermelon.amount.count:5, mango.amount.kg:6.000000, wine.amount.count:999
apple.amount.kg:10.000000, watermelon.amount.count:5, mango.amount.kg:6.000000, wine.amount.ml:888.880005
apple.amount.kg:10.000000, watermelon.amount.count:5, mango.amount.kg:6.000000, wine.amount.ml:777.770020


使用union 一个需要注意的地方是, 没有地方跟踪你存储的到底是哪个类型, 如这个union里面包含了int, float, short. 怎么知道我存储的到底是什么类型呢, 如果存储的是int, 但是使用float去格式化输出肯定是有问题的, 结果不可预期.

使用enum 枚举来判别用的union里面的什么类型是一个比较好的方法.
例如 : 

[root@db-172-16-3-150 ~]# cat f.c
#include <stdio.h>

typedef enum unit_of_measure {
  COUNT, KG, ML
} unit_of_measure;

typedef union quantity {
  short count;
  float weight;
  float volume;
} quantity;

typedef struct fruit_order {
  const char *name;
  const char *country;
  quantity amount;
  unit_of_measure units;
} fruit_order;

void display(const fruit_order * order) {
  fprintf(stdout, "This order contains ");
  if (order->units == ML) 
    fprintf(stdout, "%2.2f ml of %s\n", order->amount.volume, order->name);
  else if (order->units == KG)
    fprintf(stdout, "%2.2f kg of %s\n", order->amount.weight, order->name);
  else
    fprintf(stdout, "%i %s\n", order->amount.count, order->name);
}

int main() {
  fruit_order apples = {"apples", "china", .amount.count=188, COUNT};
  fruit_order wines = {"wines", "franch", {.volume=650.0}, ML};
  fruit_order mangos = {"mangos", "china", .amount.weight=7.6, KG};
  display(&apples);
  display(&wines);
  display(&mangos);
  return 0;
}
结果 : 
[root@db-172-16-3-150 ~]# gcc -O3 -Wall -Wextra -Werror -g ./f.c -o f && ./f
This order contains 188 apples
This order contains 650.00 ml of wines
This order contains 7.60 kg of mangos


其他 : 
1.  type name 与 struct name, enum name, union name

typedef struct fish {
  int age;
  float weight;
  char * name;
} fish;
其中strunt name 指的是struct fish这里定义的fish.
type name 指的是最后一行的fish.
struct name 和 type name 可以不一样. 也可以一样.
如果上面的定义改成 : 
struct fish {
  int age;
  float weight;
  char * name;
};
那么就没有type name.
如果没有type name , 定义变量时必须写成这样 : struct fish f1 = {.....};
如果有type name, 那么定义变量可以写成fish f1 = {.....};  (注意这里的fish是type name)


2. 赋值
struct : 

struct fish {
  int age;
  float weight;
  char * name;
};
// 定义变量的时候赋值struct fish f1 = {10, 5.6, "linux"};
// 或者写成{.age = 10, .weight=5.6, .name="linux"};
// 定义完变量再赋值
// f1.age=10;
// f1.weight=5.6;
// f1.name = "linux";


enum : 
与上面类似.

union : 

与上面类似, 唯一的不同是.
union a {
  int count;
  float weight;
  float volumn;
};
union a a1 = {1.5};
这个1.5 是union 的第一个元素count的值.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值