c# 如何使用結(jié)構(gòu)體實(shí)現(xiàn)共用體
在 C 和 C# 編程語言中,結(jié)構(gòu)體(Struct)是值類型數(shù)據(jù)結(jié)構(gòu),它使得一個(gè)單一變量可以存儲(chǔ)多種類型的相關(guān)數(shù)據(jù)。在 C 語言中還有一種和結(jié)構(gòu)體非常類似的語法,叫共用體(Union),有時(shí)也被直譯為聯(lián)合或者聯(lián)合體。而在 C# 中并沒有共用體這樣一個(gè)定義,本文將介紹如何使用 C# 實(shí)現(xiàn) C 語言中的共用體。
理解 C 語言的共用體
在 C 語言中,共用體是一種特殊的數(shù)據(jù)類型,允許你使用相同的一段內(nèi)存空間存儲(chǔ)不同的成員數(shù)據(jù)。光看定義有點(diǎn)抽象,我們來看一個(gè) 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; }
運(yùn)行結(jié)果:
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54
要想理解上面的輸出結(jié)果,就得了解共用體各個(gè)成員在內(nèi)存中的分布。此示例中的 data 各個(gè)成員在內(nèi)存中的分布示意圖如下:
也就是說共用體的所有成員占用的是同一段內(nèi)存,所占內(nèi)存等于最長的成員占用的內(nèi)存,修改一個(gè)成員會(huì)影響其它所有成員。而結(jié)構(gòu)體的各個(gè)成員占用的是各自不同的內(nèi)存,所占內(nèi)存大于等于所有成員占用的內(nèi)存的總和(成員之間可能會(huì)存在縫隙),成員相互之間沒有影響。這是共用體和結(jié)構(gòu)的主要區(qū)別。
使用 C# 實(shí)現(xiàn)共用體
和 C 語言不同的是,C# 中沒有共用體的定義。那在 C# 中如何來實(shí)現(xiàn)這種定義呢?
C# 不僅可以實(shí)現(xiàn)共用體,而且可以實(shí)現(xiàn)比 C 語言更強(qiáng)大的共用體。C 語言的共用體每個(gè)成員在共用的內(nèi)存中都必須從相同的起始位置開始存儲(chǔ),而在 C# 中可以指定各成員的起始位置(相對(duì)偏移)。好處是,不僅可以節(jié)省內(nèi)存空間,還可以實(shí)現(xiàn)一些自動(dòng)轉(zhuǎn)換操作。
以 IP 地址的存儲(chǔ)為例,IP 地址是以 4 段數(shù)字來表示的(如 192.168.1.10),每一段是一個(gè)字節(jié)(Byte),長度是 2^8,最大值是 255。我們可以用很多類型來表示 IP 地址,比如字符串、整型、自定義類和結(jié)構(gòu)等。但如果我們有時(shí)要訪問或修改其中一段,怎樣存儲(chǔ)最為方便呢?
我們可以使用 C# 的顯示布局結(jié)構(gòu)體來實(shí)現(xiàn)類似 C 語言中的共用體,以方便靈活地操作 IP 地址的每一段。實(shí)現(xiàn)方式如下:
using System.Runtime.InteropServices; [StructLayout(LayoutKind.Explicit)] public struct IpAddress { // FieldOffset 表示偏移的位置(以字節(jié)為單位) // 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 賦值時(shí),所有成員的值都會(huì)自動(dòng)被修改 Address = address; } public override string ToString() => $"{Byte1}.{Byte2}.{Byte3}.{Byte4}"; }
這里我們使用了 StructLayout 特性標(biāo)注了 IpAddress,聲明其內(nèi)存分布是顯示(Explicit)的,然后使用 FieldOffset 特性來標(biāo)注成員在共用內(nèi)存中相對(duì)起始位置的偏移量(以字節(jié)為單位)。
如此我們就用 C# 實(shí)現(xiàn)了和 C 語言一樣的共用體。可能你不能馬上體會(huì)這樣實(shí)現(xiàn)的妙處,讓來我們來看一個(gè)應(yīng)用場景。
假設(shè)我要在 IP 段內(nèi)隨機(jī)生成一個(gè) IP,比如前兩段不變,后兩段隨機(jī),形如: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}");
輸出結(jié)果:
47.29.249.122 = 2063146287
192.168.249.122 = 2063182016
這樣不僅節(jié)省內(nèi)存,而且可以很靈活方便地讀取和修改 IP 中的某一段。由于成員 Address 和其它成員共用內(nèi)存,所以修改一個(gè)成員,其余就自動(dòng)修改。
共用體作為另一個(gè)共用體的成員
既然“共用體”是值類型,那么共用體自然也可以作為作為另一個(gè)共用體的成員。讓我們來看一個(gè)較為復(fù)雜的例子,使用共用體實(shí)現(xiàn)由協(xié)議、IP 和端口三部分組成的服務(wù)端地址的表示,形如:協(xié)議://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) { // 參數(shù)長度可能不足填滿每個(gè)成員,所以這里先對(duì)成員設(shè)初始值 Address = new IpAddress(0); Port = 80; Protocol = Protocol.http; // 填值 Payload = payload; } public Server Copy() => new Server(Payload); public override string ToString() => $"{Protocol}://{Address}:{Port}"; }
我們來用一段測試代碼驗(yàn)證一下這個(gè)Server結(jié)構(gòu)體的內(nèi)存使用情況:
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}");
輸出結(jié)果:
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 字節(jié),長度為 4 字節(jié);端口號(hào)偏移 4 字節(jié),長度為 2 字節(jié);協(xié)議偏移 6 字節(jié),長度為 1 字節(jié)??傞L度應(yīng)為 4+2+1=7 字節(jié),但實(shí)際打印出來卻是 8 字節(jié),請(qǐng)問是為什么?
以上就是c# 如何使用結(jié)構(gòu)體實(shí)現(xiàn)共用體的詳細(xì)內(nèi)容,更多關(guān)于c# 結(jié)構(gòu)體實(shí)現(xiàn)共用體的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
DevExpress實(shí)現(xiàn)GridControl同步列頭checkbox與列中checkbox狀態(tài)
這篇文章主要介紹了DevExpress實(shí)現(xiàn)GridControl同步列頭checkbox與列中checkbox狀態(tài),需要的朋友可以參考下2014-08-08算法練習(xí)之從String.indexOf的模擬實(shí)現(xiàn)開始
這篇文章主要介紹了算法練習(xí)從String.indexOf的模擬實(shí)現(xiàn)開始,需要的朋友可以參考下2014-12-12C#對(duì)Windows服務(wù)組的啟動(dòng)與停止操作
這篇文章主要為大家詳細(xì)介紹了C#對(duì)Windows服務(wù)組的啟動(dòng)與停止操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Unity實(shí)現(xiàn)游戲卡牌滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)游戲卡牌滾動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02