深入理解C#指针之美

目录
  • 一、简洁优美的代码
  • 二、C# 指针基础
  • 三、几种常用用法
    • 1、使用Dispose模式管理非托管内存
    • 2、使用 stackalloc 在栈中分配内存
    • 3、模拟C中的union(联合体)类型
  • 四、C# 指针操作的几个缺点
  • 五、引入模板机制
  • 六、迭代器
  • 七、风情万种的Lambda表达式
  • 八、与C/C++的比较
  • 九、接下来的工作
  • 总结

一、简洁优美的代码

本来初稿这节写了好几百字,将C#指针开发与C/C++开发,Java开发、D语言开发等进行对比,阐述理念。不过现在觉得,阐述一个新事物,没有比用例子更直接的了。

例子:打开一张图像,先将它转化为灰度图像,再进行二值化(变成黑白图像),然后进行染色,将白色的像素变成红色。以上每一个过程都弹出窗体显示出来。

代码截图更有视觉冲击力:

二、C# 指针基础

在C#中使用指针,需要在项目属性中选中“Allow unsafe code”:

接着,还需要在使用指针的代码的上下文中使用unsafe关键字,表明这是一段unsafe代码。可以用unsafe { } 将代码围住,如:

                     unsafe
                     {
                         new ImageArgb32(path).ShowDialog("原始图像")
                             .ToGrayscaleImage().ShowDialog("灰度图像")
                             .ApplyOtsuThreshold().ShowDialog("二值化图像")
                             .ToImageArgb32()
                             .ForEach((Argb32* p) => { if (p->Red == 255) *p = Argb32.RED; })
                             .ShowDialog("染色");
                     }

也可在方法或属性上加入unsafe关键字,如:

   private unsafe void btnSubmit_Click(object sender, EventArgs e)

也可在class或struct 上加上unsafe 关键字,如:

public partial unsafe class FrmDemo1 : Form

指针配合fixed关键字可以操作托管堆上的值类型,如:

  public unsafe class Person
    {
        public int Age;
        public void SetAge(int age)
        {
            fixed (int* p = &Age)
            {
                *p = age;
            }
        }
    }

指针可以操作栈上的值类型,如:

       int age = 0;
             int* p = &age;
             *p = 20;
             MessageBox.Show(p->ToString());

指针也可以操作非托管堆上的内存,如:

         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,自己管理内存了。

三、几种常用用法

1、使用Dispose模式管理非托管内存

如果使用非托管内存,建议用Dispose模式来管理内存,这样做有以下好处: 可以手动dispose来释放内存;可以使用using 关键字开管理内存;即使不释放,当Dispose对象被GC回收时,也会收回内存。

下面是Dispose模式的简单例子:

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());
            }

2、使用 stackalloc 在栈中分配内存

C# 提供了stackalloc 关键字可以直接在栈中分配内存,一般情况下,使用栈内存会比使用堆内存速度快,且栈内存不用担心内存泄漏。下面是例子:

       int* p = stackalloc int[10];
             for (int i = 0; i < 10; i++)
             {
                 p[i] = 2 * i + 2;
             }
             MessageBox.Show(p[9].ToString());

3、模拟C中的union(联合体)类型

使用 StructLayout 可以模拟C中的union:

  [StructLayout(LayoutKind.Explicit)]
        public struct Argb32
        {
            [FieldOffset(0)]
            public Byte Blue;
            [FieldOffset(1)]
            public Byte Green;
            [FieldOffset(2)]
            public Byte Red;
            [FieldOffset(3)]
            public Byte Alpha;
            [FieldOffset(0)]
            public Int32 IntVal;
        }

这个和指针无关,非unsafe环境下也可使用,有很多用途,比如,序列化和反序列化,求hash值 ……

四、C# 指针操作的几个缺点

C# 指针操作的缺点也不少。下面一一道来。

缺点1:只能用来操作值类型

.Net中,引用类型的内存管理全部是由GC代劳,无法取得其地址,因此,无法用指针来操作引用类型。所以,C#中指针操作受到值类型的限制,其中,最主要的一点就是:值类型无法继承。

这一点看起来是致命的,其实不然。首先,需要用到指针来提高性能的地方,其类型是很少变动的。其次,在OO编程中有个名言:组合优于继承。使用组合,我们可以解决很多需要继承的地方。第三,最后,我们还可以使用引用类型来对值类型打包,进行继承,权衡两者的比重来完成任务。

缺点2:泛型不支持指针类型

C# 中泛型不支持指针类型。这是个很大的限制,在后面的篇幅中,我会引入模板机制来克服这个问题。同理,迭代器也不支持指针,因此,我们需要自己实现迭代机制。

缺点3:没有函数指针

幸运的是,C# 中有delegate,delegate 支持支持指针类型,lambda 表达式也支持指针。后面会详细讲解。

五、引入模板机制

没有泛型,但是我们可以模拟出一套类似C++的模板机制出来,进行代码复用。这里大量的用到了C#的语法糖和IDE的支持。

先介绍原理:

partial 关键字让我们可以将一个类的代码分在多个文件,那么可以这样分:第一个文件是我们自己写的代码,第二个文件用来描述模板,第三个文件,用来根据模板自动生成代码。

三个文件这样取名字的:

XXXClassHelper 是模板定义文件,XXXClassHelper_Csmacro.cs 是自动生成的模板实现代码。

ClassHelper文件的例子:

namespace Geb.Image
{
    using TPixel = Argb32;
    using TCache = System.Int32;
    using TKernel = System.Int32;
    using TImage = Geb.Image.ImageArgb32;
    using TChannel = System.Byte;
    public static partial class ImageArgb32ClassHelper
    {
        #region include "ImageClassHelper_Template.cs"
        #endregion
    }
    public partial class ImageArgb32
    {
        #region include "Image_Template.cs"
        #endregion
        #region include "Image_Paramid_Argb_Templete.cs"
        #endregion
    }
    public partial struct Argb32
    {
        #region include "TPixel_Template.cs"
        #endregion
    }
}

这里用到了using 语法糖。using 关键字,可以为一个类型取别名。使用 VS 的 #region 来定义所使用的模板文件的位置。上面这个文件中,引用了4个模板文件:ImageClassHelper_Template.csImage_Template.csImage_Paramid_Argb_Templete.csTPixel_Template.cs

只看其中的一个模板文件 Image_Template.cs

 using TPixel = System.Byte;
 using TCache = System.Int32;
 using TKernel = System.Int32;
 using System;
 using System.Collections.Generic;
 using System.Text;
 namespace Geb.Image.Hidden
 {
     public abstract class Image_Template : UnmanagedImage<TPixel>
     {
         private Image_Template()
             : base(1,1)
         {
             throw new NotImplementedException();
         }
         #region mixin
         public unsafe TPixel* Start { get { return (TPixel*)this.StartIntPtr; } }
         public unsafe TPixel this[int index]
         {
             get
             {
                 return Start[index];
             }
             set
             {
                 Start[index] = value;
             }
         }

   ……

         #endregion
     }
 }

这个模板文件是编译通过的。也使用了 using 关键字来对使用的类型取别名,同时,在代码中,有一段用 #region mixin #endregion 环绕的代码。只需要写一个工具,将模板文件中 #region mixin#endregion 环绕的代码提取出来,替换到模板定义中 #region include "Image_Template.cs" 和 #endregion 之间,生成第三个文件 ClassHelper_Csmacro.cs 即可实现模板机制。由于都使用了 using 关键字对类型取别名,因此,ClassHelper_Csmacro.cs 文件也是可以编译通过的。在不同的模板定义中,令同样的符号来代表不同的类型,实现了模板代码的公用。

上面机制可以全部自动化。Csmacro 是我写的一个工具,可以完成上面的过程。将它放在系统路径下,然后在项目的build event中添加pre-build 指令即可。Csmacro程序在代码包的lib的目录下。

如此实装,我们就有模板用了!一切自动化,就好像内置的一样。强类型、有编译器进行类型约束,减少出错的可能。调试也很容易,就和调试普通的C#代码一样,不存在C++中的模板的难调试问题。缺点嘛,就是没有C++中模板的语法优美,但是,也看的过去,至少比C中的宏好看多了是吧。

参照上面对模板的实现,完全可以定义出一套C#的宏出来。没这样做,是因为没这个需求。

下面是一个完整的例子,为 Person 类和 Cat 类添加模板扩展方法(非扩展方法也可类似添加),由于这个方法有指针,无法用泛型实现:

void SetAge(this T item,  int* age)

首先,建一个可编译通过的模板类 Template.cs

 namespace Introduce.Hide
 {
     using T = Person;
     public static class Template
     {
         #region mixin
         public static unsafe void SetAge(this T item,  int* age)
         {
             item.Age = *age;
         }
         #endregion
     }
 }

我在命名空间中加入了 Hide,只要不引用这个命名空间,这个扩展方法不会出现对程序产生干扰。

接着,建立 PersonClassHelper.cs 文件:

namespace Introduce
 {
     using T = Person;
     public static partial class PersonClassHelper
     {
         #region include "Template.cs"
         #endregion
     }
 }

建立 CatClassHelper.cs 文件:

 namespace Introduce
 {
     using T = Cat;
     public static partial class CatClassHelper
     {
         #region include "Template.cs"
         #endregion
     }
 }

为了节省篇幅,我省略了命名空间的引用,实际代码中是有命名空间的引用的。下载包里包含了全部的代码。接下来,编译一下,哈哈,编译通过。

且慢,怎么看不到编译生成的两个 Csmacro.cs 文件呢?

这两个文件已经生成了,需要手动将它们添加到项目中,只用添加一次即可。添加进来,再编译一下,哈哈,通过。

这个例子虽小,可不要小看模板啊,在Geb.Image库里,大量使用了模板:

有了模板,只用维护公共代码。

六、迭代器

下面来实现迭代器。这里,要放弃使用foreach,返回古老的迭代器模式,来访问图像的每一个像素:

   public unsafe struct ItArgb32Old
    {
        public unsafe Argb32* Current;
        public unsafe Argb32* End;
        public unsafe Argb32* Next()
        {
            if (Current < End) return Current ++;
            else return null;
        }
    }
    public static class ImageArgb32Helper
    {
        public unsafe static ItArgb32Old CreateItorOld(this ImageArgb32 img)
        {
            ItArgb32Old itor = new ItArgb32Old();
            itor.Current = img.Start;
            itor.End = img.Start + img.Length;
            return itor;
        }
    }

不幸的是,测试性能,这个迭代器比单纯的while循环慢很多。对一个100万像素的图像,将其每一个像素值的Red分量设为200,循环100遍,使用迭代器在我的电脑上耗时242 ms,直接使用循环耗时 72 ms。我测试了很多种方案,均未得到和直接循环性能近似的迭代器实现方案。

没有办法,只好对迭代器来打折了,只进行部分抽象(这已经不能算迭代器了,但这里仍沿用这个名称):

     public unsafe struct ItArgb32
     {
         public unsafe Argb32* Start;
         public unsafe Argb32* End;
         public int Step(Argb32* ptr)
         {
             return 1;
         }
     }

产生迭代器的代码:

   public unsafe static ItArgb32 CreateItor(this ImageArgb32 img)
     {
         ItArgb32 itor = new ItArgb32();
         itor.Start = img.Start;
         itor.End = img.Start + img.Length;
         return itor;
     }

使用:

   ItArgb32 itor = img.CreateItor();
     for (Argb32* p = itor.Start; p < itor.End; p+= itor.Step(p))
     {
         p->Red = 200;
     }

测试性能和直接循环性能几乎一样。有人可能要问,你这样有什么优势?和for循环有什么区别?

这个例子中当然看不出优势,换个例子就可以看出来了。

在图像编程中,有 ROI(Region of Interest,感兴趣区域)的概念。比如,在下面这张女王出场的画面中,假设我们只对她的头部感兴趣(ROI区域),只对该区域进行处理(标注为红色区域)。

对ROI区域创建一个迭代器,用来迭代ROI中的每一行:

  public unsafe struct ItRoiArgb32
    {
        public unsafe Argb32* Start;
        public unsafe Argb32* End;
        public int Width;
        public int RoiWidth;
        public int Step(Argb32* ptr)
        {
            return Width;
        }
        public ItArgb32 Itor(Argb32* p)
        {
            ItArgb32 it = new ItArgb32();
            it.Start = p;
            it.End = p + RoiWidth;
            return it;
        }
    }

这个ROI迭代器又可以产生一个ItArgb32迭代器,来迭代该行中的像素。

产生ROI迭代器的代码如下,为了简化代码,我这里没有进行ROI的验证:

     public unsafe static ItRoiArgb32 CreateRoiItor(this ImageArgb32 img,
            int x, int y, int roiWidth, int roiHeight)
        {
            ItRoiArgb32 itor = new ItRoiArgb32();
            itor.Width = img.Width;
            itor.RoiWidth = roiWidth;
            itor.Start = img.Start + img.Width * y + x;
            itor.End = itor.Start + img.Width * roiHeight;
            return itor;
        }

性能测试表明,使用ROI迭代器进行迭代和直接进行循环,性能一致。为一副图像添加ROI字段,设置ROI值来控制不同的处理区域,然后用ROI迭代器进行迭代,比直接使用循环要方便得多。

七、风情万种的Lambda表达式

接下来,来看看C#指针最有风情的一面——Lambda表达式。 C# 里 delegate 支持指针,下面这种写法是没有问题的:

 void ActionOnPixel(TPixel* p);

对于图像处理,我定义了许多扩展方法,ForEach是其中的一种,下面是它的模板定义:

     public unsafe static UnmanagedImage<TPixel> ForEach(this UnmanagedImage<TPixel> src, ActionOnPixel handler)
        {
            TPixel* start = (TPixel*)src.StartIntPtr;
            TPixel* end = start + src.Length;
            while (start != end)
            {
                handler(start);
                ++start;
            }
            return src;
        }

让我们用lambda表达式对图像迭代,将每像素的Red分量设为200吧,一行代码搞定:

img.ForEach((Argb32* p) => { p->Red = 200; });

用ForEach测试,对100万像素的图像设置Red通道值为200,循环100次,我的测试结果是 400 ms,约是直接循环的 4-5 倍。可见这是个性能不高的操作(其实也够高了,100万象素,循环100遍,耗时400ms),可以在对性能要求不是特别高时使用。

八、与C/C++的比较

我测试了很多场景,C# 下指针性能约是 C/C++ 的 70-80%,性能差距,可以忽略。

相对于C/C++来说,C#无法直接操作硬件是其遗憾,这种情况,可以使用C/C++写段小程序来弥补,不过,我还没遇到这种场景。很多情况都可以P/Invoke解决。

做图像的话,很多时候需要使用显卡加速,如使用CUDA或OpenCL,幸运的是,C#也可以直接写CUDA或OpenCL代码,但是功能可能会受到所用的库的限制。也可以用传统方式写CUDA或OpenCL代码,再P/Invoke调用。如果用传统的C/C++开发的话,也需要做同样的工作。

和C比较:

这套方案比C的抽象程度高,我们有模板,有lambda表达式,还有一大票的语法糖。在类库上,比C的类库完善的多。我们还有反射,有命名空间等等一大票的东西。

和C++比较:

这套方案的抽象程度比C++要低一些。毕竟,值类型无法继承,模板机制比C++ 差一点。但是在生产力上比C++要高很多。抛开C++那一大票陷阱不说,以秒计算的编译速度就够让C++程序员流口水的。当我们在咖啡馆里约会喝咖啡时,C++程序员还正端着一杯咖啡坐在电脑前等待程序编译结束。

九、接下来的工作

接下来的工作主要有两个:

内联工具:C# 的内联还不够强大。需要一个内联工具,对想要内联的方法使用特性标记一下,在编译结束后,在IL代码层面内联。

翻译工具:移动开发是个痛。如何将C#的代码翻译成C/C++的代码,在缺乏.Net的运行时下运行?

这两个工作都不紧要。C#内联效果不好的地方(这种情况很少),可以手动内联。至于移动开发嘛,在哥的一云三端大计中,C# 的定位是云图像开发(C#+CUDA),三端中,桌面运用是用C#和Flash开发,Web和移动应用使用Flash开发,没有C#的事情。

C/C++ 呢?更没有它们的位置啦!不对,还是有的。用它们来开发Flash应用的核心算法!够另类吧!

总结

本篇文章就到这里了,希望可以帮助到你,也希望你能够多多关注我们的更对内容!

(0)

相关推荐

  • 关于C#调用C++dll传指针释放内存问题

    一.传入dll前,在C#中申请内存空间 c#里面的指针即 IntPtr 申请如下: IntPtr SrcImgData = Marshal.AllocHGlobal(length); 这种需要提前知道空间大小,否则无法确定空间大小,会导致dll内部处理时越界报错. c#里面申请空间了,那么c++里面一般就是在这些空间里面操作了,一般不会重新分配内存,那么就不需要加引用了. c++: uchar* SrcImg c#导入dll函数时申明: IntPtr SrcImg 那么内存释放自然也是由c#来进

  • 如何在C#中使用指针

    一:背景 1. 讲故事 高级语言玩多了,可能很多人对指针或者汇编都淡忘了,本篇就和大家聊一聊指针,虽然C#中是不提倡使用的,但你能说指针在C#中不重要吗?你要知道FCL内库中大量的使用指针,如String,Encoding,FileStream等等数不胜数,如例代码: private unsafe static bool EqualsHelper(string strA, string strB) { fixed (char* ptr = &strA.m_firstChar) { fixed (

  • 浅谈C#指针问题

    花了很长时间的实践,终于搞清楚了.类或者链表等,在指针赋值的时候,会使用新的指针.比如: Foo a = c; Foo b = new Foo(); Foo a = b; 这种情况下,会把b的指针传给a,a不再指向c,a以后的操作都会对b生效. 如下情况下: Foo b = new Foo(); Foo a{get {return b;}} 这种情况下,表示a无法被修改,但是如果你a.bar = 5;的话,那么是可以修改的,为什么呢?因为此时任何针对a的属性的修改,本质上都是对b的修改,只有a

  • 基于C#调用c++Dll结构体数组指针的问题详解

    C#调用c++dll文件是一件很麻烦的事情,首先面临的是数据类型转换的问题,相信经常做c#开发的都和我一样把学校的那点c++底子都忘光了吧(语言特性类). 网上有一大堆得转换对应表,也有一大堆的转换实例,但是都没有强调一个更重要的问题,就是c#数据类型和c++数据类型占内存长度的对应关系. 如果dll文件中只包含一些基础类型,那这个问题可能可以被忽略,但是如果是组合类型(这个叫法也许不妥),如结构体.类类型等,在其中的成员变量的长度的申明正确与否将决定你对dll文件调用的成败. 如有以下代码,其

  • C#访问C++动态分配的数组指针(实例讲解)

    项目中遇到C#调用C++算法库的情况,C++内部运算结果返回矩形坐标数组(事先长度未知且不可预计),下面方法适用于访问C++内部分配的任何结构体类型数组.当时想当然的用ref array[]传递参数,能计算能分配,但是在C#里只得到arr长度是1,无法访问后续数组Item. C++ 接口示例: void Call(int *count, Rect **arr) { //-.. //重新Malloc一段内存,指针复制给入参,外部调用前并不知道长度,另外提供接口Free内存 //-. } 结构体:

  • 深入理解C#指针之美

    目录 一.简洁优美的代码 二.C# 指针基础 三.几种常用用法 1.使用Dispose模式管理非托管内存 2.使用 stackalloc 在栈中分配内存 3.模拟C中的union(联合体)类型 四.C# 指针操作的几个缺点 五.引入模板机制 六.迭代器 七.风情万种的Lambda表达式 八.与C/C++的比较 九.接下来的工作 总结 一.简洁优美的代码 本来初稿这节写了好几百字,将C#指针开发与C/C++开发,Java开发.D语言开发等进行对比,阐述理念.不过现在觉得,阐述一个新事物,没有比用例

  • 深入理解c++指针的指针和指针的引用

    展示一下使用指针的指针和指针的引用修改传递给方法的指针,以便更好的使用它.(这里说的指针的指针不是一个二维数组) 为什么需要使用它们 当我们把一个指针做为参数传一个方法时,其实是把指针的复本传递给了方法,也可以说传递指针是指针的值传递. 如果我们在方法内部修改指针会出现问题,在方法里做修改只是修改的指针的copy而不是指针本身,原来的指针还保留着原来 的值.我们用下边的代码说明一下问题: int m_value = 1; void func(int *p) { p = &m_value; } i

  • 如何理解C++指针常量和常量指针

    目录 术语 常量指针(pointer to const) 指针常量(指针) 前言: 应该怎样正确的姿势去理解指针常量和常量指针,到我发布这篇文章时候,感觉自己对指针常量和常量指针理解还是不够透彻,可能接触还是比较少吧,希望自己用一个月的时间可以将自己的 c++ 的功力有一个提升,可以靠 c++ 来混一碗饭. 术语 常量 指针 和引用类似,可以定义指针可以指向常量或者非常量类型. 常量指针(pointer to const) 常量指针,根据字面上理解就是指向常量的一个指针,也就是指针保存了一个存放

  • 深入理解数组指针与指针数组的区别

    数组指针与指针数组的区别在于:数组指针p是一个指针,而指针数组p是一个存放N个指针变量的数组. 一.数组指针int (*p)[n]重点:()优先级高([].()的优先级是一样的,但它们的方向是从左至右的,所以先运行括号里的*p),首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长.也就是说执行p+1时,p要跨过n个整型数据的长度(n*sizeof(int)).如要将二维数组赋给一指针,应这样赋值:       int a[3][4];       int (

  • 深度理解c++中的this指针

    1.this指针,就是一个指向当前对象的指针.我们知道,定义出一个类,它在内存中是不占空间的,只有定义了该类类型的对象时,系统就会为该对象分配一段存储空间,这段空间里只存储成员变量,对于成员函数,是存放在代码区的.(复习:内存分为5大区:静态区.常量区.栈.堆.代码区).下边给出一个日期类,通过这个实例,深度理解this指针. #define _CRT_SECURE_NO_WARNINGS 1 #include using namespace std; class Date { public:

  • java及C++中传值传递、引用传递和指针方式的理解

    java的值传递理解: 代码1: public class Test { /** * @param args */ public static void main(String[] args) { StringBuffer buffer= new StringBuffer("colin"); SChange(buffer); System.out.println( buffer); } public static void SChange (StringBuffer str) { st

  • C++中this指针的理解与作用详解

    01 C++ 程序到 C 程序的翻译 要想理解 C++ 的 this 指针,我们先把下面的 C++ 代码转换成 C 的代码 class Car { public: int m_price; // 成员变量 void SetPrice(int p) // 成员函数 { m_price = p; } }; int main() { Car car; car.SetPrice(20000); // 给car对象m_price成员变量赋值 return 0; } C 语言是没有类定义的class关键词,

  • 手拉手教你如何理解c/c++中的指针

    目录 前言 一,内存和地址 二,指针的本质就是地址 三,常量指针与指针常量 四,指针与数组 五,数组指针与指针数组 六,指针函数与函数指针 总结 前言 指针是c语言为什么如此流行的一个重要原因,正是有了指针的存在,才使得c/c++能够可以比使用其他语言编写出更为紧凑和有效的程序,可以说,没有掌握指针,就没有权利说自己会用c/c++.然而.然而对于大多数初学者,面对指针这个概念简直是望而生畏,如果前期指针运用的不熟练,后期编写的程序随时都有可能成为一颗定时炸弹,因此今天我就花点时间给大家解释一下我

  • C++中this指针理解及作用

    目录 01.C++程序到C程序的翻译 02.this指针的作用 03.this指针和静态成员函数 04.小结 01.C++程序到C程序的翻译 想要理解C++语言中的this指针,下面我们做一个举例,我们要先把下面的C++代码转换成C代码: (1)大家知道,C语言中没有类定义class关键词,但是有跟class类似的定义关键词,就是struct结构体定义.m_height变量是Tree类的成员变量,那么我们就可以把Tree类和成员变量翻译成下面的C代码: (2)SetHeight函数是Tree类的

  • C语言指针详解及用法示例

    新手在C语言的学习过程中遇到的最头疼的知识点应该就是指针了,指针在C语言中有非常大的用处.下面我就带着问题来写下我对于指针的一些理解. 指针是什么? 指针本身是一个变量,它存储的是数据在内存中的地址而不是数据本身的值.它的定义如下: int a=10,*p; p=&a int a=10; int *p=&a; 首先我们可以理解 int* 这个是要定义一个指针p,然后因为这个指针存储的是地址所以要对a取地址(&)将值赋给指针p,也就是说这个指针p指向a. 很多新手都会对这两种定义方法

随机推荐