在 C 和 C# 编程语言中,结构体(Struct)是值类型数据结构,它使得一个单一变量可以存储多种类型的相关数据。在 C 语言中还有一种和结构体非常类似的语法,叫共用体(Union),有时也被直译为联合或者联合体。而在 C# 中并没有共用体这样一个定义,本文将介绍如何使用 C# 实现 C 语言中的共用体。

理解 C 语言的共用体

在 C 语言中,共用体是一种特殊的数据类型,允许你使用相同的一段内存空间存储不同的成员数据。光看定义有点抽象,我们来看一个 C 语言的共用体示例:

#include <stdio.h>union data{int n;char ch;short m;
};int main(){union data a;printf("%d, %d\n", sizeof(a), sizeof(union data) );a.n = 0x40;printf("%X, %c, %hX\n", a.n, a.ch, a.m);a.ch = '9';printf("%X, %c, %hX\n", a.n, a.ch, a.m);a.m = 0x2059;printf("%X, %c, %hX\n", a.n, a.ch, a.m);a.n = 0x3E25AD54;printf("%X, %c, %hX\n", a.n, a.ch, a.m);return 0;
}

运行结果:

4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54

要想理解上面的输出结果,就得了解共用体各个成员在内存中的分布。此示例中的 data 各个成员在内存中的分布示意图如下:

也就是说共用体的所有成员占用的是同一段内存,所占内存等于最长的成员占用的内存,修改一个成员会影响其它所有成员。而结构体的各个成员占用的是各自不同的内存,所占内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),成员相互之间没有影响。这是共用体和结构的主要区别。

使用 C# 实现共用体

和 C 语言不同的是,C# 中没有共用体的定义。那在 C# 中如何来实现这种定义呢?

C# 不仅可以实现共用体,而且可以实现比 C 语言更强大的共用体。C 语言的共用体每个成员在共用的内存中都必须从相同的起始位置开始存储,而在 C# 中可以指定各成员的起始位置(相对偏移)。好处是,不仅可以节省内存空间,还可以实现一些自动转换操作。

以 IP 地址的存储为例,IP 地址是以 4 段数字来表示的(如 192.168.1.10),每一段是一个字节(Byte),长度是 2^8,最大值是 255。我们可以用很多类型来表示 IP 地址,比如字符串、整型、自定义类和结构等。但如果我们有时要访问或修改其中一段,怎样存储最为方便呢?

我们可以使用 C# 的显示布局结构体来实现类似 C 语言中的共用体,以方便灵活地操作 IP 地址的每一段。实现方式如下:

using System.Runtime.InteropServices;[StructLayout(LayoutKind.Explicit)]
public struct IpAddress
{// FieldOffset 表示偏移的位置(以字节为单位)// sizeof(int) = 4, sizeof(byte) = 1[FieldOffset(0)] public int Address;[FieldOffset(0)] public byte Byte1;[FieldOffset(1)] public byte Byte2;[FieldOffset(2)] public byte Byte3;[FieldOffset(3)] public byte Byte4;public IpAddress(int address) : this(){// 给 Address 赋值时,所有成员的值都会自动被修改Address = address;}public override string ToString() => $"{Byte1}.{Byte2}.{Byte3}.{Byte4}";
}

这里我们使用了 StructLayout 特性标注了 IpAddress,声明其内存分布是显示(Explicit)的,然后使用 FieldOffset 特性来标注成员在共用内存中相对起始位置的偏移量(以字节为单位)。

如此我们就用 C# 实现了和 C 语言一样的共用体。可能你不能马上体会这样实现的妙处,让来我们来看一个应用场景。

假设我要在 IP 段内随机生成一个 IP,比如前两段不变,后两段随机,形如:192.163.X.X。使用上面定义好的“共用体”,我们可以这样做:

var ip = new IpAddress(new Random().Next());
Console.WriteLine($"{ip} = {ip.Address}");
ip.Byte1 = 192;
ip.Byte2 = 168;
Console.WriteLine($"{ip} = {ip.Address}");

输出结果:

47.29.249.122 = 2063146287
192.168.249.122 = 2063182016

这样不仅节省内存,而且可以很灵活方便地读取和修改 IP 中的某一段。由于成员 Address 和其它成员共用内存,所以修改一个成员,其余就自动修改。

共用体作为另一个共用体的成员

既然“共用体”是值类型,那么共用体自然也可以作为作为另一个共用体的成员。让我们来看一个较为复杂的例子,使用共用体实现由协议、IP 和端口三部分组成的服务端地址的表示,形如:协议://IP:端口。

using System;
using System.Runtime.InteropServices;[StructLayout(LayoutKind.Explicit)]
public struct IpAddress
{[FieldOffset(0)] public int Address;[FieldOffset(0)] public byte Byte1;[FieldOffset(1)] public byte Byte2;[FieldOffset(2)] public byte Byte3;[FieldOffset(3)] public byte Byte4;public IpAddress(int address) : this(){Address = address;}public override string ToString() => $"{Byte1}.{Byte2}.{Byte3}.{Byte4}";
}public enum Protocol : byte { http, https, ftp, sftp, tcp };[StructLayout(LayoutKind.Explicit)]
public struct Server
{[FieldOffset(0)] public IpAddress Address;[FieldOffset(4)] public ushort Port;[FieldOffset(6)] public Protocol Protocol;[FieldOffset(0)] public long Payload;public Server(IpAddress addr, ushort port, Protocol prot) : this(){Address = addr;Port = port;Protocol = prot;}public Server(long payload){// 参数长度可能不足填满每个成员,所以这里先对成员设初始值Address = new IpAddress(0);Port = 80;Protocol = Protocol.http;// 填值Payload = payload;}public Server Copy() =>  new Server(Payload);public override string ToString() => $"{Protocol}://{Address}:{Port}";
}

我们来用一段测试代码验证一下这个Server结构体的内存使用情况:

var ip = new IpAddress(new Random().Next());
Console.WriteLine($"Size: {Marshal.SizeOf(ip)} bytes. Value: {ip.Address} = {ip}");var s1 = new Server(ip, 8080, Protocol.https);
var s2 = new Server(s1.Payload);
s2.Address.Byte1 = 100;
s2.Protocol = Protocol.ftp;
Console.WriteLine($"Size: {Marshal.SizeOf(s1)} bytes. Value: {s1.Address} = {s1}");
Console.WriteLine($"Size: {Marshal.SizeOf(s2)} bytes. Value: {s2.Address} = {s2}");

输出结果:

Size: 4 bytes. Value: 2102736192 = 64.53.85.125
Size: 8 bytes. Value: 64.53.85.125 = https://64.53.85.125:8080
Size: 8 bytes. Value: 100.53.85.125 = ftp://100.53.85.125:8080

示例中,IP 地址偏移 0 字节,长度为 4 字节;端口号偏移 4 字节,长度为 2 字节;协议偏移 6 字节,长度为 1 字节。总长度应为 4+2+1=7 字节,但实际打印出来却是 8 字节,请问是为什么?

参考:https://bit.ly/3qmH92V

-

精致码农

带你洞悉编程与架构

↑长按图片识别二维码关注,不要错过网海相遇的缘分

[C#.NET 拾遗补漏]14:使用结构体实现共用体相关推荐

  1. 江哥带你玩转C语言 | 14 - 结构体-枚举-共用体

    什么是结构体 结构体和数组一样属于构造类型 数组是用于保存一组相同类型数据的, 而结构体是用于保存一组不同类型数组的 例如,在学生登记表中,姓名应为字符型;学号可为整型或字符型;年龄应为整型;性别应为 ...

  2. 结构体与共用体(联合体)

    参考:结构体与共用体 作者:一只青木呀 发布时间: 2020-08-09 08:29:22 网址:https://blog.csdn.net/weixin_45309916/article/detai ...

  3. C语言入门系列之10.结构体和共用体

    文章目录 一.结构体变量的基本使用 1.概述 2.定义结构体类型变量的方法 3.结构体变量的引用 4.结构体变量的初始化 二.结构体的高级应用 1.结构体数组 Ⅰ定义结构体数组 Ⅱ结构体数组的初始化 ...

  4. 重温C语言十四-----结构体与共用体

    文章目录 结构体和共用体 1.基本介绍 -1,需求: 2.走向结构体 -1.结构体关系图 -2,面向对象的方式(struct)解决养猫的问题 -3.结构体和结构体变量的区别与联系 -4,结构体在内存中 ...

  5. 第九章、结构体与共用体

    文章目录 9.1 结构体变量定义.引用.初始化 9.1.1 结构体概述 9.1.2 定义结构体类型变量的方法 9.1.3 结构体类型变量的引用 9.1.4 结构体变量的初始化 9.2 结构体数组.结构 ...

  6. 数组、结构体和共用体的长度计算?

    数组.结构体和共用体的长度计算? 运算符sizeof可以计算出给定类型的大小,对于32位系统来说,sizeof(char) = 1; sizeof(int) = 4.基本数据类型的大小很好计算,我们来 ...

  7. 结构体与共用体05 - 零基础入门学习C语言57

    第十章:结构体与共用体05 让编程改变世界 Change the world by program 对链表结点的删除操作实现 实现源代码: [codesyntax lang="c" ...

  8. 结构体与共用体07 - 零基础入门学习C语言59

    第十章:结构体与共用体07 让编程改变世界 Change the world by program 用typedef定义类型 用typedef声明新的类型名来代替已有的类型名   声明INTEGER为 ...

  9. C语言之结构体和共用体

    C语言之结构体和共用体 算上这篇笔记加上之前的四篇笔记,C语言基础我们也就告一段落了,对于刚刚接触c语言的童鞋们来说,这些以及足够了,稍后我会发布数据结构,对于想要深入学习的童鞋可以继续关注.本人也算 ...

  10. 结构体、共用体、位操作和枚举类型

    1 引言 ● 结构体(Structure)[在C标准中有时也称为聚合体(Aggregate)]是统一在同一个名字之下的一组相关变量的集合,它可以包含不同类型的变量 ● 结构体通常用来定义储存在文件中的 ...

最新文章

  1. 安卓电视版linux,MythTV 30.0 发布,前端支持选择Android电视设备
  2. 2015蓝桥杯省赛---java---B---3(三羊献瑞)
  3. 【OpenGL从入门到精通(三)】第一个点的理论
  4. js php c语言for循环,小蚂蚁学习C语言(8)——C语言for循环
  5. python线性输出_Python实现基本线性数据结构
  6. Be动词的缩写形式_3
  7. 手机modem开发(8)---TS 系列规范总结
  8. Asp.Net NPOI excl文件导入导出
  9. 一位 中国70 后老程序员的 26 个职场感悟
  10. 自动发射子弹c语言,C语言实现简单飞机大战
  11. UltraVNC源码编译运行
  12. 笔记-知识产权与标准化知识-中华人民共和国政府采购法
  13. OpenCV如何进行图像的平滑和锐化处理?
  14. 移动魔百盒CM311-3-YST-晨星MSO9385-语音首页正常-TTL刷机包
  15. 深入浅析Service Workers
  16. 当知识图谱遇上推荐系统之MKR模型(论文笔记三)
  17. 【CF#715C】Digit Tree 点分治+乘法逆元
  18. HDU - 1284 钱币兑换问题 (找规律/完全背包)
  19. 四色定理(本人本科论文题目)
  20. fopen中mode参数 r, w, a, r+, w+, a+ 具体区别(转)

热门文章

  1. POM思想__首页页面元素查找、功能点实现进行封装
  2. 《Effective.Enterprise.Java中文版》知识点摘要
  3. 深圳dotnet俱乐部新群
  4. STOLUCK:经济下行的当下 ,STO或将帮助中小企业度过寒冬
  5. 导入工程后编译不过,报错: apply plugin: 'com.github.dcendents.android-maven'
  6. 【Unity3D基础】让物体动起来②--UGUI鼠标点击逐帧移动
  7. c语言全局变量和局部变量问题汇总
  8. windows phone 8 的新特性
  9. ITU-R BT.656 协议
  10. 为or、in平反——or、in到底能不能利用索引?