C#指针- 常用手段
指针真是一把神器啊!使用C#加指针来搞一下图像去色效果,整个过程低于15毫秒。而在不加指针的情况下却需要500毫秒。如果用C语言+指针来搞那就更快了。难怪操作系统用C#写不了。国庆这几天,除了和爸爸聊聊天,看看电影。没事就百度,谷歌,博客园搜一些C#指针的文章来看,但能找到的资料很少。
我看了好几篇C#指针的文章后,遇到一个问题,C#和JAVA一样请了一个保姆,C#这个保姆比JAVA还能干,在C#几乎全部类都是托管的,而指针只能操作非托管的类,所以C#里的那些操作数据库的类,那些网络编程的类,那些文件处理的类,指针统统不能用。那该怎么办好呢?我百度了N次,找有没有办法把托管的类转换成非托管的,答案是NO。
虽然这个问题我没找到解决,但不影响指针此刻在我心中肃立起的高大形象,我觉的它就是玄话武侠小说《诛仙》最强大的武器----诛仙剑。
来看一下C#操作指针的一些常用手段。
C#要使用指针必需先把这个勾打上。
常用代码我就直接COPY别人的了, 常用C#+指针原代码的出处是这篇文章《C# 指针复习示例》
/// <summary> /// 指针,存储的是一个地址的整数。 /// </summary> class Program { delegate void Methods(); static void Main(string[] args) { Demo6(); Console.ReadKey(); } static void S(object o) { Console.WriteLine(o.ToString()); } static void S(params object[] o) { foreach (object obj in o) S(obj); } #region 指针简单示例 unsafe static void Demo1() { int x = 10;//整数类型 int* pX, pY;//指针坐标X、Y pX = &x;//取pX地址 pY = pX;//将pX赋值给pY x = 15; *pX = 16; *pY = 17; S(x); S(*pX);//取得地址的内容 S(*pY); } #endregion #region 指针的类型转换 unsafe static void Demo2() { //NOTE:类型转换后,uint获得的是地址的十进制格式,并非获取地址的内容。 int x = 10;//整数类型 int* pX, pY;//指针坐标X、Y pX = &x;//取pX地址 pY = pX;//将pX的地址赋值给pY uint ux = (uint)pX;//类型转换,获取地址的十进制 int* pZ = (int*)ux;//类型转换,将地址赋值给pZ S(x); S(((uint)pX).ToString("x"));//十六进制内存地址 S((uint)pY);//十进制内存地址 S(ux); S((uint)pZ);//十进制内存地址 S(*pZ);//输出地址的内容 } #endregion #region DWORD内存块 unsafe static void Demo3() { decimal m2 = 4.00m; byte b = 100; short s = 200; int i = 300; long l = 400; float f = 1.00f; double d = 2.00; decimal m = 3.00m; S(string.Format("byte:{0}", sizeof(byte))); S(string.Format("short:{0}", sizeof(short))); S(string.Format("int:{0}", sizeof(int))); S(string.Format("long:{0}", sizeof(long))); S(string.Format("float:{0}", sizeof(float))); S(string.Format("double:{0}", sizeof(double))); S(string.Format("decimal:{0}", sizeof(decimal))); S(""); S("从高到矮……"); S(string.Format("decimal:{0} +16 ↑", (uint)&m2)); S(string.Format("byte:{0} +1 ↑", (uint)&b)); S(string.Format("short:{0} +2 ↑", (uint)&s)); S(string.Format("int:{0} +4 ↑", (uint)&i)); S(string.Format("long:{0} +8 ↑", (uint)&l)); S(string.Format("float:{0} +4 ↑", (uint)&f)); S(string.Format("double:{0} +8 ↑", (uint)&d)); S(string.Format("decimal:{0} +16 ↑", (uint)&m)); S("有没有发现byte和short也是4个字节的内存块?因为.NET约定,最少要占用4个字节。"); } #endregion #region 指针的运算 unsafe static void Demo4() { byte b = 8; uint u = 3; double d = 10.0; byte* pByte = &b; uint* pUint = &u; double* pDouble = &d; S(string.Format("byte:{0}", (uint)pByte)); S(string.Format("uint:{0}", (uint)pUint)); S(string.Format("double:{0}", (uint)pDouble)); pByte -= 3; ++pUint; S("\n"); double* pDouble2 = pDouble + 4; S(string.Format("byte:{0}。old - 3 * 1", (uint)pByte)); S(string.Format("uint:{0}。old + 4 * 1", (uint)pUint)); S(string.Format("double2:{0}。pDouble + 4 * 8", (uint)pDouble2)); } #endregion #region 结构指针 unsafe static void Demo5() { MyStruct ms = new MyStruct(); MyStruct* pms = &ms; (*pms).X = 5;//传统方式 pms->Y = 10;//与C++雷同方式 S(ms.X); S(ms.Y); int* X = &(pms->X); S(*X); *X = 15; S(*X); S(ms.X); } struct MyStruct { public int X; public int Y; } #endregion #region 类指针 unsafe static void Demo6() { MyClass mc = new MyClass(); //MyClass* pmc = &mc;//error,无法获取托管类型,因为它们嵌入一个对象。(结构是值类型) fixed (int* X = &(mc.X)) fixed (int* Y = &(mc.Y))//*X和*Y固定或者fixed (int* X = &(mc.X), Q = &(mc.Y))。唯一限制,数据类型必须都是int*。 { *X = 5; *Y = 6; S(mc.X + "<-X Y-> " + mc.Y); fixed (int* Z = &(mc.Z))//*Z固定在X中。生命周期在于X、Y之间。 { *Z = 7; *X = 8; *Y = 9; S(mc.X + "<-X Y-> " + mc.Y + " Z->" + mc.Z); } *X = 10; *Y = 11; S(mc.X + "<-X Y-> " + mc.Y); } } class MyClass { public int X; public int Y; public int Z; } #endregion
另外,C#提供一个的关键字stackalloc用于申请堆栈内存。当函数执行完毕后,内存会被自动回收。使用这个堆内存的时候不必担心内存泄漏问题。
public class Program { static unsafe void Main(string[] args) { //分配p1一个100的大小 int* p = stackalloc int[100]; //赋值操作 for (int i = 0; i < 100; i++) { p[i] = i; } //测试内容 for (int i = 0; i < 100; i++) { Console.WriteLine(*(p + i)); } Console.ReadLine(); } }
最后有一篇《C# 指针之美》的文章,下面这些话摘抄自那里
指针也可以操作非托管堆上的内存,如:
IntPtr handle = System.Runtime.InteropServices.Marshal.AllocHGlobal(4); Int32* p = (Int32*)handle; *p = 20; MessageBox.Show(p->ToString()); System.Runtime.InteropServices.Marshal.FreeHGlobal(handle);
System.Runtime.InteropServices.Marshal.AllocHGlobal 用来从非托管堆上分配内存。System.Runtime.InteropServices.Marshal.FreeHGlobal(handle)用来释放从非托管对上分配的内存。这样我们就可以避开GC,自己管理内存了。
如果使用非托管内存,建议用Dispose模式来管理内存,这样做有以下好处: 可以手动dispose来释放内存;可以使用using 关键字开管理内存;即使不释放,当Dispose对象被GC回收时,也会收回内存。
下面是Dispose模式的简单例子:
View Code public unsafe class UnmanagedMemory : IDisposable { public int Count { get; private set; } private byte* Handle; private bool _disposed = false; public UnmanagedMemory(int bytes) { Handle = (byte*) System.Runtime.InteropServices.Marshal.AllocHGlobal(bytes); Count = bytes; } public void Dispose() { Dispose(true); GC.SuppressFinalize(true); } protected virtual void Dispose( bool isDisposing ) { if (_disposed) return; if (isDisposing) { if (Handle != null) { System.Runtime.InteropServices.Marshal.FreeHGlobal((IntPtr)Handle); } } _disposed = true; } ~UnmanagedMemory() { Dispose( false ); } }
使用代码
using (UnmanagedMemory memory = new UnmanagedMemory(10)) { int* p = (int*)memory.Handle; *p = 20; MessageBox.Show(p->ToString()); }