如何在C#中模拟C++的联合.doc_第1页
如何在C#中模拟C++的联合.doc_第2页
如何在C#中模拟C++的联合.doc_第3页
如何在C#中模拟C++的联合.doc_第4页
如何在C#中模拟C++的联合.doc_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

如何在C#中模拟C+的联合如何在C#中模拟C+的联合(Union)?C#, C+0 如何阅读本文?如果你. .希望了解联合的概念,请阅读“什么是联合?”。 .希望了解联合的内存使用情况,请阅读“联合的内存布局与内存使用情况。”。 .希望了解如何在C#中模拟联合,请阅读“第一次尝试:在C#中模拟这种布局方式。”。 .希望了解在C+中使用联合有哪些要注意的地方,请阅读“在实际的C+代码中,我们是如何使用联合的?”。 .希望了解如何在C#中更好的使用模拟的联合,请阅读“第二次尝试:改进型的联合模拟。”。 .希望了解在C#中使用模拟的联合有些什么注意事项,请阅读“别在模拟的联合中同时使用值类型和引用类型!”。 .希望了解为何我要写这篇文章,请阅读“为什么要在C#里面模拟这个用处不大的东西?”。否则. .你应该从头到尾阅读全文。 1 什么是联合?联合(Union)是一种特殊的类,一个联合中的数据成员在内存中的存储是互相重叠的。每个数据成员都在相同的内存地址开始。分配给联合的存储区数量是“要包含它最大的数据成员”所需的内存数。同一时刻只有一个成员可以被赋给一个值。下面我们来看看C+中如何表达联合:/ Code #01union TokenValue char _cval; int _ival; double _dval; 2 联合的内存布局与内存使用情况。下面我们来考察一下TokenValue的内存布局。首先,我们使用sizeof运算符来获取该联合各个成员的内存占用字节数:/ Code #02int _tmain(int argc, _TCHAR* argv) cout sizeof(char): sizeof(char) endl; cout sizeof(int): sizeof(int) endl; cout sizeof(double): sizeof(double) endl; return 0;/*/* * Output: * sizeof(char): 1 * sizeof(int): 4 * sizeof(double): 8 * */这样,分配给该联合的内存就是8个字节。接着,我们来看看具体使用该联合时,所分配的内存的字节占用情况如何:/ Code #03int _tmain(int argc, _TCHAR* argv) TokenValue tv; / _ tv._cval = K; / X_ tv._ival = 1412; / XXXX_ tv._dval = 3.14159; / XXXXXXXX return 0; 3 第一次尝试:在C#中模拟这种布局方式。在C#中,要指定成员的内存布局情况,我们需要结合使用StructLayoutAttribute特性、LayoutKind枚举和FieldOffsetAttribute特性,它们都位于System.Runtime.InteropServices命名空间中。下面我用struct来试着模拟上面的TokenValue联合:/ Code #04StructLayout(LayoutKind.Explicit, Size=8)struct TokenValue FieldOffset(0) public char _cval; FieldOffset(0) public int _ival; FieldOffset(0) public double _dval;我们知道,联合的每个数据成员都在相同的内存地址开始,通过把FieldOffset(0)应用到TokenValue的每一个成员,我们就指定了这些成员都处于同一起始位置。当然,我们得事先告诉.NET这些成员的内存布局由我们来作主,把LayoutKind.Explicit枚举传递给StructLayoutAttribute特性的构造函数,并应用到TokenValue,.NET就不会再干涉该struct的成员在内存中的布局了。另外,我显式的把TokenValue的大小设置为8字节,当然,这样做是可选的。 4 在实际的C+代码中,我们是如何使用联合的?在实际的C+代码中,我们应尽量避免让客户端直接使用联合,Code #03就是一个很好的反面例子了。为什么呢?熟悉C/C+的开发人员都知道,联合提供我们这样一个节省空间的储存方式,是要我们付出一定的代价的。这个代价就是代码的安全性,不恰当地使用联合可能会导致程序崩溃的。由于每一次只有一个联合成员处于激活状态,如果我们不小心或者因为其它原因使用处于休眠状态的成员,轻则得到错误的结果,重则整个程序中止。请看下面的代码:/ Code #05union TokenValue char _cval; int _ival; double _dval; char* _sval;int _tmain(int argc, _TCHAR* argv) TokenValue tv; tv._cval = K; cout tv._cval endl; / Line #01 cout tv._ival endl; / Line #02 cout tv._dval endl; / Line #03 cout tv._sval endl; / Line #04 return 0;这里的TokenValue比起Code #01的仅仅多了一个_sval,它是C风格的字符串,实质上,它是指向字符串的第一个字符的指针,它占用4字节的内存空间。当程序运行到Line #04时,就会出现Unhandled Exception,程序中止,并指出_sval的值非法(即所谓的“野指针”)。程序无法把它的值输出控制台,然而,Line #01 Line #03都能输出,只是Line #02和Line #03所输出的值是错误的而已。实际的应用中,我们一般不会看到如此低级且显而易见的错误,但复杂的实际应用中,不恰当地使用联合的确会为我们带来不少的麻烦。 5 第二次尝试:改进型的联合模拟。一般情况下,联合作为一种内部数据的储存手段,没有必要让客户端对其有所了解,更没必要让客户端直接使用它。为了使我们的联合模拟用起来更安全,我们需要对它进行一番包装:/ Code #06class Program static void Main(string args) Token t = new Token(); Console.WriteLine(t); Console.WriteLine(t.GetTokenValue(); t.SetTokenValue(K); Console.WriteLine(t); Console.WriteLine(t.GetTokenValue(); public struct Token private TokenValue tv; private TokenKind tk; public void SetTokenValue(char c) tk = TokenKind.CharValue; tv._cval = c; public void SetTokenValue(int i) tk = TokenKind.IntValue; tv._ival = i; public void SetTokenValue(double d) tk = TokenKind.DoubleValue; tv._dval = d; public object GetTokenValue() switch (tk) case TokenKind.CharValue: return tv._cval; case TokenKind.IntValue: return tv._ival; case TokenKind.DoubleValue: return tv._dval; default: return NoValue; public override string ToString() switch (tk) case TokenKind.CharValue: return tv._cval.ToString(); case TokenKind.IntValue: return tv._ival.ToString(); case TokenKind.DoubleValue: return tv._dval.ToString(); default: return NoValue; StructLayout(LayoutKind.Explicit, Size = 8) private struct TokenValue FieldOffset(0)public char _cval; FieldOffset(0)public int _ival; FieldOffset(0)public double _dval; private enum TokenKind NoValue, CharValue, IntValue, DoubleValue /*/* * Output: * NoValue * NoValue * K * K * */由于Token是值类型,实例化时,对应的成员(tv和tk)会自动被赋予与之对应的零值。此时,tv._cval为0、tv._ival和tv._dval均为0(实质上它们是同一个值在不同的类型中的表现)。而tk也被自动赋予0:tk = 0;这里,你无需进行强类型转换,0是任何枚举的默认初始值,.NET会负责把0转换成对应的枚举类型。例如,你可以:/ Code #07System.DayOfWeek d = 0;Console.WriteLine(d);该代码能正确输出Sunday一个星期的第一天(西方习惯),也是该枚举的第一个成员。一般情况下,0对应着枚举的第一个成员(除非你在定义枚举的时候,把第一个成员指定为别的值,并为别的成员赋予0值)。这样,我们就不难看出代码的输出是合理的,而且代码本身也是安全的。 6 别在模拟的联合中同时使用值类型和引用类型!到目前为止,我们所模拟的联合中,所有的成员都是值类型,如果我们为它加入一个引用类型,例如String呢?/ Code #08StructLayout(LayoutKind.Explicit, Size=8)struct TokenValue FieldOffset(0) public char _cval; FieldOffset(0) public int _ival; FieldOffset(0) public double _dval; FieldOffset(0) public string _sval;这样,Code #06的代码运行时就会提示出错:Could not load type TokenValue from assembly UnionLab, Version=1.0.1820.28531, Culture=neutral, PublicKeyToken=null because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field. TokenValue初始化的时候,_cval、_ival和_dval都能正确的被赋予对应的零值,而这些零值也能被统一起来(别的值就不行了)。但_sval不同,它是引用类型,如果没有显示初始化为某个有意义的值,它将被赋予null值!这个null值跟之前的有意义的零值是不能被统一起来的!所以,要么你就去掉这个_sval,要么就重新定义它的起始位置(当然,你也得去掉Size=8!),但这样一来,TokenValue就不再称得上联合的模拟了。在C+中,我们可以直接使用指针来解决这个问题,如Code #05,但C#中,问题就会变得有点辣手。如果你有兴趣的话,可以使用不安全代码(Unsafe code)来试着解决,但这样一来,你的代码又会引入一些新的问题。 7 为什么要在C#里面模拟这个用处不大的东西?NEW相信很多人都有这样一个疑问:为什么要在C#里面模拟这个用处不大的东西?就我个人来说,我始终坚信事物的存在必定有它的理由,否则就不会存在。其实,联合在我们平时的编码中的确很少用到,但在某些情况下,我们必须使用它!.NET为我们提供巨大的便利的同时,也不忘让我们能够与非托管代码交互。你知道,早期的Win32 API使用C来完成的,这里面就有很多函数的参数是以联合的形式表达的,要在C#中跟这些API交互,我们就得“尊重”原函数的用法约束。 8 终点与起点的交界处。回顾整个探索旅程,我们为了使用联合节省空

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论